sovrium 0.0.2 → 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,131 @@
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
+ // Comment Schemas
12
+ // ============================================================================
13
+
14
+ /**
15
+ * Comment user metadata schema
16
+ */
17
+ export const commentUserSchema = z.object({
18
+ id: z.string().describe('User identifier'),
19
+ name: z.string().describe('User display name'),
20
+ email: z.string().describe('User email address'),
21
+ image: z.string().nullable().optional().describe('User avatar URL'),
22
+ })
23
+
24
+ /**
25
+ * Comment response schema
26
+ */
27
+ export const commentSchema = z.object({
28
+ id: z.string().describe('Comment identifier'),
29
+ content: z.string().describe('Comment content'),
30
+ userId: z.string().describe('Author user ID'),
31
+ recordId: z.union([z.string(), z.number()]).describe('Parent record ID'),
32
+ tableId: z.union([z.string(), z.number()]).describe('Parent table ID'),
33
+ createdAt: z.string().describe('ISO 8601 creation timestamp'),
34
+ updatedAt: z.string().describe('ISO 8601 last update timestamp'),
35
+ user: commentUserSchema.describe('Comment author details'),
36
+ })
37
+
38
+ /**
39
+ * Comment pagination schema
40
+ */
41
+ export const commentPaginationSchema = z.object({
42
+ total: z.number().int().describe('Total count of comments'),
43
+ limit: z.number().int().describe('Items returned'),
44
+ offset: z.number().int().describe('Items skipped'),
45
+ hasMore: z.boolean().describe('Whether more results exist'),
46
+ })
47
+
48
+ /**
49
+ * List comments response schema
50
+ *
51
+ * GET /api/tables/:tableId/records/:recordId/comments
52
+ */
53
+ export const listCommentsResponseSchema = z.object({
54
+ comments: z.array(commentSchema).describe('List of comments'),
55
+ pagination: commentPaginationSchema.describe('Pagination metadata'),
56
+ })
57
+
58
+ /**
59
+ * Get single comment response schema
60
+ *
61
+ * GET /api/tables/:tableId/records/:recordId/comments/:commentId
62
+ */
63
+ export const getCommentResponseSchema = commentSchema.describe('Single comment details')
64
+
65
+ /**
66
+ * Create comment response schema
67
+ *
68
+ * POST /api/tables/:tableId/records/:recordId/comments
69
+ */
70
+ export const createCommentResponseSchema = z.object({
71
+ comment: commentSchema.describe('Created comment'),
72
+ })
73
+
74
+ /**
75
+ * Update comment response schema
76
+ *
77
+ * PATCH /api/tables/:tableId/records/:recordId/comments/:commentId
78
+ */
79
+ export const updateCommentResponseSchema = commentSchema.describe('Updated comment details')
80
+
81
+ // ============================================================================
82
+ // Record History Schemas
83
+ // ============================================================================
84
+
85
+ /**
86
+ * Record history entry schema
87
+ */
88
+ export const recordHistoryEntrySchema = z.object({
89
+ id: z.string().describe('Activity log identifier'),
90
+ userId: z.string().optional().describe('User who performed the action'),
91
+ action: z.enum(['create', 'update', 'delete', 'restore']).describe('Action type'),
92
+ tableName: z.string().describe('Name of the affected table'),
93
+ recordId: z.union([z.string(), z.number()]).describe('ID of the affected record'),
94
+ changes: z
95
+ .record(z.string(), z.unknown())
96
+ .nullable()
97
+ .describe('Field changes (null for delete/restore)'),
98
+ createdAt: z.string().describe('ISO 8601 timestamp'),
99
+ user: z
100
+ .object({
101
+ id: z.string().describe('User identifier'),
102
+ name: z.string().describe('User display name'),
103
+ email: z.string().describe('User email address'),
104
+ })
105
+ .nullable()
106
+ .describe('User details (null for system activities)'),
107
+ })
108
+
109
+ /**
110
+ * Get record history response schema
111
+ *
112
+ * GET /api/tables/:tableId/records/:recordId/history
113
+ */
114
+ export const getRecordHistoryResponseSchema = z.object({
115
+ history: z.array(recordHistoryEntrySchema).describe('List of history entries'),
116
+ pagination: z
117
+ .object({
118
+ total: z.number().int().describe('Total activity count'),
119
+ limit: z.number().int().describe('Items returned'),
120
+ offset: z.number().int().describe('Items skipped'),
121
+ })
122
+ .optional()
123
+ .describe('Pagination metadata'),
124
+ })
125
+
126
+ // ============================================================================
127
+ // TypeScript Types
128
+ // ============================================================================
129
+
130
+ export type Comment = z.infer<typeof commentSchema>
131
+ export type RecordHistoryEntry = z.infer<typeof recordHistoryEntrySchema>
@@ -20,6 +20,9 @@
20
20
  * - request: Request input validation
21
21
  */
22
22
 
23
+ // Activity log schemas
24
+ export * from './activity'
25
+
23
26
  // Analytics schemas
24
27
  export * from './analytics'
25
28
 
@@ -35,8 +38,14 @@ export * from './health'
35
38
  // Authentication schemas
36
39
  export * from './auth'
37
40
 
41
+ // Comment and history schemas
42
+ export * from './comments'
43
+
38
44
  // Table and record schemas
39
45
  export * from './tables'
40
46
 
47
+ // OpenAPI parameter schemas
48
+ export * from './params'
49
+
41
50
  // Request schemas (input validation)
42
51
  export * from './request'
@@ -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
+ }