feat: introduce Gatekeeper application, Directus utilities, and monorepo configuration for linting, testing, and husky hooks.
This commit is contained in:
3
packages/next-utils/eslint.config.mjs
Normal file
3
packages/next-utils/eslint.config.mjs
Normal file
@@ -0,0 +1,3 @@
|
||||
import baseConfig from "@mintel/eslint-config";
|
||||
|
||||
export default baseConfig;
|
||||
@@ -11,17 +11,20 @@
|
||||
"scripts": {
|
||||
"build": "tsup src/index.ts --format cjs,esm --dts",
|
||||
"dev": "tsup src/index.ts --format cjs,esm --watch --dts",
|
||||
"lint": "eslint src/"
|
||||
"lint": "eslint src/",
|
||||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@directus/sdk": "^21.0.0",
|
||||
"next": "15.1.6",
|
||||
"next-intl": "^3.0.0",
|
||||
"zod": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsup": "^8.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"@mintel/eslint-config": "workspace:*",
|
||||
"@mintel/tsconfig": "workspace:*",
|
||||
"@mintel/eslint-config": "workspace:*"
|
||||
"eslint": "^9.39.2",
|
||||
"tsup": "^8.0.0",
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
47
packages/next-utils/src/directus.ts
Normal file
47
packages/next-utils/src/directus.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
createDirectus,
|
||||
rest,
|
||||
authentication,
|
||||
DirectusClient,
|
||||
RestClient,
|
||||
AuthenticationClient,
|
||||
} from "@directus/sdk";
|
||||
|
||||
export type MintelDirectusClient = DirectusClient<any> &
|
||||
RestClient<any> &
|
||||
AuthenticationClient<any>;
|
||||
|
||||
/**
|
||||
* Creates a Directus client configured with Mintel standards
|
||||
*/
|
||||
export function createMintelDirectusClient(url?: string): MintelDirectusClient {
|
||||
const directusUrl =
|
||||
url || process.env.DIRECTUS_URL || "http://localhost:8055";
|
||||
|
||||
return createDirectus(directusUrl).with(rest()).with(authentication());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the client is authenticated using either a static token or admin credentials
|
||||
*/
|
||||
export async function ensureDirectusAuthenticated(
|
||||
client: MintelDirectusClient,
|
||||
) {
|
||||
const token = process.env.DIRECTUS_API_TOKEN || process.env.DIRECTUS_TOKEN;
|
||||
const email = process.env.DIRECTUS_ADMIN_EMAIL;
|
||||
const password = process.env.DIRECTUS_ADMIN_PASSWORD;
|
||||
|
||||
if (token) {
|
||||
client.setToken(token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (email && password) {
|
||||
try {
|
||||
await client.login({ email, password });
|
||||
} catch (e) {
|
||||
console.error("Failed to authenticate with Directus:", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import createMiddleware from 'next-intl/middleware';
|
||||
import { getRequestConfig } from 'next-intl/server';
|
||||
import { NextResponse } from 'next/server';
|
||||
import type { NextRequest } from 'next/server';
|
||||
import createMiddleware from "next-intl/middleware";
|
||||
import { getRequestConfig } from "next-intl/server";
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
export interface MintelI18nConfig {
|
||||
locales: string[];
|
||||
@@ -25,7 +24,10 @@ export function createMintelMiddleware(config: MintelI18nConfig) {
|
||||
return intlMiddleware(request);
|
||||
} catch (error) {
|
||||
if (config.logRequests) {
|
||||
console.error(`Request failed: ${request.method} ${request.url}`, error);
|
||||
console.error(
|
||||
`Request failed: ${request.method} ${request.url}`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
@@ -35,7 +37,7 @@ export function createMintelMiddleware(config: MintelI18nConfig) {
|
||||
export function createMintelI18nRequestConfig(
|
||||
locales: string[],
|
||||
defaultLocale: string,
|
||||
importMessages: (locale: string) => Promise<any>
|
||||
importMessages: (locale: string) => Promise<any>,
|
||||
) {
|
||||
return getRequestConfig(async ({ requestLocale }) => {
|
||||
let locale = await requestLocale;
|
||||
@@ -48,19 +50,19 @@ export function createMintelI18nRequestConfig(
|
||||
locale,
|
||||
messages: (await importMessages(locale)).default,
|
||||
onError(error: any) {
|
||||
if (error.code === 'MISSING_MESSAGE') {
|
||||
if (error.code === "MISSING_MESSAGE") {
|
||||
console.error(error.message);
|
||||
} else {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
getMessageFallback({ namespace, key, error }: any) {
|
||||
const path = [namespace, key].filter((part) => part != null).join('.');
|
||||
if (error.code === 'MISSING_MESSAGE') {
|
||||
const path = [namespace, key].filter((part) => part != null).join(".");
|
||||
if (error.code === "MISSING_MESSAGE") {
|
||||
return path;
|
||||
}
|
||||
return 'fallback';
|
||||
}
|
||||
return "fallback";
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
10
packages/next-utils/src/index.test.ts
Normal file
10
packages/next-utils/src/index.test.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { isValidLang } from "../src/index";
|
||||
|
||||
describe("next-utils", () => {
|
||||
it("should validate languages correctly", () => {
|
||||
expect(isValidLang("en")).toBe(true);
|
||||
expect(isValidLang("de")).toBe(true);
|
||||
expect(isValidLang("fr")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,11 @@ const submissions: Record<string, number> = {};
|
||||
const RATE_LIMIT_WINDOW = 60 * 60 * 1000; // 1 hour
|
||||
const MAX_SUBMISSIONS_PER_WINDOW = 3;
|
||||
|
||||
export async function rateLimit(identifier: string, windowMs = RATE_LIMIT_WINDOW, maxSubmissions = MAX_SUBMISSIONS_PER_WINDOW) {
|
||||
export async function rateLimit(
|
||||
identifier: string,
|
||||
windowMs = RATE_LIMIT_WINDOW,
|
||||
maxSubmissions = MAX_SUBMISSIONS_PER_WINDOW,
|
||||
) {
|
||||
const now = Date.now();
|
||||
|
||||
// Clean up old submissions
|
||||
@@ -15,7 +19,7 @@ export async function rateLimit(identifier: string, windowMs = RATE_LIMIT_WINDOW
|
||||
|
||||
// Check if identifier has exceeded submission limit
|
||||
const currentSubmissions = Object.values(submissions).filter(
|
||||
(timestamp) => now - timestamp <= windowMs
|
||||
(timestamp) => now - timestamp <= windowMs,
|
||||
);
|
||||
|
||||
if (currentSubmissions.length >= maxSubmissions) {
|
||||
@@ -35,3 +39,4 @@ export function isValidLang(lang: string): lang is Lang {
|
||||
|
||||
export * from "./i18n";
|
||||
export * from "./env";
|
||||
export * from "./directus";
|
||||
|
||||
Reference in New Issue
Block a user