@robsun/create-keystone-app 0.2.15 → 0.4.1

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.
Files changed (96) hide show
  1. package/README.md +46 -44
  2. package/dist/create-keystone-app.js +347 -10
  3. package/dist/create-module.js +1217 -1187
  4. package/package.json +1 -1
  5. package/template/.claude/skills/keystone-implement/SKILL.md +113 -0
  6. package/template/.claude/skills/keystone-implement/references/CHECKLIST.md +91 -0
  7. package/template/.claude/skills/keystone-implement/references/PATTERNS.md +1088 -0
  8. package/template/.claude/skills/keystone-implement/references/SCHEMA.md +135 -0
  9. package/template/.claude/skills/keystone-implement/references/TESTING.md +231 -0
  10. package/template/.claude/skills/keystone-requirements/SKILL.md +296 -0
  11. package/template/.claude/skills/keystone-requirements/references/CONFIRM_TEMPLATE.md +170 -0
  12. package/template/.claude/skills/keystone-requirements/references/SCHEMA.md +135 -0
  13. package/template/.eslintrc.js +3 -0
  14. package/template/.github/workflows/ci.yml +30 -0
  15. package/template/.github/workflows/release.yml +32 -0
  16. package/template/.golangci.yml +11 -0
  17. package/template/README.md +82 -81
  18. package/template/apps/server/README.md +8 -0
  19. package/template/apps/server/cmd/server/main.go +27 -185
  20. package/template/apps/server/config.example.yaml +31 -1
  21. package/template/apps/server/config.yaml +31 -1
  22. package/template/apps/server/go.mod +61 -19
  23. package/template/apps/server/go.sum +185 -32
  24. package/template/apps/server/internal/frontend/embed.go +3 -8
  25. package/template/apps/server/internal/modules/example/README.md +18 -0
  26. package/template/apps/server/internal/modules/example/api/handler/handler_test.go +9 -0
  27. package/template/apps/server/internal/modules/example/api/handler/item_handler.go +468 -165
  28. package/template/apps/server/internal/modules/example/bootstrap/seeds/item.go +217 -8
  29. package/template/apps/server/internal/modules/example/domain/models/item.go +40 -7
  30. package/template/apps/server/internal/modules/example/domain/service/approval_callback.go +68 -0
  31. package/template/apps/server/internal/modules/example/domain/service/approval_schema.go +41 -0
  32. package/template/apps/server/internal/modules/example/domain/service/errors.go +20 -22
  33. package/template/apps/server/internal/modules/example/domain/service/item_service.go +267 -7
  34. package/template/apps/server/internal/modules/example/domain/service/item_service_test.go +281 -0
  35. package/template/apps/server/internal/modules/example/i18n/keys.go +32 -20
  36. package/template/apps/server/internal/modules/example/i18n/locales/en-US.json +30 -18
  37. package/template/apps/server/internal/modules/example/i18n/locales/zh-CN.json +30 -18
  38. package/template/apps/server/internal/modules/example/infra/exporter/item_exporter.go +119 -0
  39. package/template/apps/server/internal/modules/example/infra/importer/item_importer.go +77 -0
  40. package/template/apps/server/internal/modules/example/infra/repository/item_repository.go +99 -49
  41. package/template/apps/server/internal/modules/example/module.go +171 -97
  42. package/template/apps/server/internal/modules/example/tests/integration_test.go +7 -0
  43. package/template/apps/server/internal/modules/manifest.go +7 -7
  44. package/template/apps/web/README.md +4 -2
  45. package/template/apps/web/package.json +1 -1
  46. package/template/apps/web/src/app.config.ts +8 -6
  47. package/template/apps/web/src/index.css +7 -3
  48. package/template/apps/web/src/main.tsx +2 -5
  49. package/template/apps/web/src/modules/example/help/en-US/faq.md +27 -0
  50. package/template/apps/web/src/modules/example/help/en-US/items.md +30 -0
  51. package/template/apps/web/src/modules/example/help/en-US/overview.md +31 -0
  52. package/template/apps/web/src/modules/example/help/zh-CN/faq.md +27 -0
  53. package/template/apps/web/src/modules/example/help/zh-CN/items.md +31 -0
  54. package/template/apps/web/src/modules/example/help/zh-CN/overview.md +32 -0
  55. package/template/apps/web/src/modules/example/locales/en-US/example.json +99 -32
  56. package/template/apps/web/src/modules/example/locales/zh-CN/example.json +85 -18
  57. package/template/apps/web/src/modules/example/pages/ExampleItemsPage.tsx +840 -237
  58. package/template/apps/web/src/modules/example/services/exampleItems.ts +79 -8
  59. package/template/apps/web/src/modules/example/types.ts +14 -1
  60. package/template/apps/web/src/modules/index.ts +1 -0
  61. package/template/apps/web/vite.config.ts +9 -3
  62. package/template/docs/CONVENTIONS.md +17 -17
  63. package/template/package.json +4 -5
  64. package/template/pnpm-lock.yaml +76 -5
  65. package/template/scripts/build.bat +15 -3
  66. package/template/scripts/build.sh +9 -3
  67. package/template/scripts/check-help.js +249 -0
  68. package/template/scripts/compress-assets.js +89 -0
  69. package/template/scripts/test.bat +23 -0
  70. package/template/scripts/test.sh +16 -0
  71. package/template/.claude/skills/keystone-dev/SKILL.md +0 -90
  72. package/template/.claude/skills/keystone-dev/references/ADVANCED_PATTERNS.md +0 -716
  73. package/template/.claude/skills/keystone-dev/references/APPROVAL.md +0 -121
  74. package/template/.claude/skills/keystone-dev/references/CAPABILITIES.md +0 -261
  75. package/template/.claude/skills/keystone-dev/references/CHECKLIST.md +0 -285
  76. package/template/.claude/skills/keystone-dev/references/GOTCHAS.md +0 -390
  77. package/template/.claude/skills/keystone-dev/references/PATTERNS.md +0 -605
  78. package/template/.claude/skills/keystone-dev/references/TEMPLATES.md +0 -2710
  79. package/template/.claude/skills/keystone-dev/references/TESTING.md +0 -44
  80. package/template/.codex/skills/keystone-dev/SKILL.md +0 -90
  81. package/template/.codex/skills/keystone-dev/references/ADVANCED_PATTERNS.md +0 -716
  82. package/template/.codex/skills/keystone-dev/references/APPROVAL.md +0 -121
  83. package/template/.codex/skills/keystone-dev/references/CAPABILITIES.md +0 -261
  84. package/template/.codex/skills/keystone-dev/references/CHECKLIST.md +0 -285
  85. package/template/.codex/skills/keystone-dev/references/GOTCHAS.md +0 -390
  86. package/template/.codex/skills/keystone-dev/references/PATTERNS.md +0 -605
  87. package/template/.codex/skills/keystone-dev/references/TEMPLATES.md +0 -2710
  88. package/template/.codex/skills/keystone-dev/references/TESTING.md +0 -44
  89. package/template/apps/server/internal/app/routes/module_routes.go +0 -16
  90. package/template/apps/server/internal/app/routes/routes.go +0 -226
  91. package/template/apps/server/internal/app/startup/startup.go +0 -74
  92. package/template/apps/server/internal/frontend/handler.go +0 -122
  93. package/template/apps/server/internal/modules/registry.go +0 -145
  94. package/template/apps/web/src/modules/example/help/faq.md +0 -23
  95. package/template/apps/web/src/modules/example/help/items.md +0 -26
  96. package/template/apps/web/src/modules/example/help/overview.md +0 -25
@@ -1,44 +0,0 @@
1
- # Keystone 开发测试清单
2
-
3
- ## 快速入口
4
- - 仓库根目录:`pnpm test`(模板测试 + `go test ./...`)
5
- - 仅 Go:`go test ./...`
6
- - 模板项目:`pnpm -C packages/create-keystone-app/template test`
7
-
8
- ## 按改动范围选择
9
- 后端(api/domain/infra/bootstrap/server)
10
- - `go test ./...`
11
- - 若改动较大可补 `go vet ./...`
12
-
13
- 模板前端(packages/create-keystone-app/template/apps/web)
14
- - `pnpm -C packages/create-keystone-app/template/apps/web typecheck`
15
- - `pnpm -C packages/create-keystone-app/template/apps/web lint`
16
- - `pnpm -C packages/create-keystone-app/template/apps/web test`
17
-
18
- 前端核心包(packages/keystone-web-core)
19
- - `pnpm -C packages/keystone-web-core build`
20
-
21
- 脚手架/模板变更
22
- - `pnpm scaffold:verify`
23
-
24
- 合同变更
25
- - `pnpm contracts:check`
26
- - 如更新了合同内容,执行 `pnpm contracts:sync`
27
-
28
- ## 写测试(必做)
29
- 后端
30
- - 领域/服务逻辑新增或改动:在 `tests/unit/platform/service` 或对应 domain 下补单测。
31
- - Repository 变更:在 `tests/unit/platform/repository` 补单测。
32
- - 新增/调整 API:在 `tests/integration/platform/api` 补集成测试。
33
- - 参考用例:`tests/unit/platform/approval/service_test.go`、`tests/integration/platform/api/approval_schema_test.go`。
34
-
35
- 前端(模板 web)
36
- - 新增页面/交互:在 `packages/create-keystone-app/template/apps/web/tests` 新增 `*.test.tsx`。
37
- - 使用 vitest + Testing Library(已在 `vite.config.ts` 配好 test 配置)。
38
-
39
- 前端核心包(keystone-web-core)
40
- - 新增核心组件/逻辑:优先在模板 web 侧写集成测试覆盖。
41
- - 如必须在包内写单测,需要补齐包内测试配置后再添加用例。
42
-
43
- ## 最小通过标准
44
- - 单点改动跑对应子项;跨域改动直接跑 `pnpm test`。
@@ -1,16 +0,0 @@
1
- package routes
2
-
3
- import (
4
- "github.com/gin-gonic/gin"
5
-
6
- "github.com/robsuncn/keystone/bootstrap/config"
7
-
8
- "__APP_NAME__/apps/server/internal/modules"
9
- )
10
-
11
- func registerModuleRoutes(v1 *gin.RouterGroup, cfg *config.Config) {
12
- if v1 == nil || cfg == nil {
13
- return
14
- }
15
- modules.LoadEnabled(cfg.Modules.Enabled, v1)
16
- }
@@ -1,226 +0,0 @@
1
- package routes
2
-
3
- import (
4
- "log"
5
- "time"
6
-
7
- "github.com/gin-gonic/gin"
8
- swaggerFiles "github.com/swaggo/files"
9
- ginSwagger "github.com/swaggo/gin-swagger"
10
- "gorm.io/gorm"
11
-
12
- "github.com/robsuncn/keystone/api/handler/audit"
13
- "github.com/robsuncn/keystone/api/handler/auth"
14
- "github.com/robsuncn/keystone/api/handler/files"
15
- "github.com/robsuncn/keystone/api/handler/health"
16
- jobHandlers "github.com/robsuncn/keystone/api/handler/jobs"
17
- "github.com/robsuncn/keystone/api/handler/notifications"
18
- "github.com/robsuncn/keystone/api/handler/org"
19
- "github.com/robsuncn/keystone/api/handler/rbac"
20
- "github.com/robsuncn/keystone/api/handler/settings"
21
- "github.com/robsuncn/keystone/api/handler/tenant"
22
- "github.com/robsuncn/keystone/api/handler/user"
23
- "github.com/robsuncn/keystone/api/middleware"
24
- coreRoutes "github.com/robsuncn/keystone/api/routes"
25
- "github.com/robsuncn/keystone/bootstrap/config"
26
- approvalHandler "github.com/robsuncn/keystone/domain/approval/handler"
27
- approvalRepo "github.com/robsuncn/keystone/domain/approval/repository"
28
- approvalSvc "github.com/robsuncn/keystone/domain/approval/service"
29
- "github.com/robsuncn/keystone/domain/permissions"
30
- "github.com/robsuncn/keystone/domain/service"
31
- jobInfra "github.com/robsuncn/keystone/infra/jobs"
32
- "github.com/robsuncn/keystone/infra/queue"
33
- "github.com/robsuncn/keystone/infra/repository"
34
- "github.com/robsuncn/keystone/infra/storage"
35
-
36
- "__APP_NAME__/apps/server/docs"
37
- "__APP_NAME__/apps/server/internal/frontend"
38
- )
39
-
40
- // SetupRouter wires repositories, services, handlers and registers routes.
41
- func SetupRouter(cfg *config.Config, db *gorm.DB, jobQueue queue.Queue) *gin.Engine {
42
- r := gin.New()
43
- r.Use(gin.Logger(), gin.Recovery())
44
-
45
- if cfg != nil && cfg.Server.SwaggerEnabled {
46
- docs.SwaggerInfo.BasePath = "/api/v1"
47
- r.GET("/api/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
48
- }
49
-
50
- userRepo := repository.NewUserRepository(db)
51
- userProfileRepo := repository.NewUserProfileRepository(db)
52
- userInvitationRepo := repository.NewUserInvitationRepository(db)
53
- platformUserRepo := repository.NewPlatformUserRepository(db)
54
- roleRepo := repository.NewRoleRepository(db)
55
- permRepo := repository.NewPermissionRepository(db)
56
- dataScopeRepo := repository.NewDataScopePolicyRepository(db)
57
- loginLogRepo := repository.NewLoginLogRepository(db)
58
- blacklistRepo := repository.NewTokenBlacklistRepository(db)
59
- auditLogRepo := repository.NewAuditLogRepository(db)
60
- tenantRepo := repository.NewTenantRepository(db)
61
- departmentRepo := repository.NewDepartmentRepository(db)
62
- membershipRepo := repository.NewMembershipRepository(db)
63
- settingRepo := repository.NewSettingRepository(db)
64
- notificationRepo := repository.NewNotificationRepository(db)
65
- jobRepo := repository.NewJobRepository(db)
66
-
67
- approvalFlowRepo := approvalRepo.NewFlowRepository(db)
68
- approvalNodeRepo := approvalRepo.NewNodeRepository(db)
69
- approvalInstanceRepo := approvalRepo.NewInstanceRepository(db)
70
- approvalRecordRepo := approvalRepo.NewRecordRepository(db)
71
-
72
- permSvc := service.NewPermissionService(permRepo, userRepo)
73
- platformPermSvc := service.NewPlatformPermissionService(platformUserRepo)
74
- roleSvc := service.NewRoleService(roleRepo)
75
- userSvc := service.NewUserService(userRepo, roleRepo, blacklistRepo)
76
- tenantSvc := service.NewTenantService(db, tenantRepo, userRepo, roleRepo)
77
- authSvc := service.NewAuthService(userRepo, roleRepo, blacklistRepo, loginLogRepo, cfg.JWT).
78
- WithTenantRepository(tenantRepo)
79
- platformAuthSvc := service.NewPlatformAuthService(platformUserRepo, blacklistRepo, loginLogRepo, cfg.JWT)
80
- loginLogSvc := service.NewLoginLogService(loginLogRepo)
81
- auditLogSvc := service.NewAuditLogService(db, auditLogRepo)
82
- departmentSvc := service.NewDepartmentService(db, departmentRepo, membershipRepo)
83
- membershipSvc := service.NewMembershipService(db, membershipRepo, departmentRepo)
84
- profileSvc := service.NewUserProfileService(userRepo, userProfileRepo)
85
- invitationSvc := service.NewUserInvitationService(userInvitationRepo)
86
- settingSvc := service.NewSettingService(db, settingRepo)
87
- prefixSvc := service.NewSettingPrefixService(settingSvc)
88
- notificationSvc := service.NewNotificationService(db, notificationRepo)
89
- jobSvc := service.NewJobService(db, jobRepo)
90
-
91
- policySvc := permissions.NewPolicyService(dataScopeRepo)
92
- evaluator := permissions.NewEvaluator(departmentSvc)
93
- enforcer := permissions.NewEnforcer(policySvc, evaluator, userRepo)
94
-
95
- approvalService := approvalSvc.NewService(
96
- db,
97
- approvalInstanceRepo,
98
- approvalFlowRepo,
99
- approvalNodeRepo,
100
- approvalRecordRepo,
101
- userRepo,
102
- permSvc,
103
- nil,
104
- )
105
- auditRecorder := service.NewAuditLogRecorder(auditLogSvc, userRepo)
106
- approvalService.WithAuditRecorder(approvalSvc.NewAuditRecorderAdapter(auditRecorder))
107
-
108
- healthHandler := health.NewHealthHandler(db, nil)
109
- authHandler := auth.NewAuthHandler(authSvc)
110
- platformAuthHandler := auth.NewPlatformAuthHandler(platformAuthSvc)
111
- tenantHandler := tenant.NewTenantHandler(tenantSvc)
112
- userHandler := user.NewUserHandler(userSvc, departmentSvc, invitationSvc, profileSvc)
113
- roleHandler := rbac.NewRoleHandler(roleSvc, permSvc, policySvc)
114
- permissionHandler := rbac.NewPermissionHandler(permSvc)
115
- loginLogHandler := audit.NewLoginLogHandler(loginLogSvc)
116
- auditLogHandler := audit.NewAuditLogHandler(auditLogSvc)
117
- departmentHandler := org.NewDepartmentHandler(departmentSvc)
118
- membershipHandler := org.NewMembershipHandler(membershipSvc)
119
- systemSettingsHandler := settings.NewSystemSettingsHandler(settingSvc).WithPrefixSettings(prefixSvc)
120
- notificationHandler := notifications.NewNotificationHandler(notificationSvc)
121
- jobHandler := jobHandlers.NewJobHandler(jobSvc)
122
-
123
- approvalSchemaHandler := approvalHandler.NewSchemaHandler()
124
- approvalFlowHandler := approvalHandler.NewFlowHandler(db, approvalFlowRepo, approvalNodeRepo, approvalInstanceRepo)
125
- approvalNodeHandler := approvalHandler.NewNodeHandler(approvalNodeRepo, approvalFlowRepo)
126
- approvalInstanceHandler := approvalHandler.NewInstanceHandler(
127
- approvalService,
128
- approvalInstanceRepo,
129
- approvalRecordRepo,
130
- approvalFlowRepo,
131
- approvalNodeRepo,
132
- userRepo,
133
- enforcer,
134
- nil,
135
- )
136
- approvalWorkbenchHandler := approvalHandler.NewWorkbenchHandler(
137
- approvalService,
138
- approvalInstanceRepo,
139
- approvalRecordRepo,
140
- approvalFlowRepo,
141
- userRepo,
142
- enforcer,
143
- nil,
144
- )
145
- approvalRecordHandler := approvalHandler.NewRecordHandler(
146
- approvalRecordRepo,
147
- approvalFlowRepo,
148
- userRepo,
149
- enforcer,
150
- nil,
151
- )
152
-
153
- var fileHandler *files.FileHandler
154
- storageSvc, err := storage.New(cfg.Storage)
155
- if err != nil {
156
- log.Printf("storage init failed: %v", err)
157
- } else {
158
- fileHandler = files.NewFileHandler(storageSvc)
159
- }
160
-
161
- var dispatcher *jobInfra.Dispatcher
162
- var importHandler *jobHandlers.ImportHandler
163
- var exportHandler *jobHandlers.ExportHandler
164
- if jobQueue != nil {
165
- dispatcher = jobInfra.NewDispatcher(jobQueue, jobSvc)
166
- }
167
- if dispatcher != nil {
168
- importHandler = jobHandlers.NewImportHandler(dispatcher)
169
- exportHandler = jobHandlers.NewExportHandler(dispatcher)
170
- }
171
-
172
- authMiddleware := middleware.NewAuthMiddleware(cfg.JWT, blacklistRepo, userRepo)
173
- rbacMiddleware := middleware.NewRBACMiddleware(permSvc)
174
- platformAuthMiddleware := middleware.NewPlatformAuthMiddleware(cfg.JWT, blacklistRepo, platformUserRepo)
175
- platformRBACMiddleware := middleware.NewRBACMiddleware(platformPermSvc)
176
- tenantGuards := []gin.HandlerFunc{
177
- middleware.RequireTenant(),
178
- middleware.NewTenantStatusMiddleware(tenantSvc),
179
- middleware.NewTenantRLSMiddleware(db),
180
- }
181
- authRateLimiter := middleware.NewRateLimiter(20, time.Minute)
182
-
183
- v1 := r.Group("/api/v1")
184
- coreRoutes.RegisterCoreRoutes(v1, coreRoutes.CoreRouteDeps{
185
- HealthHandler: healthHandler,
186
- AuthMiddleware: authMiddleware,
187
- PlatformAuth: platformAuthMiddleware,
188
- PlatformRBAC: platformRBACMiddleware,
189
- TenantGuards: tenantGuards,
190
- RBAC: rbacMiddleware,
191
- AuthHandler: authHandler,
192
- PlatformAuthHandler: platformAuthHandler,
193
- TenantHandler: tenantHandler,
194
- AuthRateLimiter: authRateLimiter,
195
- UserHandler: userHandler,
196
- RoleHandler: roleHandler,
197
- PermissionHandler: permissionHandler,
198
- LoginLogHandler: loginLogHandler,
199
- AuditLogHandler: auditLogHandler,
200
- FileHandler: fileHandler,
201
- DepartmentHandler: departmentHandler,
202
- MembershipHandler: membershipHandler,
203
- ApprovalSchema: approvalSchemaHandler,
204
- ApprovalFlow: approvalFlowHandler,
205
- ApprovalNode: approvalNodeHandler,
206
- ApprovalInstance: approvalInstanceHandler,
207
- ApprovalWorkbench: approvalWorkbenchHandler,
208
- ApprovalRecord: approvalRecordHandler,
209
- NotificationHandler: notificationHandler,
210
- JobHandler: jobHandler,
211
- ImportHandler: importHandler,
212
- ExportHandler: exportHandler,
213
- SystemSettingsHandler: systemSettingsHandler,
214
- })
215
- registerModuleRoutes(v1, cfg)
216
-
217
- spaHandler, err := frontend.NewSPAHandler()
218
- if err == nil {
219
- r.NoRoute(spaHandler.GinHandler())
220
- log.Println("Frontend: embedded SPA handler enabled")
221
- } else if cfg != nil && cfg.Server.Mode == "release" {
222
- log.Printf("Warning: frontend not available: %v", err)
223
- }
224
-
225
- return r
226
- }
@@ -1,74 +0,0 @@
1
- package startup
2
-
3
- import (
4
- "fmt"
5
- "log"
6
- "time"
7
-
8
- "gorm.io/gorm"
9
-
10
- "github.com/robsuncn/keystone/bootstrap/config"
11
- "github.com/robsuncn/keystone/bootstrap/migrations"
12
- "github.com/robsuncn/keystone/bootstrap/seeds"
13
- "github.com/robsuncn/keystone/domain/permissions"
14
-
15
- "__APP_NAME__/apps/server/internal/modules"
16
- )
17
-
18
- // InitializeDatabase runs base + module migrations and seeds.
19
- func InitializeDatabase(cfg *config.Config) (*gorm.DB, error) {
20
- if cfg == nil {
21
- return nil, fmt.Errorf("config is nil")
22
- }
23
-
24
- dbCfg := &cfg.Database
25
- start := time.Now()
26
- log.Println("========================================")
27
- log.Println("Keystone Database Initialization")
28
- log.Println("========================================")
29
-
30
- log.Printf("[startup] Connecting to database (driver: %s)...", dbCfg.Driver)
31
- db, err := config.InitDB(dbCfg, cfg.Server.Mode)
32
- if err != nil {
33
- return nil, fmt.Errorf("database connection failed: %w", err)
34
- }
35
-
36
- log.Println("[startup] Phase 1: Auth model migration...")
37
- if err := config.MigrateDB(db, config.AuthModels()...); err != nil {
38
- return nil, fmt.Errorf("auth model migration failed: %w", err)
39
- }
40
-
41
- log.Println("[startup] Phase 2: Platform schema migrations...")
42
- if err := migrations.RunAll(db); err != nil {
43
- return nil, fmt.Errorf("platform migrations failed: %w", err)
44
- }
45
-
46
- log.Println("[startup] Phase 3: Module schema migrations...")
47
- if err := modules.MigrateEnabled(cfg.Modules.Enabled, db); err != nil {
48
- return nil, fmt.Errorf("module migrations failed: %w", err)
49
- }
50
-
51
- log.Println("[startup] Phase 4: Data seeding...")
52
- if err := seeds.RunAll(db, seeds.Options{
53
- RegisterPermissions: func(reg *permissions.Registry) error {
54
- return modules.RegisterPermissions(cfg.Modules.Enabled, reg)
55
- },
56
- }); err != nil {
57
- return nil, fmt.Errorf("data seeding failed: %w", err)
58
- }
59
- if err := modules.SeedEnabled(cfg.Modules.Enabled, db); err != nil {
60
- return nil, fmt.Errorf("module seeding failed: %w", err)
61
- }
62
-
63
- log.Println("[startup] Phase 5: Security policies...")
64
- if err := migrations.MigrateTenantRLS(db); err != nil {
65
- return nil, fmt.Errorf("RLS setup failed: %w", err)
66
- }
67
-
68
- elapsed := time.Since(start).Round(time.Millisecond)
69
- log.Println("========================================")
70
- log.Printf("Database initialization complete (%v)", elapsed)
71
- log.Println("========================================")
72
-
73
- return db, nil
74
- }
@@ -1,122 +0,0 @@
1
- package frontend
2
-
3
- import (
4
- "io"
5
- "net/http"
6
- "path"
7
- "strings"
8
-
9
- "github.com/gin-gonic/gin"
10
- )
11
-
12
- // SPAHandler serves the embedded frontend files with SPA route fallback.
13
- type SPAHandler struct {
14
- fs http.FileSystem
15
- fileServer http.Handler
16
- indexHTML []byte
17
- }
18
-
19
- // NewSPAHandler creates a new SPA handler with the embedded file system.
20
- func NewSPAHandler() (*SPAHandler, error) {
21
- fsys, err := GetFileSystem()
22
- if err != nil {
23
- return nil, err
24
- }
25
-
26
- handler := &SPAHandler{
27
- fs: fsys,
28
- fileServer: http.FileServer(fsys),
29
- }
30
-
31
- if err := handler.loadIndex(); err != nil {
32
- return nil, err
33
- }
34
-
35
- return handler, nil
36
- }
37
-
38
- func (h *SPAHandler) loadIndex() error {
39
- file, err := h.fs.Open("/index.html")
40
- if err != nil {
41
- return err
42
- }
43
- defer file.Close()
44
-
45
- h.indexHTML, err = io.ReadAll(file)
46
- if err != nil {
47
- return err
48
- }
49
-
50
- return nil
51
- }
52
-
53
- // ServeHTTP implements http.Handler for the SPA.
54
- func (h *SPAHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
55
- urlPath := path.Clean(r.URL.Path)
56
-
57
- if urlPath == "/" || urlPath == "" {
58
- h.serveIndex(w)
59
- return
60
- }
61
-
62
- file, err := h.fs.Open(urlPath)
63
- if err != nil {
64
- h.serveIndex(w)
65
- return
66
- }
67
- defer file.Close()
68
-
69
- stat, err := file.Stat()
70
- if err != nil || stat.IsDir() {
71
- h.serveIndex(w)
72
- return
73
- }
74
-
75
- h.setContentType(w, urlPath)
76
- h.fileServer.ServeHTTP(w, r)
77
- }
78
-
79
- func (h *SPAHandler) serveIndex(w http.ResponseWriter) {
80
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
81
- w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
82
- w.WriteHeader(http.StatusOK)
83
- w.Write(h.indexHTML)
84
- }
85
-
86
- func (h *SPAHandler) setContentType(w http.ResponseWriter, filePath string) {
87
- ext := strings.ToLower(path.Ext(filePath))
88
- contentTypes := map[string]string{
89
- ".html": "text/html; charset=utf-8",
90
- ".js": "application/javascript; charset=utf-8",
91
- ".mjs": "application/javascript; charset=utf-8",
92
- ".css": "text/css; charset=utf-8",
93
- ".json": "application/json; charset=utf-8",
94
- ".svg": "image/svg+xml",
95
- ".png": "image/png",
96
- ".jpg": "image/jpeg",
97
- ".jpeg": "image/jpeg",
98
- ".gif": "image/gif",
99
- ".webp": "image/webp",
100
- ".ico": "image/x-icon",
101
- ".woff": "font/woff",
102
- ".woff2": "font/woff2",
103
- ".ttf": "font/ttf",
104
- ".eot": "application/vnd.ms-fontobject",
105
- ".map": "application/json",
106
- }
107
-
108
- if ct, ok := contentTypes[ext]; ok {
109
- w.Header().Set("Content-Type", ct)
110
- }
111
-
112
- if strings.Contains(filePath, "/assets/") {
113
- w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
114
- }
115
- }
116
-
117
- // GinHandler returns a gin.HandlerFunc for router.NoRoute().
118
- func (h *SPAHandler) GinHandler() gin.HandlerFunc {
119
- return func(c *gin.Context) {
120
- h.ServeHTTP(c.Writer, c.Request)
121
- }
122
- }
@@ -1,145 +0,0 @@
1
- package modules
2
-
3
- import (
4
- "sync"
5
-
6
- "github.com/gin-gonic/gin"
7
- "gorm.io/gorm"
8
-
9
- "github.com/robsuncn/keystone/domain/permissions"
10
- "github.com/robsuncn/keystone/infra/jobs"
11
- )
12
-
13
- // Module registers routes and lifecycle hooks for a business module.
14
- type Module interface {
15
- Name() string
16
- RegisterRoutes(rg *gin.RouterGroup)
17
- RegisterModels() []interface{}
18
- RegisterPermissions(registry *permissions.Registry) error
19
- RegisterJobs(registry *jobs.Registry) error
20
- Migrate(db *gorm.DB) error
21
- Seed(db *gorm.DB) error
22
- }
23
-
24
- var (
25
- registryMu sync.RWMutex
26
- registry = make(map[string]Module)
27
- order []string
28
- )
29
-
30
- // Register adds or replaces a module by name.
31
- func Register(module Module) {
32
- if module == nil {
33
- return
34
- }
35
- name := module.Name()
36
- if name == "" {
37
- return
38
- }
39
- registryMu.Lock()
40
- defer registryMu.Unlock()
41
- if _, ok := registry[name]; !ok {
42
- order = append(order, name)
43
- }
44
- registry[name] = module
45
- }
46
-
47
- // Clear removes all registered modules.
48
- func Clear() {
49
- registryMu.Lock()
50
- defer registryMu.Unlock()
51
- registry = make(map[string]Module)
52
- order = nil
53
- }
54
-
55
- // Registered returns a snapshot of the current modules.
56
- func Registered() []Module {
57
- registryMu.RLock()
58
- defer registryMu.RUnlock()
59
- out := make([]Module, 0, len(order))
60
- for _, name := range order {
61
- if module, ok := registry[name]; ok {
62
- out = append(out, module)
63
- }
64
- }
65
- return out
66
- }
67
-
68
- func enabledModules(enabled []string) []Module {
69
- if len(enabled) == 0 {
70
- return nil
71
- }
72
- registryMu.RLock()
73
- defer registryMu.RUnlock()
74
- out := make([]Module, 0, len(enabled))
75
- for _, name := range enabled {
76
- module, ok := registry[name]
77
- if !ok || module == nil {
78
- continue
79
- }
80
- out = append(out, module)
81
- }
82
- return out
83
- }
84
-
85
- // LoadEnabled registers enabled module routes.
86
- func LoadEnabled(enabled []string, rg *gin.RouterGroup) {
87
- if len(enabled) == 0 || rg == nil {
88
- return
89
- }
90
- for _, module := range enabledModules(enabled) {
91
- module.RegisterRoutes(rg)
92
- }
93
- }
94
-
95
- // RegisterPermissions registers permission definitions for enabled modules.
96
- func RegisterPermissions(enabled []string, reg *permissions.Registry) error {
97
- if len(enabled) == 0 || reg == nil {
98
- return nil
99
- }
100
- for _, module := range enabledModules(enabled) {
101
- if err := module.RegisterPermissions(reg); err != nil {
102
- return err
103
- }
104
- }
105
- return nil
106
- }
107
-
108
- // RegisterJobs registers job handlers for enabled modules.
109
- func RegisterJobs(enabled []string, reg *jobs.Registry) error {
110
- if len(enabled) == 0 || reg == nil {
111
- return nil
112
- }
113
- for _, module := range enabledModules(enabled) {
114
- if err := module.RegisterJobs(reg); err != nil {
115
- return err
116
- }
117
- }
118
- return nil
119
- }
120
-
121
- // MigrateEnabled runs migrations for enabled modules.
122
- func MigrateEnabled(enabled []string, db *gorm.DB) error {
123
- if len(enabled) == 0 || db == nil {
124
- return nil
125
- }
126
- for _, module := range enabledModules(enabled) {
127
- if err := module.Migrate(db); err != nil {
128
- return err
129
- }
130
- }
131
- return nil
132
- }
133
-
134
- // SeedEnabled runs seed data setup for enabled modules.
135
- func SeedEnabled(enabled []string, db *gorm.DB) error {
136
- if len(enabled) == 0 || db == nil {
137
- return nil
138
- }
139
- for _, module := range enabledModules(enabled) {
140
- if err := module.Seed(db); err != nil {
141
- return err
142
- }
143
- }
144
- return nil
145
- }
@@ -1,23 +0,0 @@
1
- ---
2
- helpKey: "example/faq"
3
- title: "Example Module FAQ"
4
- description: "Common questions for the Example module."
5
- category: "example"
6
- tags: ["example", "faq"]
7
- ---
8
-
9
- # FAQ
10
-
11
- ## 为什么列表为空?
12
- - 确认已运行迁移与种子(首次启动会自动执行)。
13
- - 检查当前用户是否有 `example:item:view` 权限。
14
- - 确认 `modules.enabled` 中已启用 `example`。
15
-
16
- ## 为什么保存失败?
17
- - Title 必填,且不能为空字符串。
18
- - 状态仅支持 `active` / `inactive`。
19
- - 查看后端日志获取更详细错误信息。
20
-
21
- ## 为什么前后端权限不一致?
22
- - 检查 `routes.tsx` 中的 `handle.permission`。
23
- - 检查 `RegisterPermissions` 中的权限注册是否一致。
@@ -1,26 +0,0 @@
1
- ---
2
- helpKey: "example/items"
3
- title: "Example Items"
4
- description: "Manage example items with full CRUD operations."
5
- category: "example"
6
- tags: ["example", "items", "crud"]
7
- ---
8
-
9
- # Example Items
10
-
11
- 该页面展示 Example 模块的完整 CRUD 流程,适合用来对照学习:
12
-
13
- ## 字段说明
14
- - **Title**:必填,标题信息。
15
- - **Description**:可选,简短描述。
16
- - **Status**:`active` / `inactive`。
17
-
18
- ## 权限建议
19
- - `example:item:view`:访问列表与详情。
20
- - `example:item:manage`:创建、编辑、删除。
21
-
22
- ## API 端点
23
- - `GET /api/v1/example/items`
24
- - `POST /api/v1/example/items`
25
- - `PATCH /api/v1/example/items/:id`
26
- - `DELETE /api/v1/example/items/:id`