create-fullstack-scaffold 0.4.24 → 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 +439 -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/drizzle/0000_rainy_boomer.sql +0 -101
- package/template/drizzle/0001_add_todo_attachments.sql +0 -12
- package/template/drizzle/0002_chilly_magneto.sql +0 -89
- package/template/drizzle/0003_ambiguous_magdalene.sql +0 -100
- package/template/drizzle/0004_add_merchants_products.sql +0 -30
- package/template/drizzle/meta/0000_snapshot.json +0 -652
- package/template/drizzle/meta/0001_snapshot.json +0 -735
- package/template/drizzle/meta/0002_snapshot.json +0 -1198
- package/template/drizzle/meta/0003_snapshot.json +0 -1837
- package/template/drizzle/meta/_journal.json +0 -41
- package/template/patches/typescript+5.9.3.patch +0 -24
- package/template/pnpm-lock.yaml +0 -7137
- /package/template/patches/{hono+4.12.16.patch → hono+4.12.34.patch} +0 -0
|
@@ -50,8 +50,13 @@ export async function seedOrdersIfEmpty(): Promise<void> {
|
|
|
50
50
|
export async function getOrders(filters?: {
|
|
51
51
|
status?: OrderStatus
|
|
52
52
|
customerName?: string
|
|
53
|
-
|
|
53
|
+
page?: number
|
|
54
|
+
limit?: number
|
|
55
|
+
}): Promise<{ orders: Order[]; total: number; page: number; limit: number }> {
|
|
54
56
|
const db = await getDb()
|
|
57
|
+
const page = filters?.page ?? 1
|
|
58
|
+
const limit = filters?.limit ?? 20
|
|
59
|
+
const offset = (page - 1) * limit
|
|
55
60
|
const conditions = []
|
|
56
61
|
|
|
57
62
|
if (filters?.status) {
|
|
@@ -66,7 +71,7 @@ export async function getOrders(filters?: {
|
|
|
66
71
|
)
|
|
67
72
|
}
|
|
68
73
|
|
|
69
|
-
const
|
|
74
|
+
const allRows =
|
|
70
75
|
conditions.length > 0
|
|
71
76
|
? await db
|
|
72
77
|
.select()
|
|
@@ -75,17 +80,24 @@ export async function getOrders(filters?: {
|
|
|
75
80
|
.orderBy(desc(orders.createdAt))
|
|
76
81
|
: await db.select().from(orders).orderBy(desc(orders.createdAt))
|
|
77
82
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
const rows = allRows.slice(offset, offset + limit)
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
orders: rows.map((row: OrderTable) => ({
|
|
87
|
+
id: `order-${row.id}`,
|
|
88
|
+
orderNo: row.orderNo,
|
|
89
|
+
customerName: row.customerName,
|
|
90
|
+
customerEmail: row.customerEmail,
|
|
91
|
+
productName: row.productName,
|
|
92
|
+
amount: row.amount,
|
|
93
|
+
status: row.status,
|
|
94
|
+
createdAt: toISOString(row.createdAt),
|
|
95
|
+
updatedAt: toISOString(row.updatedAt),
|
|
96
|
+
})),
|
|
97
|
+
total: allRows.length,
|
|
98
|
+
page,
|
|
99
|
+
limit,
|
|
100
|
+
}
|
|
89
101
|
}
|
|
90
102
|
|
|
91
103
|
export async function getOrderById(id: string): Promise<Order | null> {
|
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import { AuditLogService } from '../services/audit-log-service'
|
|
3
|
+
import { getRawClient, getDb } from '@server/db'
|
|
4
|
+
import { setupTestDatabase, cleanupTestDatabase } from '@server/db/test-setup'
|
|
5
|
+
import { RESOURCE_TYPES, ACTION_TYPES } from '@shared/constants'
|
|
6
|
+
|
|
7
|
+
describe('AuditLog Service', () => {
|
|
8
|
+
let service: AuditLogService
|
|
9
|
+
|
|
10
|
+
beforeAll(async () => {
|
|
11
|
+
await setupTestDatabase()
|
|
12
|
+
const db = await getDb()
|
|
13
|
+
expect(db).toBeDefined()
|
|
14
|
+
service = new AuditLogService()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
afterAll(async () => {
|
|
18
|
+
await cleanupTestDatabase()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
beforeEach(async () => {
|
|
22
|
+
const client = await getRawClient()
|
|
23
|
+
if (client && 'execute' in client) {
|
|
24
|
+
await client.execute('DELETE FROM permission_audit_logs')
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
afterEach(async () => {
|
|
29
|
+
const client = await getRawClient()
|
|
30
|
+
if (client && 'execute' in client) {
|
|
31
|
+
await client.execute('DELETE FROM permission_audit_logs')
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
async function insertTestLog(overrides: {
|
|
36
|
+
id: string
|
|
37
|
+
userId: string
|
|
38
|
+
action: string
|
|
39
|
+
resourceType: string
|
|
40
|
+
resourceId?: string
|
|
41
|
+
oldValue?: string
|
|
42
|
+
newValue?: string
|
|
43
|
+
ipAddress?: string
|
|
44
|
+
userAgent?: string
|
|
45
|
+
createdAt?: number
|
|
46
|
+
}) {
|
|
47
|
+
const client = await getRawClient()
|
|
48
|
+
if (client && 'execute' in client) {
|
|
49
|
+
await client.execute({
|
|
50
|
+
sql: `INSERT INTO permission_audit_logs (id, user_id, action, resource_type, resource_id, old_value, new_value, ip_address, user_agent, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
51
|
+
args: [
|
|
52
|
+
overrides.id,
|
|
53
|
+
overrides.userId,
|
|
54
|
+
overrides.action,
|
|
55
|
+
overrides.resourceType,
|
|
56
|
+
overrides.resourceId ?? null,
|
|
57
|
+
overrides.oldValue ?? null,
|
|
58
|
+
overrides.newValue ?? null,
|
|
59
|
+
overrides.ipAddress ?? null,
|
|
60
|
+
overrides.userAgent ?? null,
|
|
61
|
+
overrides.createdAt ?? Date.now(),
|
|
62
|
+
],
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
describe('create', () => {
|
|
68
|
+
it('should create an audit log entry', async () => {
|
|
69
|
+
const data = {
|
|
70
|
+
id: 'log_1',
|
|
71
|
+
userId: 'user-1',
|
|
72
|
+
action: ACTION_TYPES.CREATE,
|
|
73
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
74
|
+
resourceId: 'role-1',
|
|
75
|
+
newValue: '{"name":"Admin"}',
|
|
76
|
+
ipAddress: '127.0.0.1',
|
|
77
|
+
userAgent: 'TestAgent/1.0',
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const result = await service.create(data)
|
|
81
|
+
|
|
82
|
+
expect(result.id).toBe('log_1')
|
|
83
|
+
expect(result.userId).toBe('user-1')
|
|
84
|
+
expect(result.action).toBe(ACTION_TYPES.CREATE)
|
|
85
|
+
expect(result.resourceType).toBe(RESOURCE_TYPES.ROLE)
|
|
86
|
+
expect(result.resourceId).toBe('role-1')
|
|
87
|
+
expect(result.newValue).toBe('{"name":"Admin"}')
|
|
88
|
+
expect(result.ipAddress).toBe('127.0.0.1')
|
|
89
|
+
expect(result.userAgent).toBe('TestAgent/1.0')
|
|
90
|
+
expect(typeof result.createdAt).toBe('string')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('should create log with minimal fields', async () => {
|
|
94
|
+
const data = {
|
|
95
|
+
id: 'log_minimal',
|
|
96
|
+
userId: 'user-1',
|
|
97
|
+
action: ACTION_TYPES.UPDATE,
|
|
98
|
+
resourceType: RESOURCE_TYPES.USER,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const result = await service.create(data)
|
|
102
|
+
|
|
103
|
+
expect(result.id).toBe('log_minimal')
|
|
104
|
+
expect(result.userId).toBe('user-1')
|
|
105
|
+
expect(result.action).toBe(ACTION_TYPES.UPDATE)
|
|
106
|
+
expect(result.resourceType).toBe(RESOURCE_TYPES.USER)
|
|
107
|
+
expect(result.resourceId).toBeNull()
|
|
108
|
+
expect(result.oldValue).toBeNull()
|
|
109
|
+
expect(result.newValue).toBeNull()
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
describe('getAll', () => {
|
|
114
|
+
it('should return empty array when no logs exist', async () => {
|
|
115
|
+
const result = await service.getAll()
|
|
116
|
+
|
|
117
|
+
expect(result).toEqual([])
|
|
118
|
+
expect(Array.isArray(result)).toBe(true)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('should return logs ordered by createdAt descending', async () => {
|
|
122
|
+
const now = Date.now()
|
|
123
|
+
await insertTestLog({
|
|
124
|
+
id: 'log_1',
|
|
125
|
+
userId: 'user-1',
|
|
126
|
+
action: ACTION_TYPES.CREATE,
|
|
127
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
128
|
+
createdAt: now,
|
|
129
|
+
})
|
|
130
|
+
await insertTestLog({
|
|
131
|
+
id: 'log_2',
|
|
132
|
+
userId: 'user-2',
|
|
133
|
+
action: ACTION_TYPES.UPDATE,
|
|
134
|
+
resourceType: RESOURCE_TYPES.USER,
|
|
135
|
+
createdAt: now + 1000,
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
const result = await service.getAll()
|
|
139
|
+
|
|
140
|
+
expect(result).toHaveLength(2)
|
|
141
|
+
expect(result[0].id).toBe('log_2')
|
|
142
|
+
expect(result[1].id).toBe('log_1')
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('should respect limit parameter', async () => {
|
|
146
|
+
for (let i = 0; i < 5; i++) {
|
|
147
|
+
await insertTestLog({
|
|
148
|
+
id: `log_${i}`,
|
|
149
|
+
userId: 'user-1',
|
|
150
|
+
action: ACTION_TYPES.CREATE,
|
|
151
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
152
|
+
createdAt: Date.now() + i,
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const result = await service.getAll(2)
|
|
157
|
+
|
|
158
|
+
expect(result).toHaveLength(2)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('should respect offset parameter', async () => {
|
|
162
|
+
for (let i = 0; i < 5; i++) {
|
|
163
|
+
await insertTestLog({
|
|
164
|
+
id: `log_${i}`,
|
|
165
|
+
userId: 'user-1',
|
|
166
|
+
action: ACTION_TYPES.CREATE,
|
|
167
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
168
|
+
createdAt: Date.now() + i,
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const result = await service.getAll(2, 3)
|
|
173
|
+
|
|
174
|
+
expect(result).toHaveLength(2)
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
describe('getByUserId', () => {
|
|
179
|
+
it('should return logs for specific user', async () => {
|
|
180
|
+
await insertTestLog({
|
|
181
|
+
id: 'log_1',
|
|
182
|
+
userId: 'user-1',
|
|
183
|
+
action: ACTION_TYPES.CREATE,
|
|
184
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
185
|
+
})
|
|
186
|
+
await insertTestLog({
|
|
187
|
+
id: 'log_2',
|
|
188
|
+
userId: 'user-2',
|
|
189
|
+
action: ACTION_TYPES.UPDATE,
|
|
190
|
+
resourceType: RESOURCE_TYPES.USER,
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
const result = await service.getByUserId('user-1')
|
|
194
|
+
|
|
195
|
+
expect(result).toHaveLength(1)
|
|
196
|
+
expect(result[0].userId).toBe('user-1')
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('should return empty array for unknown user', async () => {
|
|
200
|
+
const result = await service.getByUserId('unknown')
|
|
201
|
+
|
|
202
|
+
expect(result).toEqual([])
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it('should respect limit parameter', async () => {
|
|
206
|
+
for (let i = 0; i < 5; i++) {
|
|
207
|
+
await insertTestLog({
|
|
208
|
+
id: `log_${i}`,
|
|
209
|
+
userId: 'user-1',
|
|
210
|
+
action: ACTION_TYPES.CREATE,
|
|
211
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
212
|
+
createdAt: Date.now() + i,
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const result = await service.getByUserId('user-1', 3)
|
|
217
|
+
|
|
218
|
+
expect(result).toHaveLength(3)
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
describe('getByResource', () => {
|
|
223
|
+
it('should return logs for resource type', async () => {
|
|
224
|
+
await insertTestLog({
|
|
225
|
+
id: 'log_1',
|
|
226
|
+
userId: 'user-1',
|
|
227
|
+
action: ACTION_TYPES.CREATE,
|
|
228
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
229
|
+
resourceId: 'role-1',
|
|
230
|
+
})
|
|
231
|
+
await insertTestLog({
|
|
232
|
+
id: 'log_2',
|
|
233
|
+
userId: 'user-1',
|
|
234
|
+
action: ACTION_TYPES.UPDATE,
|
|
235
|
+
resourceType: RESOURCE_TYPES.USER,
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
const result = await service.getByResource(RESOURCE_TYPES.ROLE)
|
|
239
|
+
|
|
240
|
+
expect(result).toHaveLength(1)
|
|
241
|
+
expect(result[0].resourceType).toBe(RESOURCE_TYPES.ROLE)
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it('should filter by resourceId when provided', async () => {
|
|
245
|
+
await insertTestLog({
|
|
246
|
+
id: 'log_1',
|
|
247
|
+
userId: 'user-1',
|
|
248
|
+
action: ACTION_TYPES.CREATE,
|
|
249
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
250
|
+
resourceId: 'role-1',
|
|
251
|
+
})
|
|
252
|
+
await insertTestLog({
|
|
253
|
+
id: 'log_2',
|
|
254
|
+
userId: 'user-1',
|
|
255
|
+
action: ACTION_TYPES.UPDATE,
|
|
256
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
257
|
+
resourceId: 'role-2',
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
const result = await service.getByResource(RESOURCE_TYPES.ROLE, 'role-1')
|
|
261
|
+
|
|
262
|
+
expect(result).toHaveLength(1)
|
|
263
|
+
expect(result[0].resourceId).toBe('role-1')
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
it('should return all logs for resource type when resourceId is omitted', async () => {
|
|
267
|
+
await insertTestLog({
|
|
268
|
+
id: 'log_1',
|
|
269
|
+
userId: 'user-1',
|
|
270
|
+
action: ACTION_TYPES.CREATE,
|
|
271
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
272
|
+
resourceId: 'role-1',
|
|
273
|
+
})
|
|
274
|
+
await insertTestLog({
|
|
275
|
+
id: 'log_2',
|
|
276
|
+
userId: 'user-1',
|
|
277
|
+
action: ACTION_TYPES.UPDATE,
|
|
278
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
279
|
+
resourceId: 'role-2',
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
const result = await service.getByResource(RESOURCE_TYPES.ROLE)
|
|
283
|
+
|
|
284
|
+
expect(result).toHaveLength(2)
|
|
285
|
+
})
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
describe('search', () => {
|
|
289
|
+
it('should return all logs when no filters provided', async () => {
|
|
290
|
+
await insertTestLog({
|
|
291
|
+
id: 'log_1',
|
|
292
|
+
userId: 'user-1',
|
|
293
|
+
action: ACTION_TYPES.CREATE,
|
|
294
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
295
|
+
})
|
|
296
|
+
await insertTestLog({
|
|
297
|
+
id: 'log_2',
|
|
298
|
+
userId: 'user-2',
|
|
299
|
+
action: ACTION_TYPES.UPDATE,
|
|
300
|
+
resourceType: RESOURCE_TYPES.USER,
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
const result = await service.search({})
|
|
304
|
+
|
|
305
|
+
expect(result).toHaveLength(2)
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
it('should filter by userId', async () => {
|
|
309
|
+
await insertTestLog({
|
|
310
|
+
id: 'log_1',
|
|
311
|
+
userId: 'user-1',
|
|
312
|
+
action: ACTION_TYPES.CREATE,
|
|
313
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
314
|
+
})
|
|
315
|
+
await insertTestLog({
|
|
316
|
+
id: 'log_2',
|
|
317
|
+
userId: 'user-2',
|
|
318
|
+
action: ACTION_TYPES.CREATE,
|
|
319
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
const result = await service.search({ userId: 'user-1' })
|
|
323
|
+
|
|
324
|
+
expect(result).toHaveLength(1)
|
|
325
|
+
expect(result[0].userId).toBe('user-1')
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
it('should filter by action', async () => {
|
|
329
|
+
await insertTestLog({
|
|
330
|
+
id: 'log_1',
|
|
331
|
+
userId: 'user-1',
|
|
332
|
+
action: ACTION_TYPES.CREATE,
|
|
333
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
334
|
+
})
|
|
335
|
+
await insertTestLog({
|
|
336
|
+
id: 'log_2',
|
|
337
|
+
userId: 'user-1',
|
|
338
|
+
action: ACTION_TYPES.ASSIGN,
|
|
339
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
const result = await service.search({ action: ACTION_TYPES.CREATE })
|
|
343
|
+
|
|
344
|
+
expect(result).toHaveLength(1)
|
|
345
|
+
expect(result[0].action).toBe(ACTION_TYPES.CREATE)
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
it('should filter by resourceType', async () => {
|
|
349
|
+
await insertTestLog({
|
|
350
|
+
id: 'log_1',
|
|
351
|
+
userId: 'user-1',
|
|
352
|
+
action: ACTION_TYPES.CREATE,
|
|
353
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
354
|
+
})
|
|
355
|
+
await insertTestLog({
|
|
356
|
+
id: 'log_2',
|
|
357
|
+
userId: 'user-1',
|
|
358
|
+
action: ACTION_TYPES.CREATE,
|
|
359
|
+
resourceType: RESOURCE_TYPES.USER,
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
const result = await service.search({ resourceType: RESOURCE_TYPES.ROLE })
|
|
363
|
+
|
|
364
|
+
expect(result).toHaveLength(1)
|
|
365
|
+
expect(result[0].resourceType).toBe(RESOURCE_TYPES.ROLE)
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
it('should return logs ordered by createdAt descending', async () => {
|
|
369
|
+
await service.create({
|
|
370
|
+
id: 'log_old',
|
|
371
|
+
userId: 'user-1',
|
|
372
|
+
action: ACTION_TYPES.CREATE,
|
|
373
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
374
|
+
})
|
|
375
|
+
await service.create({
|
|
376
|
+
id: 'log_new',
|
|
377
|
+
userId: 'user-1',
|
|
378
|
+
action: ACTION_TYPES.UPDATE,
|
|
379
|
+
resourceType: RESOURCE_TYPES.USER,
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
const result = await service.search({})
|
|
383
|
+
|
|
384
|
+
expect(result).toHaveLength(2)
|
|
385
|
+
const ids = result.map(r => r.id)
|
|
386
|
+
expect(ids).toContain('log_old')
|
|
387
|
+
expect(ids).toContain('log_new')
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
it('should combine multiple filters', async () => {
|
|
391
|
+
await insertTestLog({
|
|
392
|
+
id: 'log_1',
|
|
393
|
+
userId: 'user-1',
|
|
394
|
+
action: ACTION_TYPES.CREATE,
|
|
395
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
396
|
+
})
|
|
397
|
+
await insertTestLog({
|
|
398
|
+
id: 'log_2',
|
|
399
|
+
userId: 'user-1',
|
|
400
|
+
action: ACTION_TYPES.UPDATE,
|
|
401
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
402
|
+
})
|
|
403
|
+
await insertTestLog({
|
|
404
|
+
id: 'log_3',
|
|
405
|
+
userId: 'user-2',
|
|
406
|
+
action: ACTION_TYPES.CREATE,
|
|
407
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
const result = await service.search({
|
|
411
|
+
userId: 'user-1',
|
|
412
|
+
action: ACTION_TYPES.CREATE,
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
expect(result).toHaveLength(1)
|
|
416
|
+
expect(result[0].id).toBe('log_1')
|
|
417
|
+
})
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
describe('deleteOlderThan', () => {
|
|
421
|
+
it('should delete logs older than specified days', async () => {
|
|
422
|
+
await service.create({
|
|
423
|
+
id: 'log_old',
|
|
424
|
+
userId: 'user-1',
|
|
425
|
+
action: ACTION_TYPES.CREATE,
|
|
426
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
427
|
+
})
|
|
428
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
429
|
+
await service.create({
|
|
430
|
+
id: 'log_new',
|
|
431
|
+
userId: 'user-1',
|
|
432
|
+
action: ACTION_TYPES.UPDATE,
|
|
433
|
+
resourceType: RESOURCE_TYPES.USER,
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
const allLogs = await service.getAll()
|
|
437
|
+
expect(allLogs).toHaveLength(2)
|
|
438
|
+
|
|
439
|
+
const deletedCount = await service.deleteOlderThan(0)
|
|
440
|
+
|
|
441
|
+
expect(deletedCount).toBe(2)
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
it('should return 0 when no logs are old enough', async () => {
|
|
445
|
+
await insertTestLog({
|
|
446
|
+
id: 'log_recent',
|
|
447
|
+
userId: 'user-1',
|
|
448
|
+
action: ACTION_TYPES.CREATE,
|
|
449
|
+
resourceType: RESOURCE_TYPES.ROLE,
|
|
450
|
+
createdAt: Date.now(),
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
const deletedCount = await service.deleteOlderThan(30)
|
|
454
|
+
|
|
455
|
+
expect(deletedCount).toBe(0)
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
it('should return 0 when no logs exist', async () => {
|
|
459
|
+
const deletedCount = await service.deleteOlderThan(30)
|
|
460
|
+
|
|
461
|
+
expect(deletedCount).toBe(0)
|
|
462
|
+
})
|
|
463
|
+
})
|
|
464
|
+
})
|