@robsun/create-keystone-app 0.2.13 → 0.4.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.
Files changed (88) hide show
  1. package/README.md +46 -43
  2. package/dist/create-keystone-app.js +347 -10
  3. package/dist/create-module.js +1219 -607
  4. package/package.json +22 -23
  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 +81 -73
  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 +60 -18
  23. package/template/apps/server/go.sum +183 -31
  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 +10 -7
  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 -103
  72. package/template/.claude/skills/keystone-dev/references/APPROVAL.md +0 -121
  73. package/template/.claude/skills/keystone-dev/references/CAPABILITIES.md +0 -261
  74. package/template/.claude/skills/keystone-dev/references/TEMPLATES.md +0 -532
  75. package/template/.claude/skills/keystone-dev/references/TESTING.md +0 -44
  76. package/template/.codex/skills/keystone-dev/SKILL.md +0 -103
  77. package/template/.codex/skills/keystone-dev/references/APPROVAL.md +0 -121
  78. package/template/.codex/skills/keystone-dev/references/CAPABILITIES.md +0 -261
  79. package/template/.codex/skills/keystone-dev/references/TEMPLATES.md +0 -532
  80. package/template/.codex/skills/keystone-dev/references/TESTING.md +0 -44
  81. package/template/apps/server/internal/app/routes/module_routes.go +0 -16
  82. package/template/apps/server/internal/app/routes/routes.go +0 -226
  83. package/template/apps/server/internal/app/startup/startup.go +0 -74
  84. package/template/apps/server/internal/frontend/handler.go +0 -122
  85. package/template/apps/server/internal/modules/registry.go +0 -145
  86. package/template/apps/web/src/modules/example/help/faq.md +0 -23
  87. package/template/apps/web/src/modules/example/help/items.md +0 -26
  88. package/template/apps/web/src/modules/example/help/overview.md +0 -25
@@ -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`
@@ -1,25 +0,0 @@
1
- ---
2
- helpKey: "example/overview"
3
- title: "Example Module Overview"
4
- description: "Overview of the Example module and its full CRUD flow."
5
- category: "example"
6
- tags: ["example", "items", "crud"]
7
- ---
8
-
9
- # Example Module
10
-
11
- Example 模块演示完整的 CRUD、权限命名与前后端分层结构,适合作为新模块开发参考。
12
-
13
- ## 你能看到什么
14
- - 前端模块注册(`index.ts`)与路由配置(`routes.tsx`)
15
- - CRUD 页面与 API 调用(`pages/` + `services/`)
16
- - 后端 DDD 分层(`domain/` + `infra/` + `bootstrap/`)
17
- - 权限注册与菜单同步(`RegisterPermissions`)
18
-
19
- ## 关键文件
20
- - 前端:`apps/web/src/modules/example/`
21
- - 后端:`apps/server/internal/modules/example/`
22
-
23
- ## API 与权限
24
- - API:`/api/v1/example/items`
25
- - 权限:`example:item:view`、`example:item:manage`