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.
- package/README.md +67 -185
- package/package.json +26 -17
- package/src/application/use-cases/server/static-content-generators.ts +18 -2
- package/src/domain/models/api/activity.ts +87 -0
- package/src/domain/models/api/comments.ts +131 -0
- package/src/domain/models/api/index.ts +9 -0
- package/src/domain/models/api/params.ts +83 -0
- package/src/infrastructure/server/route-setup/openapi-routes/activity-routes.ts +72 -0
- package/src/infrastructure/server/route-setup/openapi-routes/analytics-routes.ts +176 -0
- package/src/infrastructure/server/route-setup/openapi-routes/batch-routes.ts +215 -0
- package/src/infrastructure/server/route-setup/openapi-routes/health-routes.ts +38 -0
- package/src/infrastructure/server/route-setup/openapi-routes/record-routes.ts +444 -0
- package/src/infrastructure/server/route-setup/openapi-routes/table-routes.ts +100 -0
- package/src/infrastructure/server/route-setup/openapi-routes/view-routes.ts +104 -0
- package/src/infrastructure/server/route-setup/openapi-schema.ts +40 -40
- package/src/presentation/styling/parse-style.ts +17 -9
- package/CHANGELOG.md +0 -3497
- package/schemas/0.0.1/app.openapi.json +0 -70
- package/schemas/0.0.1/app.schema.json +0 -7961
- package/schemas/0.0.2/app.openapi.json +0 -80
- package/schemas/0.0.2/app.schema.json +0 -8829
- package/schemas/development/app.openapi.json +0 -70
- package/schemas/development/app.schema.json +0 -7456
|
@@ -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
|
+
}
|