create-fullstack-scaffold 0.4.9 → 0.4.10

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 (127) hide show
  1. package/dist/cli/index.js +499 -100
  2. package/dist/cli/index.js.map +1 -1
  3. package/package.json +2 -2
  4. package/template/CLAUDE.md +18 -6
  5. package/template/drizzle/0004_add_merchants_products.sql +30 -0
  6. package/template/drizzle/meta/_journal.json +7 -0
  7. package/template/eslint-rules/module-boundary.js +6 -2
  8. package/template/eslint-rules/no-cross-module-service-import.js +105 -0
  9. package/template/eslint-rules/no-disable-type-safe-client.js +5 -0
  10. package/template/eslint.config.js +9 -0
  11. package/template/lint-scripts/config/project.config.ts +21 -0
  12. package/template/lint-scripts/validate-all.ts +48 -12
  13. package/template/lint-scripts/validators/index.ts +29 -0
  14. package/template/lint-scripts/validators/module-public-api.validator.ts +103 -0
  15. package/template/lint-scripts/validators/schema-uniqueness.validator.ts +147 -0
  16. package/template/modules.config.ts +2 -0
  17. package/template/package.json +2 -0
  18. package/template/playwright.config.ts +14 -0
  19. package/template/src/admin/App.tsx +40 -3
  20. package/template/src/admin/components/LanguageSwitcher.tsx +22 -0
  21. package/template/src/admin/components/NotificationDrawer.tsx +108 -38
  22. package/template/src/admin/components/StatsCard.tsx +3 -1
  23. package/template/src/admin/components/ThemeToggle.tsx +28 -0
  24. package/template/src/admin/hooks/useRoles.ts +2 -2
  25. package/template/src/admin/i18n/index.ts +18 -0
  26. package/template/src/admin/i18n/locales/en-US.json +381 -0
  27. package/template/src/admin/i18n/locales/zh-CN.json +381 -0
  28. package/template/src/admin/i18n/useLanguage.ts +21 -0
  29. package/template/src/admin/layouts/Header.tsx +37 -16
  30. package/template/src/admin/layouts/Layout.tsx +7 -3
  31. package/template/src/admin/layouts/Sidebar.tsx +70 -68
  32. package/template/src/admin/main.tsx +1 -0
  33. package/template/src/admin/pages/ContentPage.tsx +68 -56
  34. package/template/src/admin/pages/DashboardPage.tsx +82 -76
  35. package/template/src/admin/pages/DisputesPage.tsx +61 -53
  36. package/template/src/admin/pages/LoginPage.tsx +41 -33
  37. package/template/src/admin/pages/OrdersPage.tsx +69 -64
  38. package/template/src/admin/pages/PermissionsPage.tsx +10 -8
  39. package/template/src/admin/pages/PluginDashboardPage.tsx +3 -1
  40. package/template/src/admin/pages/PluginManagementPage.tsx +3 -1
  41. package/template/src/admin/pages/RegisterPage.tsx +18 -16
  42. package/template/src/admin/pages/RolesPage.tsx +51 -49
  43. package/template/src/admin/pages/SettingsPage.tsx +22 -22
  44. package/template/src/admin/pages/SystemLogsPage.tsx +32 -30
  45. package/template/src/admin/pages/TicketsPage.tsx +67 -59
  46. package/template/src/admin/pages/UsersPage.tsx +63 -53
  47. package/template/src/admin/pages/__tests__/RolesPage.test.tsx +2 -2
  48. package/template/src/admin/stores/themeStore.ts +32 -0
  49. package/template/src/client/index.css +73 -0
  50. package/template/src/merchant/App.tsx +2 -0
  51. package/template/src/merchant/components/MerchantGuard.tsx +2 -6
  52. package/template/src/merchant/components/__tests__/MerchantGuard.test.tsx +117 -0
  53. package/template/src/merchant/layouts/Header.tsx +2 -2
  54. package/template/src/merchant/pages/DashboardPage.tsx +2 -2
  55. package/template/src/merchant/pages/DisputesPage.tsx +14 -10
  56. package/template/src/merchant/pages/LoginPage.tsx +49 -0
  57. package/template/src/merchant/pages/OrdersPage.tsx +17 -10
  58. package/template/src/merchant/pages/ProductsPage.tsx +31 -8
  59. package/template/src/merchant/pages/SettingsPage.tsx +3 -7
  60. package/template/src/merchant/stores/__tests__/merchantStore.test.ts +317 -0
  61. package/template/src/merchant/stores/merchantStore.ts +24 -27
  62. package/template/src/server/db/schema/index.ts +2 -0
  63. package/template/src/server/db/schema/merchants.ts +26 -0
  64. package/template/src/server/db/schema/products.ts +25 -0
  65. package/template/src/server/db/test-setup.ts +237 -0
  66. package/template/src/server/middleware/__tests__/audit-log.test.ts +144 -0
  67. package/template/src/server/middleware/__tests__/cors.test.ts +84 -0
  68. package/template/src/server/middleware/__tests__/logger.test.ts +117 -0
  69. package/template/src/server/middleware/__tests__/rate-limit.test.ts +73 -0
  70. package/template/src/server/middleware/__tests__/realtime-env.test.ts +56 -0
  71. package/template/src/server/middleware/__tests__/tenant-isolation.test.ts +150 -0
  72. package/template/src/server/module-admin/__tests__/admin-routes.test.ts +1 -1
  73. package/template/src/server/module-admin/module.ts +4 -0
  74. package/template/src/server/module-admin/routes/admin-notification-routes.ts +3 -3
  75. package/template/src/server/module-admin/routes/user-management-routes.ts +2 -2
  76. package/template/src/server/module-auth/__tests__/auth-routes.test.ts +315 -0
  77. package/template/src/server/module-auth/__tests__/profile-routes.test.ts +83 -0
  78. package/template/src/server/module-auth/module.ts +2 -0
  79. package/template/src/server/module-content/module.ts +1 -0
  80. package/template/src/server/module-content/routes/content-routes.ts +2 -2
  81. package/template/src/server/module-dispute/module.ts +1 -0
  82. package/template/src/server/module-dispute/routes/dispute-routes.ts +2 -2
  83. package/template/src/server/module-merchant/__tests__/merchant-routes.test.ts +218 -0
  84. package/template/src/server/module-merchant/__tests__/merchant-service.test.ts +183 -0
  85. package/template/src/server/module-merchant/index.ts +10 -0
  86. package/template/src/server/module-merchant/module.ts +33 -0
  87. package/template/src/server/module-merchant/routes/merchant-routes.ts +138 -0
  88. package/template/src/server/module-merchant/services/merchant-service.ts +292 -0
  89. package/template/src/server/module-notifications/index.ts +18 -0
  90. package/template/src/server/module-notifications/module.ts +2 -0
  91. package/template/src/server/module-order/module.ts +1 -0
  92. package/template/src/server/module-order/routes/order-routes.ts +2 -2
  93. package/template/src/server/module-permission/routes/role-routes.ts +3 -3
  94. package/template/src/server/module-plugin/__tests__/admin-category-service.test.ts +181 -0
  95. package/template/src/server/module-plugin/__tests__/admin-plugin-service.test.ts +277 -0
  96. package/template/src/server/module-plugin/__tests__/admin-stats-service.test.ts +170 -0
  97. package/template/src/server/module-plugin/__tests__/plugin-review-service.test.ts +260 -0
  98. package/template/src/server/module-plugin/__tests__/plugin-seed-service.test.ts +170 -0
  99. package/template/src/server/module-plugin/module.ts +3 -0
  100. package/template/src/server/module-tenant/__tests__/tenant-routes.test.ts +142 -0
  101. package/template/src/server/module-tenant/__tests__/tenant-service.test.ts +152 -0
  102. package/template/src/server/module-tenant/module.ts +1 -0
  103. package/template/src/server/module-tenant/services/tenant-service.ts +1 -37
  104. package/template/src/server/module-ticket/module.ts +1 -0
  105. package/template/src/server/module-ticket/routes/ticket-routes.ts +2 -2
  106. package/template/src/server/module-todos/module.ts +3 -0
  107. package/template/src/server/route-registry.ts +4 -0
  108. package/template/src/server/utils/__tests__/date.test.ts +130 -0
  109. package/template/src/server/utils/__tests__/env.test.ts +25 -0
  110. package/template/src/server/utils/__tests__/generate.test.ts +82 -0
  111. package/template/src/server/utils/__tests__/id-helpers.test.ts +29 -0
  112. package/template/src/server/utils/__tests__/json.test.ts +49 -0
  113. package/template/src/server/utils/__tests__/permission-utils.test.ts +26 -0
  114. package/template/src/server/utils/__tests__/uuid.test.ts +25 -0
  115. package/template/src/shared/core/module-manifest.ts +15 -0
  116. package/template/src/shared/modules/admin/schemas.ts +1 -1
  117. package/template/src/shared/modules/content/schemas.ts +2 -2
  118. package/template/src/shared/modules/dispute/schemas.ts +2 -2
  119. package/template/src/shared/modules/index.ts +181 -1
  120. package/template/src/shared/modules/merchant/index.ts +12 -0
  121. package/template/src/shared/modules/merchant/schemas.ts +63 -0
  122. package/template/src/shared/modules/order/schemas.ts +2 -2
  123. package/template/src/shared/modules/permission/index.ts +1 -1
  124. package/template/src/shared/modules/permission/schemas.ts +1 -1
  125. package/template/src/shared/modules/role/schemas.ts +2 -2
  126. package/template/src/shared/modules/ticket/schemas.ts +2 -2
  127. package/template/src/shared/schemas/index.ts +74 -2
@@ -0,0 +1,317 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
2
+ import type { Merchant, MerchantStats } from '@shared/schemas'
3
+
4
+ interface MerchantState {
5
+ merchant: Merchant | null
6
+ token: string | null
7
+ isAuthenticated: boolean
8
+ stats: MerchantStats | null
9
+ loading: boolean
10
+ error: string | null
11
+ startLoading: (message?: string) => void
12
+ stopLoading: () => void
13
+ checkAuth: () => Promise<void>
14
+ login: (username: string, password: string) => Promise<void>
15
+ logout: () => void
16
+ fetchStats: () => Promise<void>
17
+ setLoading: (loading: boolean) => void
18
+ setError: (error: string | null) => void
19
+ }
20
+
21
+ const mockMerchant: Merchant = {
22
+ id: 1,
23
+ userId: 'user-1',
24
+ tenantId: 100,
25
+ businessName: 'Test Shop',
26
+ businessType: 'retail',
27
+ status: 'active',
28
+ description: 'A test shop',
29
+ phone: '1234567890',
30
+ email: 'shop@test.com',
31
+ address: '123 Test St',
32
+ createdAt: '2024-01-01T00:00:00Z',
33
+ updatedAt: '2024-01-01T00:00:00Z',
34
+ }
35
+
36
+ const mockStats: MerchantStats = {
37
+ totalOrders: 150,
38
+ totalRevenue: 50000,
39
+ totalProducts: 80,
40
+ activeProducts: 65,
41
+ pendingOrders: 12,
42
+ thisMonthRevenue: 8500,
43
+ }
44
+
45
+ const mockCheckAuthResponse = {
46
+ success: true,
47
+ data: mockMerchant,
48
+ }
49
+
50
+ const mockLoginResponse = {
51
+ success: true,
52
+ data: {
53
+ merchant: mockMerchant,
54
+ token: 'merchant-token-123',
55
+ },
56
+ }
57
+
58
+ const mockStatsResponse = {
59
+ success: true,
60
+ data: mockStats,
61
+ }
62
+
63
+ // Extract mock functions to top level (same pattern as todoStore.test.ts)
64
+ const mockMeGet = vi.fn(async () => ({
65
+ json: async () => mockCheckAuthResponse,
66
+ }))
67
+ const mockLoginPost = vi.fn(async () => ({
68
+ json: async () => mockLoginResponse,
69
+ }))
70
+ const mockStatsGet = vi.fn(async () => ({
71
+ json: async () => mockStatsResponse,
72
+ }))
73
+
74
+ vi.mock('@client/services/apiClient', () => ({
75
+ apiClient: {
76
+ api: {
77
+ merchant: {
78
+ me: { $get: mockMeGet },
79
+ login: { $post: mockLoginPost },
80
+ stats: { $get: mockStatsGet },
81
+ },
82
+ },
83
+ },
84
+ }))
85
+
86
+ describe('useMerchantStore', () => {
87
+ let useMerchantStore: {
88
+ getState: () => MerchantState
89
+ setState: (s: Partial<MerchantState>) => void
90
+ }
91
+
92
+ beforeEach(async () => {
93
+ const mod = await import('../merchantStore')
94
+ useMerchantStore = mod.useMerchantStore
95
+ useMerchantStore.setState({
96
+ merchant: null,
97
+ token: null,
98
+ isAuthenticated: false,
99
+ stats: null,
100
+ loading: false,
101
+ error: null,
102
+ })
103
+ vi.clearAllMocks()
104
+ })
105
+
106
+ it('should have correct initial state', () => {
107
+ const state = useMerchantStore.getState()
108
+ expect(state.merchant).toBeNull()
109
+ expect(state.token).toBeNull()
110
+ expect(state.isAuthenticated).toBe(false)
111
+ expect(state.stats).toBeNull()
112
+ expect(state.loading).toBe(false)
113
+ expect(state.error).toBeNull()
114
+ })
115
+
116
+ describe('checkAuth', () => {
117
+ it('should set merchant and isAuthenticated on successful check', async () => {
118
+ await useMerchantStore.getState().checkAuth()
119
+
120
+ const state = useMerchantStore.getState()
121
+ expect(state.merchant).toEqual(mockMerchant)
122
+ expect(state.isAuthenticated).toBe(true)
123
+ expect(state.loading).toBe(false)
124
+ expect(state.error).toBeNull()
125
+ })
126
+
127
+ it('should clear merchant on failed check (success: false)', async () => {
128
+ mockMeGet.mockImplementationOnce(
129
+ async () =>
130
+ ({
131
+ json: async () => ({
132
+ success: false,
133
+ error: 'Not authenticated',
134
+ }),
135
+ } as never)
136
+ )
137
+
138
+ await useMerchantStore.getState().checkAuth()
139
+
140
+ const state = useMerchantStore.getState()
141
+ expect(state.merchant).toBeNull()
142
+ expect(state.isAuthenticated).toBe(false)
143
+ expect(state.loading).toBe(false)
144
+ })
145
+
146
+ it('should handle network error gracefully', async () => {
147
+ mockMeGet.mockImplementationOnce(async () => {
148
+ throw new Error('Network error')
149
+ })
150
+
151
+ await useMerchantStore.getState().checkAuth()
152
+
153
+ const state = useMerchantStore.getState()
154
+ expect(state.merchant).toBeNull()
155
+ expect(state.isAuthenticated).toBe(false)
156
+ expect(state.error).toBe('Authentication failed')
157
+ expect(state.loading).toBe(false)
158
+ })
159
+ })
160
+
161
+ describe('login', () => {
162
+ it('should set merchant, token, isAuthenticated on successful login', async () => {
163
+ await useMerchantStore.getState().login('testmerchant', 'password123')
164
+
165
+ const state = useMerchantStore.getState()
166
+ expect(state.merchant).toEqual(mockMerchant)
167
+ expect(state.token).toBe('merchant-token-123')
168
+ expect(state.isAuthenticated).toBe(true)
169
+ expect(state.loading).toBe(false)
170
+ expect(state.error).toBeNull()
171
+ })
172
+
173
+ it('should set error on failed login (success: false)', async () => {
174
+ mockLoginPost.mockImplementationOnce(
175
+ async () =>
176
+ ({
177
+ json: async () => ({
178
+ success: false,
179
+ error: 'Invalid username or password',
180
+ }),
181
+ } as never)
182
+ )
183
+
184
+ await useMerchantStore.getState().login('wrong', 'creds')
185
+
186
+ const state = useMerchantStore.getState()
187
+ expect(state.merchant).toBeNull()
188
+ expect(state.token).toBeNull()
189
+ expect(state.isAuthenticated).toBe(false)
190
+ expect(state.error).toBe('Invalid username or password')
191
+ expect(state.loading).toBe(false)
192
+ })
193
+
194
+ it('should handle network error', async () => {
195
+ mockLoginPost.mockImplementationOnce(async () => {
196
+ throw new Error('Network error')
197
+ })
198
+
199
+ await useMerchantStore.getState().login('testmerchant', 'password123')
200
+
201
+ const state = useMerchantStore.getState()
202
+ expect(state.merchant).toBeNull()
203
+ expect(state.isAuthenticated).toBe(false)
204
+ expect(state.error).toBe('Network error')
205
+ expect(state.loading).toBe(false)
206
+ })
207
+
208
+ it('should set loading state during login', async () => {
209
+ let resolveResponse!: () => void
210
+ const responsePromise = new Promise<void>(resolve => {
211
+ resolveResponse = resolve
212
+ })
213
+
214
+ mockLoginPost.mockImplementationOnce(async () => {
215
+ await responsePromise
216
+ return {
217
+ json: async () => mockLoginResponse,
218
+ }
219
+ })
220
+
221
+ const loginPromise = useMerchantStore.getState().login('testmerchant', 'password123')
222
+
223
+ expect(useMerchantStore.getState().loading).toBe(true)
224
+
225
+ resolveResponse()
226
+ await loginPromise
227
+
228
+ expect(useMerchantStore.getState().loading).toBe(false)
229
+ })
230
+ })
231
+
232
+ describe('logout', () => {
233
+ it('should clear all auth state on logout', async () => {
234
+ await useMerchantStore.getState().login('testmerchant', 'password123')
235
+ expect(useMerchantStore.getState().isAuthenticated).toBe(true)
236
+
237
+ await useMerchantStore.getState().fetchStats()
238
+ expect(useMerchantStore.getState().stats).toEqual(mockStats)
239
+
240
+ useMerchantStore.getState().logout()
241
+
242
+ const state = useMerchantStore.getState()
243
+ expect(state.merchant).toBeNull()
244
+ expect(state.token).toBeNull()
245
+ expect(state.isAuthenticated).toBe(false)
246
+ expect(state.stats).toBeNull()
247
+ })
248
+ })
249
+
250
+ describe('fetchStats', () => {
251
+ it('should populate stats on successful fetch', async () => {
252
+ await useMerchantStore.getState().fetchStats()
253
+
254
+ const state = useMerchantStore.getState()
255
+ expect(state.stats).toEqual(mockStats)
256
+ expect(state.loading).toBe(false)
257
+ expect(state.error).toBeNull()
258
+ })
259
+
260
+ it('should handle failed response', async () => {
261
+ mockStatsGet.mockImplementationOnce(
262
+ async () =>
263
+ ({
264
+ json: async () => ({
265
+ success: false,
266
+ error: 'Unauthorized',
267
+ }),
268
+ } as never)
269
+ )
270
+
271
+ await useMerchantStore.getState().fetchStats()
272
+
273
+ const state = useMerchantStore.getState()
274
+ expect(state.stats).toBeNull()
275
+ expect(state.loading).toBe(false)
276
+ })
277
+
278
+ it('should handle network error', async () => {
279
+ mockStatsGet.mockImplementationOnce(async () => {
280
+ throw new Error('Network error')
281
+ })
282
+
283
+ await useMerchantStore.getState().fetchStats()
284
+
285
+ const state = useMerchantStore.getState()
286
+ expect(state.stats).toBeNull()
287
+ expect(state.error).toBe('Failed to fetch statistics')
288
+ expect(state.loading).toBe(false)
289
+ })
290
+ })
291
+
292
+ describe('setLoading / setError', () => {
293
+ it('should update loading state', () => {
294
+ useMerchantStore.getState().setLoading(true)
295
+ expect(useMerchantStore.getState().loading).toBe(true)
296
+
297
+ useMerchantStore.getState().setLoading(false)
298
+ expect(useMerchantStore.getState().loading).toBe(false)
299
+ })
300
+
301
+ it('should update error state', () => {
302
+ useMerchantStore.getState().setError('Something went wrong')
303
+ expect(useMerchantStore.getState().error).toBe('Something went wrong')
304
+
305
+ useMerchantStore.getState().setError(null)
306
+ expect(useMerchantStore.getState().error).toBeNull()
307
+ })
308
+
309
+ it('should update loading state via startLoading/stopLoading', () => {
310
+ useMerchantStore.getState().startLoading()
311
+ expect(useMerchantStore.getState().loading).toBe(true)
312
+
313
+ useMerchantStore.getState().stopLoading()
314
+ expect(useMerchantStore.getState().loading).toBe(false)
315
+ })
316
+ })
317
+ })
@@ -1,14 +1,10 @@
1
1
  import { create } from 'zustand'
2
- import type { User } from '@shared/schemas'
3
-
4
- interface MerchantStats {
5
- totalSales: number
6
- totalOrders: number
7
- pendingOrders: number
8
- }
2
+ import { apiClient } from '@client/services/apiClient'
3
+ import type { Merchant, MerchantStats } from '@shared/schemas'
9
4
 
10
5
  interface MerchantState {
11
- user: User | null
6
+ merchant: Merchant | null
7
+ token: string | null
12
8
  isAuthenticated: boolean
13
9
  stats: MerchantStats | null
14
10
  loading: boolean
@@ -24,7 +20,8 @@ interface MerchantState {
24
20
  }
25
21
 
26
22
  export const useMerchantStore = create<MerchantState>(set => ({
27
- user: null,
23
+ merchant: null,
24
+ token: null,
28
25
  isAuthenticated: false,
29
26
  stats: null,
30
27
  loading: false,
@@ -36,16 +33,16 @@ export const useMerchantStore = create<MerchantState>(set => ({
36
33
  checkAuth: async () => {
37
34
  set({ loading: true, error: null })
38
35
  try {
39
- const response = await fetch('/api/merchant/me')
40
- const result = (await response.json()) as { success?: boolean; data?: User }
36
+ const response = await apiClient.api.merchant.me.$get()
37
+ const result = await response.json()
41
38
  if (result.success === true && result.data) {
42
- set({ user: result.data, isAuthenticated: true })
39
+ set({ merchant: result.data, isAuthenticated: true })
43
40
  } else {
44
- set({ user: null, isAuthenticated: false })
41
+ set({ merchant: null, isAuthenticated: false })
45
42
  }
46
43
  } catch (error) {
47
44
  console.error('Auth check failed:', error)
48
- set({ user: null, isAuthenticated: false, error: 'Authentication failed' })
45
+ set({ merchant: null, isAuthenticated: false, error: 'Authentication failed' })
49
46
  } finally {
50
47
  set({ loading: false })
51
48
  }
@@ -54,18 +51,18 @@ export const useMerchantStore = create<MerchantState>(set => ({
54
51
  login: async (username: string, password: string) => {
55
52
  set({ loading: true, error: null })
56
53
  try {
57
- const response = await fetch('/api/merchant/login', {
58
- method: 'POST',
59
- headers: {
60
- 'Content-Type': 'application/json',
61
- },
62
- body: JSON.stringify({ username, password }),
54
+ const response = await apiClient.api.merchant.login.$post({
55
+ json: { username, password },
63
56
  })
64
- const result = (await response.json()) as { success?: boolean; data?: User; error?: string }
57
+ const result = await response.json()
65
58
  if (result.success === true && result.data) {
66
- set({ user: result.data, isAuthenticated: true })
67
- } else {
68
- set({ error: result.error || 'Login failed' })
59
+ set({
60
+ merchant: result.data.merchant,
61
+ token: result.data.token,
62
+ isAuthenticated: true,
63
+ })
64
+ } else if (result.success === false) {
65
+ set({ error: result.error })
69
66
  }
70
67
  } catch (error) {
71
68
  console.error('Login failed:', error)
@@ -76,14 +73,14 @@ export const useMerchantStore = create<MerchantState>(set => ({
76
73
  },
77
74
 
78
75
  logout: () => {
79
- set({ user: null, isAuthenticated: false, stats: null })
76
+ set({ merchant: null, token: null, isAuthenticated: false, stats: null })
80
77
  },
81
78
 
82
79
  fetchStats: async () => {
83
80
  set({ loading: true, error: null })
84
81
  try {
85
- const response = await fetch('/api/merchant/stats')
86
- const result = (await response.json()) as { success?: boolean; data?: MerchantStats }
82
+ const response = await apiClient.api.merchant.stats.$get()
83
+ const result = await response.json()
87
84
  if (result.success === true && result.data) {
88
85
  set({ stats: result.data })
89
86
  }
@@ -15,3 +15,5 @@ export * from './contents'
15
15
  export * from './developers'
16
16
  export * from './plugins'
17
17
  export * from './tenants'
18
+ export * from './merchants'
19
+ export * from './products'
@@ -0,0 +1,26 @@
1
+ import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
2
+
3
+ export const merchantStatuses = ['active', 'inactive', 'suspended'] as const
4
+ export type MerchantStatus = (typeof merchantStatuses)[number]
5
+
6
+ export const businessTypes = ['retail', 'wholesale', 'service', 'restaurant'] as const
7
+ export type BusinessType = (typeof businessTypes)[number]
8
+
9
+ export const merchants = sqliteTable('merchants', {
10
+ id: integer('id').primaryKey({ autoIncrement: true }),
11
+ userId: text('user_id').notNull(),
12
+ tenantId: integer('tenant_id').notNull(),
13
+ businessName: text('business_name').notNull(),
14
+ businessType: text('business_type', { enum: businessTypes }).notNull().default('retail'),
15
+ status: text('status', { enum: merchantStatuses }).notNull().default('active'),
16
+ description: text('description'),
17
+ phone: text('phone'),
18
+ email: text('email'),
19
+ address: text('address'),
20
+ password: text('password').notNull(), // Simplified - use bcrypt hash in production
21
+ createdAt: text('created_at').notNull(),
22
+ updatedAt: text('updated_at').notNull(),
23
+ })
24
+
25
+ export type MerchantTable = typeof merchants.$inferSelect
26
+ export type NewMerchant = typeof merchants.$inferInsert
@@ -0,0 +1,25 @@
1
+ import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core'
2
+ import { sql } from 'drizzle-orm'
3
+
4
+ export const productStatuses = ['active', 'inactive', 'out_of_stock'] as const
5
+ export type ProductStatus = (typeof productStatuses)[number]
6
+
7
+ export const products = sqliteTable('products', {
8
+ id: text('id').primaryKey(),
9
+ name: text('name').notNull(),
10
+ description: text('description'),
11
+ price: integer('price').notNull(), // 单位:分
12
+ status: text('status', { enum: productStatuses }).notNull().default('active'),
13
+ stock: integer('stock').notNull().default(0),
14
+ imageUrl: text('image_url'),
15
+ merchantId: integer('merchant_id').notNull(), // Changed to integer to match merchants.id
16
+ createdAt: integer('created_at', { mode: 'timestamp' })
17
+ .notNull()
18
+ .default(sql`(unixepoch() * 1000)`),
19
+ updatedAt: integer('updated_at', { mode: 'timestamp' })
20
+ .notNull()
21
+ .default(sql`(unixepoch() * 1000)`),
22
+ })
23
+
24
+ export type Product = typeof products.$inferSelect
25
+ export type NewProduct = typeof products.$inferInsert