create-fullstack-scaffold 0.4.25 → 0.5.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/dist/cli/index.js +432 -15
- package/dist/cli/index.js.map +1 -1
- package/package.json +10 -13
- package/template/eslint-rules/__tests__/no-merged-api-type-export.test.ts +96 -0
- package/template/eslint-rules/no-cross-module-service-import.js +4 -0
- package/template/eslint-rules/no-merged-api-type-export.js +190 -0
- package/template/eslint.config.js +3 -0
- package/template/package.json +9 -6
- package/template/scripts/sync-agent-hooks.mjs +189 -0
- package/template/src/admin/components/__tests__/StatsCard.test.tsx +2 -2
- package/template/src/admin/pages/ContentPage.tsx +1 -1
- package/template/src/admin/pages/DisputesPage.tsx +1 -1
- package/template/src/admin/pages/OrdersPage.tsx +1 -1
- package/template/src/admin/pages/TicketsPage.tsx +1 -1
- package/template/src/admin/pages/__tests__/ContentPage.test.tsx +5 -1
- package/template/src/admin/pages/__tests__/DashboardPage.test.tsx +62 -8
- package/template/src/admin/pages/__tests__/DisputesPage.test.tsx +8 -1
- package/template/src/admin/pages/__tests__/OrdersPage.test.tsx +4 -2
- package/template/src/admin/pages/__tests__/RegisterPage.test.tsx +40 -43
- package/template/src/admin/pages/__tests__/SettingsPage.test.tsx +83 -21
- package/template/src/admin/pages/__tests__/TicketsPage.test.tsx +3 -1
- package/template/src/admin/services/apiClient.ts +2 -3
- package/template/src/cli/modules/content/index.ts +3 -3
- package/template/src/cli/modules/dispute/index.ts +3 -3
- package/template/src/cli/modules/ticket/index.ts +3 -3
- package/template/src/cli/modules/todo/index.ts +1 -1
- package/template/src/cli/rpc/client.ts +3 -4
- package/template/src/cli/rpc/index.ts +1 -1
- package/template/src/client/App.tsx +3 -39
- package/template/src/client/AppRoutes.tsx +53 -0
- package/template/src/client/entry-server.tsx +75 -0
- package/template/src/client/main.tsx +3 -1
- package/template/src/client/pages/SearchPage.tsx +1 -1
- package/template/src/client/services/apiClient.ts +12 -4
- package/template/src/client/stores/__tests__/todoStore.test.ts +12 -3
- package/template/src/client/stores/entry-stores.ts +36 -0
- package/template/src/client/stores/notificationStore.ts +4 -4
- package/template/src/client/stores/todoStore.ts +2 -2
- package/template/src/merchant/pages/DisputesPage.tsx +3 -3
- package/template/src/merchant/pages/OrdersPage.tsx +1 -1
- package/template/src/merchant/pages/ProductsPage.tsx +1 -1
- package/template/src/merchant/pages/SettingsPage.tsx +1 -1
- package/template/src/server/__tests__/integration/isr-full-flow.test.ts +251 -0
- package/template/src/server/__tests__/integration/todos-api.test.ts +7 -4
- package/template/src/server/app.ts +6 -4
- package/template/src/server/core/__tests__/isr-cache.test.ts +96 -92
- package/template/src/server/core/__tests__/isr-invalidation.test.ts +29 -4
- package/template/src/server/core/__tests__/isr-registry.test.ts +215 -0
- package/template/src/server/core/__tests__/isr-renderer.test.ts +121 -0
- package/template/src/server/core/isr-cache.ts +6 -12
- package/template/src/server/core/isr-invalidation.ts +6 -12
- package/template/src/server/core/isr-registry.ts +104 -0
- package/template/src/server/core/isr-renderer.ts +75 -0
- package/template/src/server/db/schema/contents.ts +28 -20
- package/template/src/server/db/schema/disputes.ts +30 -22
- package/template/src/server/db/schema/notifications.ts +20 -14
- package/template/src/server/db/schema/orders.ts +23 -16
- package/template/src/server/db/schema/plugins.ts +56 -38
- package/template/src/server/db/schema/products.ts +24 -17
- package/template/src/server/db/schema/tickets.ts +44 -31
- package/template/src/server/db/schema/todos.ts +26 -18
- package/template/src/server/entries/cloudflare.ts +82 -19
- package/template/src/server/entries/node.ts +0 -2
- package/template/src/server/index.ts +0 -1
- package/template/src/server/isr-modules.ts +10 -0
- package/template/src/server/module-admin/__tests__/admin-service.test.ts +36 -12
- package/template/src/server/module-admin/routes/admin-notification-routes.ts +2 -1
- package/template/src/server/module-admin/routes/admin-routes.ts +3 -0
- package/template/src/server/module-admin/routes/dashboard-routes.ts +3 -0
- package/template/src/server/module-admin/services/admin-service.ts +57 -13
- package/template/src/server/module-auth/routes/auth-routes.ts +3 -0
- package/template/src/server/module-auth/routes/profile-routes.ts +3 -0
- package/template/src/server/module-captcha/routes/captcha-routes.ts +3 -0
- package/template/src/server/module-chat/routes/chat-routes.ts +4 -1
- package/template/src/server/module-content/__tests__/content-route.test.ts +2 -1
- package/template/src/server/module-content/__tests__/content-service.test.ts +2 -1
- package/template/src/server/module-content/__tests__/isr.test.ts +138 -0
- package/template/src/server/module-content/isr.ts +63 -0
- package/template/src/server/module-content/routes/content-routes.ts +10 -10
- package/template/src/server/module-content/routes/public-content-routes.ts +8 -4
- package/template/src/server/module-content/routes/topics-routes.ts +3 -0
- package/template/src/server/module-content/services/content-service.ts +23 -10
- package/template/src/server/module-dispute/__tests__/dispute-route.test.ts +2 -1
- package/template/src/server/module-dispute/__tests__/dispute-service.test.ts +2 -1
- package/template/src/server/module-dispute/routes/dispute-routes.ts +11 -10
- package/template/src/server/module-dispute/services/dispute-service.ts +15 -3
- package/template/src/server/module-file/routes/file-routes.ts +3 -0
- package/template/src/server/module-merchant/routes/merchant-routes.ts +3 -0
- package/template/src/server/module-merchant/services/merchant-service.ts +8 -8
- package/template/src/server/module-notifications/routes/notification-routes.ts +5 -1
- package/template/src/server/module-order/__tests__/order-route.test.ts +8 -8
- package/template/src/server/module-order/__tests__/order-service.test.ts +4 -3
- package/template/src/server/module-order/routes/cart-routes.ts +3 -0
- package/template/src/server/module-order/routes/order-routes.ts +9 -4
- package/template/src/server/module-order/routes/orders-mock-routes.ts +3 -0
- package/template/src/server/module-order/services/order-service.ts +25 -13
- package/template/src/server/module-permission/__tests__/audit-log-service.test.ts +464 -0
- package/template/src/server/module-permission/__tests__/role-service.test.ts +348 -0
- package/template/src/server/module-permission/routes/audit-log-routes.ts +3 -0
- package/template/src/server/module-permission/routes/permission-routes.ts +3 -0
- package/template/src/server/module-permission/routes/role-routes.ts +3 -0
- package/template/src/server/module-plugin/routes/plugin-admin-routes.ts +3 -0
- package/template/src/server/module-plugin/routes/plugin-routes.ts +3 -0
- package/template/src/server/module-plugin/services/admin-plugin-service.ts +10 -18
- package/template/src/server/module-plugin/services/admin-stats-service.ts +28 -9
- package/template/src/server/module-plugin/services/plugin-query-service.ts +42 -66
- package/template/src/server/module-tenant/routes/tenant-routes.ts +5 -1
- package/template/src/server/module-ticket/__tests__/ticket-route.test.ts +2 -1
- package/template/src/server/module-ticket/__tests__/ticket-service.test.ts +4 -3
- package/template/src/server/module-ticket/routes/ticket-routes.ts +11 -10
- package/template/src/server/module-ticket/services/ticket-service.ts +16 -5
- package/template/src/server/module-todos/__tests__/isr.test.ts +105 -0
- package/template/src/server/module-todos/__tests__/todo-service.test.ts +12 -9
- package/template/src/server/module-todos/__tests__/todos-route-rpc.test.ts +6 -6
- package/template/src/server/module-todos/isr.ts +41 -0
- package/template/src/server/module-todos/routes/todos-routes.ts +12 -5
- package/template/src/server/module-todos/services/todo-service.ts +31 -10
- package/template/src/server/route-registry.ts +0 -4
- package/template/src/server/rpc-merge.ts +27 -0
- package/template/src/server/rpc-surface.ts +117 -0
- package/template/src/server/rpc-type-canary.ts +80 -0
- package/template/src/server/test-utils/test-client.ts +7 -9
- package/template/src/server/test-utils/test-isr-helper.ts +140 -0
- package/template/src/shared/modules/content/schemas.ts +14 -0
- package/template/src/shared/modules/dispute/schemas.ts +14 -0
- package/template/src/shared/modules/order/schemas.ts +9 -1
- package/template/src/shared/modules/ticket/schemas.ts +14 -0
- package/template/src/shared/modules/todos/index.ts +4 -0
- package/template/src/shared/modules/todos/schemas.ts +14 -0
- package/template/src/shared/schemas/index.ts +18 -0
- package/template/tsup.config.ts +48 -1
- package/template/vitest.config.ts +19 -3
- package/template/vitest.setup.ts +68 -5
- package/template/patches/typescript+5.9.3.patch +0 -24
- /package/template/patches/{hono+4.12.16.patch → hono+4.12.34.patch} +0 -0
|
@@ -2,16 +2,12 @@
|
|
|
2
2
|
* @framework-baseline a124c5bce28416ca
|
|
3
3
|
*
|
|
4
4
|
* @framework-modify
|
|
5
|
-
* @reason
|
|
6
|
-
* @impact
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* ISR cache invalidation utilities.
|
|
11
|
-
* Call these from service layer when content changes.
|
|
5
|
+
* @reason 模块化改造:purgeAllPages 改为遍历注册表
|
|
6
|
+
* @impact 不再硬编码路由列表
|
|
12
7
|
*/
|
|
13
8
|
|
|
14
9
|
import type { ISRCache } from './isr-cache'
|
|
10
|
+
import { isrRegistry } from './isr-registry'
|
|
15
11
|
|
|
16
12
|
let _cache: ISRCache | null = null
|
|
17
13
|
|
|
@@ -36,10 +32,8 @@ export async function purgeContentPages(): Promise<void> {
|
|
|
36
32
|
|
|
37
33
|
export async function purgeAllPages(): Promise<void> {
|
|
38
34
|
if (!_cache) return
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
await _cache.purge('/notifications')
|
|
43
|
-
await _cache.purge('/websocket')
|
|
35
|
+
for (const path of isrRegistry.getExactPaths()) {
|
|
36
|
+
await _cache.purge(path)
|
|
37
|
+
}
|
|
44
38
|
await _cache.purgePattern('isr:/content/*')
|
|
45
39
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @framework-baseline faa47d8c6e71847d
|
|
3
|
+
*
|
|
4
|
+
*
|
|
5
|
+
* @framework-modify
|
|
6
|
+
* @reason prettier 格式化与注释结构整理(无逻辑改动)
|
|
7
|
+
* @impact 框架文件维护性修改,行为见测试
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* ISR Route Registry — modules register their ISR routes here.
|
|
12
|
+
* Full SSG mode: fetches data for meta tags AND pre-populates stores for React SSR.
|
|
13
|
+
* The CF entry and ISR cache use this to discover which routes need ISR.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export type ISRMatcher = string | ((pathname: string) => boolean)
|
|
17
|
+
|
|
18
|
+
export interface ISRRouterContext {
|
|
19
|
+
/** D1 database binding (Cloudflare) or null (Node.js / test) */
|
|
20
|
+
db?: unknown
|
|
21
|
+
/** Raw env object (for module-specific needs) */
|
|
22
|
+
env?: unknown
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ISRRouteEntry {
|
|
26
|
+
/** Module name that owns this route */
|
|
27
|
+
module: string
|
|
28
|
+
/**
|
|
29
|
+
* Path matcher: exact string or function.
|
|
30
|
+
* String starting with '/' and ending with '/' = prefix match.
|
|
31
|
+
* String starting with '/' = exact match.
|
|
32
|
+
* Function = custom matcher.
|
|
33
|
+
*/
|
|
34
|
+
match: ISRMatcher
|
|
35
|
+
/** Fetch data needed for SSR rendering and meta tags */
|
|
36
|
+
fetch: (pathname: string, ctx: ISRRouterContext) => Promise<unknown>
|
|
37
|
+
/** Generate meta tags from fetched data */
|
|
38
|
+
meta: (data: unknown, pathname: string) => { title: string; description: string }
|
|
39
|
+
/** Max age in seconds (optional, per-route override) */
|
|
40
|
+
maxAge?: number
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
class ISRRegistry {
|
|
44
|
+
private entries: ISRRouteEntry[] = []
|
|
45
|
+
|
|
46
|
+
register(entry: ISRRouteEntry): void {
|
|
47
|
+
const idx = this.entries.findIndex(e => e.module === entry.module && e.match === entry.match)
|
|
48
|
+
if (idx >= 0) {
|
|
49
|
+
this.entries[idx] = entry
|
|
50
|
+
} else {
|
|
51
|
+
this.entries.push(entry)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
registerMany(entries: ISRRouteEntry[]): void {
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
this.register(entry)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
match(pathname: string): ISRRouteEntry | null {
|
|
62
|
+
for (const entry of this.entries) {
|
|
63
|
+
if (typeof entry.match === 'string' && !entry.match.endsWith('/')) {
|
|
64
|
+
if (pathname === entry.match) return entry
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
for (const entry of this.entries) {
|
|
68
|
+
if (typeof entry.match === 'string' && entry.match.endsWith('/') && entry.match !== '/') {
|
|
69
|
+
if (pathname.startsWith(entry.match)) return entry
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
for (const entry of this.entries) {
|
|
73
|
+
if (typeof entry.match === 'function') {
|
|
74
|
+
if (entry.match(pathname)) return entry
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const entry of this.entries) {
|
|
78
|
+
if (entry.match === '/' && pathname === '/') return entry
|
|
79
|
+
}
|
|
80
|
+
return null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
isISRRoute(pathname: string): boolean {
|
|
84
|
+
return this.match(pathname) !== null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
getExactPaths(): string[] {
|
|
88
|
+
return this.entries.filter(e => typeof e.match === 'string').map(e => e.match as string)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
getAll(): ISRRouteEntry[] {
|
|
92
|
+
return [...this.entries]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
clear(): void {
|
|
96
|
+
this.entries = []
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const isrRegistry = new ISRRegistry()
|
|
101
|
+
|
|
102
|
+
export function createISRRegistry(): ISRRegistry {
|
|
103
|
+
return new ISRRegistry()
|
|
104
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @framework-baseline cec621fae360ba2b
|
|
3
|
+
*
|
|
4
|
+
*
|
|
5
|
+
* @framework-modify
|
|
6
|
+
* @reason prettier 格式化与注释结构整理;body 改为可选字段
|
|
7
|
+
* @impact 框架文件维护性修改,行为见测试
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* ISR HTML renderer — injects SSR body content AND meta tags into HTML template.
|
|
12
|
+
* Full SSG mode: combines React SSR output with SEO meta tags.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export function escapeHtml(str: string): string {
|
|
16
|
+
return str
|
|
17
|
+
.replace(/&/g, '&')
|
|
18
|
+
.replace(/</g, '<')
|
|
19
|
+
.replace(/>/g, '>')
|
|
20
|
+
.replace(/"/g, '"')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ISRPageMeta {
|
|
24
|
+
title: string
|
|
25
|
+
description: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ISRRenderOptions {
|
|
29
|
+
/** Pre-built HTML template (from ASSETS or readFileSync) */
|
|
30
|
+
template: string | null
|
|
31
|
+
/** SSR body content (React renderToString output); omitted in meta-only mode */
|
|
32
|
+
body?: string
|
|
33
|
+
/** Meta tags */
|
|
34
|
+
meta: ISRPageMeta
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Inject ISR content (SSR body + meta tags) into an HTML template.
|
|
39
|
+
* If template is null, generates a minimal standalone HTML.
|
|
40
|
+
*/
|
|
41
|
+
export function renderISRPage(opts: ISRRenderOptions): string {
|
|
42
|
+
const { template, body = '', meta } = opts
|
|
43
|
+
const safeTitle = escapeHtml(meta.title)
|
|
44
|
+
const safeDesc = escapeHtml(meta.description)
|
|
45
|
+
|
|
46
|
+
if (template) {
|
|
47
|
+
let html = template
|
|
48
|
+
html = html.replace(/<title>[^<]*<\/title>/, `<title>${safeTitle}</title>`)
|
|
49
|
+
html = html.replace(/<meta\s+name="description"[^>]*>/, '')
|
|
50
|
+
html = html.replace(/<meta\s+property="og:title"[^>]*>/, '')
|
|
51
|
+
html = html.replace(/<meta\s+property="og:description"[^>]*>/, '')
|
|
52
|
+
html = html.replace(
|
|
53
|
+
'</head>',
|
|
54
|
+
` <meta name="description" content="${safeDesc}" />\n <meta property="og:title" content="${safeTitle}" />\n <meta property="og:description" content="${safeDesc}" />\n <meta name="generator" content="ISR-SSG" />\n </head>`
|
|
55
|
+
)
|
|
56
|
+
html = html.replace('<div id="root"></div>', `<div id="root">${body}</div>`)
|
|
57
|
+
return html
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return `<!DOCTYPE html>
|
|
61
|
+
<html lang="en">
|
|
62
|
+
<head>
|
|
63
|
+
<meta charset="UTF-8" />
|
|
64
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
65
|
+
<title>${safeTitle}</title>
|
|
66
|
+
<meta name="description" content="${safeDesc}" />
|
|
67
|
+
<meta property="og:title" content="${safeTitle}" />
|
|
68
|
+
<meta property="og:description" content="${safeDesc}" />
|
|
69
|
+
<meta name="generator" content="ISR-SSG" />
|
|
70
|
+
</head>
|
|
71
|
+
<body>
|
|
72
|
+
<div id="root">${body}</div>
|
|
73
|
+
</body>
|
|
74
|
+
</html>`
|
|
75
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core'
|
|
1
|
+
import { sqliteTable, integer, text, index } from 'drizzle-orm/sqlite-core'
|
|
2
2
|
import { sql } from 'drizzle-orm'
|
|
3
3
|
|
|
4
4
|
export const contentStatuses = ['draft', 'published', 'archived'] as const
|
|
@@ -7,25 +7,33 @@ export type ContentStatus = (typeof contentStatuses)[number]
|
|
|
7
7
|
export const contentCategories = ['article', 'announcement', 'tutorial', 'news', 'policy'] as const
|
|
8
8
|
export type ContentCategory = (typeof contentCategories)[number]
|
|
9
9
|
|
|
10
|
-
export const contents = sqliteTable(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
10
|
+
export const contents = sqliteTable(
|
|
11
|
+
'contents',
|
|
12
|
+
{
|
|
13
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
14
|
+
title: text('title').notNull(),
|
|
15
|
+
body: text('body').notNull(),
|
|
16
|
+
excerpt: text('excerpt'),
|
|
17
|
+
category: text('category', { enum: contentCategories }).notNull(),
|
|
18
|
+
tags: text('tags'),
|
|
19
|
+
status: text('status', { enum: contentStatuses }).notNull().default('draft'),
|
|
20
|
+
author: text('author').notNull(),
|
|
21
|
+
viewCount: integer('view_count').notNull().default(0),
|
|
22
|
+
likeCount: integer('like_count').notNull().default(0),
|
|
23
|
+
publishedAt: integer('published_at', { mode: 'timestamp' }),
|
|
24
|
+
createdAt: integer('created_at', { mode: 'timestamp' })
|
|
25
|
+
.notNull()
|
|
26
|
+
.default(sql`(unixepoch() * 1000)`),
|
|
27
|
+
updatedAt: integer('updated_at', { mode: 'timestamp' })
|
|
28
|
+
.notNull()
|
|
29
|
+
.default(sql`(unixepoch() * 1000)`),
|
|
30
|
+
},
|
|
31
|
+
table => ({
|
|
32
|
+
statusIdx: index('contents_status_idx').on(table.status),
|
|
33
|
+
categoryIdx: index('contents_category_idx').on(table.category),
|
|
34
|
+
createdAtIdx: index('contents_created_at_idx').on(table.createdAt),
|
|
35
|
+
})
|
|
36
|
+
)
|
|
29
37
|
|
|
30
38
|
export type ContentTable = typeof contents.$inferSelect
|
|
31
39
|
export type NewContent = typeof contents.$inferInsert
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core'
|
|
1
|
+
import { sqliteTable, integer, text, index } from 'drizzle-orm/sqlite-core'
|
|
2
2
|
import { sql } from 'drizzle-orm'
|
|
3
3
|
|
|
4
4
|
export const disputeTypes = [
|
|
@@ -13,27 +13,35 @@ export type DisputeType = (typeof disputeTypes)[number]
|
|
|
13
13
|
export const disputeStatuses = ['pending', 'investigating', 'resolved', 'rejected'] as const
|
|
14
14
|
export type DisputeStatus = (typeof disputeStatuses)[number]
|
|
15
15
|
|
|
16
|
-
export const disputes = sqliteTable(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
16
|
+
export const disputes = sqliteTable(
|
|
17
|
+
'disputes',
|
|
18
|
+
{
|
|
19
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
20
|
+
disputeNo: text('dispute_no').notNull(),
|
|
21
|
+
orderId: text('order_id').notNull(),
|
|
22
|
+
orderNo: text('order_no').notNull(),
|
|
23
|
+
customerName: text('customer_name').notNull(),
|
|
24
|
+
customerEmail: text('customer_email').notNull(),
|
|
25
|
+
type: text('type', { enum: disputeTypes }).notNull(),
|
|
26
|
+
status: text('status', { enum: disputeStatuses }).notNull().default('pending'),
|
|
27
|
+
description: text('description').notNull(),
|
|
28
|
+
resolution: text('resolution'),
|
|
29
|
+
amount: integer('amount').notNull(),
|
|
30
|
+
resolvedAt: integer('resolved_at', { mode: 'timestamp' }),
|
|
31
|
+
resolvedBy: text('resolved_by'),
|
|
32
|
+
createdAt: integer('created_at', { mode: 'timestamp' })
|
|
33
|
+
.notNull()
|
|
34
|
+
.default(sql`(unixepoch() * 1000)`),
|
|
35
|
+
updatedAt: integer('updated_at', { mode: 'timestamp' })
|
|
36
|
+
.notNull()
|
|
37
|
+
.default(sql`(unixepoch() * 1000)`),
|
|
38
|
+
},
|
|
39
|
+
table => ({
|
|
40
|
+
statusIdx: index('disputes_status_idx').on(table.status),
|
|
41
|
+
orderIdIdx: index('disputes_order_id_idx').on(table.orderId),
|
|
42
|
+
createdAtIdx: index('disputes_created_at_idx').on(table.createdAt),
|
|
43
|
+
})
|
|
44
|
+
)
|
|
37
45
|
|
|
38
46
|
export type DisputeTable = typeof disputes.$inferSelect
|
|
39
47
|
export type NewDispute = typeof disputes.$inferInsert
|
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core'
|
|
2
|
-
import { sql } from 'drizzle-orm'
|
|
1
|
+
import { sqliteTable, integer, text, index } from 'drizzle-orm/sqlite-core'
|
|
2
|
+
import { sql } from 'drizzle-orm'
|
|
3
3
|
|
|
4
|
-
export const notifications = sqliteTable(
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
.notNull()
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
export const notifications = sqliteTable(
|
|
5
|
+
'notifications',
|
|
6
|
+
{
|
|
7
|
+
id: text('id').primaryKey(),
|
|
8
|
+
type: text('type').notNull(),
|
|
9
|
+
title: text('title').notNull(),
|
|
10
|
+
message: text('message').notNull(),
|
|
11
|
+
read: integer('read', { mode: 'boolean' }).notNull().default(false),
|
|
12
|
+
createdAt: integer('created_at', { mode: 'timestamp' })
|
|
13
|
+
.notNull()
|
|
14
|
+
.default(sql`(unixepoch() * 1000)`),
|
|
15
|
+
},
|
|
16
|
+
table => ({
|
|
17
|
+
createdAtIdx: index('notifications_created_at_idx').on(table.createdAt),
|
|
18
|
+
})
|
|
19
|
+
)
|
|
14
20
|
|
|
15
|
-
export type NotificationTable = typeof notifications.$inferSelect
|
|
16
|
-
export type NewNotification = typeof notifications.$inferInsert
|
|
21
|
+
export type NotificationTable = typeof notifications.$inferSelect
|
|
22
|
+
export type NewNotification = typeof notifications.$inferInsert
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core'
|
|
1
|
+
import { sqliteTable, integer, text, index } from 'drizzle-orm/sqlite-core'
|
|
2
2
|
import { sql } from 'drizzle-orm'
|
|
3
3
|
|
|
4
4
|
export const orderStatuses = [
|
|
@@ -10,21 +10,28 @@ export const orderStatuses = [
|
|
|
10
10
|
] as const
|
|
11
11
|
export type OrderStatus = (typeof orderStatuses)[number]
|
|
12
12
|
|
|
13
|
-
export const orders = sqliteTable(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
.notNull()
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
13
|
+
export const orders = sqliteTable(
|
|
14
|
+
'orders',
|
|
15
|
+
{
|
|
16
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
17
|
+
orderNo: text('order_no').notNull(),
|
|
18
|
+
customerName: text('customer_name').notNull(),
|
|
19
|
+
customerEmail: text('customer_email').notNull(),
|
|
20
|
+
productName: text('product_name').notNull(),
|
|
21
|
+
amount: integer('amount').notNull(),
|
|
22
|
+
status: text('status', { enum: orderStatuses }).notNull().default('pending'),
|
|
23
|
+
createdAt: integer('created_at', { mode: 'timestamp' })
|
|
24
|
+
.notNull()
|
|
25
|
+
.default(sql`(unixepoch() * 1000)`),
|
|
26
|
+
updatedAt: integer('updated_at', { mode: 'timestamp' })
|
|
27
|
+
.notNull()
|
|
28
|
+
.default(sql`(unixepoch() * 1000)`),
|
|
29
|
+
},
|
|
30
|
+
table => ({
|
|
31
|
+
statusIdx: index('orders_status_idx').on(table.status),
|
|
32
|
+
createdAtIdx: index('orders_created_at_idx').on(table.createdAt),
|
|
33
|
+
})
|
|
34
|
+
)
|
|
28
35
|
|
|
29
36
|
export type OrderTable = typeof orders.$inferSelect
|
|
30
37
|
export type NewOrder = typeof orders.$inferInsert
|
|
@@ -1,38 +1,47 @@
|
|
|
1
|
-
import { sqliteTable, text, integer, uniqueIndex } from 'drizzle-orm/sqlite-core'
|
|
1
|
+
import { sqliteTable, text, integer, uniqueIndex, index } from 'drizzle-orm/sqlite-core'
|
|
2
2
|
import { sql } from 'drizzle-orm'
|
|
3
3
|
|
|
4
4
|
export const pluginStatus = ['pending', 'approved', 'rejected'] as const
|
|
5
5
|
export type PluginStatus = (typeof pluginStatus)[number]
|
|
6
6
|
|
|
7
|
-
export const plugins = sqliteTable(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
7
|
+
export const plugins = sqliteTable(
|
|
8
|
+
'plugins',
|
|
9
|
+
{
|
|
10
|
+
id: text('id').primaryKey(),
|
|
11
|
+
name: text('name').notNull(),
|
|
12
|
+
slug: text('slug').notNull().unique(),
|
|
13
|
+
description: text('description').notNull().default(''),
|
|
14
|
+
readme: text('readme'),
|
|
15
|
+
authorId: text('author_id').notNull(),
|
|
16
|
+
authorName: text('author_name').notNull(),
|
|
17
|
+
repositoryUrl: text('repository_url'),
|
|
18
|
+
homepageUrl: text('homepage_url'),
|
|
19
|
+
npmPackage: text('npm_package'),
|
|
20
|
+
license: text('license'),
|
|
21
|
+
version: text('version').notNull().default('0.0.1'),
|
|
22
|
+
status: text('status', { enum: pluginStatus }).notNull().default('pending'),
|
|
23
|
+
downloadCount: integer('download_count').notNull().default(0),
|
|
24
|
+
viewCount: integer('view_count').notNull().default(0),
|
|
25
|
+
featured: integer('featured', { mode: 'boolean' }).notNull().default(false),
|
|
26
|
+
screenshotUrl: text('screenshot_url'),
|
|
27
|
+
siteUrls: text('site_urls'),
|
|
28
|
+
tags: text('tags'),
|
|
29
|
+
commands: text('commands'),
|
|
30
|
+
rejectReason: text('reject_reason'),
|
|
31
|
+
createdAt: integer('created_at', { mode: 'timestamp' })
|
|
32
|
+
.notNull()
|
|
33
|
+
.default(sql`(unixepoch() * 1000)`),
|
|
34
|
+
updatedAt: integer('updated_at', { mode: 'timestamp' })
|
|
35
|
+
.notNull()
|
|
36
|
+
.default(sql`(unixepoch() * 1000)`),
|
|
37
|
+
},
|
|
38
|
+
table => ({
|
|
39
|
+
statusIdx: index('plugins_status_idx').on(table.status),
|
|
40
|
+
slugIdx: index('plugins_slug_idx').on(table.slug),
|
|
41
|
+
authorIdIdx: index('plugins_author_id_idx').on(table.authorId),
|
|
42
|
+
createdAtIdx: index('plugins_created_at_idx').on(table.createdAt),
|
|
43
|
+
})
|
|
44
|
+
)
|
|
36
45
|
|
|
37
46
|
export const pluginVersions = sqliteTable(
|
|
38
47
|
'plugin_versions',
|
|
@@ -53,6 +62,7 @@ export const pluginVersions = sqliteTable(
|
|
|
53
62
|
},
|
|
54
63
|
table => ({
|
|
55
64
|
pluginVersionUnique: uniqueIndex('plugin_version_unique').on(table.pluginId, table.version),
|
|
65
|
+
pluginIdIdx: index('plugin_versions_plugin_id_idx').on(table.pluginId),
|
|
56
66
|
})
|
|
57
67
|
)
|
|
58
68
|
|
|
@@ -77,14 +87,20 @@ export const pluginReviews = sqliteTable(
|
|
|
77
87
|
})
|
|
78
88
|
)
|
|
79
89
|
|
|
80
|
-
export const pluginCategories = sqliteTable(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
export const pluginCategories = sqliteTable(
|
|
91
|
+
'plugin_categories',
|
|
92
|
+
{
|
|
93
|
+
id: text('id').primaryKey(),
|
|
94
|
+
name: text('name').notNull().unique(),
|
|
95
|
+
slug: text('slug').notNull().unique(),
|
|
96
|
+
description: text('description'),
|
|
97
|
+
icon: text('icon'),
|
|
98
|
+
sortOrder: integer('sort_order').notNull().default(0),
|
|
99
|
+
},
|
|
100
|
+
table => ({
|
|
101
|
+
slugIdx: index('plugin_categories_slug_idx').on(table.slug),
|
|
102
|
+
})
|
|
103
|
+
)
|
|
88
104
|
|
|
89
105
|
export const pluginCategoryMappings = sqliteTable(
|
|
90
106
|
'plugin_category_mappings',
|
|
@@ -101,6 +117,8 @@ export const pluginCategoryMappings = sqliteTable(
|
|
|
101
117
|
table.pluginId,
|
|
102
118
|
table.categoryId
|
|
103
119
|
),
|
|
120
|
+
categoryIdIdx: index('plugin_category_mappings_category_id_idx').on(table.categoryId),
|
|
121
|
+
pluginIdIdx: index('plugin_category_mappings_plugin_id_idx').on(table.pluginId),
|
|
104
122
|
})
|
|
105
123
|
)
|
|
106
124
|
|
|
@@ -1,25 +1,32 @@
|
|
|
1
|
-
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core'
|
|
1
|
+
import { sqliteTable, integer, text, index } from 'drizzle-orm/sqlite-core'
|
|
2
2
|
import { sql } from 'drizzle-orm'
|
|
3
3
|
|
|
4
4
|
export const productStatuses = ['active', 'inactive', 'out_of_stock'] as const
|
|
5
5
|
export type ProductStatus = (typeof productStatuses)[number]
|
|
6
6
|
|
|
7
|
-
export const products = sqliteTable(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
.notNull()
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
7
|
+
export const products = sqliteTable(
|
|
8
|
+
'products',
|
|
9
|
+
{
|
|
10
|
+
id: text('id').primaryKey(),
|
|
11
|
+
name: text('name').notNull(),
|
|
12
|
+
description: text('description'),
|
|
13
|
+
price: integer('price').notNull(), // 单位:分
|
|
14
|
+
status: text('status', { enum: productStatuses }).notNull().default('active'),
|
|
15
|
+
stock: integer('stock').notNull().default(0),
|
|
16
|
+
imageUrl: text('image_url'),
|
|
17
|
+
merchantId: integer('merchant_id').notNull(), // Changed to integer to match merchants.id
|
|
18
|
+
createdAt: integer('created_at', { mode: 'timestamp' })
|
|
19
|
+
.notNull()
|
|
20
|
+
.default(sql`(unixepoch() * 1000)`),
|
|
21
|
+
updatedAt: integer('updated_at', { mode: 'timestamp' })
|
|
22
|
+
.notNull()
|
|
23
|
+
.default(sql`(unixepoch() * 1000)`),
|
|
24
|
+
},
|
|
25
|
+
table => ({
|
|
26
|
+
statusIdx: index('products_status_idx').on(table.status),
|
|
27
|
+
merchantIdIdx: index('products_merchant_id_idx').on(table.merchantId),
|
|
28
|
+
})
|
|
29
|
+
)
|
|
23
30
|
|
|
24
31
|
export type Product = typeof products.$inferSelect
|
|
25
32
|
export type NewProduct = typeof products.$inferInsert
|