sovrium 0.1.0 → 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.
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Copyright (c) 2025 ESSENTIAL SERVICES
3
+ *
4
+ * This source code is licensed under the Business Source License 1.1
5
+ * found in the LICENSE.md file in the root directory of this source tree.
6
+ */
7
+
8
+ import { z } from 'zod'
9
+
10
+ // ============================================================================
11
+ // OpenAPI Path Parameter Schemas
12
+ // ============================================================================
13
+
14
+ /**
15
+ * Table ID path parameter
16
+ */
17
+ export const tableIdParamSchema = z.object({
18
+ tableId: z.string().describe('Table identifier'),
19
+ })
20
+
21
+ /**
22
+ * Record ID path parameters (includes tableId)
23
+ */
24
+ export const recordIdParamSchema = z.object({
25
+ tableId: z.string().describe('Table identifier'),
26
+ recordId: z.string().describe('Record identifier'),
27
+ })
28
+
29
+ /**
30
+ * Comment ID path parameters (includes tableId and recordId)
31
+ */
32
+ export const commentIdParamSchema = z.object({
33
+ tableId: z.string().describe('Table identifier'),
34
+ recordId: z.string().describe('Record identifier'),
35
+ commentId: z.string().describe('Comment identifier'),
36
+ })
37
+
38
+ /**
39
+ * View ID path parameters (includes tableId)
40
+ */
41
+ export const viewIdParamSchema = z.object({
42
+ tableId: z.string().describe('Table identifier'),
43
+ viewId: z.string().describe('View identifier'),
44
+ })
45
+
46
+ /**
47
+ * Activity ID path parameter
48
+ */
49
+ export const activityIdParamSchema = z.object({
50
+ activityId: z.string().describe('Activity log identifier'),
51
+ })
52
+
53
+ // ============================================================================
54
+ // OpenAPI Query Parameter Schemas
55
+ // ============================================================================
56
+
57
+ /**
58
+ * List records query parameters
59
+ */
60
+ export const listRecordsQuerySchema = z.object({
61
+ page: z.string().optional().describe('Page number (1-indexed)'),
62
+ limit: z.string().optional().describe('Items per page'),
63
+ sort: z.string().optional().describe('Sort field'),
64
+ order: z.enum(['asc', 'desc']).optional().describe('Sort order'),
65
+ format: z.enum(['raw', 'display']).optional().describe('Field value format'),
66
+ })
67
+
68
+ /**
69
+ * Activity log query parameters
70
+ */
71
+ export const activityQuerySchema = z.object({
72
+ page: z.string().optional().describe('Page number'),
73
+ pageSize: z.string().optional().describe('Items per page'),
74
+ tableId: z.string().optional().describe('Filter by table ID'),
75
+ action: z.enum(['create', 'update', 'delete', 'restore']).optional().describe('Filter by action'),
76
+ })
77
+
78
+ /**
79
+ * Create/update comment request body
80
+ */
81
+ export const commentBodySchema = z.object({
82
+ content: z.string().min(1).max(10_000).describe('Comment text'),
83
+ })
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Copyright (c) 2025 ESSENTIAL SERVICES
3
+ *
4
+ * This source code is licensed under the Business Source License 1.1
5
+ * found in the LICENSE.md file in the root directory of this source tree.
6
+ */
7
+
8
+ import { type OpenAPIHono, createRoute } from '@hono/zod-openapi'
9
+ import {
10
+ getActivityLogResponseSchema,
11
+ listActivityLogsResponseSchema,
12
+ } from '@/domain/models/api/activity'
13
+ import { errorResponseSchema } from '@/domain/models/api/error'
14
+ import { activityIdParamSchema, activityQuerySchema } from '@/domain/models/api/params'
15
+
16
+ /**
17
+ * Register activity log routes for OpenAPI schema generation
18
+ */
19
+ export function registerActivityRoutes(app: OpenAPIHono): void {
20
+ // GET /api/activity
21
+ app.openapi(
22
+ createRoute({
23
+ method: 'get',
24
+ path: '/api/activity',
25
+ summary: 'List activity logs',
26
+ description:
27
+ 'Returns paginated activity logs with optional filtering by table or action type.',
28
+ operationId: 'listActivityLogs',
29
+ tags: ['activity'],
30
+ request: { query: activityQuerySchema },
31
+ responses: {
32
+ 200: {
33
+ content: { 'application/json': { schema: listActivityLogsResponseSchema } },
34
+ description: 'Activity logs',
35
+ },
36
+ 401: {
37
+ content: { 'application/json': { schema: errorResponseSchema } },
38
+ description: 'Unauthorized',
39
+ },
40
+ },
41
+ }),
42
+ (c) => c.json({} as never)
43
+ )
44
+
45
+ // GET /api/activity/{activityId}
46
+ app.openapi(
47
+ createRoute({
48
+ method: 'get',
49
+ path: '/api/activity/{activityId}',
50
+ summary: 'Get activity log details',
51
+ description: 'Returns a single activity log entry with full change details.',
52
+ operationId: 'getActivityLog',
53
+ tags: ['activity'],
54
+ request: { params: activityIdParamSchema },
55
+ responses: {
56
+ 200: {
57
+ content: { 'application/json': { schema: getActivityLogResponseSchema } },
58
+ description: 'Activity log details',
59
+ },
60
+ 401: {
61
+ content: { 'application/json': { schema: errorResponseSchema } },
62
+ description: 'Unauthorized',
63
+ },
64
+ 404: {
65
+ content: { 'application/json': { schema: errorResponseSchema } },
66
+ description: 'Activity not found',
67
+ },
68
+ },
69
+ }),
70
+ (c) => c.json({} as never)
71
+ )
72
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Copyright (c) 2025 ESSENTIAL SERVICES
3
+ *
4
+ * This source code is licensed under the Business Source License 1.1
5
+ * found in the LICENSE.md file in the root directory of this source tree.
6
+ */
7
+
8
+ import { type OpenAPIHono, createRoute } from '@hono/zod-openapi'
9
+ import {
10
+ analyticsCollectSchema,
11
+ analyticsQuerySchema,
12
+ analyticsOverviewResponseSchema,
13
+ analyticsTopPagesResponseSchema,
14
+ analyticsTopReferrersResponseSchema,
15
+ analyticsDevicesResponseSchema,
16
+ analyticsCampaignsResponseSchema,
17
+ } from '@/domain/models/api/analytics'
18
+ import { successResponseSchema } from '@/domain/models/api/common'
19
+ import { errorResponseSchema } from '@/domain/models/api/error'
20
+
21
+ /**
22
+ * Register analytics routes for OpenAPI schema generation
23
+ */
24
+ export function registerAnalyticsRoutes(app: OpenAPIHono): void {
25
+ // POST /api/analytics/collect
26
+ app.openapi(
27
+ createRoute({
28
+ method: 'post',
29
+ path: '/api/analytics/collect',
30
+ summary: 'Collect analytics event',
31
+ description: 'Records a page view event. Uses single-letter keys to minimize payload size.',
32
+ operationId: 'collectAnalytics',
33
+ tags: ['analytics'],
34
+ request: {
35
+ body: {
36
+ content: { 'application/json': { schema: analyticsCollectSchema } },
37
+ },
38
+ },
39
+ responses: {
40
+ 200: {
41
+ content: { 'application/json': { schema: successResponseSchema } },
42
+ description: 'Event collected',
43
+ },
44
+ 400: {
45
+ content: { 'application/json': { schema: errorResponseSchema } },
46
+ description: 'Validation error',
47
+ },
48
+ },
49
+ }),
50
+ (c) => c.json({} as never)
51
+ )
52
+
53
+ // GET /api/analytics/overview
54
+ app.openapi(
55
+ createRoute({
56
+ method: 'get',
57
+ path: '/api/analytics/overview',
58
+ summary: 'Get analytics overview',
59
+ description: 'Returns summary statistics and time series data for the given date range.',
60
+ operationId: 'getAnalyticsOverview',
61
+ tags: ['analytics'],
62
+ request: { query: analyticsQuerySchema },
63
+ responses: {
64
+ 200: {
65
+ content: { 'application/json': { schema: analyticsOverviewResponseSchema } },
66
+ description: 'Analytics overview',
67
+ },
68
+ 400: {
69
+ content: { 'application/json': { schema: errorResponseSchema } },
70
+ description: 'Invalid date range',
71
+ },
72
+ 401: {
73
+ content: { 'application/json': { schema: errorResponseSchema } },
74
+ description: 'Unauthorized',
75
+ },
76
+ },
77
+ }),
78
+ (c) => c.json({} as never)
79
+ )
80
+
81
+ // GET /api/analytics/pages
82
+ app.openapi(
83
+ createRoute({
84
+ method: 'get',
85
+ path: '/api/analytics/pages',
86
+ summary: 'Get top pages',
87
+ description: 'Returns most visited pages with view counts and unique visitors.',
88
+ operationId: 'getAnalyticsTopPages',
89
+ tags: ['analytics'],
90
+ request: { query: analyticsQuerySchema },
91
+ responses: {
92
+ 200: {
93
+ content: { 'application/json': { schema: analyticsTopPagesResponseSchema } },
94
+ description: 'Top pages',
95
+ },
96
+ 401: {
97
+ content: { 'application/json': { schema: errorResponseSchema } },
98
+ description: 'Unauthorized',
99
+ },
100
+ },
101
+ }),
102
+ (c) => c.json({} as never)
103
+ )
104
+
105
+ // GET /api/analytics/referrers
106
+ app.openapi(
107
+ createRoute({
108
+ method: 'get',
109
+ path: '/api/analytics/referrers',
110
+ summary: 'Get top referrers',
111
+ description: 'Returns traffic sources ranked by page views.',
112
+ operationId: 'getAnalyticsTopReferrers',
113
+ tags: ['analytics'],
114
+ request: { query: analyticsQuerySchema },
115
+ responses: {
116
+ 200: {
117
+ content: { 'application/json': { schema: analyticsTopReferrersResponseSchema } },
118
+ description: 'Top referrers',
119
+ },
120
+ 401: {
121
+ content: { 'application/json': { schema: errorResponseSchema } },
122
+ description: 'Unauthorized',
123
+ },
124
+ },
125
+ }),
126
+ (c) => c.json({} as never)
127
+ )
128
+
129
+ // GET /api/analytics/devices
130
+ app.openapi(
131
+ createRoute({
132
+ method: 'get',
133
+ path: '/api/analytics/devices',
134
+ summary: 'Get device breakdown',
135
+ description: 'Returns visitor breakdown by device type, browser, and operating system.',
136
+ operationId: 'getAnalyticsDevices',
137
+ tags: ['analytics'],
138
+ request: { query: analyticsQuerySchema },
139
+ responses: {
140
+ 200: {
141
+ content: { 'application/json': { schema: analyticsDevicesResponseSchema } },
142
+ description: 'Device breakdown',
143
+ },
144
+ 401: {
145
+ content: { 'application/json': { schema: errorResponseSchema } },
146
+ description: 'Unauthorized',
147
+ },
148
+ },
149
+ }),
150
+ (c) => c.json({} as never)
151
+ )
152
+
153
+ // GET /api/analytics/campaigns
154
+ app.openapi(
155
+ createRoute({
156
+ method: 'get',
157
+ path: '/api/analytics/campaigns',
158
+ summary: 'Get campaign breakdown',
159
+ description: 'Returns UTM campaign performance data.',
160
+ operationId: 'getAnalyticsCampaigns',
161
+ tags: ['analytics'],
162
+ request: { query: analyticsQuerySchema },
163
+ responses: {
164
+ 200: {
165
+ content: { 'application/json': { schema: analyticsCampaignsResponseSchema } },
166
+ description: 'Campaign breakdown',
167
+ },
168
+ 401: {
169
+ content: { 'application/json': { schema: errorResponseSchema } },
170
+ description: 'Unauthorized',
171
+ },
172
+ },
173
+ }),
174
+ (c) => c.json({} as never)
175
+ )
176
+ }
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Copyright (c) 2025 ESSENTIAL SERVICES
3
+ *
4
+ * This source code is licensed under the Business Source License 1.1
5
+ * found in the LICENSE.md file in the root directory of this source tree.
6
+ */
7
+
8
+ import { type OpenAPIHono, createRoute } from '@hono/zod-openapi'
9
+ import { errorResponseSchema } from '@/domain/models/api/error'
10
+ import { tableIdParamSchema } from '@/domain/models/api/params'
11
+ import {
12
+ batchCreateRecordsRequestSchema,
13
+ batchDeleteRecordsRequestSchema,
14
+ batchRestoreRecordsRequestSchema,
15
+ batchUpdateRecordsRequestSchema,
16
+ upsertRecordsRequestSchema,
17
+ } from '@/domain/models/api/request'
18
+ import {
19
+ batchCreateRecordsResponseSchema,
20
+ batchDeleteRecordsResponseSchema,
21
+ batchRestoreRecordsResponseSchema,
22
+ batchUpdateRecordsResponseSchema,
23
+ upsertRecordsResponseSchema,
24
+ } from '@/domain/models/api/tables'
25
+
26
+ /**
27
+ * Register batch operation routes for OpenAPI schema generation
28
+ */
29
+ export function registerBatchRoutes(app: OpenAPIHono): void {
30
+ // POST /api/tables/{tableId}/records/batch (create)
31
+ app.openapi(
32
+ createRoute({
33
+ method: 'post',
34
+ path: '/api/tables/{tableId}/records/batch',
35
+ summary: 'Batch create records',
36
+ description: 'Creates multiple records in a single request (up to 1000).',
37
+ operationId: 'batchCreateRecords',
38
+ tags: ['records'],
39
+ request: {
40
+ params: tableIdParamSchema,
41
+ body: {
42
+ content: { 'application/json': { schema: batchCreateRecordsRequestSchema } },
43
+ },
44
+ },
45
+ responses: {
46
+ 201: {
47
+ content: { 'application/json': { schema: batchCreateRecordsResponseSchema } },
48
+ description: 'Records created',
49
+ },
50
+ 400: {
51
+ content: { 'application/json': { schema: errorResponseSchema } },
52
+ description: 'Validation error',
53
+ },
54
+ 401: {
55
+ content: { 'application/json': { schema: errorResponseSchema } },
56
+ description: 'Unauthorized',
57
+ },
58
+ 404: {
59
+ content: { 'application/json': { schema: errorResponseSchema } },
60
+ description: 'Table not found',
61
+ },
62
+ },
63
+ }),
64
+ (c) => c.json({} as never, 201)
65
+ )
66
+
67
+ // PATCH /api/tables/{tableId}/records/batch (update)
68
+ app.openapi(
69
+ createRoute({
70
+ method: 'patch',
71
+ path: '/api/tables/{tableId}/records/batch',
72
+ summary: 'Batch update records',
73
+ description: 'Updates multiple records in a single request (up to 100).',
74
+ operationId: 'batchUpdateRecords',
75
+ tags: ['records'],
76
+ request: {
77
+ params: tableIdParamSchema,
78
+ body: {
79
+ content: { 'application/json': { schema: batchUpdateRecordsRequestSchema } },
80
+ },
81
+ },
82
+ responses: {
83
+ 200: {
84
+ content: { 'application/json': { schema: batchUpdateRecordsResponseSchema } },
85
+ description: 'Records updated',
86
+ },
87
+ 400: {
88
+ content: { 'application/json': { schema: errorResponseSchema } },
89
+ description: 'Validation error',
90
+ },
91
+ 401: {
92
+ content: { 'application/json': { schema: errorResponseSchema } },
93
+ description: 'Unauthorized',
94
+ },
95
+ 404: {
96
+ content: { 'application/json': { schema: errorResponseSchema } },
97
+ description: 'Table not found',
98
+ },
99
+ },
100
+ }),
101
+ (c) => c.json({} as never)
102
+ )
103
+
104
+ // DELETE /api/tables/{tableId}/records/batch
105
+ app.openapi(
106
+ createRoute({
107
+ method: 'delete',
108
+ path: '/api/tables/{tableId}/records/batch',
109
+ summary: 'Batch delete records',
110
+ description: 'Soft-deletes multiple records in a single request (up to 100).',
111
+ operationId: 'batchDeleteRecords',
112
+ tags: ['records'],
113
+ request: {
114
+ params: tableIdParamSchema,
115
+ body: {
116
+ content: { 'application/json': { schema: batchDeleteRecordsRequestSchema } },
117
+ },
118
+ },
119
+ responses: {
120
+ 200: {
121
+ content: { 'application/json': { schema: batchDeleteRecordsResponseSchema } },
122
+ description: 'Records deleted',
123
+ },
124
+ 400: {
125
+ content: { 'application/json': { schema: errorResponseSchema } },
126
+ description: 'Validation error',
127
+ },
128
+ 401: {
129
+ content: { 'application/json': { schema: errorResponseSchema } },
130
+ description: 'Unauthorized',
131
+ },
132
+ 404: {
133
+ content: { 'application/json': { schema: errorResponseSchema } },
134
+ description: 'Table not found',
135
+ },
136
+ },
137
+ }),
138
+ (c) => c.json({} as never)
139
+ )
140
+
141
+ // POST /api/tables/{tableId}/records/batch/restore
142
+ app.openapi(
143
+ createRoute({
144
+ method: 'post',
145
+ path: '/api/tables/{tableId}/records/batch/restore',
146
+ summary: 'Batch restore deleted records',
147
+ description: 'Restores multiple soft-deleted records in a single request (up to 100).',
148
+ operationId: 'batchRestoreRecords',
149
+ tags: ['records'],
150
+ request: {
151
+ params: tableIdParamSchema,
152
+ body: {
153
+ content: { 'application/json': { schema: batchRestoreRecordsRequestSchema } },
154
+ },
155
+ },
156
+ responses: {
157
+ 200: {
158
+ content: { 'application/json': { schema: batchRestoreRecordsResponseSchema } },
159
+ description: 'Records restored',
160
+ },
161
+ 400: {
162
+ content: { 'application/json': { schema: errorResponseSchema } },
163
+ description: 'Validation error',
164
+ },
165
+ 401: {
166
+ content: { 'application/json': { schema: errorResponseSchema } },
167
+ description: 'Unauthorized',
168
+ },
169
+ 404: {
170
+ content: { 'application/json': { schema: errorResponseSchema } },
171
+ description: 'Table not found',
172
+ },
173
+ },
174
+ }),
175
+ (c) => c.json({} as never)
176
+ )
177
+
178
+ // POST /api/tables/{tableId}/records/upsert
179
+ app.openapi(
180
+ createRoute({
181
+ method: 'post',
182
+ path: '/api/tables/{tableId}/records/upsert',
183
+ summary: 'Upsert records',
184
+ description:
185
+ 'Creates or updates records based on merge fields. Existing records matching the merge fields are updated; new records are created.',
186
+ operationId: 'upsertRecords',
187
+ tags: ['records'],
188
+ request: {
189
+ params: tableIdParamSchema,
190
+ body: {
191
+ content: { 'application/json': { schema: upsertRecordsRequestSchema } },
192
+ },
193
+ },
194
+ responses: {
195
+ 200: {
196
+ content: { 'application/json': { schema: upsertRecordsResponseSchema } },
197
+ description: 'Records upserted',
198
+ },
199
+ 400: {
200
+ content: { 'application/json': { schema: errorResponseSchema } },
201
+ description: 'Validation error',
202
+ },
203
+ 401: {
204
+ content: { 'application/json': { schema: errorResponseSchema } },
205
+ description: 'Unauthorized',
206
+ },
207
+ 404: {
208
+ content: { 'application/json': { schema: errorResponseSchema } },
209
+ description: 'Table not found',
210
+ },
211
+ },
212
+ }),
213
+ (c) => c.json({} as never)
214
+ )
215
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Copyright (c) 2025 ESSENTIAL SERVICES
3
+ *
4
+ * This source code is licensed under the Business Source License 1.1
5
+ * found in the LICENSE.md file in the root directory of this source tree.
6
+ */
7
+
8
+ import { type OpenAPIHono, createRoute } from '@hono/zod-openapi'
9
+ import { healthResponseSchema } from '@/domain/models/api/health'
10
+
11
+ /**
12
+ * Register health check routes for OpenAPI schema generation
13
+ */
14
+ export function registerHealthRoutes(app: OpenAPIHono): void {
15
+ const healthRoute = createRoute({
16
+ method: 'get',
17
+ path: '/api/health',
18
+ summary: 'Health check endpoint',
19
+ description:
20
+ 'Returns server health status. Used by monitoring tools and E2E tests to verify server is running.',
21
+ operationId: 'healthCheck',
22
+ tags: ['infrastructure'],
23
+ responses: {
24
+ 200: {
25
+ content: { 'application/json': { schema: healthResponseSchema } },
26
+ description: 'Server is healthy',
27
+ },
28
+ },
29
+ })
30
+
31
+ app.openapi(healthRoute, (c) => {
32
+ return c.json({
33
+ status: 'ok' as const,
34
+ timestamp: new Date().toISOString(),
35
+ app: { name: 'Sovrium' },
36
+ })
37
+ })
38
+ }