@env-hopper/backend-core 2.0.1-alpha → 2.0.1-alpha.1
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/index.d.ts +1584 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1806 -0
- package/dist/index.js.map +1 -0
- package/package.json +26 -11
- package/prisma/migrations/20250526183023_init/migration.sql +71 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +121 -0
- package/src/db/client.ts +34 -0
- package/src/db/index.ts +17 -0
- package/src/db/syncAppCatalog.ts +67 -0
- package/src/db/tableSyncMagazine.ts +22 -0
- package/src/db/tableSyncPrismaAdapter.ts +202 -0
- package/src/index.ts +96 -3
- package/src/modules/admin/chat/createAdminChatHandler.ts +152 -0
- package/src/modules/admin/chat/createDatabaseTools.ts +261 -0
- package/src/modules/appCatalog/service.ts +79 -0
- package/src/modules/appCatalogAdmin/appCatalogAdminRouter.ts +113 -0
- package/src/modules/assets/assetRestController.ts +309 -0
- package/src/modules/assets/assetUtils.ts +81 -0
- package/src/modules/assets/screenshotRestController.ts +195 -0
- package/src/modules/assets/screenshotRouter.ts +116 -0
- package/src/modules/assets/syncAssets.ts +261 -0
- package/src/modules/auth/auth.ts +51 -0
- package/src/modules/auth/authProviders.ts +108 -0
- package/src/modules/auth/authRouter.ts +77 -0
- package/src/modules/auth/authorizationUtils.ts +114 -0
- package/src/modules/auth/registerAuthRoutes.ts +33 -0
- package/src/modules/icons/iconRestController.ts +190 -0
- package/src/modules/icons/iconRouter.ts +157 -0
- package/src/modules/icons/iconService.ts +73 -0
- package/src/server/controller.ts +102 -29
- package/src/server/ehStaticControllerContract.ts +8 -1
- package/src/server/ehTrpcContext.ts +0 -6
- package/src/types/backend/api.ts +1 -14
- package/src/types/backend/companySpecificBackend.ts +17 -0
- package/src/types/common/appCatalogTypes.ts +167 -0
- package/src/types/common/dataRootTypes.ts +72 -10
- package/src/types/index.ts +2 -0
- package/dist/esm/__tests__/dummy.test.d.ts +0 -1
- package/dist/esm/index.d.ts +0 -7
- package/dist/esm/index.js +0 -9
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/server/controller.d.ts +0 -32
- package/dist/esm/server/controller.js +0 -35
- package/dist/esm/server/controller.js.map +0 -1
- package/dist/esm/server/db.d.ts +0 -2
- package/dist/esm/server/ehStaticControllerContract.d.ts +0 -9
- package/dist/esm/server/ehStaticControllerContract.js +0 -12
- package/dist/esm/server/ehStaticControllerContract.js.map +0 -1
- package/dist/esm/server/ehTrpcContext.d.ts +0 -8
- package/dist/esm/server/ehTrpcContext.js +0 -11
- package/dist/esm/server/ehTrpcContext.js.map +0 -1
- package/dist/esm/types/backend/api.d.ts +0 -71
- package/dist/esm/types/backend/common.d.ts +0 -9
- package/dist/esm/types/backend/dataSources.d.ts +0 -20
- package/dist/esm/types/backend/deployments.d.ts +0 -34
- package/dist/esm/types/common/app/appTypes.d.ts +0 -12
- package/dist/esm/types/common/app/ui/appUiTypes.d.ts +0 -10
- package/dist/esm/types/common/appCatalogTypes.d.ts +0 -16
- package/dist/esm/types/common/dataRootTypes.d.ts +0 -32
- package/dist/esm/types/common/env/envTypes.d.ts +0 -6
- package/dist/esm/types/common/resourceTypes.d.ts +0 -8
- package/dist/esm/types/common/sharedTypes.d.ts +0 -4
- package/dist/esm/types/index.d.ts +0 -11
- package/src/server/db.ts +0 -4
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import type { Request, Response, Router } from 'express'
|
|
2
|
+
import multer from 'multer'
|
|
3
|
+
import { createHash } from 'node:crypto'
|
|
4
|
+
import { getDbClient } from '../../db'
|
|
5
|
+
|
|
6
|
+
// Configure multer for memory storage
|
|
7
|
+
const upload = multer({
|
|
8
|
+
storage: multer.memoryStorage(),
|
|
9
|
+
limits: {
|
|
10
|
+
fileSize: 10 * 1024 * 1024, // 10MB limit
|
|
11
|
+
},
|
|
12
|
+
fileFilter: (_req, file, cb) => {
|
|
13
|
+
// Accept images only
|
|
14
|
+
if (!file.mimetype.startsWith('image/')) {
|
|
15
|
+
cb(new Error('Only image files are allowed'))
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
cb(null, true)
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export interface IconRestControllerConfig {
|
|
23
|
+
/**
|
|
24
|
+
* Base path for icon endpoints (e.g., '/api/icons')
|
|
25
|
+
*/
|
|
26
|
+
basePath: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Registers REST endpoints for icon upload and retrieval
|
|
31
|
+
*
|
|
32
|
+
* Endpoints:
|
|
33
|
+
* - POST {basePath}/upload - Upload a new icon (multipart/form-data with 'icon' field and 'name' field)
|
|
34
|
+
* - GET {basePath}/:id - Get icon binary by ID
|
|
35
|
+
* - GET {basePath}/:id/metadata - Get icon metadata only
|
|
36
|
+
*/
|
|
37
|
+
export function registerIconRestController(
|
|
38
|
+
router: Router,
|
|
39
|
+
config: IconRestControllerConfig,
|
|
40
|
+
): void {
|
|
41
|
+
const { basePath } = config
|
|
42
|
+
|
|
43
|
+
// Upload endpoint - accepts multipart/form-data
|
|
44
|
+
router.post(
|
|
45
|
+
`${basePath}/upload`,
|
|
46
|
+
upload.single('icon'),
|
|
47
|
+
async (req: Request, res: Response) => {
|
|
48
|
+
try {
|
|
49
|
+
if (!req.file) {
|
|
50
|
+
res.status(400).json({ error: 'No file uploaded' })
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const name = req.body['name'] as string
|
|
55
|
+
if (!name) {
|
|
56
|
+
res.status(400).json({ error: 'Name is required' })
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const prisma = getDbClient()
|
|
61
|
+
const checksum = createHash('sha256')
|
|
62
|
+
.update(req.file.buffer)
|
|
63
|
+
.digest('hex')
|
|
64
|
+
const icon = await prisma.dbAsset.create({
|
|
65
|
+
data: {
|
|
66
|
+
name,
|
|
67
|
+
assetType: 'icon',
|
|
68
|
+
content: new Uint8Array(req.file.buffer),
|
|
69
|
+
mimeType: req.file.mimetype,
|
|
70
|
+
fileSize: req.file.size,
|
|
71
|
+
checksum,
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
res.status(201).json({
|
|
76
|
+
id: icon.id,
|
|
77
|
+
name: icon.name,
|
|
78
|
+
mimeType: icon.mimeType,
|
|
79
|
+
fileSize: icon.fileSize,
|
|
80
|
+
createdAt: icon.createdAt,
|
|
81
|
+
})
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error('Error uploading icon:', error)
|
|
84
|
+
res.status(500).json({ error: 'Failed to upload icon' })
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
// Get icon binary by ID
|
|
90
|
+
router.get(`${basePath}/:id`, async (req: Request, res: Response) => {
|
|
91
|
+
try {
|
|
92
|
+
const { id } = req.params
|
|
93
|
+
|
|
94
|
+
const prisma = getDbClient()
|
|
95
|
+
const icon = await prisma.dbAsset.findUnique({
|
|
96
|
+
where: { id },
|
|
97
|
+
select: {
|
|
98
|
+
content: true,
|
|
99
|
+
mimeType: true,
|
|
100
|
+
name: true,
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
if (!icon) {
|
|
105
|
+
res.status(404).json({ error: 'Icon not found' })
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Set appropriate headers
|
|
110
|
+
res.setHeader('Content-Type', icon.mimeType)
|
|
111
|
+
res.setHeader('Content-Disposition', `inline; filename="${icon.name}"`)
|
|
112
|
+
res.setHeader('Cache-Control', 'public, max-age=86400') // Cache for 1 day
|
|
113
|
+
|
|
114
|
+
// Send binary content
|
|
115
|
+
res.send(icon.content)
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error('Error fetching icon:', error)
|
|
118
|
+
res.status(500).json({ error: 'Failed to fetch icon' })
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// Get icon metadata only (no binary content)
|
|
123
|
+
router.get(
|
|
124
|
+
`${basePath}/:id/metadata`,
|
|
125
|
+
async (req: Request, res: Response) => {
|
|
126
|
+
try {
|
|
127
|
+
const { id } = req.params
|
|
128
|
+
|
|
129
|
+
const prisma = getDbClient()
|
|
130
|
+
const icon = await prisma.dbAsset.findUnique({
|
|
131
|
+
where: { id },
|
|
132
|
+
select: {
|
|
133
|
+
id: true,
|
|
134
|
+
name: true,
|
|
135
|
+
mimeType: true,
|
|
136
|
+
fileSize: true,
|
|
137
|
+
createdAt: true,
|
|
138
|
+
updatedAt: true,
|
|
139
|
+
},
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
if (!icon) {
|
|
143
|
+
res.status(404).json({ error: 'Icon not found' })
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
res.json(icon)
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error('Error fetching icon metadata:', error)
|
|
150
|
+
res.status(500).json({ error: 'Failed to fetch icon metadata' })
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
// Get icon binary by name
|
|
156
|
+
router.get(
|
|
157
|
+
`${basePath}/by-name/:name`,
|
|
158
|
+
async (req: Request, res: Response) => {
|
|
159
|
+
try {
|
|
160
|
+
const { name } = req.params
|
|
161
|
+
|
|
162
|
+
const prisma = getDbClient()
|
|
163
|
+
const icon = await prisma.dbAsset.findUnique({
|
|
164
|
+
where: { name },
|
|
165
|
+
select: {
|
|
166
|
+
content: true,
|
|
167
|
+
mimeType: true,
|
|
168
|
+
name: true,
|
|
169
|
+
},
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
if (!icon) {
|
|
173
|
+
res.status(404).json({ error: 'Icon not found' })
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Set appropriate headers
|
|
178
|
+
res.setHeader('Content-Type', icon.mimeType)
|
|
179
|
+
res.setHeader('Content-Disposition', `inline; filename="${icon.name}"`)
|
|
180
|
+
res.setHeader('Cache-Control', 'public, max-age=86400') // Cache for 1 day
|
|
181
|
+
|
|
182
|
+
// Send binary content
|
|
183
|
+
res.send(icon.content)
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.error('Error fetching icon by name:', error)
|
|
186
|
+
res.status(500).json({ error: 'Failed to fetch icon' })
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
)
|
|
190
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { TRPCRootObject } from '@trpc/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { getDbClient } from '../../db'
|
|
4
|
+
import type { EhTrpcContext } from '../../server/ehTrpcContext'
|
|
5
|
+
import { generateChecksum, getImageDimensions } from '../assets/assetUtils'
|
|
6
|
+
|
|
7
|
+
export function createIconRouter(t: TRPCRootObject<EhTrpcContext, {}, {}>) {
|
|
8
|
+
const router = t.router
|
|
9
|
+
const publicProcedure = t.procedure
|
|
10
|
+
|
|
11
|
+
return router({
|
|
12
|
+
list: publicProcedure.query(async () => {
|
|
13
|
+
const prisma = getDbClient()
|
|
14
|
+
return prisma.dbAsset.findMany({
|
|
15
|
+
where: { assetType: 'icon' },
|
|
16
|
+
select: {
|
|
17
|
+
id: true,
|
|
18
|
+
name: true,
|
|
19
|
+
mimeType: true,
|
|
20
|
+
fileSize: true,
|
|
21
|
+
createdAt: true,
|
|
22
|
+
updatedAt: true,
|
|
23
|
+
},
|
|
24
|
+
orderBy: { name: 'asc' },
|
|
25
|
+
})
|
|
26
|
+
}),
|
|
27
|
+
|
|
28
|
+
getOne: publicProcedure
|
|
29
|
+
.input(z.object({ id: z.string() }))
|
|
30
|
+
.query(async ({ input }) => {
|
|
31
|
+
const prisma = getDbClient()
|
|
32
|
+
return prisma.dbAsset.findFirst({
|
|
33
|
+
where: {
|
|
34
|
+
id: input.id,
|
|
35
|
+
assetType: 'icon',
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
}),
|
|
39
|
+
|
|
40
|
+
create: publicProcedure
|
|
41
|
+
.input(
|
|
42
|
+
z.object({
|
|
43
|
+
name: z.string().min(1),
|
|
44
|
+
content: z.string(), // base64 encoded binary
|
|
45
|
+
mimeType: z.string(),
|
|
46
|
+
fileSize: z.number().int().positive(),
|
|
47
|
+
}),
|
|
48
|
+
)
|
|
49
|
+
.mutation(async ({ input }) => {
|
|
50
|
+
const prisma = getDbClient()
|
|
51
|
+
// Convert base64 to Buffer
|
|
52
|
+
const buffer = Buffer.from(input.content, 'base64')
|
|
53
|
+
|
|
54
|
+
// Generate checksum and extract dimensions
|
|
55
|
+
const checksum = generateChecksum(buffer)
|
|
56
|
+
const { width, height } = await getImageDimensions(buffer)
|
|
57
|
+
|
|
58
|
+
// Check if asset with same checksum already exists
|
|
59
|
+
const existing = await prisma.dbAsset.findFirst({
|
|
60
|
+
where: { checksum, assetType: 'icon' },
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
if (existing) {
|
|
64
|
+
// Return existing asset if content is identical
|
|
65
|
+
return existing
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return prisma.dbAsset.create({
|
|
69
|
+
data: {
|
|
70
|
+
name: input.name,
|
|
71
|
+
assetType: 'icon',
|
|
72
|
+
content: new Uint8Array(buffer),
|
|
73
|
+
checksum,
|
|
74
|
+
mimeType: input.mimeType,
|
|
75
|
+
fileSize: input.fileSize,
|
|
76
|
+
width,
|
|
77
|
+
height,
|
|
78
|
+
},
|
|
79
|
+
})
|
|
80
|
+
}),
|
|
81
|
+
|
|
82
|
+
update: publicProcedure
|
|
83
|
+
.input(
|
|
84
|
+
z.object({
|
|
85
|
+
id: z.string(),
|
|
86
|
+
name: z.string().min(1).optional(),
|
|
87
|
+
content: z.string().optional(), // base64 encoded binary
|
|
88
|
+
mimeType: z.string().optional(),
|
|
89
|
+
fileSize: z.number().int().positive().optional(),
|
|
90
|
+
}),
|
|
91
|
+
)
|
|
92
|
+
.mutation(async ({ input }) => {
|
|
93
|
+
const prisma = getDbClient()
|
|
94
|
+
const { id, content, ...rest } = input
|
|
95
|
+
|
|
96
|
+
const data: Record<string, unknown> = { ...rest }
|
|
97
|
+
|
|
98
|
+
if (content) {
|
|
99
|
+
const buffer = Buffer.from(content, 'base64')
|
|
100
|
+
data.content = new Uint8Array(buffer)
|
|
101
|
+
data.checksum = generateChecksum(buffer)
|
|
102
|
+
|
|
103
|
+
const { width, height } = await getImageDimensions(buffer)
|
|
104
|
+
data.width = width
|
|
105
|
+
data.height = height
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return prisma.dbAsset.update({
|
|
109
|
+
where: { id },
|
|
110
|
+
data,
|
|
111
|
+
})
|
|
112
|
+
}),
|
|
113
|
+
|
|
114
|
+
delete: publicProcedure
|
|
115
|
+
.input(z.object({ id: z.string() }))
|
|
116
|
+
.mutation(async ({ input }) => {
|
|
117
|
+
const prisma = getDbClient()
|
|
118
|
+
return prisma.dbAsset.delete({
|
|
119
|
+
where: { id: input.id },
|
|
120
|
+
})
|
|
121
|
+
}),
|
|
122
|
+
|
|
123
|
+
deleteMany: publicProcedure
|
|
124
|
+
.input(z.object({ ids: z.array(z.string()) }))
|
|
125
|
+
.mutation(async ({ input }) => {
|
|
126
|
+
const prisma = getDbClient()
|
|
127
|
+
return prisma.dbAsset.deleteMany({
|
|
128
|
+
where: {
|
|
129
|
+
id: { in: input.ids },
|
|
130
|
+
assetType: 'icon',
|
|
131
|
+
},
|
|
132
|
+
})
|
|
133
|
+
}),
|
|
134
|
+
|
|
135
|
+
// Serve icon binary content
|
|
136
|
+
getContent: publicProcedure
|
|
137
|
+
.input(z.object({ id: z.string() }))
|
|
138
|
+
.query(async ({ input }) => {
|
|
139
|
+
const prisma = getDbClient()
|
|
140
|
+
const asset = await prisma.dbAsset.findFirst({
|
|
141
|
+
where: {
|
|
142
|
+
id: input.id,
|
|
143
|
+
assetType: 'icon',
|
|
144
|
+
},
|
|
145
|
+
select: { content: true, mimeType: true },
|
|
146
|
+
})
|
|
147
|
+
if (!asset) {
|
|
148
|
+
throw new Error('Icon not found')
|
|
149
|
+
}
|
|
150
|
+
// Return base64 encoded content
|
|
151
|
+
return {
|
|
152
|
+
content: Buffer.from(asset.content).toString('base64'),
|
|
153
|
+
mimeType: asset.mimeType,
|
|
154
|
+
}
|
|
155
|
+
}),
|
|
156
|
+
})
|
|
157
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { getDbClient } from '../../db'
|
|
2
|
+
import { generateChecksum, getImageDimensions } from '../assets/assetUtils'
|
|
3
|
+
|
|
4
|
+
export interface UpsertIconInput {
|
|
5
|
+
name: string
|
|
6
|
+
content: Buffer
|
|
7
|
+
mimeType: string
|
|
8
|
+
fileSize: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Upsert an icon to the database.
|
|
13
|
+
* If an icon with the same name exists, it will be updated.
|
|
14
|
+
* Otherwise, a new icon will be created.
|
|
15
|
+
*/
|
|
16
|
+
export async function upsertIcon(input: UpsertIconInput) {
|
|
17
|
+
const prisma = getDbClient()
|
|
18
|
+
|
|
19
|
+
const checksum = generateChecksum(input.content)
|
|
20
|
+
const { width, height } = await getImageDimensions(input.content)
|
|
21
|
+
|
|
22
|
+
return prisma.dbAsset.upsert({
|
|
23
|
+
where: { name: input.name },
|
|
24
|
+
update: {
|
|
25
|
+
content: new Uint8Array(input.content),
|
|
26
|
+
checksum,
|
|
27
|
+
mimeType: input.mimeType,
|
|
28
|
+
fileSize: input.fileSize,
|
|
29
|
+
width,
|
|
30
|
+
height,
|
|
31
|
+
},
|
|
32
|
+
create: {
|
|
33
|
+
name: input.name,
|
|
34
|
+
assetType: 'icon',
|
|
35
|
+
content: new Uint8Array(input.content),
|
|
36
|
+
checksum,
|
|
37
|
+
mimeType: input.mimeType,
|
|
38
|
+
fileSize: input.fileSize,
|
|
39
|
+
width,
|
|
40
|
+
height,
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Upsert multiple icons to the database.
|
|
47
|
+
* This is more efficient than calling upsertIcon multiple times.
|
|
48
|
+
*/
|
|
49
|
+
export async function upsertIcons(icons: Array<UpsertIconInput>) {
|
|
50
|
+
const results = []
|
|
51
|
+
for (const icon of icons) {
|
|
52
|
+
const result = await upsertIcon(icon)
|
|
53
|
+
results.push(result)
|
|
54
|
+
}
|
|
55
|
+
return results
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get an asset (icon or screenshot) by name from the database.
|
|
60
|
+
* Returns the asset content, mimeType, and name if found.
|
|
61
|
+
*/
|
|
62
|
+
export async function getAssetByName(name: string) {
|
|
63
|
+
const prisma = getDbClient()
|
|
64
|
+
|
|
65
|
+
return prisma.dbAsset.findUnique({
|
|
66
|
+
where: { name },
|
|
67
|
+
select: {
|
|
68
|
+
content: true,
|
|
69
|
+
mimeType: true,
|
|
70
|
+
name: true,
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
}
|
package/src/server/controller.ts
CHANGED
|
@@ -1,15 +1,37 @@
|
|
|
1
1
|
import { initTRPC } from '@trpc/server'
|
|
2
2
|
import z from 'zod'
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
import { getAppCatalogData } from '../modules/appCatalog/service'
|
|
5
|
+
import type { AppCatalogData, BootstrapConfigData, ResourceJumpsData } from '../types'
|
|
6
|
+
|
|
4
7
|
import type { TRPCRootObject } from '@trpc/server'
|
|
5
8
|
|
|
9
|
+
import { createAppCatalogAdminRouter } from '../modules/appCatalogAdmin/appCatalogAdminRouter.js'
|
|
10
|
+
import { createScreenshotRouter } from '../modules/assets/screenshotRouter.js'
|
|
11
|
+
import type { BetterAuth } from '../modules/auth/auth'
|
|
12
|
+
import { createAuthRouter } from '../modules/auth/authRouter.js'
|
|
13
|
+
import { createIconRouter } from '../modules/icons/iconRouter.js'
|
|
14
|
+
import type { EhTrpcContext } from './ehTrpcContext'
|
|
15
|
+
|
|
6
16
|
/**
|
|
7
17
|
* Initialization of tRPC backend
|
|
8
18
|
* Should be done only once per backend!
|
|
9
19
|
*/
|
|
10
20
|
const t: TRPCRootObject<EhTrpcContext, {}, {}> = initTRPC
|
|
11
21
|
.context<EhTrpcContext>()
|
|
12
|
-
.create(
|
|
22
|
+
.create({
|
|
23
|
+
errorFormatter({ error, shape }: { error: unknown; shape: unknown }) {
|
|
24
|
+
// Log all tRPC errors to console
|
|
25
|
+
console.error('[tRPC Error]', {
|
|
26
|
+
path: (shape as { data?: { path?: string } }).data?.path,
|
|
27
|
+
code: (error as { code?: string }).code,
|
|
28
|
+
message: (error as { message?: string }).message,
|
|
29
|
+
cause: (error as { cause?: unknown }).cause,
|
|
30
|
+
stack: (error as { stack?: string }).stack,
|
|
31
|
+
})
|
|
32
|
+
return shape
|
|
33
|
+
},
|
|
34
|
+
})
|
|
13
35
|
|
|
14
36
|
/**
|
|
15
37
|
* Export reusable router and procedure helpers
|
|
@@ -18,37 +40,88 @@ const t: TRPCRootObject<EhTrpcContext, {}, {}> = initTRPC
|
|
|
18
40
|
const router: typeof t.router = t.router
|
|
19
41
|
const publicProcedure: typeof t.procedure = t.procedure
|
|
20
42
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Create the main tRPC router with optional auth instance
|
|
45
|
+
* @param auth - Optional Better Auth instance for auth-related queries
|
|
46
|
+
*/
|
|
47
|
+
export function createTrpcRouter(auth?: BetterAuth) {
|
|
48
|
+
return router({
|
|
49
|
+
bootstrap: publicProcedure.query(
|
|
50
|
+
async ({ ctx }): Promise<BootstrapConfigData> => {
|
|
51
|
+
return await ctx.companySpecificBackend.getBootstrapData()
|
|
52
|
+
},
|
|
53
|
+
),
|
|
54
|
+
|
|
55
|
+
availabilityMatrix: publicProcedure.query(async ({ ctx }) => {
|
|
56
|
+
return await ctx.companySpecificBackend.getAvailabilityMatrix()
|
|
57
|
+
}),
|
|
58
|
+
|
|
59
|
+
tryFindRenameRule: publicProcedure
|
|
60
|
+
.input(
|
|
61
|
+
z.object({
|
|
62
|
+
envSlug: z.string().optional(),
|
|
63
|
+
resourceSlug: z.string().optional(),
|
|
64
|
+
}),
|
|
65
|
+
)
|
|
66
|
+
.query(async ({ input, ctx }) => {
|
|
67
|
+
return await ctx.companySpecificBackend.getNameMigrations(input)
|
|
68
|
+
}),
|
|
25
69
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
70
|
+
resourceJumps: publicProcedure.query(async ({ ctx }) => {
|
|
71
|
+
return await ctx.companySpecificBackend.getResourceJumps()
|
|
72
|
+
}),
|
|
29
73
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
74
|
+
resourceJumpsExtended: publicProcedure.query(async ({ ctx }) => {
|
|
75
|
+
return await ctx.companySpecificBackend.getResourceJumpsExtended()
|
|
76
|
+
}),
|
|
77
|
+
resourceJumpBySlugAndEnv: publicProcedure
|
|
78
|
+
.input(
|
|
79
|
+
z.object({
|
|
80
|
+
jumpResourceSlug: z.string(),
|
|
81
|
+
envSlug: z.string(),
|
|
82
|
+
}),
|
|
83
|
+
)
|
|
84
|
+
.query(async ({ input, ctx }) => {
|
|
85
|
+
return filterSingleResourceJump(
|
|
86
|
+
await ctx.companySpecificBackend.getResourceJumps(),
|
|
87
|
+
input.jumpResourceSlug,
|
|
88
|
+
input.envSlug,
|
|
89
|
+
)
|
|
35
90
|
}),
|
|
36
|
-
|
|
37
|
-
.query(async ({
|
|
38
|
-
return await ctx.companySpecificBackend.
|
|
91
|
+
|
|
92
|
+
appCatalog: publicProcedure.query(async ({ ctx }): Promise<AppCatalogData> => {
|
|
93
|
+
return await getAppCatalogData(ctx.companySpecificBackend.getApps)
|
|
39
94
|
}),
|
|
40
95
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
96
|
+
// Icon management routes
|
|
97
|
+
icon: createIconRouter(t),
|
|
98
|
+
|
|
99
|
+
// Screenshot management routes
|
|
100
|
+
screenshot: createScreenshotRouter(t),
|
|
101
|
+
|
|
102
|
+
// App catalog admin routes
|
|
103
|
+
appCatalogAdmin: createAppCatalogAdminRouter(t),
|
|
104
|
+
|
|
105
|
+
// Auth routes (requires auth instance)
|
|
106
|
+
auth: createAuthRouter(t, auth),
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function filterSingleResourceJump(
|
|
111
|
+
resourceJumps: ResourceJumpsData,
|
|
112
|
+
jumpResourceSlug: string,
|
|
113
|
+
envSlug: string,
|
|
114
|
+
): ResourceJumpsData {
|
|
115
|
+
const filteredResourceJump = resourceJumps.resourceJumps.find(
|
|
116
|
+
(item) => item.slug === jumpResourceSlug,
|
|
117
|
+
)
|
|
118
|
+
const filteredEnv = resourceJumps.envs.find((item) => item.slug === envSlug)
|
|
44
119
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
// }),
|
|
52
|
-
})
|
|
120
|
+
return {
|
|
121
|
+
resourceJumps: filteredResourceJump ? [filteredResourceJump] : [],
|
|
122
|
+
envs: filteredEnv ? [filteredEnv] : [],
|
|
123
|
+
lateResolvableParams: resourceJumps.lateResolvableParams,
|
|
124
|
+
}
|
|
125
|
+
}
|
|
53
126
|
|
|
54
|
-
export type TRPCRouter = typeof
|
|
127
|
+
export type TRPCRouter = ReturnType<typeof createTrpcRouter>
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export interface EhStaticControllerContract {
|
|
2
|
-
methods: {
|
|
2
|
+
methods: {
|
|
3
|
+
getIcon: { method: string; url: string }
|
|
4
|
+
getScreenshot: { method: string; url: string }
|
|
5
|
+
}
|
|
3
6
|
}
|
|
4
7
|
|
|
5
8
|
export const staticControllerContract: EhStaticControllerContract = {
|
|
@@ -8,5 +11,9 @@ export const staticControllerContract: EhStaticControllerContract = {
|
|
|
8
11
|
method: 'get',
|
|
9
12
|
url: 'icon/:icon',
|
|
10
13
|
},
|
|
14
|
+
getScreenshot: {
|
|
15
|
+
method: 'get',
|
|
16
|
+
url: 'screenshot/:id',
|
|
17
|
+
},
|
|
11
18
|
},
|
|
12
19
|
}
|
|
@@ -15,9 +15,3 @@ export function createEhTrpcContext({
|
|
|
15
15
|
companySpecificBackend,
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
// const createContext = ({
|
|
20
|
-
// req,
|
|
21
|
-
// res
|
|
22
|
-
// }: trpcExpress.CreateExpressContextOptions) => ({}); // no context
|
|
23
|
-
// type Context = Awaited<ReturnType<typeof createContext>>;
|
package/src/types/backend/api.ts
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AvailiabilityMatrixData,
|
|
3
|
-
BootstrapConfigData,
|
|
4
|
-
ResourceJumpsData,
|
|
5
|
-
} from '../common/dataRootTypes'
|
|
6
1
|
import type { EhAppIndexed } from '../common/app/appTypes'
|
|
7
2
|
import type {
|
|
8
3
|
EhAppPageIndexed,
|
|
@@ -37,8 +32,7 @@ export interface EhBackendAppUIBaseInput {
|
|
|
37
32
|
}
|
|
38
33
|
|
|
39
34
|
export interface EhBackendAppUIInput
|
|
40
|
-
extends EhBackendAppUIBaseInput,
|
|
41
|
-
EhAppUiIndexed {
|
|
35
|
+
extends EhBackendAppUIBaseInput, EhAppUiIndexed {
|
|
42
36
|
pages: Array<EhBackendPageInput>
|
|
43
37
|
}
|
|
44
38
|
|
|
@@ -88,10 +82,3 @@ export interface RenameRule {
|
|
|
88
82
|
oldSlug: string
|
|
89
83
|
targetSlug: string
|
|
90
84
|
}
|
|
91
|
-
|
|
92
|
-
export interface EhBackendCompanySpecificBackend {
|
|
93
|
-
getBootstrapData: () => Promise<BootstrapConfigData>
|
|
94
|
-
getAvailabilityMatrix: () => Promise<AvailiabilityMatrixData>
|
|
95
|
-
getNameMigrations: (params: RenameRuleParams) => Promise<RenameRule | false>
|
|
96
|
-
getResourceJumps: () => Promise<ResourceJumpsData>
|
|
97
|
-
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AppForCatalog } from '../common/appCatalogTypes'
|
|
2
|
+
import type {
|
|
3
|
+
AvailabilityMatrixData,
|
|
4
|
+
BootstrapConfigData,
|
|
5
|
+
ResourceJumpsData,
|
|
6
|
+
ResourceJumpsExtendedData,
|
|
7
|
+
} from '../common/dataRootTypes'
|
|
8
|
+
import type { RenameRule, RenameRuleParams } from './api'
|
|
9
|
+
|
|
10
|
+
export interface EhBackendCompanySpecificBackend {
|
|
11
|
+
getBootstrapData: () => Promise<BootstrapConfigData>
|
|
12
|
+
getAvailabilityMatrix: () => Promise<AvailabilityMatrixData>
|
|
13
|
+
getNameMigrations: (params: RenameRuleParams) => Promise<RenameRule | false>
|
|
14
|
+
getResourceJumps: () => Promise<ResourceJumpsData>
|
|
15
|
+
getResourceJumpsExtended: () => Promise<ResourceJumpsExtendedData>
|
|
16
|
+
getApps?: () => Promise<Array<AppForCatalog>>
|
|
17
|
+
}
|