@qlover/create-app 0.7.9 → 0.7.11
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/CHANGELOG.md +184 -0
- package/dist/index.cjs +3 -3
- package/dist/index.js +3 -3
- package/dist/templates/next-app/.env.template +8 -4
- package/dist/templates/next-app/config/IOCIdentifier.ts +4 -1
- package/dist/templates/next-app/config/Identifier/api.ts +34 -0
- package/dist/templates/next-app/config/Identifier/common.ts +7 -0
- package/dist/templates/next-app/config/Identifier/index.ts +2 -0
- package/dist/templates/next-app/config/Identifier/page.login.ts +2 -2
- package/dist/templates/next-app/config/Identifier/page.register.ts +43 -22
- package/dist/templates/next-app/config/Identifier/validator.ts +34 -0
- package/dist/templates/next-app/config/i18n/index.ts +1 -0
- package/dist/templates/next-app/config/i18n/register18n.ts +44 -0
- package/dist/templates/next-app/eslint.config.mjs +17 -0
- package/dist/templates/next-app/migrations/schema/UserSchema.ts +24 -0
- package/dist/templates/next-app/migrations/sql/1694244000000.sql +10 -0
- package/dist/templates/next-app/next.config.ts +1 -0
- package/dist/templates/next-app/package.json +12 -2
- package/dist/templates/next-app/public/locales/en.json +19 -2
- package/dist/templates/next-app/public/locales/zh.json +19 -2
- package/dist/templates/next-app/src/app/[locale]/admin/layout.tsx +18 -0
- package/dist/templates/next-app/src/app/[locale]/admin/page.tsx +22 -0
- package/dist/templates/next-app/src/app/[locale]/admin/users/page.tsx +62 -0
- package/dist/templates/next-app/src/app/[locale]/layout.tsx +1 -1
- package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +26 -6
- package/dist/templates/next-app/src/app/[locale]/login/page.tsx +7 -5
- package/dist/templates/next-app/src/app/[locale]/page.tsx +5 -5
- package/dist/templates/next-app/src/app/[locale]/register/RegisterForm.tsx +176 -0
- package/dist/templates/next-app/src/app/[locale]/register/page.tsx +79 -0
- package/dist/templates/next-app/src/app/api/admin/users/route.ts +39 -0
- package/dist/templates/next-app/src/app/api/user/login/route.ts +50 -0
- package/dist/templates/next-app/src/app/api/user/logout/route.ts +27 -0
- package/dist/templates/next-app/src/app/api/user/register/route.ts +50 -0
- package/dist/templates/next-app/src/base/cases/AdminPageManager.ts +40 -0
- package/dist/templates/next-app/src/base/cases/AppConfig.ts +19 -0
- package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +46 -0
- package/dist/templates/next-app/src/base/cases/PageParams.ts +1 -1
- package/dist/templates/next-app/src/base/cases/RequestEncryptPlugin.ts +70 -0
- package/dist/templates/next-app/src/base/cases/RequestState.ts +20 -0
- package/dist/templates/next-app/src/base/cases/RouterService.ts +4 -0
- package/dist/templates/next-app/src/base/cases/StringEncryptor.ts +67 -0
- package/dist/templates/next-app/src/base/cases/UserServiceApi.ts +48 -0
- package/dist/templates/next-app/src/base/port/AdminLayoutInterface.ts +26 -0
- package/dist/templates/next-app/src/base/port/AdminPageInterface.ts +87 -0
- package/dist/templates/next-app/src/base/port/AppApiInterface.ts +14 -0
- package/dist/templates/next-app/src/base/port/AppUserApiInterface.ts +15 -0
- package/dist/templates/next-app/src/base/port/AsyncStateInterface.ts +7 -0
- package/dist/templates/next-app/src/base/port/DBBridgeInterface.ts +21 -0
- package/dist/templates/next-app/src/base/port/DBMigrationInterface.ts +92 -0
- package/dist/templates/next-app/src/base/port/I18nServiceInterface.ts +3 -2
- package/dist/templates/next-app/src/base/port/MigrationApiInterface.ts +3 -0
- package/dist/templates/next-app/src/base/port/PaginationInterface.ts +6 -0
- package/dist/templates/next-app/src/base/port/ServerApiResponseInterface.ts +6 -0
- package/dist/templates/next-app/src/base/port/UserServiceInterface.ts +3 -2
- package/dist/templates/next-app/src/base/services/AdminUserService.ts +45 -0
- package/dist/templates/next-app/src/base/services/I18nService.ts +9 -45
- package/dist/templates/next-app/src/base/services/UserService.ts +9 -8
- package/dist/templates/next-app/src/base/services/adminApi/AdminApiRequester.ts +21 -0
- package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +34 -0
- package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +63 -0
- package/dist/templates/next-app/src/base/services/appApi/AppApiRequester.ts +56 -0
- package/dist/templates/next-app/src/base/services/appApi/AppUserApi.ts +71 -0
- package/dist/templates/next-app/src/base/services/appApi/AppUserApiBootstrap.ts +49 -0
- package/dist/templates/next-app/src/base/services/migrations/MigrationsApi.ts +43 -0
- package/dist/templates/next-app/src/core/bootstraps/BootstrapClient.ts +1 -1
- package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +26 -12
- package/dist/templates/next-app/src/core/bootstraps/BootstrapsRegistry.ts +5 -4
- package/dist/templates/next-app/src/core/bootstraps/PrintBootstrap.ts +1 -1
- package/dist/templates/next-app/src/core/clientIoc/ClientIOC.ts +1 -1
- package/dist/templates/next-app/src/core/clientIoc/ClientIOCRegister.ts +1 -1
- package/dist/templates/next-app/src/core/globals.ts +1 -1
- package/dist/templates/next-app/src/core/serverIoc/ServerIOC.ts +1 -1
- package/dist/templates/next-app/src/core/serverIoc/ServerIOCRegister.ts +1 -1
- package/dist/templates/next-app/src/server/AppErrorApi.ts +10 -0
- package/dist/templates/next-app/src/server/AppSuccessApi.ts +7 -0
- package/dist/templates/next-app/src/server/PasswordEncrypt.ts +12 -0
- package/dist/templates/next-app/src/server/ServerAuth.ts +60 -0
- package/dist/templates/next-app/src/server/SupabaseBridge.ts +159 -0
- package/dist/templates/next-app/src/server/UserCredentialToken.ts +49 -0
- package/dist/templates/next-app/src/server/port/CrentialTokenInterface.ts +5 -0
- package/dist/templates/next-app/src/server/port/DBTableInterface.ts +10 -0
- package/dist/templates/next-app/src/server/port/ServerAuthInterface.ts +11 -0
- package/dist/templates/next-app/src/server/port/ServerInterface.ts +22 -0
- package/dist/templates/next-app/src/server/port/UserRepositoryInterface.ts +15 -0
- package/dist/templates/next-app/src/server/port/UserServiceInterface.ts +8 -0
- package/dist/templates/next-app/src/server/port/ValidatorInterface.ts +23 -0
- package/dist/templates/next-app/src/server/repositorys/UserRepository.ts +94 -0
- package/dist/templates/next-app/src/server/services/AdminAuthPlugin.ts +19 -0
- package/dist/templates/next-app/src/server/services/ApiUserService.ts +26 -0
- package/dist/templates/next-app/src/server/services/UserService.ts +105 -0
- package/dist/templates/next-app/src/server/validators/LoginValidator.ts +79 -0
- package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +48 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/{_default.css → _common/_default.css} +74 -1
- package/dist/templates/next-app/src/styles/css/antd-themes/{dark.css → _common/dark.css} +73 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/_common/index.css +3 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/{pink.css → _common/pink.css} +70 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/index.css +4 -3
- package/dist/templates/next-app/src/styles/css/antd-themes/menu/_default.css +108 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/menu/dark.css +67 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/menu/index.css +3 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/menu/pink.css +67 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/_default.css +33 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/dark.css +32 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/index.css +3 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/pink.css +35 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/table/_default.css +44 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/table/dark.css +43 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/table/index.css +3 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/table/pink.css +43 -0
- package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +106 -0
- package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +68 -17
- package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +6 -1
- package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +9 -2
- package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +11 -3
- package/dist/templates/next-app/src/uikit/components/LanguageSwitcher.tsx +1 -1
- package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +21 -11
- package/dist/templates/next-app/src/uikit/hook/useIOC.ts +1 -1
- package/dist/templates/next-app/src/uikit/hook/useMountedClient.ts +7 -1
- package/dist/templates/next-app/tsconfig.json +3 -1
- package/package.json +2 -2
- package/dist/templates/next-app/src/base/cases/ServerAuth.ts +0 -17
- package/dist/templates/next-app/src/base/cases/ServerErrorHandler.ts +0 -27
- package/dist/templates/next-app/src/base/port/ServerAuthInterface.ts +0 -3
- package/dist/templates/next-app/src/base/port/ServerInterface.ts +0 -12
- /package/dist/templates/next-app/src/{app/[locale]/login → uikit/components}/FeatureItem.tsx +0 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description 用户未找到
|
|
3
|
+
* @localZh 用户未找到
|
|
4
|
+
* @localEn User not found
|
|
5
|
+
*/
|
|
6
|
+
export const API_USER_NOT_FOUND = 'api__user__not_found';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @description 用户已存在
|
|
10
|
+
* @localZh 用户已存在
|
|
11
|
+
* @localEn User already exists
|
|
12
|
+
*/
|
|
13
|
+
export const API_USER_ALREADY_EXISTS = 'api__user__already_exists';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @description 响应不正确
|
|
17
|
+
* @localZh 响应不正确
|
|
18
|
+
* @localEn Response not correct
|
|
19
|
+
*/
|
|
20
|
+
export const API_RESPONSE_NOT_OK = 'RESPONSE_NOT_OK';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @description 未授权
|
|
24
|
+
* @localZh 未授权
|
|
25
|
+
* @localEn Not authorized
|
|
26
|
+
*/
|
|
27
|
+
export const API_NOT_AUTHORIZED = 'api__not_authorized';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @description 页码不正确
|
|
31
|
+
* @localZh 页码不正确
|
|
32
|
+
* @localEn Page number is incorrect
|
|
33
|
+
*/
|
|
34
|
+
export const API_PAGE_INVALID = 'api__page__invalid';
|
|
@@ -60,3 +60,10 @@ export const PAGE_500_TITLE = 'page__500__title';
|
|
|
60
60
|
* @localEn 500 Page Description
|
|
61
61
|
*/
|
|
62
62
|
export const PAGE_500_DESCRIPTION = 'page__500__description';
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @description Admin page title
|
|
66
|
+
* @localZh 管理后台
|
|
67
|
+
* @localEn Admin Backend
|
|
68
|
+
*/
|
|
69
|
+
export const PAGE_HEAD_ADMIN_TITLE = 'page__head__admin__title';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from './api';
|
|
1
2
|
export * from './common';
|
|
2
3
|
export * from './common.error';
|
|
3
4
|
export * from './page.about';
|
|
@@ -8,3 +9,4 @@ export * from './page.jsonStorage';
|
|
|
8
9
|
export * from './page.login';
|
|
9
10
|
export * from './page.register';
|
|
10
11
|
export * from './page.request';
|
|
12
|
+
export * from './validator';
|
|
@@ -97,8 +97,8 @@ export const LOGIN_NO_ACCOUNT = 'login__no_account';
|
|
|
97
97
|
export const LOGIN_CREATE_ACCOUNT = 'login__create_account';
|
|
98
98
|
/**
|
|
99
99
|
* @description Login page email validation message
|
|
100
|
-
* @localZh
|
|
101
|
-
* @localEn Please input
|
|
100
|
+
* @localZh 请输入正确的邮箱!
|
|
101
|
+
* @localEn Please input a valid email!
|
|
102
102
|
*/
|
|
103
103
|
export const LOGIN_EMAIL_REQUIRED = 'login__email_required';
|
|
104
104
|
/**
|
|
@@ -13,17 +13,56 @@ export const PAGE_REGISTER_TITLE = 'page__register__title';
|
|
|
13
13
|
export const PAGE_REGISTER_DESCRIPTION = 'page__register__description';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* @description Register page
|
|
17
|
-
* @localZh
|
|
18
|
-
* @localEn Create Account
|
|
16
|
+
* @description Register page content
|
|
17
|
+
* @localZh 创建账号页面内容
|
|
18
|
+
* @localEn Create Account Page Content
|
|
19
19
|
*/
|
|
20
|
-
export const
|
|
20
|
+
export const PAGE_REGISTER_CONTENT = 'page__register__content';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @description Register page keywords
|
|
24
|
+
* @localZh 创建账号页面关键词
|
|
25
|
+
* @localEn Create Account Page Keywords
|
|
26
|
+
*/
|
|
27
|
+
export const PAGE_REGISTER_KEYWORDS = 'page__register__keywords';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @description Register page welcome
|
|
31
|
+
* @localZh 欢迎来到我们的平台
|
|
32
|
+
* @localEn Welcome to our platform
|
|
33
|
+
*/
|
|
34
|
+
export const REGISTER_WELCOME = 'register__welcome';
|
|
35
|
+
|
|
21
36
|
/**
|
|
22
37
|
* @description Register page subtitle
|
|
23
38
|
* @localZh 开始您的学习之旅
|
|
24
39
|
* @localEn Start your learning journey
|
|
25
40
|
*/
|
|
26
41
|
export const REGISTER_SUBTITLE = 'register__subtitle';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @description Register page feature ai paths
|
|
45
|
+
* @localZh AI路径
|
|
46
|
+
* @localEn AI Paths
|
|
47
|
+
*/
|
|
48
|
+
export const REGISTER_FEATURE_AI_PATHS = 'register__feature__ai_paths';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @description Register page feature smart recommendations
|
|
52
|
+
* @localZh 智能推荐
|
|
53
|
+
* @localEn Smart Recommendations
|
|
54
|
+
*/
|
|
55
|
+
export const REGISTER_FEATURE_SMART_RECOMMENDATIONS =
|
|
56
|
+
'register__feature__smart_recommendations';
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @description Register page feature progress tracking
|
|
60
|
+
* @localZh 进度跟踪
|
|
61
|
+
* @localEn Progress Tracking
|
|
62
|
+
*/
|
|
63
|
+
export const REGISTER_FEATURE_PROGRESS_TRACKING =
|
|
64
|
+
'register__feature__progress_tracking';
|
|
65
|
+
|
|
27
66
|
/**
|
|
28
67
|
* @description Register page username field
|
|
29
68
|
* @localZh 用户名
|
|
@@ -121,24 +160,6 @@ export const REGISTER_HAVE_ACCOUNT = 'register__have_account';
|
|
|
121
160
|
* @localEn Sign in
|
|
122
161
|
*/
|
|
123
162
|
export const REGISTER_LOGIN_LINK = 'register__login_link';
|
|
124
|
-
/**
|
|
125
|
-
* @description Register page feature item - Personalized Learning
|
|
126
|
-
* @localZh 个性化学习体验
|
|
127
|
-
* @localEn Personalized Learning Experience
|
|
128
|
-
*/
|
|
129
|
-
export const REGISTER_FEATURE_PERSONALIZED = 'register__feature__personalized';
|
|
130
|
-
/**
|
|
131
|
-
* @description Register page feature item - Expert Support
|
|
132
|
-
* @localZh 专家支持和指导
|
|
133
|
-
* @localEn Expert Support and Guidance
|
|
134
|
-
*/
|
|
135
|
-
export const REGISTER_FEATURE_SUPPORT = 'register__feature__support';
|
|
136
|
-
/**
|
|
137
|
-
* @description Register page feature item - Learning Community
|
|
138
|
-
* @localZh 活跃的学习社区
|
|
139
|
-
* @localEn Active Learning Community
|
|
140
|
-
*/
|
|
141
|
-
export const REGISTER_FEATURE_COMMUNITY = 'register__feature__community';
|
|
142
163
|
/**
|
|
143
164
|
* @description Register page terms agreement required message
|
|
144
165
|
* @localZh 请同意服务条款和隐私政策
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Validator for login params
|
|
3
|
+
* @localZh 不是一个有效的登录参数
|
|
4
|
+
* @localEn Not a valid login parameter
|
|
5
|
+
*/
|
|
6
|
+
export const V_LOGIN_PARAMS_REQUIRED = 'v_login_params_required';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @description Invalid email format validation message
|
|
10
|
+
* @localZh 邮箱格式无效的验证消息
|
|
11
|
+
* @localEn Invalid email format validation message
|
|
12
|
+
*/
|
|
13
|
+
export const V_EMAIL_INVALID = 'validator_email_invalid';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @description Password minimum length validation message
|
|
17
|
+
* @localZh 密码最小长度验证消息(6)
|
|
18
|
+
* @localEn Password minimum length validation message(6)
|
|
19
|
+
*/
|
|
20
|
+
export const V_PASSWORD_MIN_LENGTH = 'validator_password_min_length';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @description Password maximum length validation message
|
|
24
|
+
* @localZh 密码最大长度验证消息(50)
|
|
25
|
+
* @localEn Password maximum length validation message(50)
|
|
26
|
+
*/
|
|
27
|
+
export const V_PASSWORD_MAX_LENGTH = 'validator_password_max_length';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @description Password whitespace validation message
|
|
31
|
+
* @localZh 密码不能包含空格的验证消息
|
|
32
|
+
* @localEn Password cannot contain whitespace characters validation message
|
|
33
|
+
*/
|
|
34
|
+
export const V_PASSWORD_SPECIAL_CHARS = 'validator_password_special_chars';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as i18nKeys from '../Identifier/page.register';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Register page i18n interface
|
|
5
|
+
*
|
|
6
|
+
* @description
|
|
7
|
+
* - welcome: welcome message
|
|
8
|
+
*/
|
|
9
|
+
export type RegisterI18nInterface = typeof register18n;
|
|
10
|
+
|
|
11
|
+
export const register18n = Object.freeze({
|
|
12
|
+
// basic meta properties
|
|
13
|
+
title: i18nKeys.PAGE_REGISTER_TITLE,
|
|
14
|
+
description: i18nKeys.PAGE_REGISTER_DESCRIPTION,
|
|
15
|
+
content: i18nKeys.PAGE_REGISTER_CONTENT,
|
|
16
|
+
keywords: i18nKeys.PAGE_REGISTER_KEYWORDS,
|
|
17
|
+
|
|
18
|
+
// register page
|
|
19
|
+
welcome: i18nKeys.REGISTER_WELCOME,
|
|
20
|
+
subtitle: i18nKeys.REGISTER_SUBTITLE,
|
|
21
|
+
feature_ai_paths: i18nKeys.REGISTER_FEATURE_AI_PATHS,
|
|
22
|
+
feature_smart_recommendations:
|
|
23
|
+
i18nKeys.REGISTER_FEATURE_SMART_RECOMMENDATIONS,
|
|
24
|
+
feature_progress_tracking: i18nKeys.REGISTER_FEATURE_PROGRESS_TRACKING,
|
|
25
|
+
|
|
26
|
+
// register form
|
|
27
|
+
username: i18nKeys.REGISTER_USERNAME,
|
|
28
|
+
username_required: i18nKeys.REGISTER_USERNAME_REQUIRED,
|
|
29
|
+
email: i18nKeys.REGISTER_EMAIL,
|
|
30
|
+
email_required: i18nKeys.REGISTER_EMAIL_REQUIRED,
|
|
31
|
+
password: i18nKeys.REGISTER_PASSWORD,
|
|
32
|
+
password_required: i18nKeys.REGISTER_PASSWORD_REQUIRED,
|
|
33
|
+
confirm_password_required: i18nKeys.REGISTER_CONFIRM_PASSWORD_REQUIRED,
|
|
34
|
+
password_mismatch: i18nKeys.REGISTER_PASSWORD_MISMATCH,
|
|
35
|
+
button: i18nKeys.REGISTER_BUTTON,
|
|
36
|
+
terms_prefix: i18nKeys.REGISTER_TERMS_PREFIX,
|
|
37
|
+
terms_link: i18nKeys.REGISTER_TERMS_LINK,
|
|
38
|
+
terms_and: i18nKeys.REGISTER_TERMS_AND,
|
|
39
|
+
privacy_link: i18nKeys.REGISTER_PRIVACY_LINK,
|
|
40
|
+
have_account: i18nKeys.REGISTER_HAVE_ACCOUNT,
|
|
41
|
+
confirm_password: i18nKeys.REGISTER_CONFIRM_PASSWORD,
|
|
42
|
+
terms_required: i18nKeys.REGISTER_TERMS_REQUIRED,
|
|
43
|
+
login_link: i18nKeys.REGISTER_LOGIN_LINK
|
|
44
|
+
});
|
|
@@ -96,6 +96,13 @@ const eslintConfig = [
|
|
|
96
96
|
prettier: prettierPlugin,
|
|
97
97
|
'@qlover-eslint': qloverEslint
|
|
98
98
|
},
|
|
99
|
+
settings: {
|
|
100
|
+
'import/resolver': {
|
|
101
|
+
typescript: {
|
|
102
|
+
project: './tsconfig.json'
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
99
106
|
rules: {
|
|
100
107
|
'@qlover-eslint/ts-class-method-return': 'error',
|
|
101
108
|
'@qlover-eslint/require-root-testid': 'error',
|
|
@@ -140,6 +147,16 @@ const eslintConfig = [
|
|
|
140
147
|
pattern: '@/**',
|
|
141
148
|
group: 'internal',
|
|
142
149
|
position: 'after'
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
pattern: '@migrations/**',
|
|
153
|
+
group: 'internal',
|
|
154
|
+
position: 'after'
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
pattern: '@config/**',
|
|
158
|
+
group: 'internal',
|
|
159
|
+
position: 'after'
|
|
143
160
|
}
|
|
144
161
|
],
|
|
145
162
|
// "newlines-between": "always", // 不同组之间空一行
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const UserRole = {
|
|
4
|
+
ADMIN: 0,
|
|
5
|
+
USER: 1
|
|
6
|
+
} as const;
|
|
7
|
+
|
|
8
|
+
export type UserRoleType = (typeof UserRole)[keyof typeof UserRole];
|
|
9
|
+
|
|
10
|
+
export const userSchema = z.object({
|
|
11
|
+
id: z.number(),
|
|
12
|
+
role: z.enum(UserRole),
|
|
13
|
+
email: z.email(),
|
|
14
|
+
password: z.string(),
|
|
15
|
+
/**
|
|
16
|
+
* 加密的token, 包含token, 过期时间
|
|
17
|
+
*/
|
|
18
|
+
credential_token: z.string(),
|
|
19
|
+
email_confirmed_at: z.number(),
|
|
20
|
+
created_at: z.number(),
|
|
21
|
+
updated_at: z.number()
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export type UserSchema = z.infer<typeof userSchema>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS fe_users (
|
|
2
|
+
id BIGSERIAL PRIMARY KEY,
|
|
3
|
+
role TEXT NOT NULL,
|
|
4
|
+
email TEXT NOT NULL,
|
|
5
|
+
password TEXT NOT NULL,
|
|
6
|
+
credential_token TEXT,
|
|
7
|
+
email_confirmed_at TIMESTAMPTZ,
|
|
8
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
9
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
10
|
+
);
|
|
@@ -25,33 +25,43 @@
|
|
|
25
25
|
"@qlover/corekit-bridge": "^1.6.4",
|
|
26
26
|
"@qlover/fe-corekit": "^2.1.0",
|
|
27
27
|
"@qlover/slice-store-react": "^1.4.1",
|
|
28
|
+
"@supabase/auth-helpers-nextjs": "^0.10.0",
|
|
29
|
+
"@supabase/ssr": "^0.7.0",
|
|
30
|
+
"@supabase/supabase-js": "^2.57.2",
|
|
31
|
+
"@types/jsonwebtoken": "^9.0.10",
|
|
28
32
|
"antd": "^5.27.1",
|
|
29
33
|
"clsx": "^2.1.1",
|
|
30
34
|
"inversify": "^7.8.1",
|
|
35
|
+
"jsonwebtoken": "^9.0.2",
|
|
31
36
|
"lodash": "^4.17.21",
|
|
32
37
|
"next": "15.5.0",
|
|
33
38
|
"next-intl": "^4.3.5",
|
|
34
39
|
"next-themes": "^0.4.6",
|
|
35
40
|
"react": "19.1.0",
|
|
36
|
-
"react-dom": "19.1.0"
|
|
41
|
+
"react-dom": "19.1.0",
|
|
42
|
+
"zod": "^4.1.8"
|
|
37
43
|
},
|
|
38
44
|
"devDependencies": {
|
|
39
45
|
"@brain-toolkit/ts2locales": "^0.2.3",
|
|
40
46
|
"@eslint/eslintrc": "^3",
|
|
47
|
+
"@qlover/env-loader": "^0.3.0",
|
|
48
|
+
"@qlover/eslint-plugin": "latest",
|
|
41
49
|
"@tailwindcss/postcss": "^4",
|
|
42
50
|
"@types/lodash": "^4.17.20",
|
|
43
51
|
"@types/node": "^20",
|
|
44
52
|
"@types/react": "^19",
|
|
45
53
|
"@types/react-dom": "^19",
|
|
46
|
-
"@qlover/eslint-plugin": "latest",
|
|
47
54
|
"cross-env": "^7.0.3",
|
|
55
|
+
"dotenv-expand": "^12.0.3",
|
|
48
56
|
"eslint": "^9",
|
|
49
57
|
"eslint-config-next": "15.5.0",
|
|
50
58
|
"eslint-config-prettier": "^10.1.8",
|
|
59
|
+
"eslint-import-resolver-typescript": "^4.4.4",
|
|
51
60
|
"eslint-plugin-import": "^2.32.0",
|
|
52
61
|
"eslint-plugin-prettier": "^5.5.4",
|
|
53
62
|
"eslint-plugin-unused-imports": "^4.2.0",
|
|
54
63
|
"prettier": "^3.6.2",
|
|
64
|
+
"supabase": "^2.40.7",
|
|
55
65
|
"tailwindcss": "^4",
|
|
56
66
|
"typescript": "^5"
|
|
57
67
|
}
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
"login__with_google": "Sign in with Google",
|
|
115
115
|
"login__no_account": "Don't have an account?",
|
|
116
116
|
"login__create_account": "Create one here",
|
|
117
|
-
"login__email_required": "Please input
|
|
117
|
+
"login__email_required": "Please input a valid email!",
|
|
118
118
|
"login__password_required": "Please input your password!",
|
|
119
119
|
"login__feature__ai_paths": "AI-powered personalized learning paths",
|
|
120
120
|
"login__feature__smart_recommendations": "Smart content recommendations",
|
|
@@ -181,5 +181,22 @@
|
|
|
181
181
|
"page__login__content": "Login Page Content",
|
|
182
182
|
"page__login__keywords": "Login Page Keywords",
|
|
183
183
|
"err__server__auth__error": "Server auth error",
|
|
184
|
-
"page__home__keywords": "Modern frontend utility library, practical tools, components"
|
|
184
|
+
"page__home__keywords": "Modern frontend utility library, practical tools, components",
|
|
185
|
+
"api__user__not_found": "User not found",
|
|
186
|
+
"api__user__already_exists": "User already exists",
|
|
187
|
+
"RESPONSE_NOT_OK": "Response not correct",
|
|
188
|
+
"v_login_params_required": "Not a valid login parameter",
|
|
189
|
+
"validator_email_invalid": "Invalid email format validation message",
|
|
190
|
+
"validator_password_min_length": "Password minimum length validation message(6)",
|
|
191
|
+
"validator_password_max_length": "Password maximum length validation message(50)",
|
|
192
|
+
"validator_password_special_chars": "Password cannot contain whitespace characters validation message",
|
|
193
|
+
"page__register__content": "Create Account Page Content",
|
|
194
|
+
"page__register__keywords": "Create Account Page Keywords",
|
|
195
|
+
"register__welcome": "Welcome to our platform",
|
|
196
|
+
"register__feature__ai_paths": "AI Paths",
|
|
197
|
+
"register__feature__smart_recommendations": "Smart Recommendations",
|
|
198
|
+
"register__feature__progress_tracking": "Progress Tracking",
|
|
199
|
+
"page__head__admin__title": "Admin Backend",
|
|
200
|
+
"api__not_authorized": "Not authorized",
|
|
201
|
+
"api__page__invalid": "Page number is incorrect"
|
|
185
202
|
}
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
"login__with_google": "使用 Google 登录",
|
|
115
115
|
"login__no_account": "还没有账号?",
|
|
116
116
|
"login__create_account": "在此创建",
|
|
117
|
-
"login__email_required": "
|
|
117
|
+
"login__email_required": "请输入正确的邮箱!",
|
|
118
118
|
"login__password_required": "请输入您的密码!",
|
|
119
119
|
"login__feature__ai_paths": "AI驱动的个性化学习路径",
|
|
120
120
|
"login__feature__smart_recommendations": "智能内容推荐",
|
|
@@ -181,5 +181,22 @@
|
|
|
181
181
|
"page__login__content": "登录页面内容",
|
|
182
182
|
"page__login__keywords": "登录页面关键词",
|
|
183
183
|
"err__server__auth__error": "服务器认证错误",
|
|
184
|
-
"page__home__keywords": "现代前端实用库, 实用工具, 组件"
|
|
184
|
+
"page__home__keywords": "现代前端实用库, 实用工具, 组件",
|
|
185
|
+
"api__user__not_found": "用户未找到",
|
|
186
|
+
"api__user__already_exists": "用户已存在",
|
|
187
|
+
"RESPONSE_NOT_OK": "响应不正确",
|
|
188
|
+
"v_login_params_required": "不是一个有效的登录参数",
|
|
189
|
+
"validator_email_invalid": "邮箱格式无效的验证消息",
|
|
190
|
+
"validator_password_min_length": "密码最小长度验证消息(6)",
|
|
191
|
+
"validator_password_max_length": "密码最大长度验证消息(50)",
|
|
192
|
+
"validator_password_special_chars": "密码不能包含空格的验证消息",
|
|
193
|
+
"page__register__content": "创建账号页面内容",
|
|
194
|
+
"page__register__keywords": "创建账号页面关键词",
|
|
195
|
+
"register__welcome": "欢迎来到我们的平台",
|
|
196
|
+
"register__feature__ai_paths": "AI路径",
|
|
197
|
+
"register__feature__smart_recommendations": "智能推荐",
|
|
198
|
+
"register__feature__progress_tracking": "进度跟踪",
|
|
199
|
+
"page__head__admin__title": "管理后台",
|
|
200
|
+
"api__not_authorized": "未授权",
|
|
201
|
+
"api__page__invalid": "页码不正确"
|
|
185
202
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { PageParams } from '@/base/cases/PageParams';
|
|
2
|
+
import type { PageLayoutProps } from '@/base/types/PageProps';
|
|
3
|
+
import '@/styles/css/index.css';
|
|
4
|
+
import { AdminLayout } from '@/uikit/components/AdminLayout';
|
|
5
|
+
|
|
6
|
+
export default async function AdminRootLayout({
|
|
7
|
+
children,
|
|
8
|
+
params
|
|
9
|
+
}: PageLayoutProps) {
|
|
10
|
+
const pageParams = new PageParams(await params!);
|
|
11
|
+
const locale = pageParams.getLocale();
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<AdminLayout data-testid="AdminRootLayout" lang={locale}>
|
|
15
|
+
{children}
|
|
16
|
+
</AdminLayout>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Metadata } from 'next';
|
|
2
|
+
|
|
3
|
+
function AdminPageClient() {
|
|
4
|
+
return <div data-testid="AdminPage">Admin Page</div>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const metadata: Metadata = {
|
|
8
|
+
title: 'Admin Dashboard',
|
|
9
|
+
description: 'Admin dashboard for managing application resources',
|
|
10
|
+
robots: {
|
|
11
|
+
index: false,
|
|
12
|
+
follow: false
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default function AdminPage() {
|
|
17
|
+
return (
|
|
18
|
+
<div data-testid="AdminPageWrapper">
|
|
19
|
+
<AdminPageClient />
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Table } from 'antd';
|
|
4
|
+
import { useEffect, useRef } from 'react';
|
|
5
|
+
import { AdminUserService } from '@/base/services/AdminUserService';
|
|
6
|
+
import { useIOC } from '@/uikit/hook/useIOC';
|
|
7
|
+
import { useStore } from '@/uikit/hook/useStore';
|
|
8
|
+
import { userSchema, type UserSchema } from '@migrations/schema/UserSchema';
|
|
9
|
+
import type { ColumnsType } from 'antd/es/table';
|
|
10
|
+
|
|
11
|
+
const baseColumns: ColumnsType<UserSchema> = Object.keys(
|
|
12
|
+
userSchema.omit({
|
|
13
|
+
password: true,
|
|
14
|
+
credential_token: true
|
|
15
|
+
}).shape
|
|
16
|
+
).map((key) => ({
|
|
17
|
+
title: key,
|
|
18
|
+
dataIndex: key
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
export default function UsersPage() {
|
|
22
|
+
const adminUserService = useIOC(AdminUserService);
|
|
23
|
+
|
|
24
|
+
const listParams = useStore(adminUserService, (state) => state.listParams);
|
|
25
|
+
const listState = useStore(adminUserService, (state) => state.listState);
|
|
26
|
+
const mouted = useRef(false);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!mouted.current) {
|
|
30
|
+
mouted.current = true;
|
|
31
|
+
adminUserService.initialize();
|
|
32
|
+
}
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
const dataSource = listState.result?.list as UserSchema[];
|
|
36
|
+
|
|
37
|
+
const columns: ColumnsType<UserSchema> = baseColumns;
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div data-testid="UsersPage">
|
|
41
|
+
<Table
|
|
42
|
+
rowKey="id"
|
|
43
|
+
columns={columns}
|
|
44
|
+
dataSource={dataSource}
|
|
45
|
+
loading={listState.loading}
|
|
46
|
+
scroll={{ x: true }}
|
|
47
|
+
pagination={{
|
|
48
|
+
pageSizeOptions: [10, 20, 50],
|
|
49
|
+
current: listParams.page,
|
|
50
|
+
pageSize: listParams.pageSize,
|
|
51
|
+
total: listState.result?.total,
|
|
52
|
+
onChange: (page, pageSize) => {
|
|
53
|
+
adminUserService.changeListParams({
|
|
54
|
+
page,
|
|
55
|
+
pageSize
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}}
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { NextIntlClientProvider } from 'next-intl';
|
|
2
|
-
import { themeConfig } from '@config/theme';
|
|
3
2
|
import { PageParams } from '@/base/cases/PageParams';
|
|
4
3
|
import type { PageLayoutProps } from '@/base/types/PageProps';
|
|
5
4
|
import { ComboProvider } from '@/uikit/components/ComboProvider';
|
|
5
|
+
import { themeConfig } from '@config/theme';
|
|
6
6
|
import '@/styles/css/index.css';
|
|
7
7
|
|
|
8
8
|
export default async function RootLayout({
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import { UserOutlined, LockOutlined, GoogleOutlined } from '@ant-design/icons';
|
|
4
4
|
import { Form, Input, Button } from 'antd';
|
|
5
|
+
import { useTranslations } from 'next-intl';
|
|
5
6
|
import { useState } from 'react';
|
|
6
|
-
import {
|
|
7
|
+
import { LoginValidator } from '@/server/validators/LoginValidator';
|
|
7
8
|
import { LocaleLink } from '@/uikit/components/LocaleLink';
|
|
8
9
|
import { useIOC } from '@/uikit/hook/useIOC';
|
|
9
10
|
import type { LoginI18nInterface } from '@config/i18n/loginI18n';
|
|
11
|
+
import { I } from '@config/IOCIdentifier';
|
|
10
12
|
|
|
11
13
|
interface LoginFormData {
|
|
12
14
|
email: string;
|
|
@@ -15,7 +17,10 @@ interface LoginFormData {
|
|
|
15
17
|
|
|
16
18
|
export function LoginForm(props: { tt: LoginI18nInterface }) {
|
|
17
19
|
const { tt } = props;
|
|
20
|
+
const t = useTranslations();
|
|
18
21
|
const userService = useIOC(I.UserServiceInterface);
|
|
22
|
+
const logger = useIOC(I.Logger);
|
|
23
|
+
const appConfig = useIOC(I.AppConfig);
|
|
19
24
|
const routerService = useIOC(I.RouterServiceInterface);
|
|
20
25
|
const [loading, setLoading] = useState(false);
|
|
21
26
|
|
|
@@ -25,7 +30,7 @@ export function LoginForm(props: { tt: LoginI18nInterface }) {
|
|
|
25
30
|
await userService.login(values);
|
|
26
31
|
routerService.gotoHome();
|
|
27
32
|
} catch (error) {
|
|
28
|
-
|
|
33
|
+
logger.error(error);
|
|
29
34
|
} finally {
|
|
30
35
|
setLoading(false);
|
|
31
36
|
}
|
|
@@ -38,30 +43,45 @@ export function LoginForm(props: { tt: LoginI18nInterface }) {
|
|
|
38
43
|
onFinish={handleLogin}
|
|
39
44
|
layout="vertical"
|
|
40
45
|
className="space-y-4"
|
|
46
|
+
validateTrigger="onSubmit"
|
|
47
|
+
initialValues={{
|
|
48
|
+
email: appConfig.testLoginEmail,
|
|
49
|
+
password: appConfig.testLoginPassword
|
|
50
|
+
}}
|
|
41
51
|
>
|
|
42
52
|
<Form.Item
|
|
43
53
|
name="email"
|
|
44
|
-
rules={[{ required: true, message: tt.emailRequired }]}
|
|
54
|
+
rules={[{ required: true, type: 'email', message: tt.emailRequired }]}
|
|
45
55
|
>
|
|
46
56
|
<Input
|
|
47
57
|
prefix={<UserOutlined className="text-text-tertiary" />}
|
|
48
58
|
placeholder={tt.email}
|
|
49
59
|
title={tt.emailTitle}
|
|
50
60
|
className="h-12 text-base bg-secondary border-c-border"
|
|
51
|
-
autoComplete="off"
|
|
52
61
|
/>
|
|
53
62
|
</Form.Item>
|
|
54
63
|
|
|
55
64
|
<Form.Item
|
|
56
65
|
name="password"
|
|
57
|
-
rules={[
|
|
66
|
+
rules={[
|
|
67
|
+
{ required: true, message: tt.passwordRequired },
|
|
68
|
+
{
|
|
69
|
+
validator(_, value) {
|
|
70
|
+
const validator = new LoginValidator();
|
|
71
|
+
const result = validator.validatePassword(value);
|
|
72
|
+
if (result != null) {
|
|
73
|
+
return Promise.reject(t(result.message));
|
|
74
|
+
}
|
|
75
|
+
return Promise.resolve();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
]}
|
|
58
79
|
>
|
|
59
80
|
<Input.Password
|
|
60
81
|
prefix={<LockOutlined />}
|
|
61
82
|
placeholder={tt.password}
|
|
62
83
|
title={tt.passwordTitle}
|
|
63
84
|
className="h-12 text-base"
|
|
64
|
-
autoComplete="new-password"
|
|
65
85
|
/>
|
|
66
86
|
</Form.Item>
|
|
67
87
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { notFound } from 'next/navigation';
|
|
2
|
-
import { loginI18n, i18nConfig } from '@config/i18n';
|
|
3
2
|
import { PageParams, type PageParamsType } from '@/base/cases/PageParams';
|
|
4
|
-
import { ServerAuth } from '@/base/cases/ServerAuth';
|
|
5
3
|
import type { PageParamsProps } from '@/base/types/PageProps';
|
|
6
4
|
import { BootstrapServer } from '@/core/bootstraps/BootstrapServer';
|
|
7
5
|
import { redirect } from '@/i18n/routing';
|
|
6
|
+
import { ServerAuth } from '@/server/ServerAuth';
|
|
8
7
|
import { BaseLayout } from '@/uikit/components/BaseLayout';
|
|
9
|
-
import { FeatureItem } from '
|
|
8
|
+
import { FeatureItem } from '@/uikit/components/FeatureItem';
|
|
9
|
+
import { loginI18n, i18nConfig } from '@config/i18n';
|
|
10
10
|
import { LoginForm } from './LoginForm';
|
|
11
11
|
import type { Metadata } from 'next';
|
|
12
12
|
|
|
@@ -43,7 +43,7 @@ export default async function LoginPage(props: PageParamsProps) {
|
|
|
43
43
|
|
|
44
44
|
const server = new BootstrapServer();
|
|
45
45
|
|
|
46
|
-
if (await
|
|
46
|
+
if (await server.getIOC(ServerAuth).hasAuth()) {
|
|
47
47
|
return redirect({ href: '/', locale: params.locale! });
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -52,7 +52,9 @@ export default async function LoginPage(props: PageParamsProps) {
|
|
|
52
52
|
return (
|
|
53
53
|
<BaseLayout
|
|
54
54
|
data-testid="LoginPage"
|
|
55
|
-
|
|
55
|
+
mainProps={{
|
|
56
|
+
className: 'text-xs1 bg-primary flex min-h-screen'
|
|
57
|
+
}}
|
|
56
58
|
>
|
|
57
59
|
<div className="hidden lg:flex bg-secondary lg:w-1/2 p-12 flex-col">
|
|
58
60
|
<h1 className="text-4xl font-bold text-text mb-4">{tt.welcome}</h1>
|