Merge branch 'main' of github.com:MkSaaSHQ/mksaas-template

This commit is contained in:
javayhu 2025-04-28 22:58:00 +08:00
commit 299b8a3957
7 changed files with 161 additions and 49 deletions

View File

@ -20,6 +20,7 @@
"@ai-sdk/openai": "^1.1.13",
"@aws-sdk/client-s3": "^3.758.0",
"@aws-sdk/s3-request-presigner": "^3.758.0",
"@better-fetch/fetch": "^1.1.18",
"@content-collections/core": "^0.8.0",
"@content-collections/mdx": "^0.2.0",
"@content-collections/next": "^0.2.4",
@ -29,7 +30,6 @@
"@dnd-kit/utilities": "^3.2.2",
"@fumadocs/content-collections": "^1.1.8",
"@hookform/resolvers": "^4.1.0",
"@neondatabase/serverless": "^0.10.4",
"@next/third-parties": "^15.3.0",
"@openpanel/nextjs": "^1.0.7",
"@orama/orama": "^3.1.4",
@ -96,6 +96,7 @@
"next-plausible": "^3.12.4",
"next-safe-action": "^7.10.4",
"next-themes": "^0.4.4",
"postgres": "^3.4.5",
"react": "^19.0.0",
"react-day-picker": "9.6.3",
"react-dom": "^19.0.0",

114
pnpm-lock.yaml generated
View File

@ -17,6 +17,9 @@ importers:
'@aws-sdk/s3-request-presigner':
specifier: ^3.758.0
version: 3.758.0
'@better-fetch/fetch':
specifier: ^1.1.18
version: 1.1.18
'@content-collections/core':
specifier: ^0.8.0
version: 0.8.0(typescript@5.7.3)
@ -44,9 +47,6 @@ importers:
'@hookform/resolvers':
specifier: ^4.1.0
version: 4.1.0(react-hook-form@7.54.2(react@19.0.0))
'@neondatabase/serverless':
specifier: ^0.10.4
version: 0.10.4
'@next/third-parties':
specifier: ^15.3.0
version: 15.3.0(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)
@ -205,7 +205,7 @@ importers:
version: 2.2.3
drizzle-orm:
specifier: ^0.39.3
version: 0.39.3(@neondatabase/serverless@0.10.4)(@opentelemetry/api@1.9.0)(@types/pg@8.11.11)(kysely@0.27.5)(pg@8.13.3)
version: 0.39.3(@neondatabase/serverless@0.10.4)(@opentelemetry/api@1.9.0)(@types/pg@8.11.11)(kysely@0.27.5)(pg@8.15.6(pg-native@3.4.5))(postgres@3.4.5)
embla-carousel-react:
specifier: ^8.5.2
version: 8.5.2(react@19.0.0)
@ -245,6 +245,9 @@ importers:
next-themes:
specifier: ^0.4.4
version: 0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
postgres:
specifier: ^3.4.5
version: 3.4.5
react:
specifier: ^19.0.0
version: 19.0.0
@ -670,6 +673,9 @@ packages:
'@better-fetch/fetch@1.1.12':
resolution: {integrity: sha512-B3bfloI/2UBQWIATRN6qmlORrvx3Mp0kkNjmXLv0b+DtbtR+pP4/I5kQA/rDUv+OReLywCCldf6co4LdDmh8JA==}
'@better-fetch/fetch@1.1.18':
resolution: {integrity: sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA==}
'@biomejs/biome@1.9.4':
resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==}
engines: {node: '>=14.21.3'}
@ -3351,6 +3357,9 @@ packages:
better-call@0.3.3:
resolution: {integrity: sha512-N4lDVm0NGmFfDJ0XMQ4O83Zm/3dPlvIQdxvwvgSLSkjFX5PM4GUYSVAuxNzXN27QZMHDkrJTWUqxBrm4tPC3eA==}
bindings@1.5.0:
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
@ -3917,6 +3926,9 @@ packages:
picomatch:
optional: true
file-uri-to-path@1.0.0:
resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
@ -4228,6 +4240,9 @@ packages:
leac@0.6.0:
resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
libpq@1.8.14:
resolution: {integrity: sha512-/DDvQCiXP0KBMZ31U2mmURKaxoKt9kNqqgrSO2RuBKS+OJjw5b7uHi5jFoV8zPAUa2TNtq2XfcWL1OWDEyjwlg==}
lightningcss-darwin-arm64@1.29.2:
resolution: {integrity: sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==}
engines: {node: '>= 12.0.0'}
@ -4570,6 +4585,9 @@ packages:
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
nan@2.22.0:
resolution: {integrity: sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==}
nanoid@3.3.8:
resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@ -4747,30 +4765,33 @@ packages:
peberminta@0.9.0:
resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==}
pg-cloudflare@1.1.1:
resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==}
pg-cloudflare@1.2.5:
resolution: {integrity: sha512-OOX22Vt0vOSRrdoUPKJ8Wi2OpE/o/h9T8X1s4qSkCedbNah9ei2W2765be8iMVxQUsvgT7zIAT2eIa9fs5+vtg==}
pg-connection-string@2.7.0:
resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==}
pg-connection-string@2.8.5:
resolution: {integrity: sha512-Ni8FuZ8yAF+sWZzojvtLE2b03cqjO5jNULcHFfM9ZZ0/JXrgom5pBREbtnAw7oxsxJqHw9Nz/XWORUEL3/IFow==}
pg-int8@1.0.1:
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
engines: {node: '>=4.0.0'}
pg-native@3.4.5:
resolution: {integrity: sha512-X6fwcza2fuYdAWll48Cj0Xa9ikvfaLWjbKmNWZ7iC6caEMMeN7mpFtSEDjS2HgPxhCEHEjlhE7v1jLyM1k+4kA==}
pg-numeric@1.0.2:
resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==}
engines: {node: '>=4'}
pg-pool@3.8.0:
resolution: {integrity: sha512-VBw3jiVm6ZOdLBTIcXLNdSotb6Iy3uOCwDGFAksZCXmi10nyRvnP2v3jl4d+IsLYRyXf6o9hIm/ZtUzlByNUdw==}
pg-pool@3.9.6:
resolution: {integrity: sha512-rFen0G7adh1YmgvrmE5IPIqbb+IgEzENUm+tzm6MLLDSlPRoZVhzU1WdML9PV2W5GOdRA9qBKURlbt1OsXOsPw==}
peerDependencies:
pg: '>=8.0'
pg-protocol@1.7.1:
resolution: {integrity: sha512-gjTHWGYWsEgy9MsY0Gp6ZJxV24IjDqdpTW7Eh0x+WfJLFsm/TJx1MzL6T0D88mBvkpxotCQ6TwW6N+Kko7lhgQ==}
pg-protocol@1.8.0:
resolution: {integrity: sha512-jvuYlEkL03NRvOoyoRktBK7+qU5kOvlAwvmrH8sr3wbLrOdVWsRxQfz8mMy9sZFsqJ1hEWNfdWKI4SAmoL+j7g==}
pg-protocol@1.9.5:
resolution: {integrity: sha512-DYTWtWpfd5FOro3UnAfwvhD8jh59r2ig8bPtc9H8Ds7MscE/9NYruUQWFAOuraRl29jwcT2kyMFQ3MxeaVjUhg==}
pg-types@2.2.0:
resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
@ -4780,8 +4801,8 @@ packages:
resolution: {integrity: sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==}
engines: {node: '>=10'}
pg@8.13.3:
resolution: {integrity: sha512-P6tPt9jXbL9HVu/SSRERNYaYG++MjnscnegFh9pPHihfoBSujsrka0hyuymMzeJKFWrcG8wvCKy8rCe8e5nDUQ==}
pg@8.15.6:
resolution: {integrity: sha512-yvao7YI3GdmmrslNVsZgx9PfntfWrnXwtR+K/DjI0I/sTKif4Z623um+sjVZ1hk5670B+ODjvHDAckKdjmPTsg==}
engines: {node: '>= 8.0.0'}
peerDependencies:
pg-native: '>=3.0.1'
@ -4854,6 +4875,10 @@ packages:
postgres-range@1.1.4:
resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==}
postgres@3.4.5:
resolution: {integrity: sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg==}
engines: {node: '>=12'}
prettier@3.4.2:
resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==}
engines: {node: '>=14'}
@ -6249,6 +6274,8 @@ snapshots:
'@better-fetch/fetch@1.1.12': {}
'@better-fetch/fetch@1.1.18': {}
'@biomejs/biome@1.9.4':
optionalDependencies:
'@biomejs/cli-darwin-arm64': 1.9.4
@ -6984,6 +7011,7 @@ snapshots:
'@neondatabase/serverless@0.10.4':
dependencies:
'@types/pg': 8.11.6
optional: true
'@next/env@15.1.2': {}
@ -8542,6 +8570,7 @@ snapshots:
'@types/node@20.17.24':
dependencies:
undici-types: 6.19.8
optional: true
'@types/pg@8.11.11':
dependencies:
@ -8552,8 +8581,9 @@ snapshots:
'@types/pg@8.11.6':
dependencies:
'@types/node': 20.17.24
pg-protocol: 1.8.0
pg-protocol: 1.9.5
pg-types: 4.0.2
optional: true
'@types/react-dom@19.0.3(@types/react@19.0.9)':
dependencies:
@ -8659,11 +8689,16 @@ snapshots:
better-call@0.3.3:
dependencies:
'@better-fetch/fetch': 1.1.12
'@better-fetch/fetch': 1.1.18
rou3: 0.5.1
uncrypto: 0.1.3
zod: 3.24.2
bindings@1.5.0:
dependencies:
file-uri-to-path: 1.0.0
optional: true
bl@4.1.0:
dependencies:
buffer: 5.7.1
@ -8947,13 +8982,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
drizzle-orm@0.39.3(@neondatabase/serverless@0.10.4)(@opentelemetry/api@1.9.0)(@types/pg@8.11.11)(kysely@0.27.5)(pg@8.13.3):
drizzle-orm@0.39.3(@neondatabase/serverless@0.10.4)(@opentelemetry/api@1.9.0)(@types/pg@8.11.11)(kysely@0.27.5)(pg@8.15.6(pg-native@3.4.5))(postgres@3.4.5):
optionalDependencies:
'@neondatabase/serverless': 0.10.4
'@opentelemetry/api': 1.9.0
'@types/pg': 8.11.11
kysely: 0.27.5
pg: 8.13.3
pg: 8.15.6(pg-native@3.4.5)
postgres: 3.4.5
dunder-proto@1.0.1:
dependencies:
@ -9242,6 +9278,9 @@ snapshots:
optionalDependencies:
picomatch: 4.0.2
file-uri-to-path@1.0.0:
optional: true
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
@ -9653,6 +9692,12 @@ snapshots:
leac@0.6.0: {}
libpq@1.8.14:
dependencies:
bindings: 1.5.0
nan: 2.22.0
optional: true
lightningcss-darwin-arm64@1.29.2:
optional: true
@ -10250,6 +10295,9 @@ snapshots:
ms@2.1.3: {}
nan@2.22.0:
optional: true
nanoid@3.3.8: {}
nanostores@0.11.4: {}
@ -10423,24 +10471,31 @@ snapshots:
peberminta@0.9.0: {}
pg-cloudflare@1.1.1:
pg-cloudflare@1.2.5:
optional: true
pg-connection-string@2.7.0:
pg-connection-string@2.8.5:
optional: true
pg-int8@1.0.1: {}
pg-native@3.4.5:
dependencies:
libpq: 1.8.14
pg-types: 2.2.0
optional: true
pg-numeric@1.0.2: {}
pg-pool@3.8.0(pg@8.13.3):
pg-pool@3.9.6(pg@8.15.6(pg-native@3.4.5)):
dependencies:
pg: 8.13.3
pg: 8.15.6(pg-native@3.4.5)
optional: true
pg-protocol@1.7.1: {}
pg-protocol@1.8.0: {}
pg-protocol@1.9.5:
optional: true
pg-types@2.2.0:
dependencies:
@ -10461,15 +10516,16 @@ snapshots:
postgres-interval: 3.0.0
postgres-range: 1.1.4
pg@8.13.3:
pg@8.15.6(pg-native@3.4.5):
dependencies:
pg-connection-string: 2.7.0
pg-pool: 3.8.0(pg@8.13.3)
pg-protocol: 1.8.0
pg-connection-string: 2.8.5
pg-pool: 3.9.6(pg@8.15.6(pg-native@3.4.5))
pg-protocol: 1.9.5
pg-types: 2.2.0
pgpass: 1.0.5
optionalDependencies:
pg-cloudflare: 1.1.1
pg-cloudflare: 1.2.5
pg-native: 3.4.5
optional: true
pgpass@1.0.5:
@ -10528,6 +10584,8 @@ snapshots:
postgres-range@1.1.4: {}
postgres@3.4.5: {}
prettier@3.4.2: {}
prettier@3.5.3: {}

View File

@ -1,22 +1,44 @@
import { drizzle } from 'drizzle-orm/neon-http';
/**
* Connect to PostgreSQL Database (Supabase/Neon/Local PostgreSQL)
* https://orm.drizzle.team/docs/tutorials/drizzle-with-supabase
*/
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
const connectionString = process.env.DATABASE_URL;
if (!connectionString) {
throw new Error('DATABASE_URL is not set');
}
// Disable prefetch as it is not supported for "Transaction" pool mode
const client = postgres(connectionString, { prepare: false });
const db = drizzle(client);
/**
* https://orm.drizzle.team/docs/get-started/neon-new
* Connect to Neon Database
* https://orm.drizzle.team/docs/tutorials/drizzle-with-neon
*/
// import { drizzle } from 'drizzle-orm/neon-http';
// const db = drizzle(process.env.DATABASE_URL!);
/**
* Database connection with Drizzle
* https://orm.drizzle.team/docs/connect-overview
*
* Using the browser-compatible Neon HTTP driver for better compatibility with Next.js
* This avoids the Node.js-specific modules that cause build issues
* Drizzle <> PostgreSQL
* https://orm.drizzle.team/docs/get-started-postgresql
*
* With the neon-http and neon-websockets drivers, you can access a Neon database from serverless environments over HTTP or WebSockets instead of TCP.
* Querying over HTTP is faster for single, non-interactive transactions.
* Get Started with Drizzle and Neon
* https://orm.drizzle.team/docs/get-started/neon-new
*
* Drizzle with Neon Postgres
* https://orm.drizzle.team/docs/tutorials/drizzle-with-neon
*
* Drizzle <> Neon Postgres
* https://orm.drizzle.team/docs/connect-neon
*
* Drizzle with Supabase Database
* https://orm.drizzle.team/docs/tutorials/drizzle-with-supabase
*/
// If you need to provide your existing drivers:
// import { neon } from '@neondatabase/serverless';
// const sql = neon(process.env.DATABASE_URL!);
// const db = drizzle({ client: sql });
// https://orm.drizzle.team/docs/connect-neon
const db = drizzle(process.env.DATABASE_URL!);
export default db;

3
src/lib/auth-types.ts Normal file
View File

@ -0,0 +1,3 @@
import type { auth } from './auth';
export type Session = typeof auth.$Infer.Session;

View File

@ -1,8 +1,14 @@
import 'server-only';
import { headers } from 'next/headers';
import { cache } from 'react';
import 'server-only';
import { auth } from './auth';
/**
* Get the current session
*
* NOTICE: do not call it from middleware
*/
export const getSession = cache(async () => {
const session = await auth.api.getSession({
headers: await headers(),

View File

@ -1,7 +1,8 @@
import { betterFetch } from '@better-fetch/fetch';
import createMiddleware from 'next-intl/middleware';
import { type NextRequest, NextResponse } from 'next/server';
import { LOCALES, routing } from './i18n/routing';
import { getSession } from './lib/server';
import type { Session } from './lib/auth-types';
import {
DEFAULT_LOGIN_REDIRECT,
protectedRoutes,
@ -10,11 +11,31 @@ import {
const intlMiddleware = createMiddleware(routing);
/**
* 1. Next.js middleware
* https://nextjs.org/docs/app/building-your-application/routing/middleware
*
* 2. Better Auth middleware
* https://www.better-auth.com/docs/integrations/next#middleware
*
* In Next.js middleware, it's recommended to only check for the existence of a session cookie
* to handle redirection. To avoid blocking requests by making API or database calls.
*/
export default async function middleware(req: NextRequest) {
const { nextUrl } = req;
const { nextUrl, headers } = req;
console.log('>> middleware start, pathname', nextUrl.pathname);
const session = await getSession();
// do not use getSession() here, it will cause error related to edge runtime
// const session = await getSession();
const { data: session } = await betterFetch<Session>(
'/api/auth/get-session',
{
baseURL: req.nextUrl.origin,
headers: {
cookie: req.headers.get('cookie') || '', // Forward the cookies from the request
},
}
);
const isLoggedIn = !!session;
// console.log('middleware, isLoggedIn', isLoggedIn);
@ -81,8 +102,8 @@ export const config = {
// The `matcher` is relative to the `basePath`
matcher: [
// Match all pathnames except for
// - if they start with `/api`, `/_next` or `/_vercel`
// - … the ones containing a dot (e.g. `favicon.ico`)
// - if they start with `/api`, `/_next` or `/_vercel`
// - if they contain a dot (e.g. `favicon.ico`)
'/((?!api|_next|_vercel|.*\\..*).*)',
],
};

View File

@ -73,6 +73,7 @@ export const routesNotAllowedByLoggedInUsers = [Routes.Login, Routes.Register];
*/
export const protectedRoutes = [
Routes.Dashboard,
Routes.AdminUsers,
Routes.SettingsProfile,
Routes.SettingsBilling,
Routes.SettingsSecurity,