create-pardx-scaffold 0.1.7 → 0.1.8
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/package.json +1 -1
- package/template/apps/api/prisma.config.ts +29 -0
- package/template/apps/web/i18n/config.ts +0 -1
- package/template/apps/web/i18n/types.ts +0 -3
- package/template/apps/web/lib/analytics/components/PageTracker.tsx +13 -11
- package/template/apps/web/proxy.ts +1 -41
- package/template/packages/utils/string.util.ts +0 -8
package/package.json
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prisma 7.x Configuration File
|
|
3
|
+
*
|
|
4
|
+
* This file is used by Prisma CLI commands (migrate, db push, etc.)
|
|
5
|
+
* For PrismaClient initialization, the URL is passed via the driver adapter
|
|
6
|
+
* in PrismaWriteService and PrismaReadService.
|
|
7
|
+
*
|
|
8
|
+
* 注意: 生成器配置(如 output 路径)需要在 schema.prisma 中配置,
|
|
9
|
+
* 不在此文件中配置。
|
|
10
|
+
*
|
|
11
|
+
* @see https://www.prisma.io/docs/orm/more/upgrade-guides/upgrading-versions/upgrading-to-prisma-7
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import * as dotenv from 'dotenv';
|
|
15
|
+
import * as dotenvExpand from 'dotenv-expand';
|
|
16
|
+
import { defineConfig, env } from 'prisma/config';
|
|
17
|
+
|
|
18
|
+
// 使用 dotenv-expand 展开环境变量(如 ${BASE_HOST})
|
|
19
|
+
dotenvExpand.expand(dotenv.config());
|
|
20
|
+
|
|
21
|
+
export default defineConfig({
|
|
22
|
+
schema: 'prisma/schema.prisma',
|
|
23
|
+
migrations: {
|
|
24
|
+
path: 'prisma/migrations',
|
|
25
|
+
},
|
|
26
|
+
datasource: {
|
|
27
|
+
url: env('DATABASE_URL'),
|
|
28
|
+
},
|
|
29
|
+
});
|
|
@@ -20,7 +20,6 @@ import type zhCNSettings from '../locales/zh-CN/settings.json';
|
|
|
20
20
|
import type zhCNSubscription from '../locales/zh-CN/subscription.json';
|
|
21
21
|
import type zhCNRecommendation from '../locales/zh-CN/recommendation.json';
|
|
22
22
|
import type zhCNMemory from '../locales/zh-CN/memory.json';
|
|
23
|
-
import type zhCNDailyChallenge from '../locales/zh-CN/daily-challenge.json';
|
|
24
23
|
|
|
25
24
|
/**
|
|
26
25
|
* 所有翻译消息的类型定义
|
|
@@ -39,7 +38,6 @@ export interface AppMessages {
|
|
|
39
38
|
subscription: typeof zhCNSubscription;
|
|
40
39
|
recommendation: typeof zhCNRecommendation;
|
|
41
40
|
memory: typeof zhCNMemory;
|
|
42
|
-
'daily-challenge': typeof zhCNDailyChallenge;
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
/**
|
|
@@ -49,7 +47,6 @@ export interface AppMessages {
|
|
|
49
47
|
declare global {
|
|
50
48
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
51
49
|
namespace IntlMessages {
|
|
52
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
53
50
|
interface Messages extends AppMessages {}
|
|
54
51
|
}
|
|
55
52
|
}
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
import { useEffect } from 'react';
|
|
24
24
|
import { usePathname, useSearchParams } from 'next/navigation';
|
|
25
25
|
import { analytics } from '../index';
|
|
26
|
+
import type { EventProperties } from '@repo/contracts/schemas/analytics.schema';
|
|
26
27
|
|
|
27
28
|
interface PageTrackerProps {
|
|
28
29
|
/**
|
|
@@ -56,9 +57,10 @@ export function PageTracker({
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
// Build full path with search params if enabled
|
|
59
|
-
const fullPath =
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
const fullPath =
|
|
61
|
+
trackSearchParams && searchParams.toString()
|
|
62
|
+
? `${pathname}?${searchParams.toString()}`
|
|
63
|
+
: pathname;
|
|
62
64
|
|
|
63
65
|
// Get page name (custom or pathname)
|
|
64
66
|
const pageName = pageNameMapper ? pageNameMapper(pathname) : pathname;
|
|
@@ -67,10 +69,13 @@ export function PageTracker({
|
|
|
67
69
|
analytics.pageView(fullPath, pageName);
|
|
68
70
|
|
|
69
71
|
// Track session start on first page view
|
|
70
|
-
if (
|
|
72
|
+
if (
|
|
73
|
+
typeof window !== 'undefined' &&
|
|
74
|
+
!sessionStorage.getItem('session_started')
|
|
75
|
+
) {
|
|
71
76
|
analytics.track('SESSION_START', {
|
|
72
77
|
path: fullPath,
|
|
73
|
-
} as
|
|
78
|
+
} as unknown as EventProperties);
|
|
74
79
|
sessionStorage.setItem('session_started', 'true');
|
|
75
80
|
}
|
|
76
81
|
}, [pathname, searchParams, trackSearchParams, pageNameMapper, excludePaths]);
|
|
@@ -80,7 +85,7 @@ export function PageTracker({
|
|
|
80
85
|
const handleBeforeUnload = () => {
|
|
81
86
|
analytics.track('SESSION_END', {
|
|
82
87
|
path: pathname,
|
|
83
|
-
} as
|
|
88
|
+
} as unknown as EventProperties);
|
|
84
89
|
};
|
|
85
90
|
|
|
86
91
|
window.addEventListener('beforeunload', handleBeforeUnload);
|
|
@@ -96,11 +101,11 @@ export function PageTracker({
|
|
|
96
101
|
if (document.hidden) {
|
|
97
102
|
analytics.track('APP_BACKGROUND', {
|
|
98
103
|
path: pathname,
|
|
99
|
-
} as
|
|
104
|
+
} as unknown as EventProperties);
|
|
100
105
|
} else {
|
|
101
106
|
analytics.track('APP_FOREGROUND', {
|
|
102
107
|
path: pathname,
|
|
103
|
-
} as
|
|
108
|
+
} as unknown as EventProperties);
|
|
104
109
|
}
|
|
105
110
|
};
|
|
106
111
|
|
|
@@ -121,9 +126,6 @@ export const defaultPageNameMapper = (pathname: string): string => {
|
|
|
121
126
|
const routes: Record<string, string> = {
|
|
122
127
|
'/': 'Home',
|
|
123
128
|
'/home': 'Home',
|
|
124
|
-
'/daily-challenge': 'Daily Challenge',
|
|
125
|
-
'/daily-challenge/result': 'Challenge Result',
|
|
126
|
-
'/daily-challenge/history': 'Challenge History',
|
|
127
129
|
'/settings': 'Settings',
|
|
128
130
|
'/login': 'Login',
|
|
129
131
|
'/register': 'Register',
|
|
@@ -32,7 +32,7 @@ export default async function middleware(request: NextRequest) {
|
|
|
32
32
|
const pathWithoutLocale = pathname.replace(/^\/(zh-CN|en)/, '') || '/';
|
|
33
33
|
|
|
34
34
|
// Skip daily check-in logic for public routes
|
|
35
|
-
const publicRoutes = ['/
|
|
35
|
+
const publicRoutes = ['/login', '/register'];
|
|
36
36
|
if (publicRoutes.some((route) => pathWithoutLocale.includes(route))) {
|
|
37
37
|
return intlResponse;
|
|
38
38
|
}
|
|
@@ -43,46 +43,6 @@ export default async function middleware(request: NextRequest) {
|
|
|
43
43
|
return intlResponse;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
// Check today's check-in status
|
|
47
|
-
try {
|
|
48
|
-
const apiUrl =
|
|
49
|
-
process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3100/api';
|
|
50
|
-
const response = await fetch(`${apiUrl}/daily-question/streak`, {
|
|
51
|
-
headers: {
|
|
52
|
-
Authorization: `Bearer ${token}`,
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
if (response.ok) {
|
|
57
|
-
const data = await response.json();
|
|
58
|
-
const streak = data.data;
|
|
59
|
-
|
|
60
|
-
if (streak?.lastCheckInDate) {
|
|
61
|
-
const today = new Date();
|
|
62
|
-
today.setHours(0, 0, 0, 0);
|
|
63
|
-
const lastCheckIn = new Date(streak.lastCheckInDate);
|
|
64
|
-
lastCheckIn.setHours(0, 0, 0, 0);
|
|
65
|
-
|
|
66
|
-
// If not checked in today, redirect to daily challenge
|
|
67
|
-
if (lastCheckIn.getTime() !== today.getTime()) {
|
|
68
|
-
const url = request.nextUrl.clone();
|
|
69
|
-
// 保留语言前缀
|
|
70
|
-
const locale = pathname.match(/^\/(zh-CN|en)/)?.[1] || 'zh-CN';
|
|
71
|
-
url.pathname = `/${locale}/daily-challenge`;
|
|
72
|
-
return NextResponse.redirect(url);
|
|
73
|
-
}
|
|
74
|
-
} else {
|
|
75
|
-
// No check-in history, redirect to daily challenge
|
|
76
|
-
const url = request.nextUrl.clone();
|
|
77
|
-
const locale = pathname.match(/^\/(zh-CN|en)/)?.[1] || 'zh-CN';
|
|
78
|
-
url.pathname = `/${locale}/daily-challenge`;
|
|
79
|
-
return NextResponse.redirect(url);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
} catch (error) {
|
|
83
|
-
console.error('Middleware check-in status error:', error);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
46
|
return intlResponse;
|
|
87
47
|
}
|
|
88
48
|
|
|
@@ -43,14 +43,6 @@ export default {
|
|
|
43
43
|
);
|
|
44
44
|
},
|
|
45
45
|
|
|
46
|
-
escapeMongoRegexSpecialChars(inputString: string): string {
|
|
47
|
-
// MongoDB中的正则表达式特殊字符
|
|
48
|
-
const mongoRegexSpecialChars = /[\.^$*{[\]|}\\+?\-()]/g;
|
|
49
|
-
|
|
50
|
-
// 使用replace函数和回调函数来替换特殊字符
|
|
51
|
-
return inputString.replace(mongoRegexSpecialChars, (match) => `\\${match}`);
|
|
52
|
-
},
|
|
53
|
-
|
|
54
46
|
generateString(input: string, suffixLength = 3): string {
|
|
55
47
|
// 判断是否为中文
|
|
56
48
|
const isChinese = /^[\u4e00-\u9fa5]+$/.test(input);
|