@igstack/app-catalog-backend-core 0.0.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/LICENSE +21 -0
- package/dist/index.d.ts +1934 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2539 -0
- package/dist/index.js.map +1 -0
- package/package.json +84 -0
- package/prisma/migrations/20250526183023_init/migration.sql +71 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +149 -0
- package/src/__tests__/dummy.test.ts +7 -0
- package/src/db/client.ts +42 -0
- package/src/db/index.ts +21 -0
- package/src/db/syncAppCatalog.ts +312 -0
- package/src/db/tableSyncMagazine.ts +32 -0
- package/src/db/tableSyncPrismaAdapter.ts +203 -0
- package/src/index.ts +126 -0
- package/src/middleware/backendResolver.ts +42 -0
- package/src/middleware/createEhMiddleware.ts +171 -0
- package/src/middleware/database.ts +62 -0
- package/src/middleware/featureRegistry.ts +173 -0
- package/src/middleware/index.ts +43 -0
- package/src/middleware/types.ts +202 -0
- package/src/modules/admin/chat/createAdminChatHandler.ts +152 -0
- package/src/modules/admin/chat/createDatabaseTools.ts +261 -0
- package/src/modules/appCatalog/service.ts +130 -0
- package/src/modules/appCatalogAdmin/appCatalogAdminRouter.ts +187 -0
- package/src/modules/appCatalogAdmin/catalogBackupController.ts +213 -0
- package/src/modules/approvalMethod/approvalMethodRouter.ts +169 -0
- package/src/modules/approvalMethod/slugUtils.ts +17 -0
- package/src/modules/approvalMethod/syncApprovalMethods.ts +38 -0
- package/src/modules/assets/assetRestController.ts +271 -0
- package/src/modules/assets/assetUtils.ts +114 -0
- package/src/modules/assets/screenshotRestController.ts +195 -0
- package/src/modules/assets/screenshotRouter.ts +112 -0
- package/src/modules/assets/syncAssets.ts +277 -0
- package/src/modules/assets/upsertAsset.ts +46 -0
- package/src/modules/auth/auth.ts +51 -0
- package/src/modules/auth/authProviders.ts +40 -0
- package/src/modules/auth/authRouter.ts +75 -0
- package/src/modules/auth/authorizationUtils.ts +132 -0
- package/src/modules/auth/devMockUserUtils.ts +49 -0
- package/src/modules/auth/registerAuthRoutes.ts +33 -0
- package/src/modules/icons/iconRestController.ts +171 -0
- package/src/modules/icons/iconRouter.ts +180 -0
- package/src/modules/icons/iconService.ts +73 -0
- package/src/modules/icons/iconUtils.ts +46 -0
- package/src/prisma-json-types.d.ts +34 -0
- package/src/server/controller.ts +47 -0
- package/src/server/ehStaticControllerContract.ts +19 -0
- package/src/server/ehTrpcContext.ts +26 -0
- package/src/server/trpcSetup.ts +89 -0
- package/src/types/backend/api.ts +73 -0
- package/src/types/backend/common.ts +10 -0
- package/src/types/backend/companySpecificBackend.ts +5 -0
- package/src/types/backend/dataSources.ts +25 -0
- package/src/types/backend/deployments.ts +40 -0
- package/src/types/common/app/appTypes.ts +13 -0
- package/src/types/common/app/ui/appUiTypes.ts +12 -0
- package/src/types/common/appCatalogTypes.ts +65 -0
- package/src/types/common/approvalMethodTypes.ts +149 -0
- package/src/types/common/env/envTypes.ts +7 -0
- package/src/types/common/resourceTypes.ts +8 -0
- package/src/types/common/sharedTypes.ts +5 -0
- package/src/types/index.ts +21 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["prismaClient: PrismaClient | null","duplicateKeys: Array<string>","results: Array<MakeTFromPrismaModel<TPrismaModelName>>","assetIds: Array<string>","error: unknown","updateData: {\n screenshotIds?: Array<string>\n iconName?: string | null\n }","group","router","t","publicProcedure","providers: Array<string>","data: Record<string, unknown>","staticControllerContract: EhStaticControllerContract","t","upload","results: Array<Awaited<ReturnType<typeof upsertIcon>>>","outBuffer: Uint8Array","buf: Buffer","content: Uint8Array | Buffer","width: number | null","height: number | null","FEATURES: Array<FeatureRegistration>","router","upload","userGroups: Array<string>","router","middlewareContext: MiddlewareContext"],"sources":["../src/db/client.ts","../src/modules/appCatalog/service.ts","../src/db/tableSyncPrismaAdapter.ts","../src/db/tableSyncMagazine.ts","../src/modules/assets/assetUtils.ts","../src/modules/assets/upsertAsset.ts","../src/db/syncAppCatalog.ts","../src/modules/auth/authorizationUtils.ts","../src/server/trpcSetup.ts","../src/modules/appCatalogAdmin/appCatalogAdminRouter.ts","../src/modules/approvalMethod/slugUtils.ts","../src/modules/approvalMethod/approvalMethodRouter.ts","../src/modules/assets/screenshotRouter.ts","../src/modules/auth/authRouter.ts","../src/modules/icons/iconUtils.ts","../src/modules/icons/iconRouter.ts","../src/server/controller.ts","../src/server/ehTrpcContext.ts","../src/server/ehStaticControllerContract.ts","../src/modules/auth/auth.ts","../src/modules/auth/registerAuthRoutes.ts","../src/modules/admin/chat/createAdminChatHandler.ts","../src/modules/admin/chat/createDatabaseTools.ts","../src/modules/icons/iconRestController.ts","../src/modules/icons/iconService.ts","../src/modules/assets/assetRestController.ts","../src/modules/assets/screenshotRestController.ts","../src/modules/assets/syncAssets.ts","../src/modules/approvalMethod/syncApprovalMethods.ts","../src/middleware/database.ts","../src/middleware/backendResolver.ts","../src/modules/appCatalogAdmin/catalogBackupController.ts","../src/modules/auth/devMockUserUtils.ts","../src/middleware/featureRegistry.ts","../src/middleware/createEhMiddleware.ts"],"sourcesContent":["import { PrismaClient } from '@prisma/client'\n\nlet prismaClient: PrismaClient | null = null\n\n/**\n * Gets the internal Prisma client instance.\n * Creates one if it doesn't exist.\n */\nexport function getDbClient(): PrismaClient {\n if (!prismaClient) {\n prismaClient = new PrismaClient()\n }\n return prismaClient\n}\n\n/**\n * Sets the internal Prisma client instance.\n * Used by middleware to bridge with existing getDbClient() usage.\n */\nexport function setDbClient(client: PrismaClient): void {\n prismaClient = client\n}\n\n/**\n * Connects to the database.\n * Call this before performing database operations.\n */\nexport async function connectDb(): Promise<void> {\n const client = getDbClient()\n await client.$connect()\n}\n\n/**\n * Disconnects from the database.\n * Call this when done with database operations (e.g., in scripts).\n */\nexport async function disconnectDb(): Promise<void> {\n if (prismaClient) {\n await prismaClient.$disconnect()\n prismaClient = null\n }\n}\n","import { getDbClient } from '../../db/client'\nimport type {\n AppApprovalMethod,\n AppCatalogData,\n AppCategory,\n AppForCatalog,\n GroupingTagDefinition,\n} from '../../types/common/appCatalogTypes'\nimport type {\n CustomConfig,\n PersonTeamConfig,\n ServiceConfig,\n} from '../../types/common/approvalMethodTypes'\nimport { omit } from 'radashi'\n\nfunction capitalize(word: string): string {\n if (!word) return word\n return word.charAt(0).toUpperCase() + word.slice(1)\n}\n\nexport async function getGroupingTagDefinitionsFromPrisma(): Promise<\n Array<GroupingTagDefinition>\n> {\n const prisma = getDbClient()\n\n // Fetch all apps\n const rows = await prisma.dbAppTagDefinition.findMany()\n return rows.map((row) => omit(row, ['id', 'updatedAt', 'createdAt']))\n}\n\nexport async function getApprovalMethodsFromPrisma(): Promise<\n Array<AppApprovalMethod>\n> {\n const prisma = getDbClient()\n\n // Fetch all apps\n const rows = await prisma.dbApprovalMethod.findMany()\n\n return rows.map((row) => {\n // Handle discriminated union by explicitly narrowing based on type\n const baseFields = {\n slug: row.slug,\n displayName: row.displayName,\n }\n\n // Provide default empty config if null, as AppApprovalMethod discriminated union requires config\n const config = row.config ?? {}\n\n switch (row.type) {\n case 'service':\n return {\n ...baseFields,\n type: 'service',\n config: config as ServiceConfig,\n }\n case 'personTeam':\n return {\n ...baseFields,\n type: 'personTeam',\n config: config as PersonTeamConfig,\n }\n case 'custom':\n return { ...baseFields, type: 'custom', config: config as CustomConfig }\n }\n })\n}\n\nexport async function getAppsFromPrisma(): Promise<Array<AppForCatalog>> {\n const prisma = getDbClient()\n\n // Fetch all apps\n const rows = await prisma.dbAppForCatalog.findMany()\n\n return rows.map((row) => {\n const accessRequest =\n row.accessRequest as unknown as AppForCatalog['accessRequest']\n const teams = (row.teams as unknown as Array<string> | null) ?? []\n const tags = (row.tags as unknown as AppForCatalog['tags']) ?? []\n const screenshotIds =\n (row.screenshotIds as unknown as AppForCatalog['screenshotIds']) ?? []\n const notes = row.notes == null ? undefined : row.notes\n const appUrl = row.appUrl == null ? undefined : row.appUrl\n const iconName = row.iconName == null ? undefined : row.iconName\n\n return {\n id: row.id,\n slug: row.slug,\n displayName: row.displayName,\n description: row.description,\n accessRequest,\n teams,\n notes,\n tags,\n appUrl,\n iconName,\n screenshotIds,\n }\n })\n}\n\nexport function deriveCategories(\n apps: Array<AppForCatalog>,\n): Array<AppCategory> {\n const tagSet = new Set<string>()\n for (const app of apps) {\n for (const tag of app.tags ?? []) {\n const normalized = tag.trim().toLowerCase()\n if (normalized) tagSet.add(normalized)\n }\n }\n const categories: Array<AppCategory> = [{ id: 'all', name: 'All' }]\n for (const tag of Array.from(tagSet).sort()) {\n categories.push({ id: tag, name: capitalize(tag) })\n }\n return categories\n}\n\nexport async function getAppCatalogData(\n getAppsOptional?: () => Promise<Array<AppForCatalog>>,\n): Promise<AppCatalogData> {\n const apps = getAppsOptional\n ? await getAppsOptional()\n : await getAppsFromPrisma()\n\n return {\n apps,\n tagsDefinitions: await getGroupingTagDefinitionsFromPrisma(),\n approvalMethods: await getApprovalMethodsFromPrisma(),\n }\n}\n","import { tableSync } from '@igstack/app-catalog-table-sync'\nimport type { Prisma, PrismaClient } from '@prisma/client'\nimport type * as runtime from '@prisma/client/runtime/library'\nimport { mapValues, omit, pick } from 'radashi'\n\nexport type ScalarKeys<TPrismaModelName extends Prisma.ModelName> =\n keyof Prisma.TypeMap['model'][TPrismaModelName]['payload']['scalars']\nexport type ObjectKeys<TPrismaModelName extends Prisma.ModelName> =\n keyof Prisma.TypeMap['model'][TPrismaModelName]['payload']['objects']\n\nexport type ScalarFilter<TPrismaModelName extends Prisma.ModelName> = Partial<\n Prisma.TypeMap['model'][TPrismaModelName]['payload']['scalars']\n>\n\nexport type GetOperationFns<TModel extends Prisma.ModelName> = {\n [TOperation in keyof Prisma.TypeMap['model']['DbAppForCatalog']['operations']]: (\n args: Prisma.TypeMap['model'][TModel]['operations'][TOperation]['args'],\n ) => Promise<\n Prisma.TypeMap['model'][TModel]['operations'][TOperation]['result']\n >\n}\n\nexport interface TableSyncParamsPrisma<\n TPrismaClient extends PrismaClient,\n TPrismaModelName extends Prisma.ModelName,\n TUniqColumns extends ReadonlyArray<ScalarKeys<TPrismaModelName>>,\n TRelationColumns extends ReadonlyArray<ObjectKeys<TPrismaModelName>>,\n> {\n id?: ScalarKeys<TPrismaModelName>\n prisma: TPrismaClient\n prismaModelName: TPrismaModelName\n uniqColumns: TUniqColumns\n relationColumns?: TRelationColumns\n where?: ScalarFilter<TPrismaModelName>\n upsertOnly?: boolean\n}\n\nfunction getPrismaModelOperations<\n TPrismaClient extends Omit<PrismaClient, runtime.ITXClientDenyList>,\n TPrismaModelName extends Prisma.ModelName,\n>(prisma: TPrismaClient, prismaModelName: TPrismaModelName) {\n const key = (prismaModelName.slice(0, 1).toLowerCase() +\n prismaModelName.slice(1)) as keyof TPrismaClient\n return prisma[key] as GetOperationFns<TPrismaModelName>\n}\n\nexport type MakeTFromPrismaModel<TPrismaModelName extends Prisma.ModelName> =\n NonNullable<\n Prisma.TypeMap['model'][TPrismaModelName]['operations']['findUnique']['result']\n >\n\nexport function tableSyncPrisma<\n TPrismaClient extends PrismaClient,\n TPrismaModelName extends Prisma.ModelName,\n TUniqColumns extends ReadonlyArray<ScalarKeys<TPrismaModelName>>,\n TRelationColumns extends ReadonlyArray<ObjectKeys<TPrismaModelName>>,\n TId extends ScalarKeys<TPrismaModelName> = ScalarKeys<TPrismaModelName>,\n>(\n params: TableSyncParamsPrisma<\n TPrismaClient,\n TPrismaModelName,\n TUniqColumns,\n TRelationColumns\n >,\n) {\n const {\n prisma,\n prismaModelName,\n uniqColumns,\n where: whereGlobal,\n upsertOnly,\n } = params\n const prismOperations = getPrismaModelOperations(prisma, prismaModelName)\n\n const idColumn = (params.id ?? 'id') as TId\n // @ts-ignore maybe someday later (never)\n return tableSync<MakeTFromPrismaModel<TPrismaModelName>, TUniqColumns, TId>({\n id: idColumn,\n uniqColumns,\n readAll: async () => {\n const findManyArgs = whereGlobal\n ? {\n where: whereGlobal,\n }\n : {}\n return (await prismOperations.findMany(findManyArgs)) as Array<\n MakeTFromPrismaModel<TPrismaModelName>\n >\n },\n writeAll: async (createData, update, deleteIds) => {\n const prismaUniqKey = params.uniqColumns.join('_')\n const relationColumnList =\n params.relationColumns ?? ([] as Array<ObjectKeys<TPrismaModelName>>)\n\n return prisma.$transaction(async (tx) => {\n const txOps = getPrismaModelOperations(tx, prismaModelName)\n for (const { data, where } of update) {\n const uniqKeyWhere =\n Object.keys(where).length > 1\n ? {\n [prismaUniqKey]: where,\n }\n : where\n\n const dataScalar = omit(data, relationColumnList)\n const dataRelations = mapValues(\n pick(data, relationColumnList),\n (value) => {\n return {\n set: value,\n }\n },\n )\n\n // @ts-expect-error This is too difficult for me to come up with right types\n await txOps.update({\n data: { ...dataScalar, ...dataRelations },\n where: { ...uniqKeyWhere },\n })\n }\n\n if (upsertOnly !== true) {\n await txOps.deleteMany({\n where: {\n [idColumn]: {\n in: deleteIds,\n },\n },\n })\n }\n\n // Validate uniqueness of uniqColumns before creating records\n const createDataMapped = createData.map((data) => {\n // @ts-expect-error This is too difficult for me to come up with right types\n const dataScalar = omit(data, relationColumnList)\n\n // @ts-expect-error This is too difficult for me to come up with right types\n const onlyRelationColumns = pick(data, relationColumnList)\n const dataRelations = mapValues(onlyRelationColumns, (value) => {\n return {\n connect: value,\n }\n })\n\n return { ...dataScalar, ...dataRelations }\n })\n\n // Check for duplicates in the data to be created\n if (createDataMapped.length > 0) {\n const uniqKeysInCreate = new Set<string>()\n const duplicateKeys: Array<string> = []\n\n for (const data of createDataMapped) {\n const keyParts = params.uniqColumns.map((col) => {\n const value = data[col as keyof typeof data]\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- defensive: unique columns may be nullable in schemas\n return value === null || value === undefined\n ? 'null'\n : String(value)\n })\n const key = keyParts.join(':')\n\n if (uniqKeysInCreate.has(key)) {\n duplicateKeys.push(key)\n } else {\n uniqKeysInCreate.add(key)\n }\n }\n\n if (duplicateKeys.length > 0) {\n const uniqColumnsStr = params.uniqColumns.join(', ')\n throw new Error(\n `Duplicate unique key values found in data to be created. ` +\n `Model: ${prismaModelName}, Unique columns: [${uniqColumnsStr}], ` +\n `Duplicate keys: [${duplicateKeys.join(', ')}]`,\n )\n }\n }\n\n const results: Array<MakeTFromPrismaModel<TPrismaModelName>> = []\n\n if (relationColumnList.length === 0) {\n // @ts-expect-error This is too difficult for me to come up with right types\n const batchResult = await txOps.createManyAndReturn({\n data: createDataMapped,\n })\n\n results.push(...batchResult)\n } else {\n for (const dataMappedElement of createDataMapped) {\n // @ts-expect-error too difficult for me\n const newVar = await txOps.create({\n data: dataMappedElement,\n })\n results.push(newVar as MakeTFromPrismaModel<TPrismaModelName>)\n }\n }\n\n return results\n })\n },\n })\n}\n","import type { Prisma } from '@prisma/client'\nimport type { ObjectKeys, ScalarKeys } from './tableSyncPrismaAdapter'\n\ninterface CommonSyncTableInfo<TPrismaModelName extends Prisma.ModelName> {\n prismaModelName: TPrismaModelName\n id?: ScalarKeys<TPrismaModelName>\n uniqColumns: Array<ScalarKeys<TPrismaModelName>>\n relationColumns?: Array<ObjectKeys<TPrismaModelName>>\n}\n\ntype TableSyncMagazineType = Partial<{\n [key in Prisma.ModelName]: CommonSyncTableInfo<key>\n}>\n\nexport const TABLE_SYNC_MAGAZINE = {\n DbAppForCatalog: {\n prismaModelName: 'DbAppForCatalog',\n uniqColumns: ['slug'],\n },\n DbAppTagDefinition: {\n prismaModelName: 'DbAppTagDefinition',\n uniqColumns: ['prefix'],\n },\n DbApprovalMethod: {\n id: 'slug',\n prismaModelName: 'DbApprovalMethod',\n uniqColumns: ['slug'],\n },\n} as const satisfies TableSyncMagazineType\n\nexport type TableSyncMagazine = typeof TABLE_SYNC_MAGAZINE\nexport type TableSyncMagazineModelNameKey = keyof TableSyncMagazine\n","import { createHash } from 'node:crypto'\nimport type { FormatEnum } from 'sharp'\nimport sharp from 'sharp'\nimport type { ParseAssetParams, ParseAssetReturn } from './assetRestController'\n\n/**\n * Extract image dimensions from a buffer using sharp\n */\nexport async function getImageDimensions(\n buffer: Buffer,\n): Promise<{ width?: number; height?: number }> {\n try {\n const metadata = await sharp(buffer).metadata()\n return {\n width: metadata.width,\n height: metadata.height,\n }\n } catch (error) {\n console.error('Error extracting image dimensions:', error)\n return { width: undefined, height: undefined }\n }\n}\n\n/**\n * Resize an image buffer to the specified dimensions\n * @param buffer - The image buffer to resize\n * @param width - Target width (optional)\n * @param height - Target height (optional)\n * @param format - Output format ('png', 'jpeg', 'webp'), auto-detected if not provided\n */\nexport async function resizeImage(\n buffer: Buffer,\n width?: number,\n height?: number,\n format?: 'png' | 'jpeg' | 'webp',\n): Promise<Buffer> {\n let pipeline = sharp(buffer)\n\n // Apply resize if dimensions provided\n if (width || height) {\n pipeline = pipeline.resize({\n width,\n height,\n fit: 'inside',\n withoutEnlargement: true,\n })\n }\n\n // Apply format conversion if specified\n if (format === 'png') {\n pipeline = pipeline.png()\n } else if (format === 'webp') {\n pipeline = pipeline.webp()\n } else if (format === 'jpeg') {\n pipeline = pipeline.jpeg()\n }\n\n return pipeline.toBuffer()\n}\n\n/**\n * Generate SHA-256 checksum for a buffer\n */\nexport function generateChecksum(buffer: Buffer): string {\n return createHash('sha256').update(buffer).digest('hex')\n}\n\n/**\n * Detect image format from mime type\n */\nexport function getImageFormat(\n mimeType: string,\n): 'png' | 'webp' | 'jpeg' | null {\n if (mimeType.includes('png')) return 'png'\n if (mimeType.includes('webp')) return 'webp'\n if (mimeType.includes('jpeg') || mimeType.includes('jpg')) return 'jpeg'\n return null\n}\n\n/**\n * Check if a mime type represents a raster image (not SVG)\n */\nexport function isRasterImage(mimeType: string): boolean {\n return mimeType.startsWith('image/') && !mimeType.includes('svg')\n}\n\nexport async function parseAssetMeta(\n p: ParseAssetParams,\n): Promise<ParseAssetReturn> {\n // Get image dimensions using our utility\n const { width, height, format, size } = await sharp(p.buffer).metadata()\n\n const formatToMime: Partial<Record<keyof FormatEnum, string>> = {\n jpeg: 'image/jpeg',\n jpg: 'image/jpeg',\n png: 'image/png',\n webp: 'image/webp',\n avif: 'image/avif',\n tiff: 'image/tiff',\n gif: 'image/gif',\n heif: 'image/heif',\n raw: 'application/octet-stream',\n }\n\n return {\n checksum: generateChecksum(p.buffer),\n width,\n height,\n mimeType: format\n ? (formatToMime[format] ?? `image/${format}`)\n : 'application/octet-stream',\n fileSize: size || 0,\n }\n}\n","import { parseAssetMeta } from './assetUtils'\nimport type { AssetType, PrismaClient } from '@prisma/client'\n\nexport interface UpsertAssetParams {\n prisma: PrismaClient\n buffer: Buffer\n name: string\n originalFilename: string\n assetType: AssetType\n}\n\nexport async function upsertAsset({\n prisma,\n buffer,\n name,\n originalFilename,\n assetType,\n}: UpsertAssetParams) {\n const { checksum, fileSize, width, height, mimeType } = await parseAssetMeta({\n buffer,\n originalFilename,\n })\n\n // If an asset with the same checksum already exists, reuse it instead of storing duplicate binary.\n const existing = await prisma.dbAsset.findUnique({\n where: { name },\n })\n\n if (existing) {\n return existing.id\n }\n\n const asset = await prisma.dbAsset.create({\n data: {\n name,\n checksum,\n assetType,\n content: new Uint8Array(buffer),\n mimeType,\n fileSize,\n width,\n height,\n },\n })\n return asset.id\n}\n","import type {\n AppForCatalog,\n GroupingTagDefinition,\n} from '../types/common/appCatalogTypes'\nimport { getDbClient } from './client'\nimport { TABLE_SYNC_MAGAZINE } from './tableSyncMagazine'\nimport { tableSyncPrisma } from './tableSyncPrismaAdapter'\nimport { readFile, readdir, stat } from 'node:fs/promises'\nimport { group } from 'radashi'\nimport { upsertAsset } from '../modules/assets/upsertAsset'\nimport type { ApprovalMethod } from '../types'\nimport type { PrismaClient } from '@prisma/client'\n\nexport interface SyncAppCatalogResult {\n created: number\n updated: number\n deleted: number\n total: number\n}\n\ninterface AssetSyncResult {\n screenshotIds: Array<string>\n iconName: string | null\n}\n\nfunction isFileNotFoundError(error: unknown): boolean {\n return (\n error instanceof Error &&\n 'code' in error &&\n (error as NodeJS.ErrnoException).code === 'ENOENT'\n )\n}\n\nasync function processAssetDirectory(\n dirPath: string,\n appSlug: string,\n assetType: 'screenshot' | 'icon',\n prisma: PrismaClient,\n): Promise<Array<string>> {\n try {\n const files = await readdir(dirPath)\n const assetIds: Array<string> = []\n\n for (let i = 0; i < files.length; i++) {\n const fileName = files[i]\n if (!fileName) continue\n\n const assetName =\n assetType === 'screenshot'\n ? `${appSlug}-screenshot-${i + 1}`\n : `${appSlug}-icon`\n\n const id = await upsertAsset({\n prisma,\n buffer: await readFile(`${dirPath}/${fileName}`),\n originalFilename: fileName,\n name: assetName,\n assetType,\n })\n assetIds.push(id)\n\n // For icons, only process the first file\n if (assetType === 'icon') {\n break\n }\n }\n\n return assetIds\n } catch (error: unknown) {\n if (isFileNotFoundError(error)) {\n return []\n }\n throw error\n }\n}\n\nasync function syncAppAssets(\n appSlug: string,\n appPath: string,\n prisma: PrismaClient,\n): Promise<AssetSyncResult> {\n const screenshotIds = await processAssetDirectory(\n `${appPath}/screenshots`,\n appSlug,\n 'screenshot',\n prisma,\n )\n\n const iconIds = await processAssetDirectory(\n `${appPath}/icons`,\n appSlug,\n 'icon',\n prisma,\n )\n\n return {\n screenshotIds,\n iconName: iconIds.length > 0 ? `${appSlug}-icon` : null,\n }\n}\n\nasync function syncAssetsFromFileSystem(\n apps: Array<AppForCatalog>,\n allAppsAssetsPath: string,\n) {\n const appDirectories = await readdir(allAppsAssetsPath)\n const prisma = getDbClient()\n const bySlug = group(apps, (a) => a.slug)\n\n for (const appDirName of appDirectories) {\n try {\n const stats = await stat(`${allAppsAssetsPath}/${appDirName}`)\n if (!stats.isDirectory()) {\n continue\n }\n } catch (error: unknown) {\n if (isFileNotFoundError(error)) {\n continue\n }\n throw error\n }\n\n const appSlug = appDirName\n if (!bySlug[appSlug]) {\n throw new Error(\n `App '${appSlug}' does not exist in the app catalog. Existing apps: ${Object.keys(bySlug).join(', ')}`,\n )\n }\n\n try {\n const { screenshotIds, iconName } = await syncAppAssets(\n appSlug,\n `${allAppsAssetsPath}/${appDirName}`,\n prisma,\n )\n\n const updateData: {\n screenshotIds?: Array<string>\n iconName?: string | null\n } = {}\n\n if (screenshotIds.length > 0) {\n updateData.screenshotIds = screenshotIds\n }\n if (iconName !== null) {\n updateData.iconName = iconName\n }\n\n if (Object.keys(updateData).length > 0) {\n await prisma.dbAppForCatalog.update({\n where: { slug: appSlug },\n data: updateData,\n })\n }\n } catch (error: unknown) {\n const errorMessage =\n error instanceof Error ? error.message : String(error)\n throw new Error(\n `Error while upserting assets for app '${appSlug}': ${errorMessage}`,\n )\n }\n }\n // private async syncScreenshots(apps: AppForCatalog[], screenshotsPath: string): Promise<void> {\n // const db = getDbClient();\n //\n // for (const app of apps) {\n // const dirPath = join(screenshotsPath, `${app.slug}-screenshots`);\n //\n // try {\n // const files = await readdir(dirPath);\n // const imageFiles = files.filter(f => /\\.(png|jpg|jpeg|gif|webp)$/i.test(f)).sort();\n //\n // if (imageFiles.length === 0) continue;\n //\n // const screenshotIds: string[] = [];\n //\n // for (const filename of imageFiles) {\n // const filePath = join(dirPath, filename);\n // const content = await readFile(filePath);\n // const buffer = Buffer.from(content);\n // const checksum = generateChecksum(buffer);\n // const { width, height } = await getImageDimensions(buffer);\n //\n // const ext = filename.split('.').pop()?.toLowerCase();\n // const mimeType = {\n // png: 'image/png',\n // jpg: 'image/jpeg',\n // jpeg: 'image/jpeg',\n // gif: 'image/gif',\n // webp: 'image/webp',\n // }[ext || ''] || 'application/octet-stream';\n //\n // const asset = await db.dbAsset.upsert({\n // where: { checksum },\n // create: {\n // name: `${app.slug}/${filename}`,\n // assetType: 'screenshot',\n // content: new Uint8Array(buffer),\n // checksum,\n // mimeType,\n // fileSize: buffer.length,\n // width,\n // height,\n // },\n // update: {\n // name: `${app.slug}/${filename}`,\n // content: new Uint8Array(buffer),\n // mimeType,\n // fileSize: buffer.length,\n // width,\n // height,\n // },\n // });\n //\n // screenshotIds.push(asset.id);\n // }\n //\n // await db.dbAppForCatalog.update({\n // where: { slug: app.slug },\n // data: { screenshotIds },\n // });\n //\n // logger.info(`Synced ${screenshotIds.length} screenshots for ${app.slug}`);\n // } catch (error: any) {\n // if (error.code === 'ENOENT') continue; // Skip if directory doesn't exist\n // throw error;\n // }\n // }\n // }\n}\n\n/**\n * Syncs app catalog data to the database using table sync.\n * This will create new apps, update existing ones, and delete any that are no longer in the input.\n *\n * Note: Call connectDb() before and disconnectDb() after if running in a script.\n */\nexport async function syncAppCatalog(\n apps: Array<AppForCatalog>,\n tagsDefinitions: Array<GroupingTagDefinition>,\n approvalMethods: Array<ApprovalMethod>,\n sreenshotsPath?: string,\n): Promise<SyncAppCatalogResult> {\n try {\n const prisma = getDbClient()\n\n await tableSyncPrisma({\n prisma,\n ...TABLE_SYNC_MAGAZINE.DbApprovalMethod,\n }).sync(approvalMethods)\n\n const sync = tableSyncPrisma({\n prisma,\n ...TABLE_SYNC_MAGAZINE.DbAppForCatalog,\n })\n\n await tableSyncPrisma({\n prisma,\n ...TABLE_SYNC_MAGAZINE.DbAppTagDefinition,\n }).sync(tagsDefinitions)\n\n // Transform AppForCatalog to DbAppForCatalog format\n const dbApps = apps.map((app) => {\n const slug =\n app.slug ||\n app.displayName\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n\n return {\n slug,\n displayName: app.displayName,\n description: app.description,\n teams: app.teams ?? [],\n accessRequest: app.accessRequest ?? null,\n notes: app.notes ?? null,\n tags: app.tags ?? [],\n appUrl: app.appUrl ?? null,\n links: app.links ?? null,\n iconName: app.iconName ?? null,\n screenshotIds: app.screenshotIds ?? [],\n }\n })\n\n const result = await sync.sync(dbApps)\n\n // Get actual synced data to calculate stats\n const actual = result.getActual()\n\n if (sreenshotsPath) {\n await syncAssetsFromFileSystem(apps, sreenshotsPath)\n } else {\n console.warn('DO not sync screenhots')\n }\n\n return {\n created: actual.length - apps.length + (apps.length - actual.length),\n updated: 0, // TableSync doesn't expose this directly\n deleted: 0, // TableSync doesn't expose this directly\n total: actual.length,\n }\n } catch (error) {\n // Wrap error with context\n const errorMessage = error instanceof Error ? error.message : String(error)\n const errorStack = error instanceof Error ? error.stack : undefined\n\n throw new Error(\n `Error syncing app catalog: ${errorMessage}\\n\\nDetails:\\n${errorStack || 'No stack trace available'}`,\n )\n }\n}\n","/**\n * Authorization utilities for checking user permissions based on groups\n *\n * Groups are automatically included in the user session when:\n * 1. Okta auth server has a \"groups\" claim configured\n * 2. The auth policy rule includes \"groups\" in scope_whitelist\n *\n * Example usage in tRPC procedures:\n * ```typescript\n * myProcedure: protectedProcedure.query(async ({ ctx }) => {\n * if (requireAdmin(ctx.user)) {\n * // Admin-only logic\n * }\n * // Regular user logic\n * })\n * ```\n */\n\nexport interface UserWithGroups {\n id: string\n email: string\n name?: string\n // Groups from Okta (or other identity provider)\n // This will be populated if groups claim is configured\n [key: string]: any\n}\n\n/**\n * Extract groups from user object\n * Groups can be stored in different locations depending on the OAuth provider\n */\nexport function getUserGroups(\n user: UserWithGroups | null | undefined,\n): Array<string> {\n if (!user) {\n console.log('[getUserGroups] No user provided')\n return []\n }\n\n // Debug: Log all user properties to see what's available\n console.log('[getUserGroups] === USER OBJECT DEBUG ===')\n console.log('[getUserGroups] User ID:', user.id)\n console.log('[getUserGroups] User email:', user.email)\n console.log(\n '[getUserGroups] User.env_hopper_groups:',\n (user as any).env_hopper_groups,\n )\n console.log('[getUserGroups] User.groups:', user.groups)\n console.log('[getUserGroups] User.oktaGroups:', (user as any).oktaGroups)\n console.log('[getUserGroups] User.roles:', (user as any).roles)\n console.log('[getUserGroups] All user keys:', Object.keys(user))\n console.log(\n '[getUserGroups] Full user object:',\n JSON.stringify(user, null, 2),\n )\n\n // Check common locations for group information\n // Order of preference: custom env_hopper_groups first\n const groups =\n (user as any).env_hopper_groups || // Custom env_hopper_groups claim (stores natera.env_hopper_ui.groups)\n user.groups || // Standard \"groups\" claim\n (user as any).oktaGroups || // Okta-specific\n (user as any).roles || // Some providers use \"roles\"\n []\n\n const result = Array.isArray(groups) ? groups : []\n console.log('[getUserGroups] Final groups result:', result)\n\n return result\n}\n\n/**\n * Check if user is a member of any of the specified groups\n */\nexport function isMemberOfAnyGroup(\n user: UserWithGroups | null | undefined,\n allowedGroups: Array<string>,\n): boolean {\n const userGroups = getUserGroups(user)\n return allowedGroups.some((group) => userGroups.includes(group))\n}\n\n/**\n * Check if user is a member of all specified groups\n */\nexport function isMemberOfAllGroups(\n user: UserWithGroups | null | undefined,\n requiredGroups: Array<string>,\n): boolean {\n const userGroups = getUserGroups(user)\n return requiredGroups.every((group) => userGroups.includes(group))\n}\n\n/**\n * Check if user has admin permissions\n * @param user User object with groups\n * @param adminGroups List of admin group names (default: ['env_hopper_ui_super_admins'])\n */\nexport function isAdmin(\n user: UserWithGroups | null | undefined,\n adminGroups: Array<string> = ['env_hopper_ui_super_admins'],\n): boolean {\n return isMemberOfAnyGroup(user, adminGroups)\n}\n\n/**\n * Require admin permissions - throws error if not admin\n * @param user User object with groups\n * @param adminGroups List of admin group names (default: ['env_hopper_ui_super_admins'])\n */\nexport function requireAdmin(\n user: UserWithGroups | null | undefined,\n adminGroups: Array<string> = ['env_hopper_ui_super_admins'],\n): void {\n if (!isAdmin(user, adminGroups)) {\n throw new Error('Forbidden: Admin access required')\n }\n}\n\n/**\n * Require membership in specific groups - throws error if not member\n */\nexport function requireGroups(\n user: UserWithGroups | null | undefined,\n groups: Array<string>,\n): void {\n if (!isMemberOfAnyGroup(user, groups)) {\n throw new Error(\n `Forbidden: Membership in one of these groups required: ${groups.join(', ')}`,\n )\n }\n}\n","import { TRPCError, initTRPC } from '@trpc/server'\nimport type { EhTrpcContext } from './ehTrpcContext'\nimport { isAdmin } from '../modules/auth/authorizationUtils'\n\n/**\n * Initialization of tRPC backend\n * Should be done only once per backend!\n */\nexport const t = initTRPC.context<EhTrpcContext>().create({\n errorFormatter({ error, shape }: { error: unknown; shape: unknown }) {\n // Log all tRPC errors to console\n console.error('[tRPC Error]', {\n path: (shape as { data?: { path?: string } }).data?.path,\n code: (error as { code?: string }).code,\n message: (error as { message?: string }).message,\n cause: (error as { cause?: unknown }).cause,\n stack: (error as { stack?: string }).stack,\n })\n return shape\n },\n})\n\n/**\n * Export reusable router and procedure helpers\n */\nexport const router = t.router\nexport const publicProcedure = t.procedure\n\n/**\n * Middleware to check if user is authenticated\n */\nconst isAuthenticated = t.middleware(({ ctx, next }) => {\n if (!ctx.user) {\n throw new TRPCError({\n code: 'UNAUTHORIZED',\n message: 'You must be logged in to access this resource',\n })\n }\n return next({\n ctx: {\n ...ctx,\n user: ctx.user,\n },\n })\n})\n\n/**\n * Middleware to check if user is an admin\n */\nconst isAdminMiddleware = t.middleware(({ ctx, next }) => {\n if (!ctx.user) {\n throw new TRPCError({\n code: 'UNAUTHORIZED',\n message: 'You must be logged in to access this resource',\n })\n }\n\n console.log('[isAdminMiddleware] === ADMIN CHECK DEBUG ===')\n console.log('[isAdminMiddleware] User:', ctx.user.email)\n console.log('[isAdminMiddleware] Required admin groups:', ctx.adminGroups)\n console.log('[isAdminMiddleware] Calling isAdmin()...')\n\n const hasAdminAccess = isAdmin(ctx.user, ctx.adminGroups)\n console.log('[isAdminMiddleware] Has admin access:', hasAdminAccess)\n\n if (!hasAdminAccess) {\n throw new TRPCError({\n code: 'FORBIDDEN',\n message: `You must be an admin to access this resource. Required groups: ${ctx.adminGroups.join(', ') || 'env_hopper_ui_super_admins'}`,\n })\n }\n\n return next({\n ctx: {\n ...ctx,\n user: ctx.user,\n },\n })\n})\n\n/**\n * Admin procedure that requires admin permissions\n */\nexport const adminProcedure = t.procedure.use(isAdminMiddleware)\n\n/**\n * Protected procedure that requires authentication (but not admin)\n */\nexport const protectedProcedure = t.procedure.use(isAuthenticated)\n","import { z } from 'zod'\nimport { getDbClient } from '../../db'\nimport { adminProcedure, router } from '../../server/trpcSetup'\nimport type { AppAccessRequest } from '../../types'\n\n// Zod schema for access method (simplified for now - you can expand this)\nconst AccessMethodSchema = z\n .object({\n type: z.enum([\n 'bot',\n 'ticketing',\n 'email',\n 'self-service',\n 'documentation',\n 'manual',\n ]),\n })\n .loose()\n\nconst AppLinkSchema = z.object({\n displayName: z.string().optional(),\n url: z.url(),\n})\n\n// New AppAccessRequest schema\nconst AppRoleSchema = z.object({\n name: z.string(),\n description: z.string().optional(),\n})\n\nconst ApproverContactSchema = z.object({\n displayName: z.string(),\n contact: z.string().optional(),\n})\n\nconst ApprovalUrlSchema = z.object({\n label: z.string().optional(),\n url: z.url(),\n})\n\nconst AppAccessRequestSchema = z.object({\n approvalMethodId: z.string(),\n comments: z.string().optional(),\n requestPrompt: z.string().optional(),\n postApprovalInstructions: z.string().optional(),\n roles: z.array(AppRoleSchema).optional(),\n approvers: z.array(ApproverContactSchema).optional(),\n urls: z.array(ApprovalUrlSchema).optional(),\n whoToReachOut: z.string().optional(),\n})\n\nconst CreateAppForCatalogSchema = z.object({\n slug: z\n .string()\n .min(1)\n .regex(/^[a-z0-9-]+$/, 'Slug must be lowercase alphanumeric with hyphens'),\n displayName: z.string().min(1),\n description: z.string(),\n access: AccessMethodSchema.optional(),\n teams: z.array(z.string()).optional(),\n accessRequest: AppAccessRequestSchema.optional(),\n notes: z.string().optional(),\n tags: z.array(z.string()).optional(),\n appUrl: z.url().optional(),\n links: z.array(AppLinkSchema).optional(),\n iconName: z.string().optional(),\n screenshotIds: z.array(z.string()).optional(),\n})\n\nconst UpdateAppForCatalogSchema = CreateAppForCatalogSchema.partial().extend({\n id: z.string(),\n})\n\nexport function createAppCatalogAdminRouter() {\n const prisma = getDbClient()\n return router({\n list: adminProcedure.query(async () => {\n return prisma.dbAppForCatalog.findMany({\n orderBy: { displayName: 'asc' },\n })\n }),\n\n getById: adminProcedure\n .input(z.object({ id: z.string() }))\n .query(async ({ input }) => {\n return prisma.dbAppForCatalog.findUnique({\n where: { id: input.id },\n })\n }),\n\n getBySlug: adminProcedure\n .input(z.object({ slug: z.string() }))\n .query(async ({ input }) => {\n return prisma.dbAppForCatalog.findUnique({\n where: { slug: input.slug },\n })\n }),\n\n create: adminProcedure\n .input(CreateAppForCatalogSchema)\n .mutation(async ({ input }) => {\n // Type assertion needed because Zod's passthrough() creates index signatures\n // that don't structurally match Prisma's typed JSON fields.\n // This is safe because Zod validates the input shape.\n return prisma.dbAppForCatalog.create({\n data: {\n slug: input.slug,\n displayName: input.displayName,\n description: input.description,\n teams: input.teams ?? [],\n accessRequest: input.accessRequest as AppAccessRequest | undefined,\n notes: input.notes,\n tags: input.tags ?? [],\n appUrl: input.appUrl,\n links: input.links as Array<{ displayName?: string; url: string }>,\n iconName: input.iconName,\n screenshotIds: input.screenshotIds ?? [],\n },\n })\n }),\n\n update: adminProcedure\n .input(UpdateAppForCatalogSchema)\n .mutation(async ({ input }) => {\n const { id, ...updateData } = input\n\n // Type assertion needed because Zod's passthrough() creates index signatures\n return prisma.dbAppForCatalog.update({\n where: { id },\n data: {\n ...(updateData.slug !== undefined && { slug: updateData.slug }),\n ...(updateData.displayName !== undefined && {\n displayName: updateData.displayName,\n }),\n ...(updateData.description !== undefined && {\n description: updateData.description,\n }),\n ...(updateData.teams !== undefined && { teams: updateData.teams }),\n ...(updateData.accessRequest !== undefined && {\n accessRequest: updateData.accessRequest as\n | AppAccessRequest\n | undefined,\n }),\n ...(updateData.notes !== undefined && { notes: updateData.notes }),\n ...(updateData.tags !== undefined && { tags: updateData.tags }),\n ...(updateData.appUrl !== undefined && {\n appUrl: updateData.appUrl,\n }),\n ...(updateData.links !== undefined && {\n links: updateData.links as Array<{\n displayName?: string\n url: string\n }>,\n }),\n ...(updateData.iconName !== undefined && {\n iconName: updateData.iconName,\n }),\n ...(updateData.screenshotIds !== undefined && {\n screenshotIds: updateData.screenshotIds,\n }),\n },\n })\n }),\n\n updateScreenshots: adminProcedure\n .input(\n z.object({\n id: z.string(),\n screenshotIds: z.array(z.string()),\n }),\n )\n .mutation(async ({ input }) => {\n return prisma.dbAppForCatalog.update({\n where: { id: input.id },\n data: { screenshotIds: input.screenshotIds },\n })\n }),\n\n delete: adminProcedure\n .input(z.object({ id: z.string() }))\n .mutation(async ({ input }) => {\n return prisma.dbAppForCatalog.delete({\n where: { id: input.id },\n })\n }),\n })\n}\n","/**\n * Generates a URL-friendly slug from a display name.\n * Converts to lowercase and replaces non-alphanumeric characters with hyphens.\n *\n * @param displayName - The display name to convert\n * @returns A slug suitable for use as a primary key\n *\n * @example\n * generateSlugFromDisplayName(\"My Service\") // \"my-service\"\n * generateSlugFromDisplayName(\"John's Team\") // \"john-s-team\"\n */\nexport function generateSlugFromDisplayName(displayName: string): string {\n return displayName\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n}\n","import { z } from 'zod'\nimport { Prisma } from '@prisma/client'\nimport { getDbClient } from '../../db'\nimport { adminProcedure, publicProcedure, router } from '../../server/trpcSetup'\nimport type {\n ApprovalMethod,\n ApprovalMethodConfig,\n CustomConfig,\n PersonTeamConfig,\n ServiceConfig,\n} from '../../types'\nimport { generateSlugFromDisplayName } from './slugUtils'\n\n// Zod schemas\nconst ReachOutContactSchema = z.object({\n displayName: z.string(),\n contact: z.string(),\n})\n\nconst ServiceConfigSchema = z.object({\n url: z.url().optional(),\n icon: z.string().optional(),\n})\n\nconst PersonTeamConfigSchema = z.object({\n reachOutContacts: z.array(ReachOutContactSchema).optional(),\n})\n\nconst CustomConfigSchema = z.object({})\n\nconst ApprovalMethodConfigSchema = z.union([\n ServiceConfigSchema,\n PersonTeamConfigSchema,\n CustomConfigSchema,\n])\n\nconst CreateApprovalMethodSchema = z.object({\n type: z.enum(['service', 'personTeam', 'custom']),\n displayName: z.string().min(1),\n config: ApprovalMethodConfigSchema.optional(),\n})\n\nconst UpdateApprovalMethodSchema = z.object({\n slug: z.string(),\n type: z.enum(['service', 'personTeam', 'custom']).optional(),\n displayName: z.string().min(1).optional(),\n config: ApprovalMethodConfigSchema.optional(),\n})\n\n/**\n * Convert Prisma DbApprovalMethod to our ApprovalMethod type.\n * This ensures tRPC infers proper types for frontend consumers.\n */\nfunction toApprovalMethod(db: {\n slug: string\n type: ApprovalMethod['type']\n displayName: string\n config: ApprovalMethodConfig | null\n createdAt: Date\n updatedAt: Date\n}): ApprovalMethod {\n // Handle discriminated union by explicitly narrowing based on type\n const baseFields = {\n slug: db.slug,\n displayName: db.displayName,\n createdAt: db.createdAt,\n updatedAt: db.updatedAt,\n }\n\n // Provide default empty config if null, as ApprovalMethod discriminated union requires config\n const config = db.config ?? {}\n\n switch (db.type) {\n case 'service':\n return { ...baseFields, type: 'service', config: config as ServiceConfig }\n case 'personTeam':\n return {\n ...baseFields,\n type: 'personTeam',\n config: config as PersonTeamConfig,\n }\n case 'custom':\n return { ...baseFields, type: 'custom', config: config as CustomConfig }\n }\n}\n\nexport function createApprovalMethodRouter() {\n return router({\n // Public: list for selection in app admin\n list: publicProcedure.query(async (): Promise<Array<ApprovalMethod>> => {\n const prisma = getDbClient()\n const results = await prisma.dbApprovalMethod.findMany({\n orderBy: { displayName: 'asc' },\n })\n return results.map(toApprovalMethod)\n }),\n\n // Public: get by ID\n getById: publicProcedure\n .input(z.object({ slug: z.string() }))\n .query(async ({ input }): Promise<ApprovalMethod | null> => {\n const prisma = getDbClient()\n const result = await prisma.dbApprovalMethod.findUnique({\n where: { slug: input.slug },\n })\n return result ? toApprovalMethod(result) : null\n }),\n\n // Admin: create\n create: adminProcedure\n .input(CreateApprovalMethodSchema)\n .mutation(async ({ input }): Promise<ApprovalMethod> => {\n const prisma = getDbClient()\n const result = await prisma.dbApprovalMethod.create({\n data: {\n slug: generateSlugFromDisplayName(input.displayName),\n type: input.type,\n displayName: input.displayName,\n config: input.config ?? Prisma.JsonNull,\n },\n })\n return toApprovalMethod(result)\n }),\n\n // Admin: update\n update: adminProcedure\n .input(UpdateApprovalMethodSchema)\n .mutation(async ({ input }): Promise<ApprovalMethod> => {\n const prisma = getDbClient()\n const { slug, ...updateData } = input\n const result = await prisma.dbApprovalMethod.update({\n where: { slug },\n data: {\n ...(updateData.type !== undefined && { type: updateData.type }),\n ...(updateData.displayName !== undefined && {\n displayName: updateData.displayName,\n }),\n ...(updateData.config !== undefined && {\n config: updateData.config ?? Prisma.JsonNull,\n }),\n },\n })\n return toApprovalMethod(result)\n }),\n\n // Admin: delete\n delete: adminProcedure\n .input(z.object({ slug: z.string() }))\n .mutation(async ({ input }): Promise<ApprovalMethod> => {\n const prisma = getDbClient()\n const result = await prisma.dbApprovalMethod.delete({\n where: { slug: input.slug },\n })\n return toApprovalMethod(result)\n }),\n\n // Admin: search by type\n listByType: publicProcedure\n .input(z.object({ type: z.enum(['service', 'personTeam', 'custom']) }))\n .query(async ({ input }): Promise<Array<ApprovalMethod>> => {\n const prisma = getDbClient()\n const results = await prisma.dbApprovalMethod.findMany({\n where: { type: input.type },\n orderBy: { displayName: 'asc' },\n })\n return results.map(toApprovalMethod)\n }),\n })\n}\n","import { z } from 'zod'\nimport { getDbClient } from '../../db'\nimport { publicProcedure, router } from '../../server/trpcSetup'\n\nexport function createScreenshotRouter() {\n return router({\n list: publicProcedure.query(async () => {\n const prisma = getDbClient()\n return prisma.dbAsset.findMany({\n where: { assetType: 'screenshot' },\n select: {\n id: true,\n name: true,\n mimeType: true,\n fileSize: true,\n width: true,\n height: true,\n createdAt: true,\n updatedAt: true,\n },\n orderBy: { createdAt: 'desc' },\n })\n }),\n\n getOne: publicProcedure\n .input(z.object({ id: z.string() }))\n .query(async ({ input }) => {\n const prisma = getDbClient()\n return prisma.dbAsset.findFirst({\n where: {\n id: input.id,\n assetType: 'screenshot',\n },\n select: {\n id: true,\n name: true,\n mimeType: true,\n fileSize: true,\n width: true,\n height: true,\n createdAt: true,\n updatedAt: true,\n },\n })\n }),\n\n getByAppSlug: publicProcedure\n .input(z.object({ appSlug: z.string() }))\n .query(async ({ input }) => {\n const prisma = getDbClient()\n\n // Find app by slug\n const app = await prisma.dbAppForCatalog.findUnique({\n where: { slug: input.appSlug },\n select: { screenshotIds: true },\n })\n\n if (!app) {\n return []\n }\n\n // Fetch all screenshots for the app\n return prisma.dbAsset.findMany({\n where: {\n id: { in: app.screenshotIds },\n assetType: 'screenshot',\n },\n select: {\n id: true,\n name: true,\n mimeType: true,\n fileSize: true,\n width: true,\n height: true,\n createdAt: true,\n updatedAt: true,\n },\n })\n }),\n\n getFirstByAppSlug: publicProcedure\n .input(z.object({ appSlug: z.string() }))\n .query(async ({ input }) => {\n const prisma = getDbClient()\n\n // Find app by slug\n const app = await prisma.dbAppForCatalog.findUnique({\n where: { slug: input.appSlug },\n select: { screenshotIds: true },\n })\n\n if (!app || app.screenshotIds.length === 0) {\n return null\n }\n\n // Fetch first screenshot\n return prisma.dbAsset.findUnique({\n where: { id: app.screenshotIds[0] },\n select: {\n id: true,\n name: true,\n mimeType: true,\n fileSize: true,\n width: true,\n height: true,\n createdAt: true,\n updatedAt: true,\n },\n })\n }),\n })\n}\n","import type { BetterAuthPlugin } from 'better-auth'\nimport type { TRPCRootObject } from '@trpc/server'\nimport type { EhTrpcContext } from '../../server/ehTrpcContext'\nimport type { BetterAuth } from './auth'\n\n/**\n * Create auth tRPC procedures\n * @param t - tRPC instance\n * @param auth - Better Auth instance (optional, for future extensions)\n * @returns tRPC router with auth procedures\n */\nexport function createAuthRouter(\n t: TRPCRootObject<EhTrpcContext, {}, {}>,\n auth?: BetterAuth,\n) {\n const router = t.router\n const publicProcedure = t.procedure\n\n return router({\n getSession: publicProcedure.query(async ({ ctx }) => {\n // User is now extracted in the tRPC context creation\n return {\n user: ctx.user ?? null,\n isAuthenticated: !!ctx.user,\n }\n }),\n getProviders: publicProcedure.query(() => {\n // Return configured social providers and OAuth providers from plugins\n const providers: Array<string> = []\n const authOptions = auth?.options\n\n // Add built-in social providers (github, google, etc.)\n if (authOptions?.socialProviders) {\n const socialProviders = authOptions.socialProviders as Record<\n string,\n unknown\n >\n Object.keys(socialProviders).forEach((key) => {\n if (socialProviders[key]) {\n providers.push(key)\n }\n })\n }\n\n // Add OAuth providers from plugins (like Okta via genericOAuth)\n if (authOptions?.plugins) {\n const plugins = authOptions.plugins\n plugins.forEach((plugin) => {\n const pluginWithConfig = plugin as BetterAuthPlugin & {\n options?: {\n config?: Array<{ providerId?: string }>\n }\n }\n if (\n pluginWithConfig.id === 'generic-oauth' &&\n pluginWithConfig.options?.config\n ) {\n const configs = Array.isArray(pluginWithConfig.options.config)\n ? pluginWithConfig.options.config\n : [pluginWithConfig.options.config]\n configs.forEach((config) => {\n if (config.providerId) {\n providers.push(config.providerId)\n }\n })\n }\n })\n }\n\n return { providers }\n }),\n })\n}\n\nexport type AuthRouter = ReturnType<typeof createAuthRouter>\n","/**\n * Get file extension from MIME type\n */\nexport function getExtensionFromMimeType(mimeType: string): string {\n const mimeMap: Record<string, string> = {\n 'image/svg+xml': 'svg',\n 'image/png': 'png',\n 'image/jpeg': 'jpg',\n 'image/jpg': 'jpg',\n 'image/webp': 'webp',\n 'image/gif': 'gif',\n 'image/bmp': 'bmp',\n 'image/tiff': 'tiff',\n 'image/x-icon': 'ico',\n 'image/vnd.microsoft.icon': 'ico',\n }\n\n return mimeMap[mimeType.toLowerCase()] || 'bin'\n}\n\n/**\n * Get file extension from filename\n */\nexport function getExtensionFromFilename(filename: string): string {\n const match = filename.match(/\\.([^.]+)$/)\n return match?.[1]?.toLowerCase() || ''\n}\n\n/**\n * Get MIME type from extension\n */\nexport function getMimeTypeFromExtension(extension: string): string {\n const extMap: Record<string, string> = {\n svg: 'image/svg+xml',\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n webp: 'image/webp',\n gif: 'image/gif',\n bmp: 'image/bmp',\n tiff: 'image/tiff',\n ico: 'image/x-icon',\n }\n\n return extMap[extension.toLowerCase()] || 'application/octet-stream'\n}\n","import { z } from 'zod'\nimport { getDbClient } from '../../db'\nimport { generateChecksum, getImageDimensions } from '../assets/assetUtils'\nimport { getExtensionFromMimeType } from './iconUtils'\nimport { adminProcedure, publicProcedure, router } from '../../server/trpcSetup'\n\nexport function createIconRouter() {\n return router({\n list: publicProcedure.query(async () => {\n const prisma = getDbClient()\n return prisma.dbAsset.findMany({\n where: { assetType: 'icon' },\n select: {\n id: true,\n name: true,\n mimeType: true,\n fileSize: true,\n createdAt: true,\n updatedAt: true,\n },\n orderBy: { name: 'asc' },\n })\n }),\n\n getOne: publicProcedure\n .input(z.object({ id: z.string() }))\n .query(async ({ input }) => {\n const prisma = getDbClient()\n return prisma.dbAsset.findFirst({\n where: {\n id: input.id,\n assetType: 'icon',\n },\n select: {\n id: true,\n name: true,\n mimeType: true,\n fileSize: true,\n createdAt: true,\n updatedAt: true,\n },\n })\n }),\n\n create: adminProcedure\n .input(\n z.object({\n name: z.string().min(1), // Name with extension (e.g., \"jira.svg\")\n content: z.string(), // base64 encoded binary\n mimeType: z.string(),\n fileSize: z.number().int().positive(),\n }),\n )\n .mutation(async ({ input }) => {\n const prisma = getDbClient()\n // Convert base64 to Buffer\n const buffer = Buffer.from(input.content, 'base64')\n\n // Generate checksum and extract dimensions\n const checksum = generateChecksum(buffer)\n const { width, height } = await getImageDimensions(buffer)\n\n let name = input.name\n // Add extension if not already present in name\n if (!name.includes('.')) {\n const extension = getExtensionFromMimeType(input.mimeType)\n name = `${name}.${extension}`\n }\n\n // Check if asset with same checksum already exists\n const existing = await prisma.dbAsset.findFirst({\n where: { checksum, assetType: 'icon' },\n })\n\n if (existing) {\n // Return existing asset if content is identical\n return existing\n }\n\n return prisma.dbAsset.create({\n data: {\n name,\n assetType: 'icon',\n content: new Uint8Array(buffer),\n checksum,\n mimeType: input.mimeType,\n fileSize: input.fileSize,\n width,\n height,\n },\n })\n }),\n\n update: adminProcedure\n .input(\n z.object({\n id: z.string(),\n name: z.string().min(1).optional(), // Name with extension (e.g., \"jira.svg\")\n content: z.string().optional(), // base64 encoded binary\n mimeType: z.string().optional(),\n fileSize: z.number().int().positive().optional(),\n }),\n )\n .mutation(async ({ input }) => {\n const prisma = getDbClient()\n const { id, content, name, ...rest } = input\n\n const data: Record<string, unknown> = { ...rest }\n\n if (content) {\n const buffer = Buffer.from(content, 'base64')\n data.content = new Uint8Array(buffer)\n data.checksum = generateChecksum(buffer)\n\n const { width, height } = await getImageDimensions(buffer)\n data.width = width\n data.height = height\n }\n\n // If name is being updated and doesn't have extension, add it\n if (name) {\n if (!name.includes('.') && input.mimeType) {\n const extension = getExtensionFromMimeType(input.mimeType)\n data.name = `${name}.${extension}`\n } else {\n data.name = name\n }\n }\n\n return prisma.dbAsset.update({\n where: { id },\n data,\n })\n }),\n\n delete: adminProcedure\n .input(z.object({ id: z.string() }))\n .mutation(async ({ input }) => {\n const prisma = getDbClient()\n return prisma.dbAsset.delete({\n where: { id: input.id },\n })\n }),\n\n deleteMany: adminProcedure\n .input(z.object({ ids: z.array(z.string()) }))\n .mutation(async ({ input }) => {\n const prisma = getDbClient()\n return prisma.dbAsset.deleteMany({\n where: {\n id: { in: input.ids },\n assetType: 'icon',\n },\n })\n }),\n\n // Serve icon binary content\n getContent: publicProcedure\n .input(z.object({ id: z.string() }))\n .query(async ({ input }) => {\n const prisma = getDbClient()\n const asset = await prisma.dbAsset.findFirst({\n where: {\n id: input.id,\n assetType: 'icon',\n },\n select: { content: true, mimeType: true, name: true },\n })\n if (!asset) {\n throw new Error('Icon not found')\n }\n // Return base64 encoded content\n return {\n content: Buffer.from(asset.content).toString('base64'),\n mimeType: asset.mimeType,\n name: asset.name,\n }\n }),\n })\n}\n","import { getAppCatalogData } from '../modules/appCatalog/service'\nimport type { AppCatalogData } from '../types'\n\nimport { createAppCatalogAdminRouter } from '../modules/appCatalogAdmin/appCatalogAdminRouter.js'\nimport { createApprovalMethodRouter } from '../modules/approvalMethod/approvalMethodRouter.js'\nimport { createScreenshotRouter } from '../modules/assets/screenshotRouter.js'\nimport type { BetterAuth } from '../modules/auth/auth'\nimport { createAuthRouter } from '../modules/auth/authRouter.js'\nimport { createIconRouter } from '../modules/icons/iconRouter.js'\nimport { publicProcedure, router, t } from './trpcSetup'\n\n/**\n * Create the main tRPC router with optional auth instance\n * @param auth - Optional Better Auth instance for auth-related queries\n */\nexport function createTrpcRouter(auth?: BetterAuth) {\n return router({\n authConfig: publicProcedure.query(async ({ ctx }) => {\n return {\n adminGroups: ctx.adminGroups,\n }\n }),\n\n appCatalog: publicProcedure.query(\n async ({ ctx }): Promise<AppCatalogData> => {\n return await getAppCatalogData(ctx.companySpecificBackend.getApps)\n },\n ),\n\n // Icon management routes\n icon: createIconRouter(),\n\n // Screenshot management routes\n screenshot: createScreenshotRouter(),\n\n // App catalog admin routes\n appCatalogAdmin: createAppCatalogAdminRouter(),\n\n // Approval method routes\n approvalMethod: createApprovalMethodRouter(),\n\n // Auth routes (requires auth instance)\n auth: createAuthRouter(t, auth),\n })\n}\n\nexport type TRPCRouter = ReturnType<typeof createTrpcRouter>\n","import type { AppCatalogCompanySpecificBackend } from '../types'\nimport type { User } from 'better-auth/types'\n\nexport interface EhTrpcContext {\n companySpecificBackend: AppCatalogCompanySpecificBackend\n user: User | null\n adminGroups: Array<string>\n}\n\nexport interface EhTrpcContextOptions {\n companySpecificBackend: AppCatalogCompanySpecificBackend\n user?: User | null\n adminGroups: Array<string>\n}\n\nexport function createEhTrpcContext({\n companySpecificBackend,\n user = null,\n adminGroups,\n}: EhTrpcContextOptions): EhTrpcContext {\n return {\n companySpecificBackend,\n user,\n adminGroups,\n }\n}\n","export interface EhStaticControllerContract {\n methods: { \n getIcon: { method: string; url: string }\n getScreenshot: { method: string; url: string }\n }\n}\n\nexport const staticControllerContract: EhStaticControllerContract = {\n methods: {\n getIcon: {\n method: 'get',\n url: 'icon/:icon',\n },\n getScreenshot: {\n method: 'get',\n url: 'screenshot/:id',\n },\n },\n}\n","import type { BetterAuthOptions, BetterAuthPlugin } from 'better-auth'\nimport { betterAuth } from 'better-auth'\nimport { prismaAdapter } from 'better-auth/adapters/prisma'\nimport { getDbClient } from '../../db'\n\nexport interface AuthConfig {\n appName?: string\n baseURL: string\n secret: string\n providers?: BetterAuthOptions['socialProviders']\n plugins?: Array<BetterAuthPlugin>\n /** Session expiration in seconds. Default: 7 days (604800) */\n sessionExpiresIn?: number\n /** Session update age in seconds. Default: 1 day (86400) */\n sessionUpdateAge?: number\n}\n\nexport function createAuth(config: AuthConfig) {\n const prisma = getDbClient()\n const isProduction = process.env.NODE_ENV === 'production'\n\n const auth = betterAuth({\n appName: config.appName || 'EnvHopper',\n baseURL: config.baseURL,\n basePath: '/api/auth',\n secret: config.secret,\n database: prismaAdapter(prisma, {\n provider: 'postgresql',\n }),\n socialProviders: config.providers || {},\n plugins: config.plugins || [],\n emailAndPassword: {\n enabled: true,\n },\n session: {\n expiresIn: config.sessionExpiresIn ?? 60 * 60 * 24 * 30,\n updateAge: config.sessionUpdateAge ?? 60 * 60 * 24,\n cookieCache: {\n enabled: true,\n maxAge: 300,\n },\n },\n advanced: {\n useSecureCookies: isProduction,\n },\n })\n\n return auth\n}\n\nexport type BetterAuth = ReturnType<typeof createAuth>\n","import { toNodeHandler } from 'better-auth/node'\nimport type { Express, Request, Response } from 'express'\nimport type { BetterAuth } from './auth'\n\n/**\n * Register Better Auth routes with Express\n * @param app - Express application instance\n * @param auth - Better Auth instance\n */\nexport function registerAuthRoutes(app: Express, auth: BetterAuth) {\n // Explicit session endpoint handler\n // Better Auth's toNodeHandler doesn't expose a direct /session endpoint\n app.get('/api/auth/session', async (req: Request, res: Response) => {\n try {\n const session = await auth.api.getSession({\n headers: req.headers as HeadersInit,\n })\n if (session) {\n res.json(session)\n } else {\n res.status(401).json({ error: 'Not authenticated' })\n }\n } catch (error) {\n console.error('[Auth Session Error]', error)\n res.status(500).json({ error: 'Internal server error' })\n }\n })\n\n // Use toNodeHandler to adapt better-auth for Express/Node.js\n // Express v5 wildcard syntax: /{*any} (also works with Express v4)\n const authHandler = toNodeHandler(auth)\n app.all('/api/auth/{*any}', authHandler)\n}\n","import { stepCountIs, streamText, tool } from 'ai'\nimport type { LanguageModel, Tool } from 'ai'\n\nimport type { Request, Response } from 'express'\n\nexport interface AdminChatHandlerOptions {\n /** The AI model to use (from @ai-sdk/openai, @ai-sdk/anthropic, etc.) */\n model: LanguageModel\n /** System prompt for the AI assistant */\n systemPrompt?: string\n /** Tools available to the AI assistant */\n tools?: Record<string, Tool>\n /**\n * Optional function to validate configuration before processing requests.\n * Should throw an error if configuration is invalid (e.g., missing API key).\n * @example\n * validateConfig: () => {\n * if (!process.env.OPENAI_API_KEY) {\n * throw new Error('OPENAI_API_KEY is not configured')\n * }\n * }\n */\n validateConfig?: () => void\n}\n\ninterface TextPart {\n type: 'text'\n text: string\n}\n\ninterface UIMessageInput {\n role: 'user' | 'assistant' | 'system'\n content?: string\n parts?: Array<TextPart | { type: string }>\n}\n\ninterface CoreMessage {\n role: 'user' | 'assistant' | 'system'\n content: string\n}\n\nfunction convertToCoreMessages(\n messages: Array<UIMessageInput>,\n): Array<CoreMessage> {\n return messages.map((msg) => {\n if (msg.content) {\n return { role: msg.role, content: msg.content }\n }\n // Extract text from parts array (AI SDK v3 format)\n const textContent =\n msg.parts\n ?.filter((part): part is TextPart => part.type === 'text')\n .map((part) => part.text)\n .join('') ?? ''\n return { role: msg.role, content: textContent }\n })\n}\n\n/**\n * Creates an Express handler for the admin chat endpoint.\n *\n * Usage in thin wrappers:\n *\n * ```typescript\n * // With OpenAI\n * import { openai } from '@ai-sdk/openai'\n * app.post('/api/admin/chat', createAdminChatHandler({\n * model: openai('gpt-4o-mini'),\n * }))\n *\n * // With Claude\n * import { anthropic } from '@ai-sdk/anthropic'\n * app.post('/api/admin/chat', createAdminChatHandler({\n * model: anthropic('claude-sonnet-4-20250514'),\n * }))\n * ```\n */\nexport function createAdminChatHandler(options: AdminChatHandlerOptions) {\n const {\n model,\n systemPrompt = 'You are a helpful admin assistant for the App Catalog application. Help users manage apps, data sources, and MCP server configurations.',\n tools = {},\n validateConfig,\n } = options\n\n return async (req: Request, res: Response) => {\n try {\n // Validate configuration if validator provided\n if (validateConfig) {\n validateConfig()\n }\n\n const { messages } = req.body as { messages: Array<UIMessageInput> }\n const coreMessages = convertToCoreMessages(messages)\n\n console.log(\n '[Admin Chat] Received messages:',\n JSON.stringify(coreMessages, null, 2),\n )\n console.log('[Admin Chat] Available tools:', Object.keys(tools))\n\n const result = streamText({\n model,\n system: systemPrompt,\n messages: coreMessages,\n tools,\n // Allow up to 5 steps so the model can call tools and then generate a response\n stopWhen: stepCountIs(5),\n onFinish: (event) => {\n console.log('[Admin Chat] Finished:', {\n finishReason: event.finishReason,\n usage: event.usage,\n hasText: !!event.text,\n textLength: event.text.length,\n })\n },\n })\n\n // Use UI message stream response which is compatible with AI SDK React hooks\n const response = result.toUIMessageStreamResponse()\n\n // Copy headers from the response\n response.headers.forEach((value, key) => {\n res.setHeader(key, value)\n })\n\n // Pipe the stream to the response\n if (response.body) {\n const reader = response.body.getReader()\n const pump = async (): Promise<void> => {\n const { done, value } = await reader.read()\n if (done) {\n res.end()\n return\n }\n res.write(value)\n return pump()\n }\n await pump()\n } else {\n console.error('[Admin Chat] No response body')\n res.status(500).json({ error: 'No response from AI model' })\n }\n } catch (error) {\n console.error('[Admin Chat] Error:', error)\n res.status(500).json({ error: 'Failed to process chat request' })\n }\n }\n}\n\n// Re-export tool helper for convenience\nexport { tool }\n","import type { Tool } from 'ai'\nimport { z } from 'zod'\nimport { getDbClient } from '../../../db'\n\n/**\n * Generic interface for executing raw SQL queries.\n * Can be implemented with Prisma's $queryRawUnsafe or any other SQL client.\n */\nexport interface DatabaseClient {\n /** Execute a SELECT query and return results */\n query: <T = unknown>(sql: string) => Promise<Array<T>>\n /** Execute an INSERT/UPDATE/DELETE and return affected row count */\n execute: (sql: string) => Promise<{ affectedRows: number }>\n /** Get list of tables in the database */\n getTables: () => Promise<Array<string>>\n /** Get columns for a specific table */\n getColumns: (\n tableName: string,\n ) => Promise<Array<{ name: string; type: string; nullable: boolean }>>\n}\n\n/**\n * Creates a DatabaseClient from a Prisma client.\n */\nexport function createPrismaDatabaseClient(prisma: {\n $queryRawUnsafe: <T>(sql: string) => Promise<T>\n $executeRawUnsafe: (sql: string) => Promise<number>\n}): DatabaseClient {\n return {\n query: async <T = unknown>(sql: string): Promise<Array<T>> => {\n const result = await prisma.$queryRawUnsafe<Array<T>>(sql)\n return result\n },\n execute: async (sql: string) => {\n const affectedRows = await prisma.$executeRawUnsafe(sql)\n return { affectedRows }\n },\n getTables: async () => {\n const tables = await prisma.$queryRawUnsafe<Array<{ tablename: string }>>(\n `SELECT tablename FROM pg_tables WHERE schemaname = 'public'`,\n )\n return tables.map((t) => t.tablename)\n },\n getColumns: async (tableName: string) => {\n const columns = await prisma.$queryRawUnsafe<\n Array<{\n column_name: string\n data_type: string\n is_nullable: string\n }>\n >(\n `SELECT column_name, data_type, is_nullable\n FROM information_schema.columns\n WHERE table_name = '${tableName}' AND table_schema = 'public'`,\n )\n return columns.map((c) => ({\n name: c.column_name,\n type: c.data_type,\n nullable: c.is_nullable === 'YES',\n }))\n },\n }\n}\n\n// Define zod schemas for tool parameters\nconst querySchema = z.object({\n sql: z.string().describe('The SELECT SQL query to execute'),\n})\n\nconst modifySchema = z.object({\n sql: z\n .string()\n .describe('The INSERT, UPDATE, or DELETE SQL query to execute'),\n confirmed: z\n .boolean()\n .describe('Must be true to execute destructive operations'),\n})\n\nconst schemaParamsSchema = z.object({\n tableName: z\n .string()\n .optional()\n .describe(\n 'Specific table name to get columns for. If not provided, returns list of all tables.',\n ),\n})\n\ntype QueryInput = z.infer<typeof querySchema>\ntype ModifyInput = z.infer<typeof modifySchema>\ntype SchemaInput = z.infer<typeof schemaParamsSchema>\n\n/**\n * Creates a DatabaseClient using the internal backend-core Prisma client.\n * This is a convenience function for apps that don't need to pass their own Prisma client.\n */\nfunction createInternalDatabaseClient(): DatabaseClient {\n return createPrismaDatabaseClient(getDbClient())\n}\n\n/**\n * Creates AI tools for generic database access.\n *\n * The AI uses these internally - users interact via natural language.\n * Results are formatted as tables by the AI based on the system prompt.\n * Uses the internal backend-core Prisma client automatically.\n */\nexport function createDatabaseTools(): Record<string, Tool> {\n const db = createInternalDatabaseClient()\n const queryDatabase: Tool<QueryInput, unknown> = {\n description: `Execute a SELECT query to read data from the database.\nUse this to list, search, or filter records from any table.\nAlways use double quotes around table and column names for PostgreSQL (e.g., SELECT * FROM \"App\").\nReturn results will be formatted as a table for the user.`,\n inputSchema: querySchema,\n execute: async ({ sql }) => {\n console.log(`Executing ${sql}`)\n // Safety check - only allow SELECT\n const normalizedSql = sql.trim().toUpperCase()\n if (!normalizedSql.startsWith('SELECT')) {\n return {\n error:\n 'Only SELECT queries are allowed with queryDatabase. Use modifyDatabase for changes.',\n }\n }\n try {\n const results = await db.query(sql)\n return {\n success: true,\n rowCount: Array.isArray(results) ? results.length : 0,\n data: results,\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Query failed',\n }\n }\n },\n }\n\n const modifyDatabase: Tool<ModifyInput, unknown> = {\n description: `Execute an INSERT, UPDATE, or DELETE query to modify data.\nUse double quotes around table and column names for PostgreSQL.\nIMPORTANT: Always ask for user confirmation before executing. Set confirmed=true only after user confirms.\nFor UPDATE/DELETE, always include a WHERE clause to avoid affecting all rows.`,\n inputSchema: modifySchema,\n execute: async ({ sql, confirmed }) => {\n if (!confirmed) {\n return {\n needsConfirmation: true,\n message: 'Please confirm you want to execute this operation.',\n sql,\n }\n }\n\n // Safety check - don't allow SELECT here\n const normalizedSql = sql.trim().toUpperCase()\n if (normalizedSql.startsWith('SELECT')) {\n return { error: 'Use queryDatabase for SELECT queries.' }\n }\n\n // Extra safety - warn about missing WHERE on UPDATE/DELETE\n if (\n (normalizedSql.startsWith('UPDATE') ||\n normalizedSql.startsWith('DELETE')) &&\n !normalizedSql.includes('WHERE')\n ) {\n return {\n error:\n 'UPDATE and DELETE queries must include a WHERE clause for safety.',\n sql,\n }\n }\n\n try {\n const result = await db.execute(sql)\n return {\n success: true,\n affectedRows: result.affectedRows,\n message: `Operation completed. ${result.affectedRows} row(s) affected.`,\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Operation failed',\n }\n }\n },\n }\n\n const getDatabaseSchema: Tool<SchemaInput, unknown> = {\n description: `Get information about database tables and their columns.\nUse this to understand the database structure before writing queries.\nCall without tableName to list all tables, or with tableName to get columns for a specific table.`,\n inputSchema: schemaParamsSchema,\n execute: async ({ tableName }) => {\n try {\n if (tableName) {\n const columns = await db.getColumns(tableName)\n return {\n success: true,\n table: tableName,\n columns,\n }\n } else {\n const tables = await db.getTables()\n return {\n success: true,\n tables,\n }\n }\n } catch (error) {\n return {\n success: false,\n error:\n error instanceof Error ? error.message : 'Failed to get schema',\n }\n }\n },\n }\n\n return {\n queryDatabase,\n modifyDatabase,\n getDatabaseSchema,\n }\n}\n\n/**\n * Default system prompt for the database admin assistant.\n * Can be customized or extended.\n */\nexport const DEFAULT_ADMIN_SYSTEM_PROMPT = `You are a helpful database admin assistant. You help users view and manage data in the database.\n\nIMPORTANT RULES:\n1. When showing data, ALWAYS format it as a numbered ASCII table so users can reference rows by number\n2. NEVER show raw SQL to users - just describe what you're doing in plain language\n3. When users ask to modify data (update, delete, create), ALWAYS confirm before executing\n4. For updates, show the current value and ask for confirmation before changing\n5. Keep responses concise and focused on the data\n\nFORMATTING EXAMPLE:\nWhen user asks \"show me all apps\", respond like:\n\"Here are all the apps:\n\n| # | ID | Slug | Display Name | Icon |\n|---|----|---------|-----------------| -------|\n| 1 | 1 | portal | Portal | portal |\n| 2 | 2 | admin | Admin Dashboard | admin |\n| 3 | 3 | api | API Service | null |\n\nFound 3 apps total.\"\n\nWhen user says \"update row 2 display name to 'Admin Panel'\":\n1. First confirm: \"I'll update the app 'Admin Dashboard' (ID: 2) to have display name 'Admin Panel'. Proceed?\"\n2. Only after user confirms, execute the update\n3. Then show the updated row\n\nAVAILABLE TABLES:\nUse getDatabaseSchema tool to discover tables and their columns.\n`\n","import type { Request, Response, Router } from 'express'\nimport multer from 'multer'\nimport { createHash } from 'node:crypto'\nimport { getDbClient } from '../../db'\nimport { getExtensionFromFilename, getExtensionFromMimeType } from './iconUtils'\n\n// Configure multer for memory storage\nconst upload = multer({\n storage: multer.memoryStorage(),\n limits: {\n fileSize: 10 * 1024 * 1024, // 10MB limit\n },\n fileFilter: (_req, file, cb) => {\n // Accept images only\n if (!file.mimetype.startsWith('image/')) {\n cb(new Error('Only image files are allowed'))\n return\n }\n cb(null, true)\n },\n})\n\nexport interface IconRestControllerConfig {\n /**\n * Base path for icon endpoints (e.g., '/api/icons')\n */\n basePath: string\n}\n\n/**\n * Registers REST endpoints for icon upload and retrieval\n *\n * Endpoints:\n * - POST {basePath}/upload - Upload a new icon (multipart/form-data with 'icon' field and 'name' field)\n * - GET {basePath}/:id - Get icon binary by ID\n * - GET {basePath}/:id/metadata - Get icon metadata only\n */\nexport function registerIconRestController(\n router: Router,\n config: IconRestControllerConfig,\n): void {\n const { basePath } = config\n\n // Upload endpoint - accepts multipart/form-data\n router.post(\n `${basePath}/upload`,\n upload.single('icon'),\n async (req: Request, res: Response) => {\n try {\n if (!req.file) {\n res.status(400).json({ error: 'No file uploaded' })\n return\n }\n\n let name = req.body['name'] as string\n if (!name) {\n res.status(400).json({ error: 'Name is required' })\n return\n }\n\n // Extract extension from original filename or derive from MIME type\n const extension =\n getExtensionFromFilename(req.file.originalname) ||\n getExtensionFromMimeType(req.file.mimetype)\n\n // Add extension to name if not already present\n if (!name.includes('.')) {\n name = `${name}.${extension}`\n }\n\n const prisma = getDbClient()\n const checksum = createHash('sha256')\n .update(req.file.buffer)\n .digest('hex')\n const icon = await prisma.dbAsset.create({\n data: {\n name,\n assetType: 'icon',\n content: new Uint8Array(req.file.buffer),\n mimeType: req.file.mimetype,\n fileSize: req.file.size,\n checksum,\n },\n })\n\n res.status(201).json({\n id: icon.id,\n name: icon.name,\n mimeType: icon.mimeType,\n fileSize: icon.fileSize,\n createdAt: icon.createdAt,\n })\n } catch (error) {\n console.error('Error uploading icon:', error)\n res.status(500).json({ error: 'Failed to upload icon' })\n }\n },\n )\n\n // Get icon binary by name (e.g., /api/icons/jira.svg)\n router.get(`${basePath}/:name`, async (req: Request, res: Response) => {\n try {\n const { name } = req.params\n\n const prisma = getDbClient()\n const icon = await prisma.dbAsset.findFirst({\n where: {\n name,\n assetType: 'icon',\n },\n select: {\n content: true,\n mimeType: true,\n name: true,\n },\n })\n\n if (!icon) {\n res.status(404).json({ error: 'Icon not found' })\n return\n }\n\n // Set appropriate headers\n res.setHeader('Content-Type', icon.mimeType)\n res.setHeader('Content-Disposition', `inline; filename=\"${icon.name}\"`)\n res.setHeader('Cache-Control', 'public, max-age=86400') // Cache for 1 day\n\n // Send binary content\n res.send(icon.content)\n } catch (error) {\n console.error('Error fetching icon:', error)\n res.status(500).json({ error: 'Failed to fetch icon' })\n }\n })\n\n // Get icon metadata only (no binary content)\n router.get(\n `${basePath}/:name/metadata`,\n async (req: Request, res: Response) => {\n try {\n const { name } = req.params\n\n const prisma = getDbClient()\n const icon = await prisma.dbAsset.findFirst({\n where: {\n name,\n assetType: 'icon',\n },\n select: {\n id: true,\n name: true,\n mimeType: true,\n fileSize: true,\n createdAt: true,\n updatedAt: true,\n },\n })\n\n if (!icon) {\n res.status(404).json({ error: 'Icon not found' })\n return\n }\n\n res.json(icon)\n } catch (error) {\n console.error('Error fetching icon metadata:', error)\n res.status(500).json({ error: 'Failed to fetch icon metadata' })\n }\n },\n )\n}\n","import { getDbClient } from '../../db'\nimport { generateChecksum, getImageDimensions } from '../assets/assetUtils'\n\nexport interface UpsertIconInput {\n name: string\n content: Buffer\n mimeType: string\n fileSize: number\n}\n\n/**\n * Upsert an icon to the database.\n * If an icon with the same name exists, it will be updated.\n * Otherwise, a new icon will be created.\n */\nexport async function upsertIcon(input: UpsertIconInput) {\n const prisma = getDbClient()\n \n const checksum = generateChecksum(input.content)\n const { width, height } = await getImageDimensions(input.content)\n \n return prisma.dbAsset.upsert({\n where: { name: input.name },\n update: {\n content: new Uint8Array(input.content),\n checksum,\n mimeType: input.mimeType,\n fileSize: input.fileSize,\n width,\n height,\n },\n create: {\n name: input.name,\n assetType: 'icon',\n content: new Uint8Array(input.content),\n checksum,\n mimeType: input.mimeType,\n fileSize: input.fileSize,\n width,\n height,\n },\n })\n}\n\n/**\n * Upsert multiple icons to the database.\n * This is more efficient than calling upsertIcon multiple times.\n */\nexport async function upsertIcons(icons: Array<UpsertIconInput>) {\n const results: Array<Awaited<ReturnType<typeof upsertIcon>>> = []\n for (const icon of icons) {\n const result = await upsertIcon(icon)\n results.push(result)\n }\n return results\n}\n\n/**\n * Get an asset (icon or screenshot) by name from the database.\n * Returns the asset content, mimeType, and name if found.\n */\nexport async function getAssetByName(name: string) {\n const prisma = getDbClient()\n \n return prisma.dbAsset.findUnique({\n where: { name },\n select: {\n content: true,\n mimeType: true,\n name: true,\n },\n })\n}\n","import type { Request, Response, Router } from 'express'\nimport multer from 'multer'\nimport sharp from 'sharp'\nimport { getDbClient } from '../../db'\nimport { getImageFormat, isRasterImage, resizeImage } from './assetUtils'\nimport { upsertAsset } from './upsertAsset'\n\n// Configure multer for memory storage\nconst upload = multer({\n storage: multer.memoryStorage(),\n limits: {\n fileSize: 10 * 1024 * 1024, // 10MB limit\n },\n fileFilter: (_req, file, cb) => {\n // Accept images only\n if (!file.mimetype.startsWith('image/')) {\n cb(new Error('Only image files are allowed'))\n return\n }\n cb(null, true)\n },\n})\n\nexport interface AssetRestControllerConfig {\n /**\n * Base path for asset endpoints (e.g., '/api/assets')\n */\n basePath: string\n}\n\nexport interface ParseAssetParams {\n buffer: Buffer\n originalFilename: string\n fileSize?: number\n}\n\nexport interface ParseAssetReturn {\n checksum: string\n fileSize: number\n mimeType: string\n width?: number\n height?: number\n}\n\n/**\n * Registers REST endpoints for universal asset upload and retrieval\n *\n * Endpoints:\n * - POST {basePath}/upload - Upload a new asset (multipart/form-data)\n * - GET {basePath}/:id - Get asset binary by ID\n * - GET {basePath}/:id/metadata - Get asset metadata only\n * - GET {basePath}/by-name/:name - Get asset binary by name\n */\nexport function registerAssetRestController(\n router: Router,\n config: AssetRestControllerConfig,\n): void {\n const { basePath } = config\n const prisma = getDbClient()\n\n // Upload endpoint - accepts multipart/form-data\n router.post(\n `${basePath}/upload`,\n upload.single('asset'),\n async (req: Request, res: Response) => {\n try {\n if (!req.file) {\n res.status(400).json({ error: 'No file uploaded' })\n return\n }\n\n const name = req.body['name'] as string\n const assetType = req.body['assetType']\n\n if (!name) {\n res.status(400).json({ error: 'Name is required' })\n return\n }\n\n const id = await upsertAsset({\n prisma,\n buffer: req.file.buffer,\n name,\n originalFilename: req.file.filename,\n assetType,\n })\n\n res.status(201).json({ id })\n } catch (error) {\n console.error('Error uploading asset:', error)\n res.status(500).json({ error: 'Failed to upload asset' })\n }\n },\n )\n\n // Get asset binary by ID\n router.get(`${basePath}/:id`, async (req: Request, res: Response) => {\n try {\n const { id } = req.params\n\n const asset = await prisma.dbAsset.findUnique({\n where: { id },\n select: {\n content: true,\n mimeType: true,\n name: true,\n width: true,\n height: true,\n },\n })\n\n if (!asset) {\n res.status(404).json({ error: 'Asset not found' })\n return\n }\n\n const resizeEnabled =\n String(process.env.EH_ASSETS_RESIZE_ENABLED || 'true') === 'true'\n const wParam = req.query['w'] as string | undefined\n const width = wParam ? Number.parseInt(wParam, 10) : undefined\n\n let outBuffer: Uint8Array = asset.content\n let outMime = asset.mimeType\n\n const shouldResize =\n resizeEnabled &&\n isRasterImage(asset.mimeType) &&\n !!width &&\n Number.isFinite(width) &&\n width > 0\n\n if (shouldResize) {\n const fmt = getImageFormat(asset.mimeType) || 'jpeg'\n const buf = await resizeImage(\n Buffer.from(asset.content),\n width,\n undefined,\n fmt,\n )\n outBuffer = new Uint8Array(buf)\n outMime = `image/${fmt}`\n }\n\n // Set appropriate headers\n res.setHeader('Content-Type', outMime)\n res.setHeader('Content-Disposition', `inline; filename=\"${asset.name}\"`)\n res.setHeader('Cache-Control', 'public, max-age=86400') // Cache for 1 day\n\n // Send binary content (resized if requested)\n res.send(outBuffer)\n } catch (error) {\n console.error('Error fetching asset:', error)\n res.status(500).json({ error: 'Failed to fetch asset' })\n }\n })\n\n // Get asset metadata only (no binary content)\n router.get(\n `${basePath}/:id/metadata`,\n async (req: Request, res: Response) => {\n try {\n const { id } = req.params\n\n const asset = await prisma.dbAsset.findUnique({\n where: { id },\n select: {\n id: true,\n name: true,\n assetType: true,\n mimeType: true,\n fileSize: true,\n width: true,\n height: true,\n createdAt: true,\n updatedAt: true,\n },\n })\n\n if (!asset) {\n res.status(404).json({ error: 'Asset not found' })\n return\n }\n\n res.json(asset)\n } catch (error) {\n console.error('Error fetching asset metadata:', error)\n res.status(500).json({ error: 'Failed to fetch asset metadata' })\n }\n },\n )\n\n // Get asset binary by name\n router.get(\n `${basePath}/by-name/:name`,\n async (req: Request, res: Response) => {\n try {\n const { name } = req.params\n\n const asset = await prisma.dbAsset.findUnique({\n where: { name },\n select: {\n content: true,\n mimeType: true,\n name: true,\n width: true,\n height: true,\n },\n })\n\n if (!asset) {\n res.status(404).json({ error: 'Asset not found' })\n return\n }\n\n const resizeEnabled =\n String(process.env.EH_ASSETS_RESIZE_ENABLED || 'true') === 'true'\n const wParam = req.query['w'] as string | undefined\n const width = wParam ? Number.parseInt(wParam, 10) : undefined\n\n let outBuffer: Uint8Array = asset.content\n let outMime = asset.mimeType\n\n const isRaster =\n asset.mimeType.startsWith('image/') && !asset.mimeType.includes('svg')\n const shouldResize =\n resizeEnabled &&\n isRaster &&\n !!width &&\n Number.isFinite(width) &&\n width > 0\n\n if (shouldResize) {\n const fmt = asset.mimeType.includes('png')\n ? 'png'\n : asset.mimeType.includes('webp')\n ? 'webp'\n : 'jpeg'\n\n let buf: Buffer\n const pipeline = sharp(Buffer.from(asset.content)).resize({\n width,\n fit: 'inside',\n withoutEnlargement: true,\n })\n if (fmt === 'png') {\n buf = await pipeline.png().toBuffer()\n outMime = 'image/png'\n } else if (fmt === 'webp') {\n buf = await pipeline.webp().toBuffer()\n outMime = 'image/webp'\n } else {\n buf = await pipeline.jpeg().toBuffer()\n outMime = 'image/jpeg'\n }\n outBuffer = new Uint8Array(buf)\n }\n\n // Set appropriate headers\n res.setHeader('Content-Type', outMime)\n res.setHeader('Content-Disposition', `inline; filename=\"${asset.name}\"`)\n res.setHeader('Cache-Control', 'public, max-age=86400') // Cache for 1 day\n\n // Send binary content (resized if requested)\n res.send(outBuffer)\n } catch (error) {\n console.error('Error fetching asset by name:', error)\n res.status(500).json({ error: 'Failed to fetch asset' })\n }\n },\n )\n}\n","import type { Request, Response, Router } from 'express'\nimport sharp from 'sharp'\nimport { getDbClient } from '../../db'\n\nexport interface ScreenshotRestControllerConfig {\n /**\n * Base path for screenshot endpoints (e.g., '/api/screenshots')\n */\n basePath: string\n}\n\n/**\n * Registers REST endpoints for screenshot retrieval\n * \n * Endpoints:\n * - GET {basePath}/app/:appId - Get all screenshots for an app\n * - GET {basePath}/:id - Get screenshot binary by ID\n * - GET {basePath}/:id/metadata - Get screenshot metadata only\n */\nexport function registerScreenshotRestController(\n router: Router,\n config: ScreenshotRestControllerConfig,\n): void {\n const { basePath } = config\n\n // Get all screenshots for an app\n router.get(`${basePath}/app/:appSlug`, async (req: Request, res: Response) => {\n try {\n const { appSlug } = req.params\n\n const prisma = getDbClient()\n \n // Find app by slug\n const app = await prisma.dbAppForCatalog.findUnique({\n where: { slug: appSlug },\n select: { screenshotIds: true },\n })\n\n if (!app) {\n res.status(404).json({ error: 'App not found' })\n return\n }\n\n // Fetch all screenshots for the app\n const screenshots = await prisma.dbAsset.findMany({\n where: {\n id: { in: app.screenshotIds },\n assetType: 'screenshot',\n },\n select: {\n id: true,\n name: true,\n mimeType: true,\n fileSize: true,\n width: true,\n height: true,\n createdAt: true,\n },\n })\n\n res.json(screenshots)\n } catch (error) {\n console.error('Error fetching app screenshots:', error)\n res.status(500).json({ error: 'Failed to fetch screenshots' })\n }\n })\n\n // Get first screenshot for an app (convenience endpoint)\n router.get(`${basePath}/app/:appSlug/first`, async (req: Request, res: Response) => {\n try {\n const { appSlug } = req.params\n\n const prisma = getDbClient()\n \n // Find app by slug\n const app = await prisma.dbAppForCatalog.findUnique({\n where: { slug: appSlug },\n select: { screenshotIds: true },\n })\n\n if (!app || app.screenshotIds.length === 0) {\n res.status(404).json({ error: 'No screenshots found' })\n return\n }\n\n // Fetch first screenshot\n const screenshot = await prisma.dbAsset.findUnique({\n where: { id: app.screenshotIds[0] },\n select: {\n id: true,\n name: true,\n mimeType: true,\n fileSize: true,\n width: true,\n height: true,\n createdAt: true,\n },\n })\n\n if (!screenshot) {\n res.status(404).json({ error: 'Screenshot not found' })\n return\n }\n\n res.json(screenshot)\n } catch (error) {\n console.error('Error fetching first screenshot:', error)\n res.status(500).json({ error: 'Failed to fetch screenshot' })\n }\n })\n\n // Get screenshot binary by ID\n router.get(`${basePath}/:id`, async (req: Request, res: Response) => {\n try {\n const { id } = req.params\n const sizeParam = req.query.size as string | undefined\n const targetSize = sizeParam ? parseInt(sizeParam, 10) : undefined\n\n const prisma = getDbClient()\n const screenshot = await prisma.dbAsset.findUnique({\n where: { id },\n select: {\n content: true,\n mimeType: true,\n name: true,\n },\n })\n\n if (!screenshot) {\n res.status(404).json({ error: 'Screenshot not found' })\n return\n }\n\n let content: Uint8Array | Buffer = screenshot.content\n\n // Resize if size parameter provided\n if (targetSize && targetSize > 0) {\n try {\n content = await sharp(screenshot.content)\n .resize(targetSize, targetSize, {\n fit: 'inside',\n withoutEnlargement: true,\n })\n .toBuffer()\n } catch (resizeError) {\n console.error('Error resizing screenshot:', resizeError)\n // Fall back to original if resize fails\n }\n }\n\n // Set appropriate headers\n res.setHeader('Content-Type', screenshot.mimeType)\n res.setHeader('Content-Disposition', `inline; filename=\"${screenshot.name}\"`)\n res.setHeader('Cache-Control', 'public, max-age=86400') // Cache for 1 day\n\n // Send binary content\n res.send(content)\n } catch (error) {\n console.error('Error fetching screenshot:', error)\n res.status(500).json({ error: 'Failed to fetch screenshot' })\n }\n })\n\n // Get screenshot metadata only (no binary content)\n router.get(`${basePath}/:id/metadata`, async (req: Request, res: Response) => {\n try {\n const { id } = req.params\n\n const prisma = getDbClient()\n const screenshot = await prisma.dbAsset.findUnique({\n where: { id },\n select: {\n id: true,\n name: true,\n mimeType: true,\n fileSize: true,\n width: true,\n height: true,\n createdAt: true,\n updatedAt: true,\n },\n })\n\n if (!screenshot) {\n res.status(404).json({ error: 'Screenshot not found' })\n return\n }\n\n res.json(screenshot)\n } catch (error) {\n console.error('Error fetching screenshot metadata:', error)\n res.status(500).json({ error: 'Failed to fetch screenshot metadata' })\n }\n })\n}\n","import { readFileSync, readdirSync } from 'node:fs'\nimport { extname, join } from 'node:path'\nimport { getDbClient } from '../../db'\nimport { generateChecksum, getImageDimensions } from './assetUtils'\n\nexport interface SyncAssetsConfig {\n /**\n * Directory containing icon files to sync\n */\n iconsDir?: string\n\n /**\n * Directory containing screenshot files to sync\n */\n screenshotsDir?: string\n}\n\n/**\n * Sync local asset files (icons and screenshots) from directories into the database.\n *\n * This function allows consuming applications to sync asset files without directly\n * exposing the Prisma client. It handles:\n * - Icon files: Assigned to apps by matching filename to icon name patterns\n * - Screenshot files: Assigned to apps by matching filename to app ID (format: <app-id>_screenshot_<no>.<ext>)\n *\n * @param config Configuration with paths to icon and screenshot directories\n */\nexport async function syncAssets(config: SyncAssetsConfig): Promise<{\n iconsUpserted: number\n screenshotsUpserted: number\n}> {\n const prisma = getDbClient()\n let iconsUpserted = 0\n let screenshotsUpserted = 0\n\n // Sync icons from local/icons directory\n if (config.iconsDir) {\n console.log(`📁 Syncing icons from ${config.iconsDir}...`)\n iconsUpserted = await syncIconsFromDirectory(prisma, config.iconsDir)\n console.log(` ✓ Upserted ${iconsUpserted} icons`)\n }\n\n // Sync screenshots from local/screenshots directory\n if (config.screenshotsDir) {\n console.log(`📷 Syncing screenshots from ${config.screenshotsDir}...`)\n screenshotsUpserted = await syncScreenshotsFromDirectory(\n prisma,\n config.screenshotsDir,\n )\n console.log(\n ` ✓ Upserted ${screenshotsUpserted} screenshots and assigned to apps`,\n )\n }\n\n return {\n iconsUpserted,\n screenshotsUpserted,\n }\n}\n\n/**\n * Sync icon files from a directory\n */\nasync function syncIconsFromDirectory(\n prisma: ReturnType<typeof getDbClient>,\n iconsDir: string,\n): Promise<number> {\n let count = 0\n\n try {\n const files = readdirSync(iconsDir)\n\n for (const file of files) {\n const filePath = join(iconsDir, file)\n const ext = extname(file).toLowerCase().slice(1) // Remove leading dot\n\n // Skip non-image files\n if (!['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'].includes(ext)) {\n continue\n }\n\n try {\n const content = readFileSync(filePath)\n const buffer = Buffer.from(content)\n const checksum = generateChecksum(buffer)\n const iconName = file.replace(/\\.[^/.]+$/, '') // Remove extension\n\n // Check if asset with same checksum already exists\n const existing = await prisma.dbAsset.findFirst({\n where: { checksum, assetType: 'icon' },\n })\n\n if (existing) {\n continue // Already synced\n }\n\n // Extract dimensions for raster images\n let width: number | null = null\n let height: number | null = null\n if (!ext.includes('svg')) {\n const { width: w, height: h } = await getImageDimensions(buffer)\n width = w ?? null\n height = h ?? null\n }\n\n // Determine MIME type\n const mimeType =\n {\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n webp: 'image/webp',\n svg: 'image/svg+xml',\n }[ext] || 'application/octet-stream'\n\n await prisma.dbAsset.create({\n data: {\n name: iconName,\n assetType: 'icon',\n content: new Uint8Array(buffer),\n checksum,\n mimeType,\n fileSize: buffer.length,\n width,\n height,\n },\n })\n\n count++\n } catch (error) {\n console.warn(` ⚠ Failed to sync icon ${file}:`, error)\n }\n }\n } catch (error) {\n console.error(` ❌ Error reading icons directory:`, error)\n }\n\n return count\n}\n\n/**\n * Sync screenshot files from a directory and assign to apps\n */\nasync function syncScreenshotsFromDirectory(\n prisma: ReturnType<typeof getDbClient>,\n screenshotsDir: string,\n): Promise<number> {\n let count = 0\n\n try {\n const files = readdirSync(screenshotsDir)\n\n // Group screenshots by app ID\n const screenshotsByApp = new Map<\n string,\n Array<{ path: string; ext: string }>\n >()\n\n for (const file of files) {\n // Parse filename: <app-id>_screenshot_<no>.<ext>\n const match = file.match(/^(.+?)_screenshot_(\\d+)\\.([^.]+)$/)\n if (!match || !match[1] || !match[3]) {\n continue\n }\n\n const appId = match[1]\n const ext = match[3]\n if (!screenshotsByApp.has(appId)) {\n screenshotsByApp.set(appId, [])\n }\n screenshotsByApp.get(appId)!.push({\n path: join(screenshotsDir, file),\n ext,\n })\n }\n\n // Process each app's screenshots\n for (const [appId, screenshots] of screenshotsByApp) {\n try {\n // Check if app exists\n const app = await prisma.dbAppForCatalog.findUnique({\n where: { slug: appId },\n select: { id: true },\n })\n\n if (!app) {\n console.warn(` ⚠ App not found: ${appId}`)\n continue\n }\n\n // Sync screenshots for this app\n for (const screenshot of screenshots) {\n try {\n const content = readFileSync(screenshot.path)\n const buffer = Buffer.from(content)\n const checksum = generateChecksum(buffer)\n\n // Check if screenshot with same checksum already exists\n const existing = await prisma.dbAsset.findFirst({\n where: { checksum, assetType: 'screenshot' },\n })\n\n if (existing) {\n // Link to app via screenshotIds array if not already linked\n const existingApp = await prisma.dbAppForCatalog.findUnique({\n where: { slug: appId },\n })\n if (\n existingApp &&\n !existingApp.screenshotIds.includes(existing.id)\n ) {\n await prisma.dbAppForCatalog.update({\n where: { slug: appId },\n data: {\n screenshotIds: [...existingApp.screenshotIds, existing.id],\n },\n })\n }\n continue\n }\n\n // Extract dimensions\n const { width, height } = await getImageDimensions(buffer)\n\n // Determine MIME type\n const mimeType =\n {\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n webp: 'image/webp',\n }[screenshot.ext.toLowerCase()] || 'application/octet-stream'\n\n // Create screenshot asset\n const asset = await prisma.dbAsset.create({\n data: {\n name: `${appId}-screenshot-${Date.now()}`,\n assetType: 'screenshot',\n content: new Uint8Array(buffer),\n checksum,\n mimeType,\n fileSize: buffer.length,\n width: width ?? null,\n height: height ?? null,\n },\n })\n\n // Link screenshot to app via screenshotIds array\n await prisma.dbAppForCatalog.update({\n where: { slug: appId },\n data: {\n screenshotIds: {\n push: asset.id,\n },\n },\n })\n\n count++\n } catch (error) {\n console.warn(\n ` ⚠ Failed to sync screenshot ${screenshot.path}:`,\n error,\n )\n }\n }\n } catch (error) {\n console.warn(` ⚠ Failed to process app ${appId}:`, error)\n }\n }\n } catch (error) {\n console.error(` ❌ Error reading screenshots directory:`, error)\n }\n\n return count\n}\n","import type { PrismaClient as CorePrismaClient } from '@prisma/client'\n\nexport interface ApprovalMethodSyncInput {\n slug: string\n type: 'service' | 'personTeam' | 'custom'\n displayName: string\n}\n\n/**\n * Syncs approval methods to the database using upsert logic based on type + displayName.\n *\n * @param prisma - The PrismaClient instance from the backend-core database\n * @param methods - Array of approval methods to sync\n */\nexport async function syncApprovalMethods(\n prisma: CorePrismaClient,\n methods: Array<ApprovalMethodSyncInput>,\n): Promise<void> {\n // Use transaction for atomicity\n await prisma.$transaction(\n methods.map((method) =>\n prisma.dbApprovalMethod.upsert({\n where: {\n slug: method.slug,\n },\n update: {\n displayName: method.displayName,\n type: method.type,\n },\n create: {\n slug: method.slug,\n type: method.type,\n displayName: method.displayName,\n },\n }),\n ),\n )\n}\n","import { PrismaClient } from '@prisma/client'\nimport type { EhDatabaseConfig } from './types'\nimport { setDbClient } from '../db/client'\n\n/**\n * Formats a database connection URL from structured config.\n */\nfunction formatConnectionUrl(config: EhDatabaseConfig): string {\n if ('url' in config) {\n return config.url\n }\n\n const { host, port, database, username, password, schema = 'public' } = config\n return `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}?schema=${schema}`\n}\n\n/**\n * Internal database manager used by the middleware.\n * Handles connection URL formatting and lifecycle.\n */\nexport class EhDatabaseManager {\n private client: PrismaClient | null = null\n private config: EhDatabaseConfig\n\n constructor(config: EhDatabaseConfig) {\n this.config = config\n }\n\n /**\n * Get or create the Prisma client instance.\n * Uses lazy initialization for flexibility.\n */\n getClient(): PrismaClient {\n if (!this.client) {\n const datasourceUrl = formatConnectionUrl(this.config)\n\n this.client = new PrismaClient({\n datasourceUrl,\n log:\n process.env.NODE_ENV === 'development'\n ? ['warn', 'error']\n : ['warn', 'error'],\n })\n\n // Bridge with existing backend-core getDbClient() usage\n setDbClient(this.client)\n }\n return this.client\n }\n\n async connect(): Promise<void> {\n const client = this.getClient()\n await client.$connect()\n }\n\n async disconnect(): Promise<void> {\n if (this.client) {\n await this.client.$disconnect()\n this.client = null\n }\n }\n}\n","import type { AppCatalogCompanySpecificBackend } from '../types/backend/companySpecificBackend'\nimport type { EhBackendProvider } from './types'\n\n/**\n * Type guard to check if an object implements AppCatalogCompanySpecificBackend.\n */\nfunction isBackendInstance(obj: unknown): obj is AppCatalogCompanySpecificBackend {\n return (\n typeof obj === 'object' &&\n obj !== null &&\n (typeof (obj as AppCatalogCompanySpecificBackend).getApps === 'function' ||\n typeof (obj as AppCatalogCompanySpecificBackend).getApps === 'undefined')\n )\n}\n\n/**\n * Normalizes different backend provider types into a consistent async factory function.\n * Supports:\n * - Direct object implementing AppCatalogCompanySpecificBackend\n * - Sync factory function that returns the backend\n * - Async factory function that returns the backend\n */\nexport function createBackendResolver(\n provider: EhBackendProvider,\n): () => Promise<AppCatalogCompanySpecificBackend> {\n // If it's already an object with the required methods, wrap it\n if (isBackendInstance(provider)) {\n return async () => provider\n }\n\n // If it's a function, call it and handle both sync and async results\n if (typeof provider === 'function') {\n return async () => {\n const result = provider()\n return result instanceof Promise ? result : result\n }\n }\n\n throw new Error(\n 'Invalid backend provider: must be an object implementing AppCatalogCompanySpecificBackend or a factory function',\n )\n}\n","import type { Request, Response } from 'express'\nimport { getDbClient } from '../../db'\n\n/**\n * Export the complete app catalog as JSON\n * Includes all fields from DbAppForCatalog and DbApprovalMethod\n */\nexport async function exportCatalog(\n _req: Request,\n res: Response,\n): Promise<void> {\n try {\n const prisma = getDbClient()\n\n // Fetch all catalog entries\n const apps = await prisma.dbAppForCatalog.findMany({\n orderBy: { slug: 'asc' },\n })\n\n // Fetch all approval methods\n const approvalMethods = await prisma.dbApprovalMethod.findMany({\n orderBy: { displayName: 'asc' },\n })\n\n res.json({\n version: '2.0',\n exportDate: new Date().toISOString(),\n apps,\n approvalMethods,\n })\n } catch (error) {\n console.error('Error exporting catalog:', error)\n res.status(500).json({ error: 'Failed to export catalog' })\n }\n}\n\n/**\n * Import/restore the complete app catalog from JSON\n * Overwrites existing data\n */\nexport async function importCatalog(\n req: Request,\n res: Response,\n): Promise<void> {\n try {\n const prisma = getDbClient()\n const { apps, approvalMethods } = req.body\n\n if (!Array.isArray(apps)) {\n res\n .status(400)\n .json({ error: 'Invalid data format: apps must be an array' })\n return\n }\n\n // Use transaction to ensure atomicity\n await prisma.$transaction(async (tx) => {\n // Delete all existing approval methods first (due to potential FK references)\n if (Array.isArray(approvalMethods)) {\n await tx.dbApprovalMethod.deleteMany({})\n }\n\n // Delete all existing catalog entries\n await tx.dbAppForCatalog.deleteMany({})\n\n // Insert approval methods first\n if (Array.isArray(approvalMethods)) {\n for (const method of approvalMethods) {\n // Remove id, createdAt, updatedAt to let Prisma generate new ones\n const { id, createdAt, updatedAt, ...methodData } = method\n\n await tx.dbApprovalMethod.create({\n data: methodData,\n })\n }\n }\n\n // Insert app catalog entries\n for (const app of apps) {\n // Remove id, createdAt, updatedAt to let Prisma generate new ones\n const { id, createdAt, updatedAt, ...appData } = app\n\n await tx.dbAppForCatalog.create({\n data: appData,\n })\n }\n })\n\n res.json({\n success: true,\n imported: {\n apps: apps.length,\n approvalMethods: Array.isArray(approvalMethods)\n ? approvalMethods.length\n : 0,\n },\n })\n } catch (error) {\n console.error('Error importing catalog:', error)\n res.status(500).json({ error: 'Failed to import catalog' })\n }\n}\n\n/**\n * Export an asset (icon or screenshot) by name\n */\nexport async function exportAsset(req: Request, res: Response): Promise<void> {\n try {\n const { name } = req.params\n const prisma = getDbClient()\n\n const asset = await prisma.dbAsset.findUnique({\n where: { name },\n })\n\n if (!asset) {\n res.status(404).json({ error: 'Asset not found' })\n return\n }\n\n // Set appropriate content type and send binary data\n res.set('Content-Type', asset.mimeType)\n res.set('Content-Disposition', `attachment; filename=\"${name}\"`)\n res.send(Buffer.from(asset.content))\n } catch (error) {\n console.error('Error exporting asset:', error)\n res.status(500).json({ error: 'Failed to export asset' })\n }\n}\n\n/**\n * List all assets with metadata\n */\nexport async function listAssets(_req: Request, res: Response): Promise<void> {\n try {\n const prisma = getDbClient()\n\n const assets = await prisma.dbAsset.findMany({\n select: {\n id: true,\n name: true,\n assetType: true,\n mimeType: true,\n fileSize: true,\n width: true,\n height: true,\n checksum: true,\n },\n orderBy: { name: 'asc' },\n })\n\n res.json({ assets })\n } catch (error) {\n console.error('Error listing assets:', error)\n res.status(500).json({ error: 'Failed to list assets' })\n }\n}\n\n/**\n * Import an asset (icon or screenshot)\n */\nexport async function importAsset(req: Request, res: Response): Promise<void> {\n try {\n const file = req.file\n const { name, assetType, mimeType, width, height } = req.body\n\n if (!file) {\n res.status(400).json({ error: 'No file uploaded' })\n return\n }\n\n const prisma = getDbClient()\n const crypto = await import('node:crypto')\n\n // Calculate checksum\n const checksum = crypto\n .createHash('sha256')\n .update(file.buffer)\n .digest('hex')\n\n // Convert Buffer to Uint8Array for Prisma Bytes type\n const content = new Uint8Array(file.buffer)\n\n // Upsert asset (update if exists, create if not)\n await prisma.dbAsset.upsert({\n where: { name },\n update: {\n content,\n checksum,\n mimeType: mimeType || file.mimetype,\n fileSize: file.size,\n width: width ? parseInt(width) : null,\n height: height ? parseInt(height) : null,\n assetType: assetType || 'icon',\n },\n create: {\n name,\n content,\n checksum,\n mimeType: mimeType || file.mimetype,\n fileSize: file.size,\n width: width ? parseInt(width) : null,\n height: height ? parseInt(height) : null,\n assetType: assetType || 'icon',\n },\n })\n\n res.json({ success: true, name, size: file.size })\n } catch (error) {\n console.error('Error importing asset:', error)\n res.status(500).json({ error: 'Failed to import asset' })\n }\n}\n","import type { EhDevMockUser } from '../../middleware/types'\nimport type { User } from 'better-auth/types'\n\n/**\n * Extended User type with app-catalog specific fields\n */\ntype EhUser = User & {\n env_hopper_groups?: Array<string>\n}\n\n/**\n * Creates a complete User object from basic dev mock user details\n */\nexport function createMockUserFromDevConfig(devUser: EhDevMockUser): EhUser {\n return {\n id: devUser.id,\n email: devUser.email,\n name: devUser.name,\n emailVerified: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n env_hopper_groups: devUser.groups,\n }\n}\n\n/**\n * Creates a mock session response for /api/auth/session endpoint\n */\nexport function createMockSessionResponse(devUser: EhDevMockUser) {\n return {\n user: {\n id: devUser.id,\n email: devUser.email,\n name: devUser.name,\n emailVerified: true,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n env_hopper_groups: devUser.groups,\n },\n session: {\n id: `${devUser.id}-session`,\n userId: devUser.id,\n expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30).toISOString(), // 30 days\n token: `${devUser.id}-token`,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n },\n }\n}\n","import type { Router } from 'express'\nimport { toNodeHandler } from 'better-auth/node'\nimport type {\n EhFeatureToggles,\n EhMiddlewareOptions,\n MiddlewareContext,\n} from './types'\nimport { registerIconRestController } from '../modules/icons/iconRestController'\nimport { registerAssetRestController } from '../modules/assets/assetRestController'\nimport { registerScreenshotRestController } from '../modules/assets/screenshotRestController'\nimport { createAdminChatHandler } from '../modules/admin/chat/createAdminChatHandler'\nimport { getAssetByName } from '../modules/icons/iconService'\nimport {\n exportAsset,\n exportCatalog,\n importAsset,\n importCatalog,\n listAssets,\n} from '../modules/appCatalogAdmin/catalogBackupController'\nimport multer from 'multer'\nimport { createMockSessionResponse } from '../modules/auth/devMockUserUtils'\n\ninterface FeatureRegistration {\n name: keyof EhFeatureToggles\n defaultEnabled: boolean\n register: (\n router: Router,\n options: Required<Pick<EhMiddlewareOptions, 'basePath'>> &\n EhMiddlewareOptions,\n context: MiddlewareContext,\n ) => void\n}\n\n// Optional features that can be toggled\nconst FEATURES: Array<FeatureRegistration> = [\n {\n name: 'auth',\n defaultEnabled: true,\n register: (router, options, ctx) => {\n const basePath = options.basePath\n\n // Explicit session endpoint handler\n router.get(\n `${basePath}/auth/session`,\n async (req, res): Promise<void> => {\n try {\n // Check if dev mock user is configured\n if (ctx.authConfig.devMockUser) {\n res.json(createMockSessionResponse(ctx.authConfig.devMockUser))\n return\n }\n\n const session = await ctx.auth.api.getSession({\n headers: req.headers as HeadersInit,\n })\n if (session) {\n res.json(session)\n } else {\n res.status(401).json({ error: 'Not authenticated' })\n }\n } catch (error) {\n console.error('[Auth Session Error]', error)\n res.status(500).json({ error: 'Internal server error' })\n }\n },\n )\n\n // Use toNodeHandler to adapt better-auth for Express/Node.js\n const authHandler = toNodeHandler(ctx.auth)\n router.all(`${basePath}/auth/{*any}`, authHandler)\n },\n },\n {\n name: 'adminChat',\n defaultEnabled: false, // Only enabled if adminChat config is provided\n register: (router, options) => {\n if (options.adminChat) {\n router.post(\n `${options.basePath}/admin/chat`,\n createAdminChatHandler(options.adminChat),\n )\n }\n },\n },\n {\n name: 'legacyIconEndpoint',\n defaultEnabled: false,\n register: (router) => {\n // Legacy endpoint at /static/icon/:icon for backwards compatibility\n router.get('/static/icon/:icon', async (req, res) => {\n const { icon } = req.params\n\n if (!icon || !/^[a-z0-9-]+$/i.test(icon)) {\n res.status(400).send('Invalid icon name')\n return\n }\n\n try {\n const dbIcon = await getAssetByName(icon)\n\n if (!dbIcon) {\n res.status(404).send('Icon not found')\n return\n }\n\n res.setHeader('Content-Type', dbIcon.mimeType)\n res.setHeader('Cache-Control', 'public, max-age=86400')\n res.send(dbIcon.content)\n } catch (error) {\n console.error('Error fetching icon:', error)\n res.status(404).send('Icon not found')\n }\n })\n },\n },\n]\n\n/**\n * Registers all enabled features on the router.\n */\nexport function registerFeatures(\n router: Router,\n options: Required<Pick<EhMiddlewareOptions, 'basePath'>> &\n EhMiddlewareOptions,\n context: MiddlewareContext,\n): void {\n const basePath = options.basePath\n\n // Always-on features (required for core functionality)\n\n // Icons\n registerIconRestController(router, {\n basePath: `${basePath}/icons`,\n })\n\n // Assets\n registerAssetRestController(router, {\n basePath: `${basePath}/assets`,\n })\n\n // Screenshots\n registerScreenshotRestController(router, {\n basePath: `${basePath}/screenshots`,\n })\n\n // Catalog backup/restore\n const upload = multer({ storage: multer.memoryStorage() })\n router.get(`${basePath}/catalog/backup/export`, exportCatalog)\n router.post(`${basePath}/catalog/backup/import`, importCatalog)\n router.get(`${basePath}/catalog/backup/assets`, listAssets)\n router.get(`${basePath}/catalog/backup/assets/:name`, exportAsset)\n router.post(\n `${basePath}/catalog/backup/assets`,\n upload.single('file'),\n importAsset,\n )\n\n // Optional toggleable features\n const toggles = options.features || {}\n\n for (const feature of FEATURES) {\n const isEnabled = toggles[feature.name] ?? feature.defaultEnabled\n\n // Special case: adminChat is only enabled if config is provided\n if (feature.name === 'adminChat' && !options.adminChat) {\n continue\n }\n\n if (isEnabled) {\n feature.register(router, options, context)\n }\n }\n}\n","import express, { Router } from 'express'\nimport * as trpcExpress from '@trpc/server/adapters/express'\nimport type {\n EhMiddlewareOptions,\n EhMiddlewareResult,\n MiddlewareContext,\n} from './types'\nimport { EhDatabaseManager } from './database'\nimport { createBackendResolver } from './backendResolver'\nimport { registerFeatures } from './featureRegistry'\nimport { createTrpcRouter } from '../server/controller'\nimport { createEhTrpcContext } from '../server/ehTrpcContext'\nimport { createAuth } from '../modules/auth/auth'\nimport { createMockUserFromDevConfig } from '../modules/auth/devMockUserUtils'\n\nexport async function createEhMiddleware(\n options: EhMiddlewareOptions,\n): Promise<EhMiddlewareResult> {\n // Normalize options with defaults\n const basePath = options.basePath ?? '/api'\n const normalizedOptions = { ...options, basePath }\n\n // Initialize database manager\n const dbManager = new EhDatabaseManager(options.database)\n // Initialize the client (which also sets the global singleton)\n dbManager.getClient()\n\n // Create auth instance\n const auth = createAuth({\n appName: options.auth.appName,\n baseURL: options.auth.baseURL,\n secret: options.auth.secret,\n providers: options.auth.providers,\n plugins: options.auth.plugins,\n sessionExpiresIn: options.auth.sessionExpiresIn,\n sessionUpdateAge: options.auth.sessionUpdateAge,\n })\n\n // Create tRPC router\n const trpcRouter = createTrpcRouter(auth)\n\n // Normalize backend provider to async factory function\n const resolveBackend = createBackendResolver(options.backend)\n\n // Get admin groups from config with default\n const adminGroups = options.auth.adminGroups ?? ['env_hopper_ui_super_admins']\n\n // Create tRPC context factory\n const createContext = async ({\n req,\n }: trpcExpress.CreateExpressContextOptions) => {\n const companySpecificBackend = await resolveBackend()\n\n let user = null\n let userGroups: Array<string> = []\n\n // Check if dev mock user is configured\n if (options.auth.devMockUser) {\n user = createMockUserFromDevConfig(options.auth.devMockUser)\n userGroups = options.auth.devMockUser.groups\n } else {\n // Extract user from session\n try {\n const session = await auth.api.getSession({\n headers: req.headers as HeadersInit,\n })\n user = session?.user ?? null\n\n // If user is authenticated and Okta is configured, decode groups from access token\n if (user && options.auth.oktaGroupsClaim) {\n try {\n // Get the current access token (auto-refreshes if expired)\n // Note: better-auth requires providerId, but we use 'okta' as default\n const tokenResult = await auth.api.getAccessToken({\n body: {\n providerId: 'okta',\n },\n headers: req.headers as HeadersInit,\n })\n\n if (tokenResult.accessToken) {\n // Decode JWT to extract groups claim\n const parts = tokenResult.accessToken.split('.')\n if (parts.length === 3 && parts[1]) {\n const payload = JSON.parse(\n Buffer.from(parts[1], 'base64').toString(),\n )\n const groups = payload[options.auth.oktaGroupsClaim]\n userGroups = Array.isArray(groups) ? groups : []\n }\n }\n } catch (error) {\n console.error('[tRPC Context] Failed to get access token:', error)\n }\n }\n } catch (error) {\n console.error('[tRPC Context] Failed to get session:', error)\n }\n }\n\n // Attach groups to user object for authorization checks\n const userWithGroups = user ? { ...user, groups: userGroups } : null\n\n return createEhTrpcContext({\n companySpecificBackend,\n user: userWithGroups,\n adminGroups,\n })\n }\n\n // Create Express router\n const router = Router()\n router.use(express.json())\n\n // Build middleware context for feature registration\n const middlewareContext: MiddlewareContext = {\n auth,\n trpcRouter,\n createContext: async () => {\n const companySpecificBackend = await resolveBackend()\n return createEhTrpcContext({\n companySpecificBackend,\n adminGroups,\n })\n },\n authConfig: options.auth,\n }\n\n // Register tRPC middleware (if enabled)\n if (normalizedOptions.features?.trpc !== false) {\n router.use(\n `${basePath}/trpc`,\n trpcExpress.createExpressMiddleware({\n router: trpcRouter,\n createContext,\n }),\n )\n }\n\n // Register all enabled features\n registerFeatures(router, normalizedOptions, middlewareContext)\n\n // Call onRoutesRegistered hook if provided\n if (options.hooks?.onRoutesRegistered) {\n await options.hooks.onRoutesRegistered(router)\n }\n\n return {\n router,\n auth,\n trpcRouter,\n\n async connect(): Promise<void> {\n await dbManager.connect()\n if (options.hooks?.onDatabaseConnected) {\n await options.hooks.onDatabaseConnected()\n }\n },\n\n async disconnect(): Promise<void> {\n if (options.hooks?.onDatabaseDisconnecting) {\n await options.hooks.onDatabaseDisconnecting()\n }\n await dbManager.disconnect()\n },\n\n addRoutes(callback: (router: Router) => void): void {\n callback(router)\n },\n }\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@igstack/app-catalog-backend-core",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Backend core library for App Catalog",
|
|
5
|
+
"homepage": "https://github.com/lislon/app-catalog",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/lislon/app-catalog.git",
|
|
9
|
+
"directory": "packages/backend-core"
|
|
10
|
+
},
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"author": "Igor Golovin",
|
|
13
|
+
"sideEffects": false,
|
|
14
|
+
"type": "module",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"my-custom-condition": "./src/index.ts",
|
|
18
|
+
"import": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"module": "dist/index.js",
|
|
26
|
+
"types": "dist/index.d.ts",
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"src",
|
|
30
|
+
"prisma"
|
|
31
|
+
],
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@kubernetes/client-node": "^1.2.0",
|
|
34
|
+
"@prisma/client": "6.19.1",
|
|
35
|
+
"@trpc/client": "^11.4.2",
|
|
36
|
+
"@trpc/server": "^11.4.2",
|
|
37
|
+
"ai": "^6.0.37",
|
|
38
|
+
"better-auth": "^1.4.18",
|
|
39
|
+
"cors": "^2.8.5",
|
|
40
|
+
"dotenv-defaults": "5.0.2",
|
|
41
|
+
"express": "^5.1.0",
|
|
42
|
+
"jsonwebtoken": "^9.0.2",
|
|
43
|
+
"multer": "^1.4.5-lts.1",
|
|
44
|
+
"node-cron": "^4.2.1",
|
|
45
|
+
"radashi": "12.5.0-beta.6d5c035",
|
|
46
|
+
"reflect-metadata": "^0.2.2",
|
|
47
|
+
"sharp": "^0.33.5",
|
|
48
|
+
"tsyringe": "^4.10.0",
|
|
49
|
+
"yaml": "^2.8.0",
|
|
50
|
+
"zod": "^4.3.5",
|
|
51
|
+
"@igstack/app-catalog-table-sync": "0.0.1",
|
|
52
|
+
"@igstack/app-catalog-shared-core": "0.0.1"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@tanstack/vite-config": "^0.4.3",
|
|
56
|
+
"@types/cors": "^2.8.18",
|
|
57
|
+
"@types/dotenv-defaults": "^2.0.4",
|
|
58
|
+
"@types/express": "^5.0.6",
|
|
59
|
+
"@types/jsonwebtoken": "^9.0.9",
|
|
60
|
+
"@types/multer": "^1.4.12",
|
|
61
|
+
"@types/node": "^24.3.0",
|
|
62
|
+
"esbuild": "^0.25.5",
|
|
63
|
+
"prisma": "^6.8.2",
|
|
64
|
+
"prisma-json-types-generator": "^3.6.2",
|
|
65
|
+
"tsdown": "^0.15.12",
|
|
66
|
+
"tsx": "^4.19.4",
|
|
67
|
+
"typescript": "5.9.2",
|
|
68
|
+
"vite-plugin-dts": "^4.5.4"
|
|
69
|
+
},
|
|
70
|
+
"engines": {
|
|
71
|
+
"node": ">=16"
|
|
72
|
+
},
|
|
73
|
+
"scripts": {
|
|
74
|
+
"build": "tsdown",
|
|
75
|
+
"build:lenient": "tsdown",
|
|
76
|
+
"clean": "premove ./dist ./coverage ./dist-ts",
|
|
77
|
+
"compile": "tsc --build",
|
|
78
|
+
"dev": "tsx watch src/index.ts",
|
|
79
|
+
"prisma:generate": "prisma generate",
|
|
80
|
+
"test:eslint": "eslint ./src",
|
|
81
|
+
"test:unit": "vitest",
|
|
82
|
+
"test:unit:dev": "pnpm run test:unit --watch"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
-- CreateTable
|
|
2
|
+
CREATE TABLE "App" (
|
|
3
|
+
"id" SERIAL NOT NULL,
|
|
4
|
+
"name" TEXT NOT NULL,
|
|
5
|
+
"displayName" TEXT NOT NULL,
|
|
6
|
+
|
|
7
|
+
CONSTRAINT "App_pkey" PRIMARY KEY ("id")
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
-- CreateTable
|
|
11
|
+
CREATE TABLE "DeployableService" (
|
|
12
|
+
"id" SERIAL NOT NULL,
|
|
13
|
+
"name" TEXT NOT NULL,
|
|
14
|
+
"displayName" TEXT NOT NULL,
|
|
15
|
+
"appId" INTEGER NOT NULL,
|
|
16
|
+
|
|
17
|
+
CONSTRAINT "DeployableService_pkey" PRIMARY KEY ("id")
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
-- CreateTable
|
|
21
|
+
CREATE TABLE "UIPageGroup" (
|
|
22
|
+
"id" SERIAL NOT NULL,
|
|
23
|
+
"name" TEXT NOT NULL,
|
|
24
|
+
"displayName" TEXT NOT NULL,
|
|
25
|
+
"appId" INTEGER NOT NULL,
|
|
26
|
+
|
|
27
|
+
CONSTRAINT "UIPageGroup_pkey" PRIMARY KEY ("id")
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
-- CreateTable
|
|
31
|
+
CREATE TABLE "UIPage" (
|
|
32
|
+
"id" SERIAL NOT NULL,
|
|
33
|
+
"name" TEXT NOT NULL,
|
|
34
|
+
"displayName" TEXT NOT NULL,
|
|
35
|
+
"uiPageGroupId" INTEGER NOT NULL,
|
|
36
|
+
|
|
37
|
+
CONSTRAINT "UIPage_pkey" PRIMARY KEY ("id")
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
-- CreateTable
|
|
41
|
+
CREATE TABLE "User" (
|
|
42
|
+
"id" SERIAL NOT NULL,
|
|
43
|
+
"uid" TEXT NOT NULL,
|
|
44
|
+
"username" TEXT NOT NULL,
|
|
45
|
+
"email" TEXT NOT NULL,
|
|
46
|
+
"firstName" TEXT NOT NULL,
|
|
47
|
+
"lastName" TEXT NOT NULL,
|
|
48
|
+
|
|
49
|
+
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
-- CreateIndex
|
|
53
|
+
CREATE UNIQUE INDEX "App_name_key" ON "App"("name");
|
|
54
|
+
|
|
55
|
+
-- CreateIndex
|
|
56
|
+
CREATE UNIQUE INDEX "User_uid_key" ON "User"("uid");
|
|
57
|
+
|
|
58
|
+
-- CreateIndex
|
|
59
|
+
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
|
60
|
+
|
|
61
|
+
-- CreateIndex
|
|
62
|
+
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
|
63
|
+
|
|
64
|
+
-- AddForeignKey
|
|
65
|
+
ALTER TABLE "DeployableService" ADD CONSTRAINT "DeployableService_appId_fkey" FOREIGN KEY ("appId") REFERENCES "App"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
66
|
+
|
|
67
|
+
-- AddForeignKey
|
|
68
|
+
ALTER TABLE "UIPageGroup" ADD CONSTRAINT "UIPageGroup_appId_fkey" FOREIGN KEY ("appId") REFERENCES "App"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
69
|
+
|
|
70
|
+
-- AddForeignKey
|
|
71
|
+
ALTER TABLE "UIPage" ADD CONSTRAINT "UIPage_uiPageGroupId_fkey" FOREIGN KEY ("uiPageGroupId") REFERENCES "UIPageGroup"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|