@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.
Files changed (44) hide show
  1. package/README.md +6 -5
  2. package/bin/create-keystone-app.js +2 -80
  3. package/package.json +1 -1
  4. package/template/README.md +5 -13
  5. package/template/apps/server/config.example.yaml +0 -1
  6. package/template/apps/server/config.yaml +0 -1
  7. package/template/apps/server/internal/modules/example/api/handler/item_handler.go +162 -0
  8. package/template/apps/server/internal/modules/example/bootstrap/migrations/item.go +21 -0
  9. package/template/apps/server/internal/modules/example/bootstrap/seeds/item.go +33 -0
  10. package/template/apps/server/internal/modules/example/domain/models/item.go +30 -0
  11. package/template/apps/server/internal/modules/{demo → example}/domain/service/errors.go +1 -1
  12. package/template/apps/server/internal/modules/example/domain/service/item_service.go +110 -0
  13. package/template/apps/server/internal/modules/example/infra/repository/item_repository.go +49 -0
  14. package/template/apps/server/internal/modules/example/module.go +55 -17
  15. package/template/apps/server/internal/modules/manifest.go +1 -3
  16. package/template/apps/web/src/app.config.ts +1 -1
  17. package/template/apps/web/src/main.tsx +0 -1
  18. package/template/apps/web/src/modules/example/help/faq.md +23 -0
  19. package/template/apps/web/src/modules/example/help/items.md +26 -0
  20. package/template/apps/web/src/modules/example/help/overview.md +18 -4
  21. package/template/apps/web/src/modules/example/pages/ExampleItemsPage.tsx +227 -0
  22. package/template/apps/web/src/modules/example/routes.tsx +33 -10
  23. package/template/apps/web/src/modules/example/services/exampleItems.ts +32 -0
  24. package/template/apps/web/src/modules/example/types.ts +10 -0
  25. package/template/docs/CONVENTIONS.md +44 -0
  26. package/template/docs/GETTING_STARTED.md +54 -0
  27. package/template/package.json +1 -1
  28. package/template/scripts/check-modules.js +7 -1
  29. package/template/apps/server/internal/modules/demo/api/handler/task_handler.go +0 -152
  30. package/template/apps/server/internal/modules/demo/bootstrap/migrations/task.go +0 -21
  31. package/template/apps/server/internal/modules/demo/bootstrap/seeds/task.go +0 -33
  32. package/template/apps/server/internal/modules/demo/domain/models/task.go +0 -30
  33. package/template/apps/server/internal/modules/demo/domain/service/task_service.go +0 -95
  34. package/template/apps/server/internal/modules/demo/infra/repository/task_repository.go +0 -49
  35. package/template/apps/server/internal/modules/demo/module.go +0 -91
  36. package/template/apps/server/internal/modules/example/handlers.go +0 -19
  37. package/template/apps/web/src/modules/demo/help/overview.md +0 -12
  38. package/template/apps/web/src/modules/demo/index.ts +0 -7
  39. package/template/apps/web/src/modules/demo/pages/DemoTasksPage.tsx +0 -185
  40. package/template/apps/web/src/modules/demo/routes.tsx +0 -43
  41. package/template/apps/web/src/modules/demo/services/demoTasks.ts +0 -28
  42. package/template/apps/web/src/modules/demo/types.ts +0 -9
  43. package/template/apps/web/src/modules/example/pages/ExamplePage.tsx +0 -41
  44. 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,9 +0,0 @@
1
- export type DemoTaskStatus = 'todo' | 'in_progress' | 'done'
2
-
3
- export interface DemoTask {
4
- id: number
5
- title: string
6
- status: DemoTaskStatus
7
- created_at: string
8
- updated_at: string
9
- }
@@ -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
- }
@@ -1,8 +0,0 @@
1
- import { api, type ApiResponse } from '@robsun/keystone-web-core'
2
-
3
- export const getHello = async () => {
4
- const { data } = await api.get<ApiResponse<{ message: string; module: string }>>(
5
- '/example/hello'
6
- )
7
- return data.data
8
- }