B2B-88: add starter kit structure and elements

This commit is contained in:
devmc-ee
2025-06-08 16:18:30 +03:00
parent 657a36a298
commit e7b25600cb
1280 changed files with 77893 additions and 5688 deletions

3
packages/next/README.md Normal file
View File

@@ -0,0 +1,3 @@
# Next.js Utilities / @kit/next
This package provides utilities for working with Next.js.

View File

@@ -0,0 +1,3 @@
import eslintConfigBase from '@kit/eslint-config/base.js';
export default eslintConfigBase;

17
packages/next/node_modules/.bin/next generated vendored Executable file
View File

@@ -0,0 +1,17 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -z "$NODE_PATH" ]; then
export NODE_PATH="/Users/devmcee/dev/mountbirch/MRB2B/node_modules/.pnpm/next@15.3.2_@babel+core@7.27.4_@opentelemetry+api@1.9.0_babel-plugin-react-compiler@19.1.0-rc_krzs4il3c2axvegn27goximifi/node_modules/next/dist/bin/node_modules:/Users/devmcee/dev/mountbirch/MRB2B/node_modules/.pnpm/next@15.3.2_@babel+core@7.27.4_@opentelemetry+api@1.9.0_babel-plugin-react-compiler@19.1.0-rc_krzs4il3c2axvegn27goximifi/node_modules/next/dist/node_modules:/Users/devmcee/dev/mountbirch/MRB2B/node_modules/.pnpm/next@15.3.2_@babel+core@7.27.4_@opentelemetry+api@1.9.0_babel-plugin-react-compiler@19.1.0-rc_krzs4il3c2axvegn27goximifi/node_modules/next/node_modules:/Users/devmcee/dev/mountbirch/MRB2B/node_modules/.pnpm/next@15.3.2_@babel+core@7.27.4_@opentelemetry+api@1.9.0_babel-plugin-react-compiler@19.1.0-rc_krzs4il3c2axvegn27goximifi/node_modules:/Users/devmcee/dev/mountbirch/MRB2B/node_modules/.pnpm/node_modules"
else
export NODE_PATH="/Users/devmcee/dev/mountbirch/MRB2B/node_modules/.pnpm/next@15.3.2_@babel+core@7.27.4_@opentelemetry+api@1.9.0_babel-plugin-react-compiler@19.1.0-rc_krzs4il3c2axvegn27goximifi/node_modules/next/dist/bin/node_modules:/Users/devmcee/dev/mountbirch/MRB2B/node_modules/.pnpm/next@15.3.2_@babel+core@7.27.4_@opentelemetry+api@1.9.0_babel-plugin-react-compiler@19.1.0-rc_krzs4il3c2axvegn27goximifi/node_modules/next/dist/node_modules:/Users/devmcee/dev/mountbirch/MRB2B/node_modules/.pnpm/next@15.3.2_@babel+core@7.27.4_@opentelemetry+api@1.9.0_babel-plugin-react-compiler@19.1.0-rc_krzs4il3c2axvegn27goximifi/node_modules/next/node_modules:/Users/devmcee/dev/mountbirch/MRB2B/node_modules/.pnpm/next@15.3.2_@babel+core@7.27.4_@opentelemetry+api@1.9.0_babel-plugin-react-compiler@19.1.0-rc_krzs4il3c2axvegn27goximifi/node_modules:/Users/devmcee/dev/mountbirch/MRB2B/node_modules/.pnpm/node_modules:$NODE_PATH"
fi
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../next/dist/bin/next" "$@"
else
exec node "$basedir/../next/dist/bin/next" "$@"
fi

1
packages/next/node_modules/@kit/auth generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../features/auth

1
packages/next/node_modules/@kit/eslint-config generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../../tooling/eslint

1
packages/next/node_modules/@kit/monitoring generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../monitoring/api

1
packages/next/node_modules/@kit/prettier-config generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../../tooling/prettier

1
packages/next/node_modules/@kit/supabase generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../supabase

1
packages/next/node_modules/@kit/tsconfig generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../../tooling/typescript

1
packages/next/node_modules/@supabase/supabase-js generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../../node_modules/.pnpm/@supabase+supabase-js@2.49.4/node_modules/@supabase/supabase-js

1
packages/next/node_modules/next generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../node_modules/.pnpm/next@15.3.2_@babel+core@7.27.4_@opentelemetry+api@1.9.0_babel-plugin-react-compiler@19.1.0-rc_krzs4il3c2axvegn27goximifi/node_modules/next

1
packages/next/node_modules/zod generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../node_modules/.pnpm/zod@3.25.56/node_modules/zod

View File

@@ -0,0 +1,34 @@
{
"name": "@kit/next",
"private": true,
"version": "0.1.0",
"scripts": {
"clean": "git clean -xdf .turbo node_modules",
"format": "prettier --check \"**/*.{ts,tsx}\"",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"prettier": "@kit/prettier-config",
"exports": {
"./actions": "./src/actions/index.ts",
"./routes": "./src/routes/index.ts"
},
"devDependencies": {
"@kit/auth": "workspace:*",
"@kit/eslint-config": "workspace:*",
"@kit/monitoring": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@supabase/supabase-js": "2.49.4",
"next": "15.3.2",
"zod": "^3.24.4"
},
"typesVersions": {
"*": {
"*": [
"src/*"
]
}
}
}

View File

@@ -0,0 +1,76 @@
import 'server-only';
import { redirect } from 'next/navigation';
import type { User } from '@supabase/supabase-js';
import { ZodType, z } from 'zod';
import { verifyCaptchaToken } from '@kit/auth/captcha/server';
import { requireUser } from '@kit/supabase/require-user';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { zodParseFactory } from '../utils';
/**
* @name enhanceAction
* @description Enhance an action with captcha, schema and auth checks
*/
export function enhanceAction<
Args,
Response,
Config extends {
auth?: boolean;
captcha?: boolean;
schema?: z.ZodType<
Config['captcha'] extends true ? Args & { captchaToken: string } : Args,
z.ZodTypeDef
>;
},
>(
fn: (
params: Config['schema'] extends ZodType ? z.infer<Config['schema']> : Args,
user: Config['auth'] extends false ? undefined : User,
) => Response | Promise<Response>,
config: Config,
) {
return async (
params: Config['schema'] extends ZodType ? z.infer<Config['schema']> : Args,
) => {
type UserParam = Config['auth'] extends false ? undefined : User;
const requireAuth = config.auth ?? true;
let user: UserParam = undefined as UserParam;
// validate the schema passed in the config if it exists
const data = config.schema
? zodParseFactory(config.schema)(params)
: params;
// by default, the CAPTCHA token is not required
const verifyCaptcha = config.captcha ?? false;
// verify the CAPTCHA token. It will throw an error if the token is invalid.
if (verifyCaptcha) {
const token = (data as Args & { captchaToken: string }).captchaToken;
// Verify the CAPTCHA token. It will throw an error if the token is invalid.
await verifyCaptchaToken(token);
}
// verify the user is authenticated if required
if (requireAuth) {
// verify the user is authenticated if required
const auth = await requireUser(getSupabaseServerClient());
// If the user is not authenticated, redirect to the specified URL.
if (!auth.data) {
redirect(auth.redirectTo);
}
user = auth.data as UserParam;
}
return fn(data, user);
};
}

View File

@@ -0,0 +1,140 @@
import 'server-only';
import { redirect } from 'next/navigation';
import { NextRequest, NextResponse } from 'next/server';
import { User } from '@supabase/supabase-js';
import { z } from 'zod';
import { verifyCaptchaToken } from '@kit/auth/captcha/server';
import { requireUser } from '@kit/supabase/require-user';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { zodParseFactory } from '../utils';
interface Config<Schema> {
auth?: boolean;
captcha?: boolean;
schema?: Schema;
}
interface HandlerParams<
Schema extends z.ZodType | undefined,
RequireAuth extends boolean | undefined,
> {
request: NextRequest;
user: RequireAuth extends false ? undefined : User;
body: Schema extends z.ZodType ? z.infer<Schema> : undefined;
params: Record<string, string>;
}
/**
* Enhanced route handler function.
*
* This function takes a request and parameters object as arguments and returns a route handler function.
* The route handler function can be used to handle HTTP requests and apply additional enhancements
* based on the provided parameters.
*
* Usage:
* export const POST = enhanceRouteHandler(
* ({ request, body, user }) => {
* return new Response(`Hello, ${body.name}!`);
* },
* {
* schema: z.object({
* name: z.string(),
* }),
* },
* );
*
*/
export const enhanceRouteHandler = <
Body,
Params extends Config<z.ZodType<Body, z.ZodTypeDef>>,
>(
// Route handler function
handler:
| ((
params: HandlerParams<Params['schema'], Params['auth']>,
) => NextResponse | Response)
| ((
params: HandlerParams<Params['schema'], Params['auth']>,
) => Promise<NextResponse | Response>),
// Parameters object
params?: Params,
) => {
/**
* Route handler function.
*
* This function takes a request object as an argument and returns a response object.
*/
return async function routeHandler(
request: NextRequest,
routeParams: {
params: Promise<Record<string, string>>;
},
) {
type UserParam = Params['auth'] extends false ? undefined : User;
let user: UserParam = undefined as UserParam;
// Check if the captcha token should be verified
const shouldVerifyCaptcha = params?.captcha ?? false;
// Verify the captcha token if required and setup
if (shouldVerifyCaptcha) {
const token = captchaTokenGetter(request);
// If the captcha token is not provided, return a 400 response.
if (token) {
await verifyCaptchaToken(token);
} else {
return new Response('Captcha token is required', { status: 400 });
}
}
const client = getSupabaseServerClient();
const shouldVerifyAuth = params?.auth ?? true;
// Check if the user should be authenticated
if (shouldVerifyAuth) {
// Get the authenticated user
const auth = await requireUser(client);
// If the user is not authenticated, redirect to the specified URL.
if (auth.error) {
return redirect(auth.redirectTo);
}
user = auth.data as UserParam;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let body: any;
if (params?.schema) {
// clone the request to read the body
// so that we can pass it to the handler safely
const json = await request.clone().json();
body = zodParseFactory(params.schema)(json);
}
return handler({
request,
body,
user,
params: await routeParams.params,
});
};
};
/**
* Get the captcha token from the request headers.
* @param request
*/
function captchaTokenGetter(request: NextRequest) {
return request.headers.get('x-captcha-token');
}

View File

@@ -0,0 +1,14 @@
import { z } from 'zod';
export const zodParseFactory =
<T extends z.ZodTypeAny>(schema: T) =>
(data: unknown): z.infer<T> => {
try {
return schema.parse(data) as unknown;
} catch (err) {
console.error(err);
// handle error
throw new Error(`Invalid data: ${err as string}`);
}
};

View File

@@ -0,0 +1,8 @@
{
"extends": "@kit/tsconfig/base.json",
"compilerOptions": {
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
},
"include": ["*.ts", "src"],
"exclude": ["node_modules"]
}