@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.
- package/bin/create-keystone-app.js +12 -0
- package/package.json +1 -1
- package/template/README.md +7 -1
- package/template/apps/server/config.example.yaml +1 -0
- package/template/apps/server/config.yaml +1 -0
- package/template/apps/server/internal/modules/demo/api/handler/task_handler.go +152 -0
- package/template/apps/server/internal/modules/demo/bootstrap/migrations/task.go +21 -0
- package/template/apps/server/internal/modules/demo/bootstrap/seeds/task.go +33 -0
- package/template/apps/server/internal/modules/demo/domain/models/task.go +30 -0
- package/template/apps/server/internal/modules/demo/domain/service/errors.go +9 -0
- package/template/apps/server/internal/modules/demo/domain/service/task_service.go +95 -0
- package/template/apps/server/internal/modules/demo/infra/repository/task_repository.go +49 -0
- package/template/apps/server/internal/modules/demo/module.go +50 -14
- package/template/apps/server/internal/modules/example/handlers.go +19 -0
- package/template/apps/server/internal/modules/example/module.go +53 -0
- package/template/apps/server/internal/modules/manifest.go +3 -1
- package/template/apps/web/node_modules/.bin/acorn +17 -0
- package/template/apps/web/node_modules/.bin/acorn.CMD +12 -0
- package/template/apps/web/node_modules/.bin/acorn.ps1 +41 -0
- package/template/apps/web/node_modules/.bin/autoprefixer +17 -0
- package/template/apps/web/node_modules/.bin/autoprefixer.CMD +12 -0
- package/template/apps/web/node_modules/.bin/autoprefixer.ps1 +41 -0
- package/template/apps/web/node_modules/.bin/browserslist +17 -0
- package/template/apps/web/node_modules/.bin/browserslist.CMD +12 -0
- package/template/apps/web/node_modules/.bin/browserslist.ps1 +41 -0
- package/template/apps/web/node_modules/.bin/eslint +17 -0
- package/template/apps/web/node_modules/.bin/eslint.CMD +12 -0
- package/template/apps/web/node_modules/.bin/eslint.ps1 +41 -0
- package/template/apps/web/node_modules/.bin/jiti +17 -0
- package/template/apps/web/node_modules/.bin/jiti.CMD +12 -0
- package/template/apps/web/node_modules/.bin/jiti.ps1 +41 -0
- package/template/apps/web/node_modules/.bin/tsc +17 -0
- package/template/apps/web/node_modules/.bin/tsc.CMD +12 -0
- package/template/apps/web/node_modules/.bin/tsc.ps1 +41 -0
- package/template/apps/web/node_modules/.bin/tsserver +17 -0
- package/template/apps/web/node_modules/.bin/tsserver.CMD +12 -0
- package/template/apps/web/node_modules/.bin/tsserver.ps1 +41 -0
- package/template/apps/web/node_modules/.bin/vite +17 -0
- package/template/apps/web/node_modules/.bin/vite.CMD +12 -0
- package/template/apps/web/node_modules/.bin/vite.ps1 +41 -0
- package/template/apps/web/node_modules/.bin/vitest +17 -0
- package/template/apps/web/node_modules/.bin/vitest.CMD +12 -0
- package/template/apps/web/node_modules/.bin/vitest.ps1 +41 -0
- package/template/apps/web/node_modules/.bin/yaml +17 -0
- package/template/apps/web/node_modules/.bin/yaml.CMD +12 -0
- package/template/apps/web/node_modules/.bin/yaml.ps1 +41 -0
- package/template/apps/web/package.json +1 -1
- package/template/apps/web/src/app.config.ts +1 -1
- package/template/apps/web/src/main.tsx +1 -0
- package/template/apps/web/src/modules/demo/routes.tsx +1 -1
- package/template/apps/web/src/modules/example/help/overview.md +11 -0
- package/template/apps/web/src/modules/example/index.ts +4 -0
- package/template/apps/web/src/modules/example/pages/ExamplePage.tsx +41 -0
- package/template/apps/web/src/modules/example/routes.tsx +20 -0
- package/template/apps/web/src/modules/example/services/api.ts +8 -0
- package/template/docs/CODE_STYLE.md +34 -0
- package/template/docs/CONVENTIONS.md +1 -0
- package/template/package.json +3 -1
- package/template/pnpm-lock.yaml +6029 -0
- package/template/scripts/check-modules.js +76 -0
- package/template/scripts/test.bat +11 -0
- package/template/scripts/test.sh +10 -0
- 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
package/template/README.md
CHANGED
|
@@ -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`。
|
|
@@ -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,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
|
-
|
|
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
|
|
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(
|
|
50
|
-
|
|
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(
|
|
54
|
-
|
|
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
|
-
|
|
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
|