@robsun/create-keystone-app 0.1.14 → 0.1.16

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 (63) hide show
  1. package/bin/create-keystone-app.js +12 -0
  2. package/package.json +1 -1
  3. package/template/README.md +7 -1
  4. package/template/apps/server/config.example.yaml +1 -0
  5. package/template/apps/server/config.yaml +1 -0
  6. package/template/apps/server/internal/modules/demo/api/handler/task_handler.go +152 -0
  7. package/template/apps/server/internal/modules/demo/bootstrap/migrations/task.go +21 -0
  8. package/template/apps/server/internal/modules/demo/bootstrap/seeds/task.go +33 -0
  9. package/template/apps/server/internal/modules/demo/domain/models/task.go +30 -0
  10. package/template/apps/server/internal/modules/demo/domain/service/errors.go +9 -0
  11. package/template/apps/server/internal/modules/demo/domain/service/task_service.go +95 -0
  12. package/template/apps/server/internal/modules/demo/infra/repository/task_repository.go +49 -0
  13. package/template/apps/server/internal/modules/demo/module.go +50 -14
  14. package/template/apps/server/internal/modules/example/handlers.go +19 -0
  15. package/template/apps/server/internal/modules/example/module.go +53 -0
  16. package/template/apps/server/internal/modules/manifest.go +3 -1
  17. package/template/apps/web/node_modules/.bin/acorn +17 -0
  18. package/template/apps/web/node_modules/.bin/acorn.CMD +12 -0
  19. package/template/apps/web/node_modules/.bin/acorn.ps1 +41 -0
  20. package/template/apps/web/node_modules/.bin/autoprefixer +17 -0
  21. package/template/apps/web/node_modules/.bin/autoprefixer.CMD +12 -0
  22. package/template/apps/web/node_modules/.bin/autoprefixer.ps1 +41 -0
  23. package/template/apps/web/node_modules/.bin/browserslist +17 -0
  24. package/template/apps/web/node_modules/.bin/browserslist.CMD +12 -0
  25. package/template/apps/web/node_modules/.bin/browserslist.ps1 +41 -0
  26. package/template/apps/web/node_modules/.bin/eslint +17 -0
  27. package/template/apps/web/node_modules/.bin/eslint.CMD +12 -0
  28. package/template/apps/web/node_modules/.bin/eslint.ps1 +41 -0
  29. package/template/apps/web/node_modules/.bin/jiti +17 -0
  30. package/template/apps/web/node_modules/.bin/jiti.CMD +12 -0
  31. package/template/apps/web/node_modules/.bin/jiti.ps1 +41 -0
  32. package/template/apps/web/node_modules/.bin/tsc +17 -0
  33. package/template/apps/web/node_modules/.bin/tsc.CMD +12 -0
  34. package/template/apps/web/node_modules/.bin/tsc.ps1 +41 -0
  35. package/template/apps/web/node_modules/.bin/tsserver +17 -0
  36. package/template/apps/web/node_modules/.bin/tsserver.CMD +12 -0
  37. package/template/apps/web/node_modules/.bin/tsserver.ps1 +41 -0
  38. package/template/apps/web/node_modules/.bin/vite +17 -0
  39. package/template/apps/web/node_modules/.bin/vite.CMD +12 -0
  40. package/template/apps/web/node_modules/.bin/vite.ps1 +41 -0
  41. package/template/apps/web/node_modules/.bin/vitest +17 -0
  42. package/template/apps/web/node_modules/.bin/vitest.CMD +12 -0
  43. package/template/apps/web/node_modules/.bin/vitest.ps1 +41 -0
  44. package/template/apps/web/node_modules/.bin/yaml +17 -0
  45. package/template/apps/web/node_modules/.bin/yaml.CMD +12 -0
  46. package/template/apps/web/node_modules/.bin/yaml.ps1 +41 -0
  47. package/template/apps/web/package.json +1 -1
  48. package/template/apps/web/src/app.config.ts +1 -1
  49. package/template/apps/web/src/main.tsx +1 -0
  50. package/template/apps/web/src/modules/demo/routes.tsx +1 -1
  51. package/template/apps/web/src/modules/example/help/overview.md +11 -0
  52. package/template/apps/web/src/modules/example/index.ts +4 -0
  53. package/template/apps/web/src/modules/example/pages/ExamplePage.tsx +41 -0
  54. package/template/apps/web/src/modules/example/routes.tsx +20 -0
  55. package/template/apps/web/src/modules/example/services/api.ts +8 -0
  56. package/template/docs/CODE_STYLE.md +34 -0
  57. package/template/docs/CONVENTIONS.md +1 -0
  58. package/template/package.json +3 -1
  59. package/template/pnpm-lock.yaml +6029 -0
  60. package/template/scripts/check-modules.js +76 -0
  61. package/template/scripts/test.bat +11 -0
  62. package/template/scripts/test.sh +10 -0
  63. package/template/apps/server/internal/modules/demo/handlers.go +0 -190
@@ -87,6 +87,9 @@ function copyDir(src, dest, replacements) {
87
87
  fs.mkdirSync(dest, { recursive: true });
88
88
  const entries = fs.readdirSync(src, { withFileTypes: true });
89
89
  for (const entry of entries) {
90
+ if (entry.isDirectory() && shouldSkipDir(entry.name)) {
91
+ continue;
92
+ }
90
93
  const srcPath = path.join(src, entry.name);
91
94
  const destPath = path.join(dest, entry.name);
92
95
  if (entry.isDirectory()) {
@@ -112,6 +115,10 @@ function copyFile(src, dest, replacements) {
112
115
  }
113
116
  }
114
117
 
118
+ function shouldSkipDir(name) {
119
+ return name === 'node_modules' || name === '.git';
120
+ }
121
+
115
122
  function stripDemo(targetDir) {
116
123
  removePath(path.join(targetDir, 'apps', 'web', 'src', 'modules', 'demo'));
117
124
  removePath(path.join(targetDir, 'apps', 'server', 'internal', 'modules', 'demo'));
@@ -142,9 +149,14 @@ function stripDemo(targetDir) {
142
149
  );
143
150
  const manifest = `package modules
144
151
 
152
+ import (
153
+ \texample "__APP_NAME__/apps/server/internal/modules/example"
154
+ )
155
+
145
156
  // RegisterAll wires the module registry for this app.
146
157
  func RegisterAll() {
147
158
  \tClear()
159
+ \tRegister(example.NewModule())
148
160
  }
149
161
  `;
150
162
  fs.writeFileSync(manifestPath, manifest, 'utf8');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robsun/create-keystone-app",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -23,7 +23,7 @@ pnpm web:dev
23
23
  - 复制 `apps/web/.env.example` 为 `apps/web/.env`(Vite 会读取)。
24
24
  - 检查 `apps/server/config.yaml`(Go 运行配置)。
25
25
  - 需要外部依赖时参考 `docker-compose.yml`。
26
- - 建议阅读:`docs/CONVENTIONS.md`、`apps/web/README.md`、`apps/server/README.md`。
26
+ - 建议阅读:`docs/CONVENTIONS.md`、`docs/CODE_STYLE.md`、`apps/web/README.md`、`apps/server/README.md`。
27
27
 
28
28
  ## 常用命令
29
29
  - `pnpm dev`:跨平台开发启动(Air + Vite)。
@@ -49,6 +49,12 @@ pnpm web:dev
49
49
  └─ scripts/ # 跨平台脚本
50
50
  ```
51
51
 
52
+ ## Example 模块(默认)
53
+ Starter 与 Full 模式默认包含 Example 模块,用于展示模块注册、权限、路由与 API。
54
+ - 菜单:Example
55
+ - API:`/api/v1/example/hello`
56
+ - 权限:`example:view`
57
+
52
58
  <!-- DEMO_START -->
53
59
  ## Demo 模块(可选)
54
60
  如果需要 Demo,可在创建时使用 `--demo` 或 `--profile=full`。
@@ -6,6 +6,7 @@ server:
6
6
  modules:
7
7
  enabled:
8
8
  - "keystone"
9
+ - "example"
9
10
  - "demo"
10
11
 
11
12
  database:
@@ -6,6 +6,7 @@ server:
6
6
  modules:
7
7
  enabled:
8
8
  - "keystone"
9
+ - "example"
9
10
  - "demo"
10
11
 
11
12
  database:
@@ -0,0 +1,152 @@
1
+ package handler
2
+
3
+ import (
4
+ "errors"
5
+
6
+ "github.com/gin-gonic/gin"
7
+
8
+ hcommon "github.com/robsuncn/keystone/api/handler/common"
9
+ "github.com/robsuncn/keystone/api/response"
10
+
11
+ "__APP_NAME__/apps/server/internal/modules/demo/domain/models"
12
+ "__APP_NAME__/apps/server/internal/modules/demo/domain/service"
13
+ )
14
+
15
+ type TaskHandler struct {
16
+ tasks *service.TaskService
17
+ }
18
+
19
+ func NewTaskHandler(tasks *service.TaskService) *TaskHandler {
20
+ if tasks == nil {
21
+ return nil
22
+ }
23
+ return &TaskHandler{tasks: tasks}
24
+ }
25
+
26
+ type taskInput struct {
27
+ Title string `json:"title"`
28
+ Status models.TaskStatus `json:"status"`
29
+ }
30
+
31
+ const defaultTenantID uint = 1
32
+
33
+ func (h *TaskHandler) List(c *gin.Context) {
34
+ if h == nil || h.tasks == nil {
35
+ response.ServiceUnavailable(c, "demo tasks unavailable")
36
+ return
37
+ }
38
+ tenantID := resolveTenantID(c)
39
+
40
+ items, err := h.tasks.List(c.Request.Context(), tenantID)
41
+ if err != nil {
42
+ response.InternalError(c, "failed to load demo tasks")
43
+ return
44
+ }
45
+
46
+ response.Success(c, gin.H{"items": items})
47
+ }
48
+
49
+ func (h *TaskHandler) Create(c *gin.Context) {
50
+ if h == nil || h.tasks == nil {
51
+ response.ServiceUnavailable(c, "demo tasks unavailable")
52
+ return
53
+ }
54
+ tenantID := resolveTenantID(c)
55
+
56
+ var input taskInput
57
+ if err := c.ShouldBindJSON(&input); err != nil {
58
+ response.BadRequest(c, "invalid payload")
59
+ return
60
+ }
61
+
62
+ task, err := h.tasks.Create(c.Request.Context(), tenantID, service.TaskInput{
63
+ Title: input.Title,
64
+ Status: input.Status,
65
+ })
66
+ if err != nil {
67
+ switch {
68
+ case errors.Is(err, service.ErrTitleRequired):
69
+ response.BadRequest(c, "title is required")
70
+ case errors.Is(err, service.ErrStatusInvalid):
71
+ response.BadRequest(c, "invalid status")
72
+ default:
73
+ response.InternalError(c, "failed to create demo task")
74
+ }
75
+ return
76
+ }
77
+
78
+ response.Created(c, task)
79
+ }
80
+
81
+ func (h *TaskHandler) Update(c *gin.Context) {
82
+ if h == nil || h.tasks == nil {
83
+ response.ServiceUnavailable(c, "demo tasks unavailable")
84
+ return
85
+ }
86
+ tenantID := resolveTenantID(c)
87
+
88
+ id, err := hcommon.ParseUintParam(c, "id")
89
+ if err != nil || id == 0 {
90
+ response.BadRequest(c, "invalid id")
91
+ return
92
+ }
93
+
94
+ var input taskInput
95
+ if err := c.ShouldBindJSON(&input); err != nil {
96
+ response.BadRequest(c, "invalid payload")
97
+ return
98
+ }
99
+
100
+ task, err := h.tasks.Update(c.Request.Context(), tenantID, id, service.TaskUpdateInput{
101
+ Title: input.Title,
102
+ Status: input.Status,
103
+ })
104
+ if err != nil {
105
+ switch {
106
+ case errors.Is(err, service.ErrTaskNotFound):
107
+ response.NotFound(c, "task not found")
108
+ case errors.Is(err, service.ErrStatusInvalid):
109
+ response.BadRequest(c, "invalid status")
110
+ default:
111
+ response.InternalError(c, "failed to update demo task")
112
+ }
113
+ return
114
+ }
115
+
116
+ response.SuccessWithMessage(c, "updated", task)
117
+ }
118
+
119
+ func (h *TaskHandler) Delete(c *gin.Context) {
120
+ if h == nil || h.tasks == nil {
121
+ response.ServiceUnavailable(c, "demo tasks unavailable")
122
+ return
123
+ }
124
+ tenantID := resolveTenantID(c)
125
+
126
+ id, err := hcommon.ParseUintParam(c, "id")
127
+ if err != nil || id == 0 {
128
+ response.BadRequest(c, "invalid id")
129
+ return
130
+ }
131
+
132
+ if err := h.tasks.Delete(c.Request.Context(), tenantID, id); err != nil {
133
+ if errors.Is(err, service.ErrTaskNotFound) {
134
+ response.NotFound(c, "task not found")
135
+ return
136
+ }
137
+ response.InternalError(c, "failed to delete demo task")
138
+ return
139
+ }
140
+
141
+ response.SuccessWithMessage(c, "deleted", gin.H{"id": id})
142
+ }
143
+
144
+ func resolveTenantID(c *gin.Context) uint {
145
+ if c == nil {
146
+ return defaultTenantID
147
+ }
148
+ if tenantID, ok := hcommon.GetTenantID(c); ok && tenantID > 0 {
149
+ return tenantID
150
+ }
151
+ return defaultTenantID
152
+ }
@@ -0,0 +1,21 @@
1
+ package migrations
2
+
3
+ import (
4
+ "fmt"
5
+ "log"
6
+
7
+ "gorm.io/gorm"
8
+
9
+ "__APP_NAME__/apps/server/internal/modules/demo/domain/models"
10
+ )
11
+
12
+ func Migrate(db *gorm.DB) error {
13
+ if db == nil {
14
+ return nil
15
+ }
16
+ log.Println("[demo] Running migrations...")
17
+ if err := db.AutoMigrate(&models.DemoTask{}); err != nil {
18
+ return fmt.Errorf("auto migrate demo_tasks: %w", err)
19
+ }
20
+ return nil
21
+ }
@@ -0,0 +1,33 @@
1
+ package seeds
2
+
3
+ import (
4
+ "log"
5
+
6
+ "gorm.io/gorm"
7
+
8
+ "__APP_NAME__/apps/server/internal/modules/demo/domain/models"
9
+ )
10
+
11
+ func Seed(db *gorm.DB) error {
12
+ if db == nil {
13
+ return nil
14
+ }
15
+
16
+ var count int64
17
+ if err := db.Model(&models.DemoTask{}).Count(&count).Error; err != nil {
18
+ return err
19
+ }
20
+ if count > 0 {
21
+ return nil
22
+ }
23
+
24
+ log.Println("[demo] Seeding initial data...")
25
+ tasks := []models.DemoTask{
26
+ {Title: "Set up project", Status: models.StatusTodo},
27
+ {Title: "Review UI states", Status: models.StatusDone},
28
+ }
29
+ for i := range tasks {
30
+ tasks[i].TenantID = 1
31
+ }
32
+ return db.Create(&tasks).Error
33
+ }
@@ -0,0 +1,30 @@
1
+ package models
2
+
3
+ import "github.com/robsuncn/keystone/domain/models"
4
+
5
+ type TaskStatus string
6
+
7
+ const (
8
+ StatusTodo TaskStatus = "todo"
9
+ StatusInProgress TaskStatus = "in_progress"
10
+ StatusDone TaskStatus = "done"
11
+ )
12
+
13
+ func (s TaskStatus) IsValid() bool {
14
+ switch s {
15
+ case StatusTodo, StatusInProgress, StatusDone:
16
+ return true
17
+ default:
18
+ return false
19
+ }
20
+ }
21
+
22
+ type DemoTask struct {
23
+ models.BaseModel
24
+ Title string `gorm:"size:200;not null" json:"title"`
25
+ Status TaskStatus `gorm:"size:20;not null;default:'todo'" json:"status"`
26
+ }
27
+
28
+ func (DemoTask) TableName() string {
29
+ return "demo_tasks"
30
+ }
@@ -0,0 +1,9 @@
1
+ package service
2
+
3
+ import "errors"
4
+
5
+ var (
6
+ ErrTaskNotFound = errors.New("task not found")
7
+ ErrTitleRequired = errors.New("title is required")
8
+ ErrStatusInvalid = errors.New("status is invalid")
9
+ )
@@ -0,0 +1,95 @@
1
+ package service
2
+
3
+ import (
4
+ "context"
5
+ "strings"
6
+
7
+ "__APP_NAME__/apps/server/internal/modules/demo/domain/models"
8
+ )
9
+
10
+ type TaskRepository interface {
11
+ List(ctx context.Context, tenantID uint) ([]models.DemoTask, error)
12
+ FindByID(tenantID, id uint) (*models.DemoTask, error)
13
+ Create(ctx context.Context, task *models.DemoTask) error
14
+ Update(ctx context.Context, task *models.DemoTask) error
15
+ Delete(ctx context.Context, task *models.DemoTask) error
16
+ }
17
+
18
+ type TaskService struct {
19
+ tasks TaskRepository
20
+ }
21
+
22
+ type TaskInput struct {
23
+ Title string
24
+ Status models.TaskStatus
25
+ }
26
+
27
+ type TaskUpdateInput struct {
28
+ Title string
29
+ Status models.TaskStatus
30
+ }
31
+
32
+ func NewTaskService(tasks TaskRepository) *TaskService {
33
+ return &TaskService{tasks: tasks}
34
+ }
35
+
36
+ func (s *TaskService) List(ctx context.Context, tenantID uint) ([]models.DemoTask, error) {
37
+ return s.tasks.List(ctx, tenantID)
38
+ }
39
+
40
+ func (s *TaskService) Create(ctx context.Context, tenantID uint, input TaskInput) (*models.DemoTask, error) {
41
+ title := strings.TrimSpace(input.Title)
42
+ if title == "" {
43
+ return nil, ErrTitleRequired
44
+ }
45
+
46
+ status := input.Status
47
+ if status == "" {
48
+ status = models.StatusTodo
49
+ }
50
+ if !status.IsValid() {
51
+ return nil, ErrStatusInvalid
52
+ }
53
+
54
+ task := &models.DemoTask{
55
+ Title: title,
56
+ Status: status,
57
+ }
58
+ task.TenantID = tenantID
59
+
60
+ if err := s.tasks.Create(ctx, task); err != nil {
61
+ return nil, err
62
+ }
63
+ return task, nil
64
+ }
65
+
66
+ func (s *TaskService) Update(ctx context.Context, tenantID, id uint, input TaskUpdateInput) (*models.DemoTask, error) {
67
+ task, err := s.tasks.FindByID(tenantID, id)
68
+ if err != nil {
69
+ return nil, err
70
+ }
71
+
72
+ if title := strings.TrimSpace(input.Title); title != "" {
73
+ task.Title = title
74
+ }
75
+
76
+ if input.Status != "" {
77
+ if !input.Status.IsValid() {
78
+ return nil, ErrStatusInvalid
79
+ }
80
+ task.Status = input.Status
81
+ }
82
+
83
+ if err := s.tasks.Update(ctx, task); err != nil {
84
+ return nil, err
85
+ }
86
+ return task, nil
87
+ }
88
+
89
+ func (s *TaskService) Delete(ctx context.Context, tenantID, id uint) error {
90
+ task, err := s.tasks.FindByID(tenantID, id)
91
+ if err != nil {
92
+ return err
93
+ }
94
+ return s.tasks.Delete(ctx, task)
95
+ }
@@ -0,0 +1,49 @@
1
+ package repository
2
+
3
+ import (
4
+ "context"
5
+ "errors"
6
+
7
+ "gorm.io/gorm"
8
+
9
+ "__APP_NAME__/apps/server/internal/modules/demo/domain/models"
10
+ "__APP_NAME__/apps/server/internal/modules/demo/domain/service"
11
+ )
12
+
13
+ type TaskRepository struct {
14
+ db *gorm.DB
15
+ }
16
+
17
+ func NewTaskRepository(db *gorm.DB) *TaskRepository {
18
+ return &TaskRepository{db: db}
19
+ }
20
+
21
+ func (r *TaskRepository) List(ctx context.Context, tenantID uint) ([]models.DemoTask, error) {
22
+ var tasks []models.DemoTask
23
+ err := r.db.WithContext(ctx).
24
+ Where("tenant_id = ?", tenantID).
25
+ Order("created_at desc").
26
+ Find(&tasks).Error
27
+ return tasks, err
28
+ }
29
+
30
+ func (r *TaskRepository) FindByID(tenantID, id uint) (*models.DemoTask, error) {
31
+ var task models.DemoTask
32
+ err := r.db.Where("tenant_id = ? AND id = ?", tenantID, id).First(&task).Error
33
+ if errors.Is(err, gorm.ErrRecordNotFound) {
34
+ return nil, service.ErrTaskNotFound
35
+ }
36
+ return &task, err
37
+ }
38
+
39
+ func (r *TaskRepository) Create(ctx context.Context, task *models.DemoTask) error {
40
+ return r.db.WithContext(ctx).Create(task).Error
41
+ }
42
+
43
+ func (r *TaskRepository) Update(ctx context.Context, task *models.DemoTask) error {
44
+ return r.db.WithContext(ctx).Save(task).Error
45
+ }
46
+
47
+ func (r *TaskRepository) Delete(ctx context.Context, task *models.DemoTask) error {
48
+ return r.db.WithContext(ctx).Delete(task).Error
49
+ }
@@ -6,27 +6,47 @@ import (
6
6
 
7
7
  "github.com/robsuncn/keystone/domain/permissions"
8
8
  "github.com/robsuncn/keystone/infra/jobs"
9
+
10
+ demohandler "__APP_NAME__/apps/server/internal/modules/demo/api/handler"
11
+ demomigrations "__APP_NAME__/apps/server/internal/modules/demo/bootstrap/migrations"
12
+ demoseeds "__APP_NAME__/apps/server/internal/modules/demo/bootstrap/seeds"
13
+ demomodels "__APP_NAME__/apps/server/internal/modules/demo/domain/models"
14
+ demoservice "__APP_NAME__/apps/server/internal/modules/demo/domain/service"
15
+ demorepository "__APP_NAME__/apps/server/internal/modules/demo/infra/repository"
9
16
  )
10
17
 
11
- type Module struct{}
18
+ type Module struct {
19
+ tasks *demoservice.TaskService
20
+ }
12
21
 
13
- func NewModule() Module {
14
- return Module{}
22
+ func NewModule() *Module {
23
+ return &Module{}
15
24
  }
16
25
 
17
- func (Module) Name() string {
26
+ func (m *Module) Name() string {
18
27
  return "demo"
19
28
  }
20
29
 
21
- func (Module) RegisterRoutes(rg *gin.RouterGroup) {
22
- registerRoutes(rg)
30
+ func (m *Module) RegisterRoutes(rg *gin.RouterGroup) {
31
+ if rg == nil || m == nil {
32
+ return
33
+ }
34
+ handler := demohandler.NewTaskHandler(m.tasks)
35
+ if handler == nil {
36
+ return
37
+ }
38
+ group := rg.Group("/demo")
39
+ group.GET("/tasks", handler.List)
40
+ group.POST("/tasks", handler.Create)
41
+ group.PATCH("/tasks/:id", handler.Update)
42
+ group.DELETE("/tasks/:id", handler.Delete)
23
43
  }
24
44
 
25
- func (Module) RegisterModels() []interface{} {
26
- return nil
45
+ func (m *Module) RegisterModels() []interface{} {
46
+ return []interface{}{&demomodels.DemoTask{}}
27
47
  }
28
48
 
29
- func (Module) RegisterPermissions(reg *permissions.Registry) error {
49
+ func (m *Module) RegisterPermissions(reg *permissions.Registry) error {
30
50
  if reg == nil {
31
51
  return nil
32
52
  }
@@ -42,14 +62,30 @@ func (Module) RegisterPermissions(reg *permissions.Registry) error {
42
62
  return nil
43
63
  }
44
64
 
45
- func (Module) RegisterJobs(_ *jobs.Registry) error {
65
+ func (m *Module) RegisterJobs(_ *jobs.Registry) error {
46
66
  return nil
47
67
  }
48
68
 
49
- func (Module) Migrate(_ *gorm.DB) error {
50
- return nil
69
+ func (m *Module) Migrate(db *gorm.DB) error {
70
+ if db == nil {
71
+ return nil
72
+ }
73
+ m.ensureServices(db)
74
+ return demomigrations.Migrate(db)
51
75
  }
52
76
 
53
- func (Module) Seed(_ *gorm.DB) error {
54
- return nil
77
+ func (m *Module) Seed(db *gorm.DB) error {
78
+ if db == nil {
79
+ return nil
80
+ }
81
+ m.ensureServices(db)
82
+ return demoseeds.Seed(db)
83
+ }
84
+
85
+ func (m *Module) ensureServices(db *gorm.DB) {
86
+ if m == nil || db == nil || m.tasks != nil {
87
+ return
88
+ }
89
+ repo := demorepository.NewTaskRepository(db)
90
+ m.tasks = demoservice.NewTaskService(repo)
55
91
  }
@@ -0,0 +1,19 @@
1
+ package example
2
+
3
+ import (
4
+ "github.com/gin-gonic/gin"
5
+
6
+ "github.com/robsuncn/keystone/api/response"
7
+ )
8
+
9
+ type HelloResponse struct {
10
+ Message string `json:"message"`
11
+ Module string `json:"module"`
12
+ }
13
+
14
+ func handleHello(c *gin.Context) {
15
+ response.Success(c, HelloResponse{
16
+ Message: "Hello from Example module!",
17
+ Module: "example",
18
+ })
19
+ }
@@ -0,0 +1,53 @@
1
+ package example
2
+
3
+ import (
4
+ "github.com/gin-gonic/gin"
5
+ "gorm.io/gorm"
6
+
7
+ "github.com/robsuncn/keystone/domain/permissions"
8
+ "github.com/robsuncn/keystone/infra/jobs"
9
+ )
10
+
11
+ type Module struct{}
12
+
13
+ func NewModule() Module {
14
+ return Module{}
15
+ }
16
+
17
+ func (Module) Name() string {
18
+ return "example"
19
+ }
20
+
21
+ func (Module) RegisterRoutes(rg *gin.RouterGroup) {
22
+ if rg == nil {
23
+ return
24
+ }
25
+ group := rg.Group("/example")
26
+ group.GET("/hello", handleHello)
27
+ }
28
+
29
+ func (Module) RegisterModels() []interface{} {
30
+ return nil
31
+ }
32
+
33
+ func (Module) RegisterPermissions(reg *permissions.Registry) error {
34
+ if reg == nil {
35
+ return nil
36
+ }
37
+ if err := reg.CreateMenu("example:main", "Example", "example", 100); err != nil {
38
+ return err
39
+ }
40
+ return reg.CreateAction("example:view", "View Example", "example", "example:main")
41
+ }
42
+
43
+ func (Module) RegisterJobs(_ *jobs.Registry) error {
44
+ return nil
45
+ }
46
+
47
+ func (Module) Migrate(_ *gorm.DB) error {
48
+ return nil
49
+ }
50
+
51
+ func (Module) Seed(_ *gorm.DB) error {
52
+ return nil
53
+ }
@@ -1,11 +1,13 @@
1
1
  package modules
2
2
 
3
3
  import (
4
- demo "__APP_NAME__/apps/server/internal/modules/demo"
4
+ demo "__APP_NAME__/apps/server/internal/modules/demo"
5
+ example "__APP_NAME__/apps/server/internal/modules/example"
5
6
  )
6
7
 
7
8
  // RegisterAll wires the module registry for this app.
8
9
  func RegisterAll() {
9
10
  Clear()
11
+ Register(example.NewModule())
10
12
  Register(demo.NewModule())
11
13
  }
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/mnt/c/Rob/source/projects/keystone/packages/create-keystone-app/template/node_modules/.pnpm/acorn@8.15.0/node_modules/acorn/bin/node_modules:/mnt/c/Rob/source/projects/keystone/packages/create-keystone-app/template/node_modules/.pnpm/acorn@8.15.0/node_modules/acorn/node_modules:/mnt/c/Rob/source/projects/keystone/packages/create-keystone-app/template/node_modules/.pnpm/acorn@8.15.0/node_modules:/mnt/c/Rob/source/projects/keystone/packages/create-keystone-app/template/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="/mnt/c/Rob/source/projects/keystone/packages/create-keystone-app/template/node_modules/.pnpm/acorn@8.15.0/node_modules/acorn/bin/node_modules:/mnt/c/Rob/source/projects/keystone/packages/create-keystone-app/template/node_modules/.pnpm/acorn@8.15.0/node_modules/acorn/node_modules:/mnt/c/Rob/source/projects/keystone/packages/create-keystone-app/template/node_modules/.pnpm/acorn@8.15.0/node_modules:/mnt/c/Rob/source/projects/keystone/packages/create-keystone-app/template/node_modules/.pnpm/node_modules:$NODE_PATH"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../../../../node_modules/.pnpm/acorn@8.15.0/node_modules/acorn/bin/acorn" "$@"
15
+ else
16
+ exec node "$basedir/../../../../node_modules/.pnpm/acorn@8.15.0/node_modules/acorn/bin/acorn" "$@"
17
+ fi