@igstack/app-catalog-backend-core 0.1.1-alpha-20260304152616 → 0.3.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/index.d.ts +11318 -1052
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4930 -1466
- package/dist/index.js.map +1 -1
- package/package.json +10 -7
- package/prisma/schema.prisma +2 -2
- package/prisma.config.ts +13 -0
- package/src/db/client.ts +20 -2
- package/src/db/prisma.ts +3 -0
- package/src/db/syncAppCatalog.ts +1 -1
- package/src/db/tableSyncMagazine.ts +1 -1
- package/src/db/tableSyncPrismaAdapter.ts +2 -3
- package/src/generated/prisma/browser.ts +59 -0
- package/src/generated/prisma/client.ts +83 -0
- package/src/generated/prisma/commonInputTypes.ts +658 -0
- package/src/generated/prisma/enums.ts +26 -0
- package/src/generated/prisma/internal/class.ts +274 -0
- package/src/generated/prisma/internal/prismaNamespace.ts +1506 -0
- package/src/generated/prisma/internal/prismaNamespaceBrowser.ts +250 -0
- package/src/generated/prisma/models/DbAppForCatalog.ts +1490 -0
- package/src/generated/prisma/models/DbAppTagDefinition.ts +1199 -0
- package/src/generated/prisma/models/DbApprovalMethod.ts +1181 -0
- package/src/generated/prisma/models/DbAsset.ts +1394 -0
- package/src/generated/prisma/models/account.ts +1632 -0
- package/src/generated/prisma/models/session.ts +1447 -0
- package/src/generated/prisma/models/user.ts +1562 -0
- package/src/generated/prisma/models/verification.ts +1180 -0
- package/src/generated/prisma/models.ts +19 -0
- package/src/generated/prisma/pjtg.ts +183 -0
- package/src/index.ts +0 -27
- package/src/middleware/database.ts +13 -2
- package/src/middleware/featureRegistry.ts +0 -38
- package/src/modules/assets/upsertAsset.ts +1 -1
- package/src/server/controller.ts +0 -16
- package/src/modules/appCatalogAdmin/appCatalogAdminRouter.ts +0 -187
- package/src/modules/appCatalogAdmin/catalogBackupController.ts +0 -213
- package/src/modules/approvalMethod/approvalMethodRouter.ts +0 -169
- package/src/modules/approvalMethod/slugUtils.ts +0 -17
- package/src/modules/approvalMethod/syncApprovalMethods.ts +0 -38
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import type { Request, Response } from 'express'
|
|
2
|
-
import { getDbClient } from '../../db'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Export the complete app catalog as JSON
|
|
6
|
-
* Includes all fields from DbAppForCatalog and DbApprovalMethod
|
|
7
|
-
*/
|
|
8
|
-
export async function exportCatalog(
|
|
9
|
-
_req: Request,
|
|
10
|
-
res: Response,
|
|
11
|
-
): Promise<void> {
|
|
12
|
-
try {
|
|
13
|
-
const prisma = getDbClient()
|
|
14
|
-
|
|
15
|
-
// Fetch all catalog entries
|
|
16
|
-
const apps = await prisma.dbAppForCatalog.findMany({
|
|
17
|
-
orderBy: { slug: 'asc' },
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
// Fetch all approval methods
|
|
21
|
-
const approvalMethods = await prisma.dbApprovalMethod.findMany({
|
|
22
|
-
orderBy: { displayName: 'asc' },
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
res.json({
|
|
26
|
-
version: '2.0',
|
|
27
|
-
exportDate: new Date().toISOString(),
|
|
28
|
-
apps,
|
|
29
|
-
approvalMethods,
|
|
30
|
-
})
|
|
31
|
-
} catch (error) {
|
|
32
|
-
console.error('Error exporting catalog:', error)
|
|
33
|
-
res.status(500).json({ error: 'Failed to export catalog' })
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Import/restore the complete app catalog from JSON
|
|
39
|
-
* Overwrites existing data
|
|
40
|
-
*/
|
|
41
|
-
export async function importCatalog(
|
|
42
|
-
req: Request,
|
|
43
|
-
res: Response,
|
|
44
|
-
): Promise<void> {
|
|
45
|
-
try {
|
|
46
|
-
const prisma = getDbClient()
|
|
47
|
-
const { apps, approvalMethods } = req.body
|
|
48
|
-
|
|
49
|
-
if (!Array.isArray(apps)) {
|
|
50
|
-
res
|
|
51
|
-
.status(400)
|
|
52
|
-
.json({ error: 'Invalid data format: apps must be an array' })
|
|
53
|
-
return
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Use transaction to ensure atomicity
|
|
57
|
-
await prisma.$transaction(async (tx) => {
|
|
58
|
-
// Delete all existing approval methods first (due to potential FK references)
|
|
59
|
-
if (Array.isArray(approvalMethods)) {
|
|
60
|
-
await tx.dbApprovalMethod.deleteMany({})
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Delete all existing catalog entries
|
|
64
|
-
await tx.dbAppForCatalog.deleteMany({})
|
|
65
|
-
|
|
66
|
-
// Insert approval methods first
|
|
67
|
-
if (Array.isArray(approvalMethods)) {
|
|
68
|
-
for (const method of approvalMethods) {
|
|
69
|
-
// Remove id, createdAt, updatedAt to let Prisma generate new ones
|
|
70
|
-
const { id, createdAt, updatedAt, ...methodData } = method
|
|
71
|
-
|
|
72
|
-
await tx.dbApprovalMethod.create({
|
|
73
|
-
data: methodData,
|
|
74
|
-
})
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Insert app catalog entries
|
|
79
|
-
for (const app of apps) {
|
|
80
|
-
// Remove id, createdAt, updatedAt to let Prisma generate new ones
|
|
81
|
-
const { id, createdAt, updatedAt, ...appData } = app
|
|
82
|
-
|
|
83
|
-
await tx.dbAppForCatalog.create({
|
|
84
|
-
data: appData,
|
|
85
|
-
})
|
|
86
|
-
}
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
res.json({
|
|
90
|
-
success: true,
|
|
91
|
-
imported: {
|
|
92
|
-
apps: apps.length,
|
|
93
|
-
approvalMethods: Array.isArray(approvalMethods)
|
|
94
|
-
? approvalMethods.length
|
|
95
|
-
: 0,
|
|
96
|
-
},
|
|
97
|
-
})
|
|
98
|
-
} catch (error) {
|
|
99
|
-
console.error('Error importing catalog:', error)
|
|
100
|
-
res.status(500).json({ error: 'Failed to import catalog' })
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Export an asset (icon or screenshot) by name
|
|
106
|
-
*/
|
|
107
|
-
export async function exportAsset(req: Request, res: Response): Promise<void> {
|
|
108
|
-
try {
|
|
109
|
-
const { name } = req.params
|
|
110
|
-
const prisma = getDbClient()
|
|
111
|
-
|
|
112
|
-
const asset = await prisma.dbAsset.findUnique({
|
|
113
|
-
where: { name },
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
if (!asset) {
|
|
117
|
-
res.status(404).json({ error: 'Asset not found' })
|
|
118
|
-
return
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Set appropriate content type and send binary data
|
|
122
|
-
res.set('Content-Type', asset.mimeType)
|
|
123
|
-
res.set('Content-Disposition', `attachment; filename="${name}"`)
|
|
124
|
-
res.send(Buffer.from(asset.content))
|
|
125
|
-
} catch (error) {
|
|
126
|
-
console.error('Error exporting asset:', error)
|
|
127
|
-
res.status(500).json({ error: 'Failed to export asset' })
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* List all assets with metadata
|
|
133
|
-
*/
|
|
134
|
-
export async function listAssets(_req: Request, res: Response): Promise<void> {
|
|
135
|
-
try {
|
|
136
|
-
const prisma = getDbClient()
|
|
137
|
-
|
|
138
|
-
const assets = await prisma.dbAsset.findMany({
|
|
139
|
-
select: {
|
|
140
|
-
id: true,
|
|
141
|
-
name: true,
|
|
142
|
-
assetType: true,
|
|
143
|
-
mimeType: true,
|
|
144
|
-
fileSize: true,
|
|
145
|
-
width: true,
|
|
146
|
-
height: true,
|
|
147
|
-
checksum: true,
|
|
148
|
-
},
|
|
149
|
-
orderBy: { name: 'asc' },
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
res.json({ assets })
|
|
153
|
-
} catch (error) {
|
|
154
|
-
console.error('Error listing assets:', error)
|
|
155
|
-
res.status(500).json({ error: 'Failed to list assets' })
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Import an asset (icon or screenshot)
|
|
161
|
-
*/
|
|
162
|
-
export async function importAsset(req: Request, res: Response): Promise<void> {
|
|
163
|
-
try {
|
|
164
|
-
const file = req.file
|
|
165
|
-
const { name, assetType, mimeType, width, height } = req.body
|
|
166
|
-
|
|
167
|
-
if (!file) {
|
|
168
|
-
res.status(400).json({ error: 'No file uploaded' })
|
|
169
|
-
return
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const prisma = getDbClient()
|
|
173
|
-
const crypto = await import('node:crypto')
|
|
174
|
-
|
|
175
|
-
// Calculate checksum
|
|
176
|
-
const checksum = crypto
|
|
177
|
-
.createHash('sha256')
|
|
178
|
-
.update(file.buffer)
|
|
179
|
-
.digest('hex')
|
|
180
|
-
|
|
181
|
-
// Convert Buffer to Uint8Array for Prisma Bytes type
|
|
182
|
-
const content = new Uint8Array(file.buffer)
|
|
183
|
-
|
|
184
|
-
// Upsert asset (update if exists, create if not)
|
|
185
|
-
await prisma.dbAsset.upsert({
|
|
186
|
-
where: { name },
|
|
187
|
-
update: {
|
|
188
|
-
content,
|
|
189
|
-
checksum,
|
|
190
|
-
mimeType: mimeType || file.mimetype,
|
|
191
|
-
fileSize: file.size,
|
|
192
|
-
width: width ? parseInt(width) : null,
|
|
193
|
-
height: height ? parseInt(height) : null,
|
|
194
|
-
assetType: assetType || 'icon',
|
|
195
|
-
},
|
|
196
|
-
create: {
|
|
197
|
-
name,
|
|
198
|
-
content,
|
|
199
|
-
checksum,
|
|
200
|
-
mimeType: mimeType || file.mimetype,
|
|
201
|
-
fileSize: file.size,
|
|
202
|
-
width: width ? parseInt(width) : null,
|
|
203
|
-
height: height ? parseInt(height) : null,
|
|
204
|
-
assetType: assetType || 'icon',
|
|
205
|
-
},
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
res.json({ success: true, name, size: file.size })
|
|
209
|
-
} catch (error) {
|
|
210
|
-
console.error('Error importing asset:', error)
|
|
211
|
-
res.status(500).json({ error: 'Failed to import asset' })
|
|
212
|
-
}
|
|
213
|
-
}
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod'
|
|
2
|
-
import { Prisma } from '@prisma/client'
|
|
3
|
-
import { getDbClient } from '../../db'
|
|
4
|
-
import { adminProcedure, publicProcedure, router } from '../../server/trpcSetup'
|
|
5
|
-
import type {
|
|
6
|
-
ApprovalMethod,
|
|
7
|
-
ApprovalMethodConfig,
|
|
8
|
-
CustomConfig,
|
|
9
|
-
PersonTeamConfig,
|
|
10
|
-
ServiceConfig,
|
|
11
|
-
} from '../../types'
|
|
12
|
-
import { generateSlugFromDisplayName } from './slugUtils'
|
|
13
|
-
|
|
14
|
-
// Zod schemas
|
|
15
|
-
const ReachOutContactSchema = z.object({
|
|
16
|
-
displayName: z.string(),
|
|
17
|
-
contact: z.string(),
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
const ServiceConfigSchema = z.object({
|
|
21
|
-
url: z.url().optional(),
|
|
22
|
-
icon: z.string().optional(),
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
const PersonTeamConfigSchema = z.object({
|
|
26
|
-
reachOutContacts: z.array(ReachOutContactSchema).optional(),
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
const CustomConfigSchema = z.object({})
|
|
30
|
-
|
|
31
|
-
const ApprovalMethodConfigSchema = z.union([
|
|
32
|
-
ServiceConfigSchema,
|
|
33
|
-
PersonTeamConfigSchema,
|
|
34
|
-
CustomConfigSchema,
|
|
35
|
-
])
|
|
36
|
-
|
|
37
|
-
const CreateApprovalMethodSchema = z.object({
|
|
38
|
-
type: z.enum(['service', 'personTeam', 'custom']),
|
|
39
|
-
displayName: z.string().min(1),
|
|
40
|
-
config: ApprovalMethodConfigSchema.optional(),
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
const UpdateApprovalMethodSchema = z.object({
|
|
44
|
-
slug: z.string(),
|
|
45
|
-
type: z.enum(['service', 'personTeam', 'custom']).optional(),
|
|
46
|
-
displayName: z.string().min(1).optional(),
|
|
47
|
-
config: ApprovalMethodConfigSchema.optional(),
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Convert Prisma DbApprovalMethod to our ApprovalMethod type.
|
|
52
|
-
* This ensures tRPC infers proper types for frontend consumers.
|
|
53
|
-
*/
|
|
54
|
-
function toApprovalMethod(db: {
|
|
55
|
-
slug: string
|
|
56
|
-
type: ApprovalMethod['type']
|
|
57
|
-
displayName: string
|
|
58
|
-
config: ApprovalMethodConfig | null
|
|
59
|
-
createdAt: Date
|
|
60
|
-
updatedAt: Date
|
|
61
|
-
}): ApprovalMethod {
|
|
62
|
-
// Handle discriminated union by explicitly narrowing based on type
|
|
63
|
-
const baseFields = {
|
|
64
|
-
slug: db.slug,
|
|
65
|
-
displayName: db.displayName,
|
|
66
|
-
createdAt: db.createdAt,
|
|
67
|
-
updatedAt: db.updatedAt,
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Provide default empty config if null, as ApprovalMethod discriminated union requires config
|
|
71
|
-
const config = db.config ?? {}
|
|
72
|
-
|
|
73
|
-
switch (db.type) {
|
|
74
|
-
case 'service':
|
|
75
|
-
return { ...baseFields, type: 'service', config: config as ServiceConfig }
|
|
76
|
-
case 'personTeam':
|
|
77
|
-
return {
|
|
78
|
-
...baseFields,
|
|
79
|
-
type: 'personTeam',
|
|
80
|
-
config: config as PersonTeamConfig,
|
|
81
|
-
}
|
|
82
|
-
case 'custom':
|
|
83
|
-
return { ...baseFields, type: 'custom', config: config as CustomConfig }
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function createApprovalMethodRouter() {
|
|
88
|
-
return router({
|
|
89
|
-
// Public: list for selection in app admin
|
|
90
|
-
list: publicProcedure.query(async (): Promise<Array<ApprovalMethod>> => {
|
|
91
|
-
const prisma = getDbClient()
|
|
92
|
-
const results = await prisma.dbApprovalMethod.findMany({
|
|
93
|
-
orderBy: { displayName: 'asc' },
|
|
94
|
-
})
|
|
95
|
-
return results.map(toApprovalMethod)
|
|
96
|
-
}),
|
|
97
|
-
|
|
98
|
-
// Public: get by ID
|
|
99
|
-
getById: publicProcedure
|
|
100
|
-
.input(z.object({ slug: z.string() }))
|
|
101
|
-
.query(async ({ input }): Promise<ApprovalMethod | null> => {
|
|
102
|
-
const prisma = getDbClient()
|
|
103
|
-
const result = await prisma.dbApprovalMethod.findUnique({
|
|
104
|
-
where: { slug: input.slug },
|
|
105
|
-
})
|
|
106
|
-
return result ? toApprovalMethod(result) : null
|
|
107
|
-
}),
|
|
108
|
-
|
|
109
|
-
// Admin: create
|
|
110
|
-
create: adminProcedure
|
|
111
|
-
.input(CreateApprovalMethodSchema)
|
|
112
|
-
.mutation(async ({ input }): Promise<ApprovalMethod> => {
|
|
113
|
-
const prisma = getDbClient()
|
|
114
|
-
const result = await prisma.dbApprovalMethod.create({
|
|
115
|
-
data: {
|
|
116
|
-
slug: generateSlugFromDisplayName(input.displayName),
|
|
117
|
-
type: input.type,
|
|
118
|
-
displayName: input.displayName,
|
|
119
|
-
config: input.config ?? Prisma.JsonNull,
|
|
120
|
-
},
|
|
121
|
-
})
|
|
122
|
-
return toApprovalMethod(result)
|
|
123
|
-
}),
|
|
124
|
-
|
|
125
|
-
// Admin: update
|
|
126
|
-
update: adminProcedure
|
|
127
|
-
.input(UpdateApprovalMethodSchema)
|
|
128
|
-
.mutation(async ({ input }): Promise<ApprovalMethod> => {
|
|
129
|
-
const prisma = getDbClient()
|
|
130
|
-
const { slug, ...updateData } = input
|
|
131
|
-
const result = await prisma.dbApprovalMethod.update({
|
|
132
|
-
where: { slug },
|
|
133
|
-
data: {
|
|
134
|
-
...(updateData.type !== undefined && { type: updateData.type }),
|
|
135
|
-
...(updateData.displayName !== undefined && {
|
|
136
|
-
displayName: updateData.displayName,
|
|
137
|
-
}),
|
|
138
|
-
...(updateData.config !== undefined && {
|
|
139
|
-
config: updateData.config ?? Prisma.JsonNull,
|
|
140
|
-
}),
|
|
141
|
-
},
|
|
142
|
-
})
|
|
143
|
-
return toApprovalMethod(result)
|
|
144
|
-
}),
|
|
145
|
-
|
|
146
|
-
// Admin: delete
|
|
147
|
-
delete: adminProcedure
|
|
148
|
-
.input(z.object({ slug: z.string() }))
|
|
149
|
-
.mutation(async ({ input }): Promise<ApprovalMethod> => {
|
|
150
|
-
const prisma = getDbClient()
|
|
151
|
-
const result = await prisma.dbApprovalMethod.delete({
|
|
152
|
-
where: { slug: input.slug },
|
|
153
|
-
})
|
|
154
|
-
return toApprovalMethod(result)
|
|
155
|
-
}),
|
|
156
|
-
|
|
157
|
-
// Admin: search by type
|
|
158
|
-
listByType: publicProcedure
|
|
159
|
-
.input(z.object({ type: z.enum(['service', 'personTeam', 'custom']) }))
|
|
160
|
-
.query(async ({ input }): Promise<Array<ApprovalMethod>> => {
|
|
161
|
-
const prisma = getDbClient()
|
|
162
|
-
const results = await prisma.dbApprovalMethod.findMany({
|
|
163
|
-
where: { type: input.type },
|
|
164
|
-
orderBy: { displayName: 'asc' },
|
|
165
|
-
})
|
|
166
|
-
return results.map(toApprovalMethod)
|
|
167
|
-
}),
|
|
168
|
-
})
|
|
169
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generates a URL-friendly slug from a display name.
|
|
3
|
-
* Converts to lowercase and replaces non-alphanumeric characters with hyphens.
|
|
4
|
-
*
|
|
5
|
-
* @param displayName - The display name to convert
|
|
6
|
-
* @returns A slug suitable for use as a primary key
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* generateSlugFromDisplayName("My Service") // "my-service"
|
|
10
|
-
* generateSlugFromDisplayName("John's Team") // "john-s-team"
|
|
11
|
-
*/
|
|
12
|
-
export function generateSlugFromDisplayName(displayName: string): string {
|
|
13
|
-
return displayName
|
|
14
|
-
.toLowerCase()
|
|
15
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
16
|
-
.replace(/^-+|-+$/g, '')
|
|
17
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { PrismaClient as CorePrismaClient } from '@prisma/client'
|
|
2
|
-
|
|
3
|
-
export interface ApprovalMethodSyncInput {
|
|
4
|
-
slug: string
|
|
5
|
-
type: 'service' | 'personTeam' | 'custom'
|
|
6
|
-
displayName: string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Syncs approval methods to the database using upsert logic based on type + displayName.
|
|
11
|
-
*
|
|
12
|
-
* @param prisma - The PrismaClient instance from the backend-core database
|
|
13
|
-
* @param methods - Array of approval methods to sync
|
|
14
|
-
*/
|
|
15
|
-
export async function syncApprovalMethods(
|
|
16
|
-
prisma: CorePrismaClient,
|
|
17
|
-
methods: Array<ApprovalMethodSyncInput>,
|
|
18
|
-
): Promise<void> {
|
|
19
|
-
// Use transaction for atomicity
|
|
20
|
-
await prisma.$transaction(
|
|
21
|
-
methods.map((method) =>
|
|
22
|
-
prisma.dbApprovalMethod.upsert({
|
|
23
|
-
where: {
|
|
24
|
-
slug: method.slug,
|
|
25
|
-
},
|
|
26
|
-
update: {
|
|
27
|
-
displayName: method.displayName,
|
|
28
|
-
type: method.type,
|
|
29
|
-
},
|
|
30
|
-
create: {
|
|
31
|
-
slug: method.slug,
|
|
32
|
-
type: method.type,
|
|
33
|
-
displayName: method.displayName,
|
|
34
|
-
},
|
|
35
|
-
}),
|
|
36
|
-
),
|
|
37
|
-
)
|
|
38
|
-
}
|