@robsun/create-keystone-app 0.1.17 → 0.2.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.
- package/README.md +6 -5
- package/bin/create-keystone-app.js +2 -80
- package/package.json +1 -1
- package/template/README.md +5 -13
- package/template/apps/server/config.example.yaml +0 -1
- package/template/apps/server/config.yaml +0 -1
- package/template/apps/server/internal/modules/example/api/handler/item_handler.go +162 -0
- package/template/apps/server/internal/modules/example/bootstrap/migrations/item.go +21 -0
- package/template/apps/server/internal/modules/example/bootstrap/seeds/item.go +33 -0
- package/template/apps/server/internal/modules/example/domain/models/item.go +30 -0
- package/template/apps/server/internal/modules/{demo → example}/domain/service/errors.go +1 -1
- package/template/apps/server/internal/modules/example/domain/service/item_service.go +110 -0
- package/template/apps/server/internal/modules/example/infra/repository/item_repository.go +49 -0
- package/template/apps/server/internal/modules/example/module.go +55 -17
- package/template/apps/server/internal/modules/manifest.go +1 -3
- package/template/apps/web/src/app.config.ts +1 -1
- package/template/apps/web/src/main.tsx +0 -1
- package/template/apps/web/src/modules/example/help/faq.md +23 -0
- package/template/apps/web/src/modules/example/help/items.md +26 -0
- package/template/apps/web/src/modules/example/help/overview.md +18 -4
- package/template/apps/web/src/modules/example/pages/ExampleItemsPage.tsx +227 -0
- package/template/apps/web/src/modules/example/routes.tsx +33 -10
- package/template/apps/web/src/modules/example/services/exampleItems.ts +32 -0
- package/template/apps/web/src/modules/example/types.ts +10 -0
- package/template/docs/CONVENTIONS.md +44 -0
- package/template/docs/GETTING_STARTED.md +54 -0
- package/template/package.json +1 -1
- package/template/scripts/check-modules.js +7 -1
- package/template/apps/server/internal/modules/demo/api/handler/task_handler.go +0 -152
- package/template/apps/server/internal/modules/demo/bootstrap/migrations/task.go +0 -21
- package/template/apps/server/internal/modules/demo/bootstrap/seeds/task.go +0 -33
- package/template/apps/server/internal/modules/demo/domain/models/task.go +0 -30
- package/template/apps/server/internal/modules/demo/domain/service/task_service.go +0 -95
- package/template/apps/server/internal/modules/demo/infra/repository/task_repository.go +0 -49
- package/template/apps/server/internal/modules/demo/module.go +0 -91
- package/template/apps/server/internal/modules/example/handlers.go +0 -19
- package/template/apps/web/src/modules/demo/help/overview.md +0 -12
- package/template/apps/web/src/modules/demo/index.ts +0 -7
- package/template/apps/web/src/modules/demo/pages/DemoTasksPage.tsx +0 -185
- package/template/apps/web/src/modules/demo/routes.tsx +0 -43
- package/template/apps/web/src/modules/demo/services/demoTasks.ts +0 -28
- package/template/apps/web/src/modules/demo/types.ts +0 -9
- package/template/apps/web/src/modules/example/pages/ExamplePage.tsx +0 -41
- package/template/apps/web/src/modules/example/services/api.ts +0 -8
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
2
|
-
import { App, Button, Card, Form, Input, Popconfirm, Select, Space, Table, Tag } from 'antd'
|
|
3
|
-
import type { ColumnsType } from 'antd/es/table'
|
|
4
|
-
import dayjs from 'dayjs'
|
|
5
|
-
import { createDemoTask, deleteDemoTask, listDemoTasks, updateDemoTask } from '../services/demoTasks'
|
|
6
|
-
import type { DemoTask, DemoTaskStatus } from '../types'
|
|
7
|
-
|
|
8
|
-
type CreateValues = {
|
|
9
|
-
title: string
|
|
10
|
-
status?: DemoTaskStatus
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const statusMeta: Record<DemoTaskStatus, { label: string; color: string }> = {
|
|
14
|
-
todo: { label: 'Todo', color: 'default' },
|
|
15
|
-
in_progress: { label: 'In Progress', color: 'processing' },
|
|
16
|
-
done: { label: 'Done', color: 'success' },
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const statusOptions = [
|
|
20
|
-
{ value: 'todo', label: 'Todo' },
|
|
21
|
-
{ value: 'in_progress', label: 'In Progress' },
|
|
22
|
-
{ value: 'done', label: 'Done' },
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
export function DemoTasksPage() {
|
|
26
|
-
const { message } = App.useApp()
|
|
27
|
-
const [items, setItems] = useState<DemoTask[]>([])
|
|
28
|
-
const [loading, setLoading] = useState(false)
|
|
29
|
-
const [submitting, setSubmitting] = useState(false)
|
|
30
|
-
const [form] = Form.useForm<CreateValues>()
|
|
31
|
-
|
|
32
|
-
const fetchTasks = useCallback(async () => {
|
|
33
|
-
setLoading(true)
|
|
34
|
-
try {
|
|
35
|
-
const data = await listDemoTasks()
|
|
36
|
-
setItems(data)
|
|
37
|
-
} catch (err) {
|
|
38
|
-
const detail = err instanceof Error ? err.message : 'Failed to load tasks'
|
|
39
|
-
message.error(detail)
|
|
40
|
-
} finally {
|
|
41
|
-
setLoading(false)
|
|
42
|
-
}
|
|
43
|
-
}, [message])
|
|
44
|
-
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
void fetchTasks()
|
|
47
|
-
}, [fetchTasks])
|
|
48
|
-
|
|
49
|
-
const handleCreate = useCallback(
|
|
50
|
-
async (values: CreateValues) => {
|
|
51
|
-
const title = values.title?.trim()
|
|
52
|
-
if (!title) {
|
|
53
|
-
message.error('Title is required')
|
|
54
|
-
return
|
|
55
|
-
}
|
|
56
|
-
setSubmitting(true)
|
|
57
|
-
try {
|
|
58
|
-
await createDemoTask({ title, status: values.status })
|
|
59
|
-
form.resetFields()
|
|
60
|
-
await fetchTasks()
|
|
61
|
-
message.success('Task created')
|
|
62
|
-
} catch (err) {
|
|
63
|
-
const detail = err instanceof Error ? err.message : 'Failed to create task'
|
|
64
|
-
message.error(detail)
|
|
65
|
-
} finally {
|
|
66
|
-
setSubmitting(false)
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
[fetchTasks, form, message]
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
const handleStatusChange = useCallback(
|
|
73
|
-
async (id: number, status: DemoTaskStatus) => {
|
|
74
|
-
try {
|
|
75
|
-
await updateDemoTask(id, { status })
|
|
76
|
-
await fetchTasks()
|
|
77
|
-
message.success('Task updated')
|
|
78
|
-
} catch (err) {
|
|
79
|
-
const detail = err instanceof Error ? err.message : 'Failed to update task'
|
|
80
|
-
message.error(detail)
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
[fetchTasks, message]
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
const handleDelete = useCallback(
|
|
87
|
-
async (id: number) => {
|
|
88
|
-
try {
|
|
89
|
-
await deleteDemoTask(id)
|
|
90
|
-
await fetchTasks()
|
|
91
|
-
message.success('Task deleted')
|
|
92
|
-
} catch (err) {
|
|
93
|
-
const detail = err instanceof Error ? err.message : 'Failed to delete task'
|
|
94
|
-
message.error(detail)
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
[fetchTasks, message]
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
const columns: ColumnsType<DemoTask> = useMemo(
|
|
101
|
-
() => [
|
|
102
|
-
{ title: 'Title', dataIndex: 'title', key: 'title' },
|
|
103
|
-
{
|
|
104
|
-
title: 'Status',
|
|
105
|
-
dataIndex: 'status',
|
|
106
|
-
key: 'status',
|
|
107
|
-
render: (value: DemoTaskStatus) => {
|
|
108
|
-
const meta = statusMeta[value]
|
|
109
|
-
return <Tag color={meta.color}>{meta.label}</Tag>
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
title: 'Updated',
|
|
114
|
-
dataIndex: 'updated_at',
|
|
115
|
-
key: 'updated_at',
|
|
116
|
-
render: (value: string) => (value ? dayjs(value).format('YYYY-MM-DD HH:mm') : '-'),
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
title: 'Actions',
|
|
120
|
-
key: 'actions',
|
|
121
|
-
render: (_, record) => (
|
|
122
|
-
<Space>
|
|
123
|
-
<Select
|
|
124
|
-
size="small"
|
|
125
|
-
value={record.status}
|
|
126
|
-
options={statusOptions}
|
|
127
|
-
style={{ width: 140 }}
|
|
128
|
-
onChange={(value) => handleStatusChange(record.id, value as DemoTaskStatus)}
|
|
129
|
-
/>
|
|
130
|
-
<Popconfirm
|
|
131
|
-
title="Delete this task?"
|
|
132
|
-
onConfirm={() => handleDelete(record.id)}
|
|
133
|
-
okText="Delete"
|
|
134
|
-
>
|
|
135
|
-
<Button size="small" danger>
|
|
136
|
-
Delete
|
|
137
|
-
</Button>
|
|
138
|
-
</Popconfirm>
|
|
139
|
-
</Space>
|
|
140
|
-
),
|
|
141
|
-
},
|
|
142
|
-
],
|
|
143
|
-
[handleDelete, handleStatusChange]
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
return (
|
|
147
|
-
<Card
|
|
148
|
-
title="Demo Tasks"
|
|
149
|
-
extra={
|
|
150
|
-
<Button onClick={fetchTasks} loading={loading}>
|
|
151
|
-
Refresh
|
|
152
|
-
</Button>
|
|
153
|
-
}
|
|
154
|
-
>
|
|
155
|
-
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
|
156
|
-
<Form
|
|
157
|
-
form={form}
|
|
158
|
-
layout="inline"
|
|
159
|
-
onFinish={handleCreate}
|
|
160
|
-
initialValues={{ status: 'todo' }}
|
|
161
|
-
>
|
|
162
|
-
<Form.Item name="title" rules={[{ required: true, message: 'Title is required' }]}>
|
|
163
|
-
<Input placeholder="Task title" allowClear style={{ width: 240 }} />
|
|
164
|
-
</Form.Item>
|
|
165
|
-
<Form.Item name="status">
|
|
166
|
-
<Select options={statusOptions} style={{ width: 160 }} />
|
|
167
|
-
</Form.Item>
|
|
168
|
-
<Form.Item>
|
|
169
|
-
<Button type="primary" htmlType="submit" loading={submitting}>
|
|
170
|
-
Add Task
|
|
171
|
-
</Button>
|
|
172
|
-
</Form.Item>
|
|
173
|
-
</Form>
|
|
174
|
-
|
|
175
|
-
<Table<DemoTask>
|
|
176
|
-
rowKey="id"
|
|
177
|
-
loading={loading}
|
|
178
|
-
columns={columns}
|
|
179
|
-
dataSource={items}
|
|
180
|
-
pagination={false}
|
|
181
|
-
/>
|
|
182
|
-
</Space>
|
|
183
|
-
</Card>
|
|
184
|
-
)
|
|
185
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { lazy, Suspense, type ComponentType, type ReactElement } from 'react'
|
|
2
|
-
import type { RouteObject } from 'react-router-dom'
|
|
3
|
-
import { AppstoreOutlined } from '@ant-design/icons'
|
|
4
|
-
import { Spin } from 'antd'
|
|
5
|
-
|
|
6
|
-
const lazyNamed = <T extends Record<string, ComponentType>, K extends keyof T>(
|
|
7
|
-
factory: () => Promise<T>,
|
|
8
|
-
name: K
|
|
9
|
-
) =>
|
|
10
|
-
lazy(async () => {
|
|
11
|
-
const module = await factory()
|
|
12
|
-
return { default: module[name] }
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
const withSuspense = (element: ReactElement) => (
|
|
16
|
-
<Suspense
|
|
17
|
-
fallback={
|
|
18
|
-
<div style={{ padding: 24, display: 'flex', justifyContent: 'center' }}>
|
|
19
|
-
<Spin />
|
|
20
|
-
</div>
|
|
21
|
-
}
|
|
22
|
-
>
|
|
23
|
-
{element}
|
|
24
|
-
</Suspense>
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
const DemoTasksPage = lazyNamed(() => import('./pages/DemoTasksPage'), 'DemoTasksPage')
|
|
28
|
-
|
|
29
|
-
export const demoRoutes: RouteObject[] = [
|
|
30
|
-
{
|
|
31
|
-
path: 'demo',
|
|
32
|
-
element: <DemoTasksPage />,
|
|
33
|
-
handle: {
|
|
34
|
-
menu: { label: 'Demo Tasks', icon: <AppstoreOutlined /> },
|
|
35
|
-
breadcrumb: 'Demo Tasks',
|
|
36
|
-
permission: 'demo:task:view',
|
|
37
|
-
helpKey: 'demo/tasks',
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
].map((route) => ({
|
|
41
|
-
...route,
|
|
42
|
-
element: route.element ? withSuspense(route.element) : route.element,
|
|
43
|
-
}))
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { api, type ApiResponse } from '@robsun/keystone-web-core'
|
|
2
|
-
import type { DemoTask, DemoTaskStatus } from '../types'
|
|
3
|
-
|
|
4
|
-
type TaskListResponse = {
|
|
5
|
-
items: DemoTask[]
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const listDemoTasks = async () => {
|
|
9
|
-
const { data } = await api.get<ApiResponse<TaskListResponse>>('/demo/tasks')
|
|
10
|
-
return data.data.items
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const createDemoTask = async (payload: { title: string; status?: DemoTaskStatus }) => {
|
|
14
|
-
const { data } = await api.post<ApiResponse<DemoTask>>('/demo/tasks', payload)
|
|
15
|
-
return data.data
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const updateDemoTask = async (
|
|
19
|
-
id: number,
|
|
20
|
-
payload: { title?: string; status?: DemoTaskStatus }
|
|
21
|
-
) => {
|
|
22
|
-
const { data } = await api.patch<ApiResponse<DemoTask>>(`/demo/tasks/${id}`, payload)
|
|
23
|
-
return data.data
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const deleteDemoTask = async (id: number) => {
|
|
27
|
-
await api.delete<ApiResponse<{ id: number }>>(`/demo/tasks/${id}`)
|
|
28
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from 'react'
|
|
2
|
-
import { App, Button, Card, Space, Typography } from 'antd'
|
|
3
|
-
import { getHello } from '../services/api'
|
|
4
|
-
|
|
5
|
-
export function ExamplePage() {
|
|
6
|
-
const { message } = App.useApp()
|
|
7
|
-
const [data, setData] = useState<{ message: string; module: string } | null>(null)
|
|
8
|
-
const [loading, setLoading] = useState(false)
|
|
9
|
-
|
|
10
|
-
const fetchData = useCallback(async () => {
|
|
11
|
-
setLoading(true)
|
|
12
|
-
try {
|
|
13
|
-
setData(await getHello())
|
|
14
|
-
} catch (err) {
|
|
15
|
-
message.error(err instanceof Error ? err.message : 'Failed')
|
|
16
|
-
} finally {
|
|
17
|
-
setLoading(false)
|
|
18
|
-
}
|
|
19
|
-
}, [message])
|
|
20
|
-
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
void fetchData()
|
|
23
|
-
}, [fetchData])
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<Card title="Example Module">
|
|
27
|
-
<Space direction="vertical">
|
|
28
|
-
<Typography.Title level={4}>Welcome!</Typography.Title>
|
|
29
|
-
<Typography.Text>This module demonstrates core Keystone patterns.</Typography.Text>
|
|
30
|
-
{data && (
|
|
31
|
-
<Card size="small">
|
|
32
|
-
<pre>{JSON.stringify(data, null, 2)}</pre>
|
|
33
|
-
</Card>
|
|
34
|
-
)}
|
|
35
|
-
<Button onClick={fetchData} loading={loading}>
|
|
36
|
-
Refresh
|
|
37
|
-
</Button>
|
|
38
|
-
</Space>
|
|
39
|
-
</Card>
|
|
40
|
-
)
|
|
41
|
-
}
|