@thinhnguyencth1204/nextcli 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -47
- package/dist/cli.js +1002 -753
- package/package.json +4 -2
- package/templates/{next-base/src/lib/axios-instance.ts → features/api/src/lib/api/axios.ts} +7 -2
- package/templates/{next-base/src/lib/api-response.ts → features/api/src/lib/api/response.ts} +1 -5
- package/templates/{next-base → features/auth}/src/app/(auth)/change-password/layout.tsx +1 -1
- package/templates/{next-base → features/auth}/src/app/(auth)/sign-in/layout.tsx +1 -1
- package/templates/{next-base → features/auth}/src/app/api/v1/auth/change-password/route.ts +3 -3
- package/templates/{next-base → features/auth}/src/app/api/v1/auth/login/route.ts +3 -3
- package/templates/{next-base → features/auth}/src/app/api/v1/auth/logout/route.ts +2 -2
- package/templates/{next-base → features/auth}/src/app/api/v1/auth/me/route.ts +2 -2
- package/templates/{next-base → features/auth}/src/app/api/v1/auth/refresh/route.ts +2 -2
- package/templates/{next-base → features/auth}/src/app/api/v1/users/[id]/route.ts +3 -3
- package/templates/{next-base → features/auth}/src/app/api/v1/users/route.ts +3 -3
- package/templates/{next-base → features/auth}/src/features/auth/components/account-panel.tsx +1 -1
- package/templates/{next-base → features/auth}/src/features/auth/components/change-password-form.tsx +1 -1
- package/templates/{next-base → features/auth}/src/features/auth/components/sign-in-form.tsx +2 -2
- package/templates/{next-base → features/auth}/src/features/users/services.ts +1 -1
- package/templates/{next-base → features/auth}/src/instrumentation.ts +1 -1
- package/templates/{next-base/src/lib → features/auth/src/lib/auth}/bootstrap.ts +2 -3
- package/templates/features/auth/src/lib/auth/index.ts +1 -0
- package/templates/{next-base/src/lib → features/auth/src/lib/auth}/rbac.ts +2 -5
- package/templates/{next-base/src/lib/auth.ts → features/auth/src/lib/auth/server.ts} +2 -1
- package/templates/{next-base → features/auth}/src/lib/constants.ts +3 -0
- package/templates/features/chat/src/app/api/v1/chat/route.ts +1 -1
- package/templates/features/chat/src/features/chat/api/use-chat-history.ts +1 -1
- package/templates/features/chat/src/features/chat/api/use-send-message.ts +1 -1
- package/templates/{next-base → features/dashboard}/src/app/(dashboard)/layout.tsx +1 -1
- package/templates/features/dashboard/src/app/page.tsx +5 -0
- package/templates/{next-base → features/dashboard}/src/components/layout/private/nav-user.tsx +1 -1
- package/templates/{next-base → features/database}/prisma/schema.prisma +0 -12
- package/templates/{next-base → features/database}/prisma.config.ts +2 -2
- package/templates/features/database/src/lib/prisma.ts +23 -0
- package/templates/{next-base → features/example}/src/app/api/v1/example/route.ts +2 -2
- package/templates/{next-base → features/example}/src/example/api/use-example.ts +1 -1
- package/templates/{next-base → features/example}/src/example/api/use-mutations.ts +1 -1
- package/templates/{next-base → features/example}/src/example/services.ts +1 -1
- package/templates/features/i18n/next.config.ts +17 -0
- package/templates/features/i18n/src/app/layout.tsx +42 -0
- package/templates/next-base/.env +0 -14
- package/templates/next-base/.env.development +0 -14
- package/templates/next-base/.env.example +0 -14
- package/templates/next-base/PROJECT_STRUCTURE.md +33 -55
- package/templates/next-base/SETUP.md +12 -60
- package/templates/next-base/bun.lock +17 -0
- package/templates/next-base/next.config.ts +1 -4
- package/templates/next-base/nextcli.json +3 -3
- package/templates/next-base/package.json +1 -21
- package/templates/next-base/src/app/layout.tsx +6 -14
- package/templates/next-base/src/app/page.tsx +25 -2
- package/templates/next-base/prisma/migrations/20260612000000_init/migration.sql +0 -104
- package/templates/next-base/prisma/migrations/migration_lock.toml +0 -3
- package/templates/next-base/src/app/(auth)/.gitkeep +0 -1
- /package/templates/{next-base → features/api}/src/components/providers/query-provider.tsx +0 -0
- /package/templates/{next-base/src/lib → features/api/src/lib/api}/token-store.ts +0 -0
- /package/templates/{next-base → features/auth}/messages/vi/auth.json +0 -0
- /package/templates/{next-base/prisma/migrations → features/auth/src/app/(auth)}/.gitkeep +0 -0
- /package/templates/{next-base → features/auth}/src/app/(auth)/change-password/page.tsx +0 -0
- /package/templates/{next-base → features/auth}/src/app/(auth)/layout.tsx +0 -0
- /package/templates/{next-base → features/auth}/src/app/(auth)/sign-in/page.tsx +0 -0
- /package/templates/{next-base → features/auth}/src/app/api/auth/[...all]/route.ts +0 -0
- /package/templates/{next-base → features/auth}/src/features/auth/validations.ts +0 -0
- /package/templates/{next-base → features/auth}/src/features/users/validations.ts +0 -0
- /package/templates/{next-base/src/lib/auth-client.ts → features/auth/src/lib/auth/client.ts} +0 -0
- /package/templates/{next-base/src/lib/auth-cookies.ts → features/auth/src/lib/auth/cookies.ts} +0 -0
- /package/templates/{next-base → features/dashboard}/src/app/(dashboard)/account/page.tsx +0 -0
- /package/templates/{next-base → features/dashboard}/src/app/(dashboard)/dashboard/page.tsx +0 -0
- /package/templates/{next-base → features/dashboard}/src/components/layout/private/app-sidebar.tsx +0 -0
- /package/templates/{next-base → features/dashboard}/src/components/layout/private/dashboard-layout.tsx +0 -0
- /package/templates/{next-base → features/dashboard}/src/components/layout/private/nav-sidebar.tsx +0 -0
- /package/templates/{next-base → features/dashboard}/src/components/ui/data-table/data-table-column-header.tsx +0 -0
- /package/templates/{next-base → features/dashboard}/src/components/ui/data-table/data-table-filter-list.tsx +0 -0
- /package/templates/{next-base → features/dashboard}/src/components/ui/data-table/data-table-pagination.tsx +0 -0
- /package/templates/{next-base → features/dashboard}/src/components/ui/data-table/data-table-skeleton.tsx +0 -0
- /package/templates/{next-base → features/dashboard}/src/components/ui/data-table/data-table-toolbar.tsx +0 -0
- /package/templates/{next-base → features/dashboard}/src/components/ui/data-table/data-table-view-options.tsx +0 -0
- /package/templates/{next-base → features/dashboard}/src/components/ui/data-table/data-table.tsx +0 -0
- /package/templates/{next-base → features/dashboard}/src/components/ui/sidebar.tsx +0 -0
- /package/templates/{next-base → features/dashboard}/src/data/sidebar-modules.ts +0 -0
- /package/templates/{next-base → features/dashboard}/src/hooks/table/use-data-table.ts +0 -0
- /package/templates/{next-base → features/dashboard}/src/hooks/use-mobile.ts +0 -0
- /package/templates/{next-base → features/dashboard}/src/types/data-table.ts +0 -0
- /package/templates/{next-base/src/lib → features/database/src/lib/db}/prisma.ts +0 -0
- /package/templates/{next-base → features/example}/messages/vi/example.json +0 -0
- /package/templates/{next-base → features/example}/src/app/(dashboard)/example/page.tsx +0 -0
- /package/templates/{next-base → features/example}/src/example/components/example-table.tsx +0 -0
- /package/templates/{next-base → features/example}/src/example/validations.ts +0 -0
- /package/templates/{next-base → features/i18n}/messages/vi/common.json +0 -0
- /package/templates/{next-base → features/i18n}/src/components/layout/private/locale-switcher.tsx +0 -0
- /package/templates/{next-base → features/i18n}/src/i18n/config.ts +0 -0
- /package/templates/{next-base → features/i18n}/src/i18n/namespaces.ts +0 -0
- /package/templates/{next-base → features/i18n}/src/i18n/request.ts +0 -0
- /package/templates/{next-base → features/supabase}/src/lib/supabase/client.ts +0 -0
- /package/templates/{next-base → features/supabase}/src/lib/supabase/storage-config.ts +0 -0
- /package/templates/{next-base → features/supabase}/src/lib/supabase/storage.ts +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thinhnguyencth1204/nextcli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "CLI scaffolder for outsourced Next.js projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
"build": "tsup",
|
|
16
16
|
"dev": "tsup --watch",
|
|
17
17
|
"typecheck": "tsc --noEmit",
|
|
18
|
+
"test": "bun test src/core/add-feature.test.ts",
|
|
19
|
+
"test:add-feature": "bun run build && bun test src/core/add-feature.test.ts",
|
|
18
20
|
"smoke": "node dist/cli.js --help",
|
|
19
|
-
"smoke:full": "bun run scripts/pre-publish-smoke.ts",
|
|
21
|
+
"smoke:full": "bun run build && bun run test && bun run scripts/pre-publish-smoke.ts",
|
|
20
22
|
"prepublishOnly": "npm run build"
|
|
21
23
|
},
|
|
22
24
|
"keywords": [
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
clearAccessToken,
|
|
4
|
+
getAccessToken,
|
|
5
|
+
setAccessToken,
|
|
6
|
+
} from "@/lib/api/token-store";
|
|
3
7
|
import type { ApiErrorResponse, ApiSuccess } from "@/types";
|
|
4
8
|
|
|
5
9
|
const baseURL = process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000";
|
|
@@ -48,7 +52,8 @@ async function refreshAccessToken(): Promise<string | null> {
|
|
|
48
52
|
const response = await publicApi.post("/api/v1/auth/refresh", null, {
|
|
49
53
|
withCredentials: true,
|
|
50
54
|
});
|
|
51
|
-
const token = (response.data as ApiSuccess<{ accessToken: string }>).data
|
|
55
|
+
const token = (response.data as ApiSuccess<{ accessToken: string }>).data
|
|
56
|
+
?.accessToken;
|
|
52
57
|
if (!token) {
|
|
53
58
|
clearAccessToken();
|
|
54
59
|
return null;
|
package/templates/{next-base/src/lib/api-response.ts → features/api/src/lib/api/response.ts}
RENAMED
|
@@ -30,11 +30,7 @@ export function ok<T>(data: T, init: SuccessInit = {}) {
|
|
|
30
30
|
return NextResponse.json(payload, { status: init.status ?? 200 });
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
export function fail(
|
|
34
|
-
code: ErrorCode,
|
|
35
|
-
message: string,
|
|
36
|
-
init: FailInit = {},
|
|
37
|
-
) {
|
|
33
|
+
export function fail(code: ErrorCode, message: string, init: FailInit = {}) {
|
|
38
34
|
const payload: ApiErrorResponse = {
|
|
39
35
|
success: false,
|
|
40
36
|
error: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import { redirect } from "next/navigation";
|
|
3
|
-
import { getSessionUser } from "@/lib/rbac";
|
|
3
|
+
import { getSessionUser } from "@/lib/auth/rbac";
|
|
4
4
|
|
|
5
5
|
export default async function SignInLayout({ children }: { children: ReactNode }) {
|
|
6
6
|
const user = await getSessionUser();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { changePasswordSchema } from "@/features/auth/validations";
|
|
2
|
-
import { fail, ok } from "@/lib/api
|
|
2
|
+
import { fail, ok } from "@/lib/api/response";
|
|
3
3
|
import { auth } from "@/lib/auth";
|
|
4
|
-
import { getSessionUser } from "@/lib/rbac";
|
|
5
|
-
import prisma from "@/lib/prisma";
|
|
4
|
+
import { getSessionUser } from "@/lib/auth/rbac";
|
|
5
|
+
import prisma from "@/lib/db/prisma";
|
|
6
6
|
|
|
7
7
|
const authBaseUrl =
|
|
8
8
|
process.env.BETTER_AUTH_URL ??
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { getRefreshCookieName, refreshCookieOptions } from "@/lib/auth
|
|
2
|
-
import { fail, ok } from "@/lib/api
|
|
3
|
-
import prisma from "@/lib/prisma";
|
|
1
|
+
import { getRefreshCookieName, refreshCookieOptions } from "@/lib/auth/cookies";
|
|
2
|
+
import { fail, ok } from "@/lib/api/response";
|
|
3
|
+
import prisma from "@/lib/db/prisma";
|
|
4
4
|
|
|
5
5
|
const authBaseUrl =
|
|
6
6
|
process.env.BETTER_AUTH_URL ??
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { getRefreshCookieName, refreshCookieOptions } from "@/lib/auth
|
|
2
|
-
import { fail, ok } from "@/lib/api
|
|
1
|
+
import { getRefreshCookieName, refreshCookieOptions } from "@/lib/auth/cookies";
|
|
2
|
+
import { fail, ok } from "@/lib/api/response";
|
|
3
3
|
|
|
4
4
|
const authBaseUrl =
|
|
5
5
|
process.env.BETTER_AUTH_URL ??
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { fail, ok } from "@/lib/api
|
|
2
|
-
import { getSessionUser } from "@/lib/rbac";
|
|
1
|
+
import { fail, ok } from "@/lib/api/response";
|
|
2
|
+
import { getSessionUser } from "@/lib/auth/rbac";
|
|
3
3
|
|
|
4
4
|
export async function GET(request: Request) {
|
|
5
5
|
const user = await getSessionUser(request.headers);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { getRefreshCookieName } from "@/lib/auth
|
|
2
|
-
import { fail, ok } from "@/lib/api
|
|
1
|
+
import { getRefreshCookieName } from "@/lib/auth/cookies";
|
|
2
|
+
import { fail, ok } from "@/lib/api/response";
|
|
3
3
|
|
|
4
4
|
const authBaseUrl =
|
|
5
5
|
process.env.BETTER_AUTH_URL ??
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
getUserByIdForActor,
|
|
5
5
|
updateUserRecord,
|
|
6
6
|
} from "@/features/users/services";
|
|
7
|
-
import { fail, ok } from "@/lib/api
|
|
7
|
+
import { fail, ok } from "@/lib/api/response";
|
|
8
8
|
import {
|
|
9
9
|
canActOnUser,
|
|
10
10
|
canAssignRole,
|
|
11
11
|
getSessionUser,
|
|
12
12
|
isSuperAdmin,
|
|
13
|
-
} from "@/lib/rbac";
|
|
14
|
-
import prisma from "@/lib/prisma";
|
|
13
|
+
} from "@/lib/auth/rbac";
|
|
14
|
+
import prisma from "@/lib/db/prisma";
|
|
15
15
|
|
|
16
16
|
type RouteContext = {
|
|
17
17
|
params: Promise<{ id: string }>;
|
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
createUserRecord,
|
|
4
4
|
listUsersForActor,
|
|
5
5
|
} from "@/features/users/services";
|
|
6
|
-
import { fail, ok } from "@/lib/api
|
|
7
|
-
import { canAssignRole, getSessionUser } from "@/lib/rbac";
|
|
8
|
-
import prisma from "@/lib/prisma";
|
|
6
|
+
import { fail, ok } from "@/lib/api/response";
|
|
7
|
+
import { canAssignRole, getSessionUser } from "@/lib/auth/rbac";
|
|
8
|
+
import prisma from "@/lib/db/prisma";
|
|
9
9
|
|
|
10
10
|
export async function GET(request: Request) {
|
|
11
11
|
const actor = await getSessionUser(request.headers);
|
package/templates/{next-base → features/auth}/src/features/auth/components/account-panel.tsx
RENAMED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
4
|
import { useTranslations } from "next-intl";
|
|
5
|
-
import { protectedApi } from "@/lib/axios
|
|
5
|
+
import { protectedApi } from "@/lib/api/axios";
|
|
6
6
|
import type { ApiSuccess } from "@/types";
|
|
7
7
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
8
8
|
|
package/templates/{next-base → features/auth}/src/features/auth/components/change-password-form.tsx
RENAMED
|
@@ -5,7 +5,7 @@ import type { FormEvent } from "react";
|
|
|
5
5
|
import { useRouter } from "next/navigation";
|
|
6
6
|
import { useTranslations } from "next-intl";
|
|
7
7
|
import { toast } from "sonner";
|
|
8
|
-
import { protectedApi } from "@/lib/axios
|
|
8
|
+
import { protectedApi } from "@/lib/api/axios";
|
|
9
9
|
import { changePasswordSchema } from "@/features/auth/validations";
|
|
10
10
|
import { Button } from "@/components/ui/button";
|
|
11
11
|
import { Card, CardContent } from "@/components/ui/card";
|
|
@@ -5,8 +5,8 @@ import type { FormEvent } from "react";
|
|
|
5
5
|
import { useRouter } from "next/navigation";
|
|
6
6
|
import { useTranslations } from "next-intl";
|
|
7
7
|
import { toast } from "sonner";
|
|
8
|
-
import { publicApi } from "@/lib/axios
|
|
9
|
-
import { setAccessToken } from "@/lib/token-store";
|
|
8
|
+
import { publicApi } from "@/lib/api/axios";
|
|
9
|
+
import { setAccessToken } from "@/lib/api/token-store";
|
|
10
10
|
import { signInSchema } from "@/features/auth/validations";
|
|
11
11
|
import type { ApiSuccess } from "@/types";
|
|
12
12
|
import { Button } from "@/components/ui/button";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Role, User } from "@prisma/client";
|
|
2
2
|
import { INTERNAL_EMAIL_DOMAIN, SUPER_ADMIN_USERNAME } from "@/lib/constants";
|
|
3
3
|
import { auth } from "@/lib/auth";
|
|
4
|
-
import prisma from "@/lib/prisma";
|
|
4
|
+
import prisma from "@/lib/db/prisma";
|
|
5
5
|
import type { CreateUserInput, UpdateUserInput } from "@/features/users/validations";
|
|
6
6
|
|
|
7
7
|
export type UserWithRole = User & { role: Role | null };
|
|
@@ -2,13 +2,12 @@ import type { PrismaClient } from "@prisma/client";
|
|
|
2
2
|
import {
|
|
3
3
|
ADMIN_ROLE_LEVEL,
|
|
4
4
|
ADMIN_ROLE_NAME,
|
|
5
|
+
DEFAULT_ADMIN_PASSWORD,
|
|
5
6
|
INTERNAL_EMAIL_DOMAIN,
|
|
6
7
|
SUPER_ADMIN_USERNAME,
|
|
7
8
|
} from "@/lib/constants";
|
|
8
9
|
import { auth } from "@/lib/auth";
|
|
9
|
-
import prisma from "@/lib/prisma";
|
|
10
|
-
|
|
11
|
-
const DEFAULT_ADMIN_PASSWORD = "admin";
|
|
10
|
+
import prisma from "@/lib/db/prisma";
|
|
12
11
|
|
|
13
12
|
function hasRoleModel(client: PrismaClient): boolean {
|
|
14
13
|
const delegate = (
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { auth } from "./server";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { headers } from "next/headers";
|
|
2
2
|
import type { Role, User } from "@prisma/client";
|
|
3
3
|
import { auth } from "@/lib/auth";
|
|
4
|
-
import prisma from "@/lib/prisma";
|
|
4
|
+
import prisma from "@/lib/db/prisma";
|
|
5
5
|
import { SUPER_ADMIN_USERNAME } from "@/lib/constants";
|
|
6
6
|
|
|
7
7
|
export type SessionUser = User & { role: Role | null };
|
|
@@ -27,10 +27,7 @@ export function isSuperAdmin(user: Pick<User, "username">): boolean {
|
|
|
27
27
|
return user.username === SUPER_ADMIN_USERNAME;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
export function canActOnUser(
|
|
31
|
-
actor: SessionUser,
|
|
32
|
-
target: SessionUser,
|
|
33
|
-
): boolean {
|
|
30
|
+
export function canActOnUser(actor: SessionUser, target: SessionUser): boolean {
|
|
34
31
|
if (isSuperAdmin(target)) {
|
|
35
32
|
return false;
|
|
36
33
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { betterAuth } from "better-auth/minimal";
|
|
2
2
|
import { prismaAdapter } from "better-auth/adapters/prisma";
|
|
3
3
|
import { jwt, username } from "better-auth/plugins";
|
|
4
|
-
import prisma from "@/lib/prisma";
|
|
4
|
+
import prisma from "@/lib/db/prisma";
|
|
5
5
|
|
|
6
6
|
const socialProviders = {
|
|
7
7
|
// AUTO_GENERATED_AUTH_PROVIDERS_START
|
|
@@ -15,6 +15,7 @@ export const auth = betterAuth({
|
|
|
15
15
|
plugins: [jwt(), username()],
|
|
16
16
|
emailAndPassword: {
|
|
17
17
|
enabled: true,
|
|
18
|
+
minPasswordLength: 8,
|
|
18
19
|
},
|
|
19
20
|
socialProviders,
|
|
20
21
|
});
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/** Built-in super-admin account username — protected from RBAC mutations. */
|
|
2
2
|
export const SUPER_ADMIN_USERNAME = "admin";
|
|
3
3
|
|
|
4
|
+
/** Dev-only bootstrap password (≥8 chars for Better Auth). Change on first login. */
|
|
5
|
+
export const DEFAULT_ADMIN_PASSWORD = "admin1234";
|
|
6
|
+
|
|
4
7
|
export const ADMIN_ROLE_NAME = "admin";
|
|
5
8
|
export const ADMIN_ROLE_LEVEL = 100;
|
|
6
9
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import { redirect } from "next/navigation";
|
|
3
3
|
import { DashboardLayout } from "@/components/layout/private/dashboard-layout";
|
|
4
|
-
import { getSessionUser } from "@/lib/rbac";
|
|
4
|
+
import { getSessionUser } from "@/lib/auth/rbac";
|
|
5
5
|
|
|
6
6
|
export default async function DashboardRouteLayout({
|
|
7
7
|
children,
|
package/templates/{next-base → features/dashboard}/src/components/layout/private/nav-user.tsx
RENAMED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { LogOut, Monitor, Moon, Sun } from "lucide-react";
|
|
4
4
|
import { useTheme } from "next-themes";
|
|
5
5
|
import { useTranslations } from "next-intl";
|
|
6
|
-
import { authClient } from "@/lib/auth
|
|
6
|
+
import { authClient } from "@/lib/auth/client";
|
|
7
7
|
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
|
8
8
|
import { Button } from "@/components/ui/button";
|
|
9
9
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
@@ -31,18 +31,6 @@ model User {
|
|
|
31
31
|
sessions Session[]
|
|
32
32
|
accounts Account[]
|
|
33
33
|
}
|
|
34
|
-
|
|
35
|
-
// Starter demo table for template onboarding only.
|
|
36
|
-
// Rename/remove this `Example` model before production migration.
|
|
37
|
-
// Keep this block under review to avoid creating unwanted prod tables.
|
|
38
|
-
model Example {
|
|
39
|
-
id String @id @default(cuid())
|
|
40
|
-
name String
|
|
41
|
-
description String?
|
|
42
|
-
createdAt DateTime @default(now())
|
|
43
|
-
updatedAt DateTime @updatedAt
|
|
44
|
-
}
|
|
45
|
-
|
|
46
34
|
model Session {
|
|
47
35
|
id String @id @default(cuid())
|
|
48
36
|
token String @unique
|
|
@@ -2,7 +2,7 @@ import "dotenv/config";
|
|
|
2
2
|
import { defineConfig, env } from "prisma/config";
|
|
3
3
|
|
|
4
4
|
type Env = {
|
|
5
|
-
|
|
5
|
+
DIRECT_URL: string;
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
export default defineConfig({
|
|
@@ -11,6 +11,6 @@ export default defineConfig({
|
|
|
11
11
|
path: "prisma/migrations",
|
|
12
12
|
},
|
|
13
13
|
datasource: {
|
|
14
|
-
url: env<Env>("
|
|
14
|
+
url: env<Env>("DIRECT_URL"),
|
|
15
15
|
},
|
|
16
16
|
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { PrismaPg } from "@prisma/adapter-pg";
|
|
2
|
+
import { PrismaClient } from "@prisma/client";
|
|
3
|
+
import { Pool } from "pg";
|
|
4
|
+
|
|
5
|
+
declare global {
|
|
6
|
+
var prisma: PrismaClient | undefined;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const connectionString = process.env.DATABASE_URL;
|
|
10
|
+
if (!connectionString) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
"DATABASE_URL is missing. Please set it in your environment.",
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const adapter = new PrismaPg(new Pool({ connectionString }));
|
|
17
|
+
const prisma = global.prisma ?? new PrismaClient({ adapter });
|
|
18
|
+
|
|
19
|
+
if (process.env.NODE_ENV !== "production") {
|
|
20
|
+
global.prisma = prisma;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default prisma;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createExampleSchema } from "@/example/validations";
|
|
2
2
|
import { listExamples } from "@/example/services";
|
|
3
|
-
import { fail, ok } from "@/lib/api
|
|
4
|
-
import prisma from "@/lib/prisma";
|
|
3
|
+
import { fail, ok } from "@/lib/api/response";
|
|
4
|
+
import prisma from "@/lib/db/prisma";
|
|
5
5
|
|
|
6
6
|
export async function GET() {
|
|
7
7
|
try {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
4
|
-
import { api } from "@/lib/axios
|
|
4
|
+
import { api } from "@/lib/api/axios";
|
|
5
5
|
import type { CreateExampleInput } from "@/example/validations";
|
|
6
6
|
import type { ApiSuccess } from "@/types";
|
|
7
7
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { NextConfig } from "next";
|
|
2
|
+
import createNextIntlPlugin from "next-intl/plugin";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const rootDir = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
const nextConfig: NextConfig = {
|
|
9
|
+
reactStrictMode: true,
|
|
10
|
+
turbopack: {
|
|
11
|
+
root: rootDir,
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts");
|
|
16
|
+
|
|
17
|
+
export default withNextIntl(nextConfig);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { Be_Vietnam_Pro } from "next/font/google";
|
|
3
|
+
import { getLocale, getMessages } from "next-intl/server";
|
|
4
|
+
import { NextIntlClientProvider } from "next-intl";
|
|
5
|
+
import { Toaster } from "sonner";
|
|
6
|
+
import type { ReactNode } from "react";
|
|
7
|
+
import { QueryProvider } from "@/components/providers/query-provider";
|
|
8
|
+
import { ThemeProvider } from "@/components/providers/theme-provider";
|
|
9
|
+
import { branding } from "@/config/branding";
|
|
10
|
+
import "@/app/globals.css";
|
|
11
|
+
|
|
12
|
+
const beVietnamPro = Be_Vietnam_Pro({
|
|
13
|
+
subsets: ["latin", "vietnamese"],
|
|
14
|
+
weight: ["400", "500", "600", "700"],
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const metadata: Metadata = {
|
|
18
|
+
title: branding.projectName,
|
|
19
|
+
description: branding.description,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default async function RootLayout({
|
|
23
|
+
children,
|
|
24
|
+
}: Readonly<{
|
|
25
|
+
children: ReactNode;
|
|
26
|
+
}>) {
|
|
27
|
+
const locale = await getLocale();
|
|
28
|
+
const messages = await getMessages();
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<html lang={locale} suppressHydrationWarning>
|
|
32
|
+
<body className={beVietnamPro.className}>
|
|
33
|
+
<NextIntlClientProvider locale={locale} messages={messages}>
|
|
34
|
+
<ThemeProvider attribute="class" defaultTheme="light" enableSystem>
|
|
35
|
+
<QueryProvider>{children}</QueryProvider>
|
|
36
|
+
<Toaster richColors position="top-right" />
|
|
37
|
+
</ThemeProvider>
|
|
38
|
+
</NextIntlClientProvider>
|
|
39
|
+
</body>
|
|
40
|
+
</html>
|
|
41
|
+
);
|
|
42
|
+
}
|
package/templates/next-base/.env
CHANGED
|
@@ -1,16 +1,2 @@
|
|
|
1
|
-
# --- Database (Supabase) ---
|
|
2
|
-
DATABASE_URL="postgresql://postgres.your-project-ref:your-supabase-db-password@aws-0-us-east-1.pooler.supabase.com:6543/postgres?pgbouncer=true"
|
|
3
|
-
DIRECT_URL="postgresql://postgres.your-project-ref:your-supabase-db-password@aws-0-us-east-1.pooler.supabase.com:5432/postgres"
|
|
4
|
-
NEXT_PUBLIC_SUPABASE_URL=""
|
|
5
|
-
# Public/browser-safe anon key from Supabase Project Settings -> API -> anon public
|
|
6
|
-
NEXT_PUBLIC_SUPABASE_ANON_KEY=""
|
|
7
|
-
# Storage bucket name used by the app (default scaffold bucket name: public)
|
|
8
|
-
NEXT_PUBLIC_SUPABASE_STORAGE_BUCKET="public"
|
|
9
|
-
|
|
10
|
-
# --- Auth ---
|
|
11
|
-
BETTER_AUTH_SECRET="__BETTER_AUTH_SECRET__"
|
|
12
|
-
BETTER_AUTH_URL="http://localhost:3000"
|
|
13
|
-
|
|
14
1
|
# --- App ---
|
|
15
2
|
NEXT_PUBLIC_APP_URL="http://localhost:3000"
|
|
16
|
-
NEXT_PUBLIC_DEFAULT_LOCALE="vi"
|
|
@@ -1,16 +1,2 @@
|
|
|
1
|
-
# --- Database (Supabase) ---
|
|
2
|
-
DATABASE_URL="postgresql://postgres.your-project-ref:your-supabase-db-password@aws-0-us-east-1.pooler.supabase.com:6543/postgres?pgbouncer=true"
|
|
3
|
-
DIRECT_URL="postgresql://postgres.your-project-ref:your-supabase-db-password@aws-0-us-east-1.pooler.supabase.com:5432/postgres"
|
|
4
|
-
NEXT_PUBLIC_SUPABASE_URL=""
|
|
5
|
-
# Public/browser-safe anon key from Supabase Project Settings -> API -> anon public
|
|
6
|
-
NEXT_PUBLIC_SUPABASE_ANON_KEY=""
|
|
7
|
-
# Storage bucket name used by the app (default scaffold bucket name: public)
|
|
8
|
-
NEXT_PUBLIC_SUPABASE_STORAGE_BUCKET="public"
|
|
9
|
-
|
|
10
|
-
# --- Auth ---
|
|
11
|
-
BETTER_AUTH_SECRET="__BETTER_AUTH_SECRET__"
|
|
12
|
-
BETTER_AUTH_URL="http://localhost:3000"
|
|
13
|
-
|
|
14
1
|
# --- App ---
|
|
15
2
|
NEXT_PUBLIC_APP_URL="http://localhost:3000"
|
|
16
|
-
NEXT_PUBLIC_DEFAULT_LOCALE="vi"
|
|
@@ -1,16 +1,2 @@
|
|
|
1
|
-
# --- Database (Supabase) ---
|
|
2
|
-
DATABASE_URL="postgresql://postgres.your-project-ref:your-supabase-db-password@aws-0-us-east-1.pooler.supabase.com:6543/postgres?pgbouncer=true"
|
|
3
|
-
DIRECT_URL="postgresql://postgres.your-project-ref:your-supabase-db-password@aws-0-us-east-1.pooler.supabase.com:5432/postgres"
|
|
4
|
-
NEXT_PUBLIC_SUPABASE_URL=""
|
|
5
|
-
# Public/browser-safe anon key from Supabase Project Settings -> API -> anon public
|
|
6
|
-
NEXT_PUBLIC_SUPABASE_ANON_KEY=""
|
|
7
|
-
# Storage bucket name used by the app (default scaffold bucket name: public)
|
|
8
|
-
NEXT_PUBLIC_SUPABASE_STORAGE_BUCKET="public"
|
|
9
|
-
|
|
10
|
-
# --- Auth ---
|
|
11
|
-
BETTER_AUTH_SECRET="your-32-plus-char-random-secret"
|
|
12
|
-
BETTER_AUTH_URL="http://localhost:3000"
|
|
13
|
-
|
|
14
1
|
# --- App ---
|
|
15
2
|
NEXT_PUBLIC_APP_URL="http://localhost:3000"
|
|
16
|
-
NEXT_PUBLIC_DEFAULT_LOCALE="vi"
|