@igstack/app-catalog-backend-core 0.3.1-alpha-20260405015231 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/db/syncAppCatalog.d.mts +3 -5
- package/dist/db/syncAppCatalog.d.mts.map +1 -1
- package/dist/db/syncAppCatalog.mjs +49 -57
- package/dist/db/syncAppCatalog.mjs.map +1 -1
- package/dist/db/tableSyncMagazine.d.mts +3 -7
- package/dist/db/tableSyncMagazine.d.mts.map +1 -1
- package/dist/db/tableSyncMagazine.mjs +3 -7
- package/dist/db/tableSyncMagazine.mjs.map +1 -1
- package/dist/db/tableSyncPrismaAdapter.mjs.map +1 -1
- package/dist/generated/prisma/client.mjs.map +1 -1
- package/dist/generated/prisma/internal/class.d.mts +5 -17
- package/dist/generated/prisma/internal/class.d.mts.map +1 -1
- package/dist/generated/prisma/internal/class.mjs +4 -4
- package/dist/generated/prisma/internal/class.mjs.map +1 -1
- package/dist/generated/prisma/internal/prismaNamespace.d.mts +46 -132
- package/dist/generated/prisma/internal/prismaNamespace.d.mts.map +1 -1
- package/dist/generated/prisma/models/DbResource.d.mts +2433 -0
- package/dist/generated/prisma/models/DbResource.d.mts.map +1 -0
- package/dist/generated/prisma/models/SourceReference.d.mts +90 -90
- package/dist/generated/prisma/models/SourceReference.d.mts.map +1 -1
- package/dist/generated/prisma/models.d.mts +2 -3
- package/dist/index.d.mts +3 -4
- package/dist/modules/appCatalog/checkLinks.mjs +1 -1
- package/dist/modules/appCatalog/checkLinks.mjs.map +1 -1
- package/dist/modules/appCatalog/service.mjs +26 -34
- package/dist/modules/appCatalog/service.mjs.map +1 -1
- package/dist/modules/assets/screenshotRestController.mjs +2 -2
- package/dist/modules/assets/screenshotRestController.mjs.map +1 -1
- package/dist/modules/assets/syncAssets.mjs +4 -4
- package/dist/modules/assets/syncAssets.mjs.map +1 -1
- package/dist/modules/lighthouseKeeper/tools.mjs +1 -1
- package/dist/modules/lighthouseKeeper/tools.mjs.map +1 -1
- package/dist/server/controller.d.mts +2 -2
- package/dist/server/controller.mjs.map +1 -1
- package/dist/types/common/appCatalogTypes.d.mts +26 -9
- package/dist/types/common/appCatalogTypes.d.mts.map +1 -1
- package/dist/types/common/approvalMethodTypes.d.mts +5 -1
- package/dist/types/common/approvalMethodTypes.d.mts.map +1 -1
- package/package.json +3 -3
- package/prisma/schema.prisma +53 -62
- package/src/db/syncAppCatalog.ts +68 -73
- package/src/db/tableSyncMagazine.ts +3 -7
- package/src/db/tableSyncPrismaAdapter.ts +1 -1
- package/src/generated/prisma/browser.ts +2 -7
- package/src/generated/prisma/client.ts +2 -7
- package/src/generated/prisma/internal/class.ts +8 -18
- package/src/generated/prisma/internal/prismaNamespace.ts +43 -131
- package/src/generated/prisma/internal/prismaNamespaceBrowser.ts +7 -20
- package/src/generated/prisma/models/DbResource.ts +2701 -0
- package/src/generated/prisma/models/SourceReference.ts +89 -89
- package/src/generated/prisma/models.ts +1 -2
- package/src/index.ts +1 -1
- package/src/modules/appCatalog/checkLinks.ts +7 -7
- package/src/modules/appCatalog/service.ts +51 -62
- package/src/modules/assets/screenshotRestController.ts +2 -2
- package/src/modules/assets/screenshotRouter.ts +2 -2
- package/src/modules/assets/syncAssets.ts +4 -4
- package/src/modules/lighthouseKeeper/tools.ts +1 -1
- package/src/prisma-json-types.d.ts +8 -8
- package/src/server/controller.ts +2 -2
- package/src/types/common/appCatalogTypes.ts +28 -9
- package/src/types/common/approvalMethodTypes.ts +6 -0
- package/src/types/index.ts +0 -1
- package/dist/generated/prisma/models/DbAppForCatalog.d.mts +0 -1778
- package/dist/generated/prisma/models/DbAppForCatalog.d.mts.map +0 -1
- package/dist/generated/prisma/models/DbSubResource.d.mts +0 -1468
- package/dist/generated/prisma/models/DbSubResource.d.mts.map +0 -1
- package/dist/types/common/subResourceTypes.d.mts +0 -24
- package/dist/types/common/subResourceTypes.d.mts.map +0 -1
- package/src/generated/prisma/models/DbAppForCatalog.ts +0 -2014
- package/src/generated/prisma/models/DbSubResource.ts +0 -1692
- package/src/types/common/subResourceTypes.ts +0 -20
|
@@ -115,7 +115,7 @@ async function syncScreenshotsFromDirectory(prisma, screenshotsDir) {
|
|
|
115
115
|
});
|
|
116
116
|
}
|
|
117
117
|
for (const [appId, screenshots] of screenshotsByApp) try {
|
|
118
|
-
if (!await prisma.
|
|
118
|
+
if (!await prisma.dbResource.findUnique({
|
|
119
119
|
where: { slug: appId },
|
|
120
120
|
select: { id: true }
|
|
121
121
|
})) {
|
|
@@ -131,8 +131,8 @@ async function syncScreenshotsFromDirectory(prisma, screenshotsDir) {
|
|
|
131
131
|
assetType: "screenshot"
|
|
132
132
|
} });
|
|
133
133
|
if (existing) {
|
|
134
|
-
const existingApp = await prisma.
|
|
135
|
-
if (existingApp && !existingApp.screenshotIds.includes(existing.id)) await prisma.
|
|
134
|
+
const existingApp = await prisma.dbResource.findUnique({ where: { slug: appId } });
|
|
135
|
+
if (existingApp && !existingApp.screenshotIds.includes(existing.id)) await prisma.dbResource.update({
|
|
136
136
|
where: { slug: appId },
|
|
137
137
|
data: { screenshotIds: [...existingApp.screenshotIds, existing.id] }
|
|
138
138
|
});
|
|
@@ -156,7 +156,7 @@ async function syncScreenshotsFromDirectory(prisma, screenshotsDir) {
|
|
|
156
156
|
width: width ?? null,
|
|
157
157
|
height: height ?? null
|
|
158
158
|
} });
|
|
159
|
-
await prisma.
|
|
159
|
+
await prisma.dbResource.update({
|
|
160
160
|
where: { slug: appId },
|
|
161
161
|
data: { screenshotIds: { push: asset.id } }
|
|
162
162
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncAssets.mjs","names":[],"sources":["../../../src/modules/assets/syncAssets.ts"],"sourcesContent":["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<string, { path: string; ext: string }[]>()\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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"syncAssets.mjs","names":[],"sources":["../../../src/modules/assets/syncAssets.ts"],"sourcesContent":["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<string, { path: string; ext: string }[]>()\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.dbResource.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.dbResource.findUnique({\n where: { slug: appId },\n })\n if (\n existingApp &&\n !existingApp.screenshotIds.includes(existing.id)\n ) {\n await prisma.dbResource.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.dbResource.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"],"mappings":"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"}
|
|
@@ -21,7 +21,7 @@ function createAppCatalogAITools(databaseConfig) {
|
|
|
21
21
|
inputSchema: z.object({ slug: z.string().describe("The app slug to fetch") }),
|
|
22
22
|
execute: async ({ slug }) => {
|
|
23
23
|
try {
|
|
24
|
-
const app = await prisma.
|
|
24
|
+
const app = await prisma.dbResource.findUnique({
|
|
25
25
|
where: { slug },
|
|
26
26
|
include: { sourceRefs: true }
|
|
27
27
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.mjs","names":[],"sources":["../../../src/modules/lighthouseKeeper/tools.ts"],"sourcesContent":["import type { Tool } from 'ai'\nimport { z } from 'zod'\nimport { PrismaClient } from '../../generated/prisma/client'\nimport { PrismaPg } from '@prisma/adapter-pg'\nimport pg from 'pg'\nimport type { AcDatabaseConfig } from '../../middleware/types.js'\n\n// ============================================================================\n// Database URL Helper\n// ============================================================================\n\nfunction getDatabaseUrl(config: AcDatabaseConfig): string {\n if ('url' in config) return config.url\n const { host, port, database, username, password, schema } = config\n const schemaParam = schema ? `?schema=${schema}` : ''\n return `postgresql://${username}:${password}@${host}:${port}/${database}${schemaParam}`\n}\n\n// ============================================================================\n// AI Tools for App Catalog\n// ============================================================================\n\n/**\n * Create AI tools for working with app catalog cards.\n * These tools allow the AI to fetch and inspect app information.\n */\nexport function createAppCatalogAITools(\n databaseConfig: AcDatabaseConfig,\n): Record<string, Tool> {\n const databaseUrl = getDatabaseUrl(databaseConfig)\n const pool = new pg.Pool({ connectionString: databaseUrl })\n const adapter = new PrismaPg(pool)\n const prisma = new PrismaClient({ adapter })\n\n const getAppCardSchema = z.object({\n slug: z.string().describe('The app slug to fetch'),\n })\n\n type GetAppCardInput = z.infer<typeof getAppCardSchema>\n\n const getAppCard: Tool<GetAppCardInput, unknown> = {\n description:\n 'Fetch complete app catalog card information by ID. Returns title, description, tags, access request details, links, and all other app metadata.',\n inputSchema: getAppCardSchema,\n execute: async ({ slug }: { slug: string }) => {\n try {\n const app = await prisma.
|
|
1
|
+
{"version":3,"file":"tools.mjs","names":[],"sources":["../../../src/modules/lighthouseKeeper/tools.ts"],"sourcesContent":["import type { Tool } from 'ai'\nimport { z } from 'zod'\nimport { PrismaClient } from '../../generated/prisma/client'\nimport { PrismaPg } from '@prisma/adapter-pg'\nimport pg from 'pg'\nimport type { AcDatabaseConfig } from '../../middleware/types.js'\n\n// ============================================================================\n// Database URL Helper\n// ============================================================================\n\nfunction getDatabaseUrl(config: AcDatabaseConfig): string {\n if ('url' in config) return config.url\n const { host, port, database, username, password, schema } = config\n const schemaParam = schema ? `?schema=${schema}` : ''\n return `postgresql://${username}:${password}@${host}:${port}/${database}${schemaParam}`\n}\n\n// ============================================================================\n// AI Tools for App Catalog\n// ============================================================================\n\n/**\n * Create AI tools for working with app catalog cards.\n * These tools allow the AI to fetch and inspect app information.\n */\nexport function createAppCatalogAITools(\n databaseConfig: AcDatabaseConfig,\n): Record<string, Tool> {\n const databaseUrl = getDatabaseUrl(databaseConfig)\n const pool = new pg.Pool({ connectionString: databaseUrl })\n const adapter = new PrismaPg(pool)\n const prisma = new PrismaClient({ adapter })\n\n const getAppCardSchema = z.object({\n slug: z.string().describe('The app slug to fetch'),\n })\n\n type GetAppCardInput = z.infer<typeof getAppCardSchema>\n\n const getAppCard: Tool<GetAppCardInput, unknown> = {\n description:\n 'Fetch complete app catalog card information by ID. Returns title, description, tags, access request details, links, and all other app metadata.',\n inputSchema: getAppCardSchema,\n execute: async ({ slug }: { slug: string }) => {\n try {\n const app = await prisma.dbResource.findUnique({\n where: { slug },\n include: {\n sourceRefs: true,\n },\n })\n\n if (!app) {\n return { error: `App not found with slug: ${slug}` }\n }\n\n // Return structured card data\n return {\n success: true,\n card: {\n id: app.id,\n slug: app.slug,\n displayName: app.displayName,\n description: app.description,\n teams: app.teams,\n tags: app.tags,\n appUrl: app.appUrl,\n links: app.links,\n iconName: app.iconName,\n sources: app.sourceRefs.map((ref) => ref.url),\n notes: app.notes,\n accessRequest: app.accessRequest,\n deprecated: app.deprecated,\n createdAt: app.createdAt,\n updatedAt: app.updatedAt,\n },\n }\n } catch (error) {\n return {\n error: error instanceof Error ? error.message : 'Failed to fetch app',\n }\n }\n },\n }\n\n return {\n getAppCard,\n }\n}\n\n// ============================================================================\n// System Prompt\n// ============================================================================\n\n/**\n * Default system prompt for AI working with app catalog cards.\n * Use this when configuring lighthouseKeeper for card updates.\n */\nexport const APP_CATALOG_AI_SYSTEM_PROMPT = `You are an assistant that helps update app catalog cards based on information from provided links.\n\nWhen given a link and an app ID:\n1. Use getAppCard to fetch the current card information\n2. Analyze the provided link for relevant information\n3. Output a new card structure with updated information based on the link\n\nReturn the updated card as a structured JSON object matching the card format:\n- displayName: App title\n- description: Full description\n- tags: Array of tags\n- teams: Array of team names\n- appUrl: Main application URL\n- links: Array of {url, title} objects for related links\n- sources: Array of source URLs (documentation, Confluence pages, etc.)\n- accessRequest: Object with approvalMethodSlug, comments, requestPrompt, postApprovalInstructions, roles, approverPersonSlugs, urls\n- notes: Additional notes\n- deprecated: Optional deprecation info with type, replacementSlug, comment\n\nDo NOT save the card to the database. Only output the updated structure.`\n"],"mappings":";;;;;;AAWA,SAAS,eAAe,QAAkC;AACxD,KAAI,SAAS,OAAQ,QAAO,OAAO;CACnC,MAAM,EAAE,MAAM,MAAM,UAAU,UAAU,UAAU,WAAW;AAE7D,QAAO,gBAAgB,SAAS,GAAG,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,WAD3C,SAAS,WAAW,WAAW;;;;;;AAYrD,SAAgB,wBACd,gBACsB;CACtB,MAAM,cAAc,eAAe,eAAe;CAGlD,MAAM,SAAS,IAAI,aAAa,EAAE,SADlB,IAAI,SADP,IAAI,GAAG,KAAK,EAAE,kBAAkB,aAAa,CAAC,CACzB,EACS,CAAC;AAsD5C,QAAO,EACL,YA/CiD;EACjD,aACE;EACF,aATuB,EAAE,OAAO,EAChC,MAAM,EAAE,QAAQ,CAAC,SAAS,wBAAwB,EACnD,CAAC;EAQA,SAAS,OAAO,EAAE,WAA6B;AAC7C,OAAI;IACF,MAAM,MAAM,MAAM,OAAO,WAAW,WAAW;KAC7C,OAAO,EAAE,MAAM;KACf,SAAS,EACP,YAAY,MACb;KACF,CAAC;AAEF,QAAI,CAAC,IACH,QAAO,EAAE,OAAO,4BAA4B,QAAQ;AAItD,WAAO;KACL,SAAS;KACT,MAAM;MACJ,IAAI,IAAI;MACR,MAAM,IAAI;MACV,aAAa,IAAI;MACjB,aAAa,IAAI;MACjB,OAAO,IAAI;MACX,MAAM,IAAI;MACV,QAAQ,IAAI;MACZ,OAAO,IAAI;MACX,UAAU,IAAI;MACd,SAAS,IAAI,WAAW,KAAK,QAAQ,IAAI,IAAI;MAC7C,OAAO,IAAI;MACX,eAAe,IAAI;MACnB,YAAY,IAAI;MAChB,WAAW,IAAI;MACf,WAAW,IAAI;MAChB;KACF;YACM,OAAO;AACd,WAAO,EACL,OAAO,iBAAiB,QAAQ,MAAM,UAAU,uBACjD;;;EAGN,EAIA;;;;;;AAWH,MAAa,+BAA+B"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AppCatalogData,
|
|
1
|
+
import { AppCatalogData, Resource } from "../types/common/appCatalogTypes.mjs";
|
|
2
2
|
import { AcTrpcContext } from "./acTrpcContext.mjs";
|
|
3
3
|
import { BetterAuth } from "../modules/auth/auth.mjs";
|
|
4
4
|
import * as _trpc_server0 from "@trpc/server";
|
|
@@ -40,7 +40,7 @@ declare function createTrpcRouter(auth?: BetterAuth, options?: {
|
|
|
40
40
|
aiPrompt?: string | null | undefined;
|
|
41
41
|
};
|
|
42
42
|
};
|
|
43
|
-
output:
|
|
43
|
+
output: Resource;
|
|
44
44
|
meta: object;
|
|
45
45
|
}>;
|
|
46
46
|
}>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.mjs","names":["updateAppService"],"sources":["../../src/server/controller.ts"],"sourcesContent":["import {\n getAppCatalogData,\n updateApp as updateAppService,\n} from '../modules/appCatalog/service'\nimport type { AppCatalogData } from '../types'\nimport type {
|
|
1
|
+
{"version":3,"file":"controller.mjs","names":["updateAppService"],"sources":["../../src/server/controller.ts"],"sourcesContent":["import {\n getAppCatalogData,\n updateApp as updateAppService,\n} from '../modules/appCatalog/service'\nimport type { AppCatalogData } from '../types'\nimport type { Resource } from '../types/common/appCatalogTypes'\n\nimport type { BetterAuth } from '../modules/auth/auth'\nimport { createAuthRouter } from '../modules/auth/authRouter.js'\nimport { publicProcedure, router, t } from './trpcSetup'\nimport { z } from 'zod'\n\nconst updateAppInputSchema = z.object({\n id: z.string(),\n data: z\n .object({\n displayName: z.string().optional(),\n abbreviation: z.string().max(20).nullable().optional(),\n slug: z.string().optional(),\n appUrl: z.string().optional(),\n description: z.string().optional(),\n sources: z.array(z.string()).optional(),\n aiPrompt: z.string().nullable().optional(),\n })\n .refine((d) => Object.keys(d).length > 0, {\n message: 'At least one field required',\n }),\n})\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(\n auth?: BetterAuth,\n options?: { devLoginEnabled?: boolean },\n) {\n return router({\n appCatalog: router({\n getData: publicProcedure.query(\n async ({ ctx }): Promise<AppCatalogData> => {\n const baseData = await getAppCatalogData()\n const versions = await ctx.companySpecificBackend.getVersionInfo?.()\n\n return {\n ...baseData,\n ...(versions && { versions }),\n }\n },\n ),\n updateApp: publicProcedure\n .input(updateAppInputSchema)\n .mutation(async ({ input }): Promise<Resource> => {\n return updateAppService(input)\n }),\n }),\n\n // Auth routes (requires auth instance)\n auth: createAuthRouter(t, auth, options),\n })\n}\n\nexport type TRPCRouter = ReturnType<typeof createTrpcRouter>\n"],"mappings":";;;;;;AAYA,MAAM,uBAAuB,EAAE,OAAO;CACpC,IAAI,EAAE,QAAQ;CACd,MAAM,EACH,OAAO;EACN,aAAa,EAAE,QAAQ,CAAC,UAAU;EAClC,cAAc,EAAE,QAAQ,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,UAAU;EACtD,MAAM,EAAE,QAAQ,CAAC,UAAU;EAC3B,QAAQ,EAAE,QAAQ,CAAC,UAAU;EAC7B,aAAa,EAAE,QAAQ,CAAC,UAAU;EAClC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,UAAU;EACvC,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,UAAU;EAC3C,CAAC,CACD,QAAQ,MAAM,OAAO,KAAK,EAAE,CAAC,SAAS,GAAG,EACxC,SAAS,+BACV,CAAC;CACL,CAAC;;;;;AAMF,SAAgB,iBACd,MACA,SACA;AACA,QAAO,OAAO;EACZ,YAAY,OAAO;GACjB,SAAS,gBAAgB,MACvB,OAAO,EAAE,UAAmC;IAC1C,MAAM,WAAW,MAAM,mBAAmB;IAC1C,MAAM,WAAW,MAAM,IAAI,uBAAuB,kBAAkB;AAEpE,WAAO;KACL,GAAG;KACH,GAAI,YAAY,EAAE,UAAU;KAC7B;KAEJ;GACD,WAAW,gBACR,MAAM,qBAAqB,CAC3B,SAAS,OAAO,EAAE,YAA+B;AAChD,WAAOA,UAAiB,MAAM;KAC9B;GACL,CAAC;EAGF,MAAM,iBAAiB,GAAG,MAAM,QAAQ;EACzC,CAAC"}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { AppAccessRequest, ApprovalMethod } from "./approvalMethodTypes.mjs";
|
|
2
2
|
import { Group, Person } from "./personGroupTypes.mjs";
|
|
3
|
-
import { SubResource } from "./subResourceTypes.mjs";
|
|
4
3
|
|
|
5
4
|
//#region src/types/common/appCatalogTypes.d.ts
|
|
6
5
|
/**
|
|
7
|
-
* A tier variant of
|
|
6
|
+
* A tier variant of a resource (e.g., prod/dev environments).
|
|
8
7
|
* Each tier can have its own URL and access process.
|
|
9
8
|
*/
|
|
10
|
-
interface
|
|
9
|
+
interface TierVariant {
|
|
11
10
|
tierSlug: string;
|
|
12
11
|
displayName?: string;
|
|
13
12
|
description?: string;
|
|
@@ -24,11 +23,14 @@ interface SourceReference {
|
|
|
24
23
|
parseDate: string | null;
|
|
25
24
|
}
|
|
26
25
|
/**
|
|
27
|
-
*
|
|
26
|
+
* Resource entry in the catalog (application or sub-resource).
|
|
27
|
+
* Unified model: applications have no parentSlug; sub-resources have parentSlug.
|
|
28
28
|
*/
|
|
29
|
-
interface
|
|
29
|
+
interface Resource {
|
|
30
30
|
id: string;
|
|
31
31
|
slug: string;
|
|
32
|
+
/** Discriminator: "application" for top-level apps, "sub-resource" for children, etc. */
|
|
33
|
+
type?: string;
|
|
32
34
|
displayName: string;
|
|
33
35
|
abbreviation?: string;
|
|
34
36
|
nicknames?: string[];
|
|
@@ -55,7 +57,23 @@ interface AppForCatalog {
|
|
|
55
57
|
/** URL health issues detected by automated scanning */
|
|
56
58
|
urlIssues?: string[];
|
|
57
59
|
/** Optional tier variants (e.g., prod/dev) with per-tier URLs and access */
|
|
58
|
-
tiers?:
|
|
60
|
+
tiers?: TierVariant[];
|
|
61
|
+
/** Slug of parent resource (undefined for top-level applications) */
|
|
62
|
+
parentSlug?: string;
|
|
63
|
+
/** Tier slug (e.g. "prod", "dev") — for sub-resources */
|
|
64
|
+
tier?: string;
|
|
65
|
+
/** Groups tier variants visually */
|
|
66
|
+
familySlug?: string;
|
|
67
|
+
/** Alternative identifiers */
|
|
68
|
+
aliases?: string[];
|
|
69
|
+
/** Person slug of the owner */
|
|
70
|
+
ownerPersonSlug?: string;
|
|
71
|
+
/** Group slugs of access maintainers */
|
|
72
|
+
accessMaintainerGroupSlugs?: string[];
|
|
73
|
+
/** Free-text access comments */
|
|
74
|
+
accessComments?: string;
|
|
75
|
+
/** Arbitrary extra data */
|
|
76
|
+
extra?: Record<string, unknown>;
|
|
59
77
|
}
|
|
60
78
|
interface AppCategory {
|
|
61
79
|
id: string;
|
|
@@ -84,14 +102,13 @@ interface AppVersionInfo {
|
|
|
84
102
|
coreVersion?: VersionInfo;
|
|
85
103
|
}
|
|
86
104
|
interface AppCatalogData {
|
|
87
|
-
|
|
105
|
+
resources: Resource[];
|
|
88
106
|
tagsDefinitions: GroupingTagDefinition[];
|
|
89
107
|
approvalMethods: AppApprovalMethod[];
|
|
90
108
|
persons: Person[];
|
|
91
109
|
groups: Group[];
|
|
92
|
-
subResources: SubResource[];
|
|
93
110
|
versions?: AppVersionInfo;
|
|
94
111
|
}
|
|
95
112
|
//#endregion
|
|
96
|
-
export { AppApprovalMethod, AppCatalogData, AppCategory,
|
|
113
|
+
export { AppApprovalMethod, AppCatalogData, AppCategory, AppVersionInfo, GroupingTagDefinition, GroupingTagValue, Resource, SourceReference, TierVariant, VersionInfo };
|
|
97
114
|
//# sourceMappingURL=appCatalogTypes.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"appCatalogTypes.d.mts","names":[],"sources":["../../../src/types/common/appCatalogTypes.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"appCatalogTypes.d.mts","names":[],"sources":["../../../src/types/common/appCatalogTypes.ts"],"mappings":";;;;;;;;UAmBiB,WAAA;EACf,QAAA;EACA,WAAA;EACA,WAAA;EACA,MAAA;EACA,aAAA,GAAgB,gBAAA;AAAA;;;;;UAWD,eAAA;EACf,UAAA;EACA,GAAA;EACA,SAAA;AAAA;;;;;UAOe,QAAA;EACf,EAAA;EACA,IAAA;EAEA;EAAA,IAAA;EACA,WAAA;EACA,YAAA;EACA,SAAA;EACA,WAAA;EACA,KAAA;EACA,aAAA,GAAgB,gBAAA;EAChB,KAAA;EACA,IAAA;EACA,MAAA;EACA,KAAA;IAAU,GAAA;IAAa,KAAA;EAAA;EACvB,QAAA;EACA,aAAA;EACA,OAAA,cAAqB,eAAA;EACrB,UAAA;IAEE,0HAAA,IAAA,iCAIA;IAFA,eAAA,WAOF;IALE,OAAA;EAAA;EAWF;EARA,QAAA;EAYA;EAVA,SAAA;EAcA;EAZA,KAAA,GAAQ,WAAA;EAgBR;EAZA,UAAA;EAcQ;EAZR,IAAA;EAYc;EAVd,UAAA;EAc0B;EAZ1B,OAAA;EAaA;EAXA,eAAA;EAee;EAbf,0BAAA;;EAEA,cAAA;EAYA;EAVA,KAAA,GAAQ,MAAA;AAAA;AAAA,UAIO,WAAA;EACf,EAAA;EACA,IAAA;AAAA;AAAA,UAGe,qBAAA;EACf,MAAA;EACA,WAAA;EACA,WAAA;EACA,MAAA,EAAQ,gBAAA;AAAA;AAAA,KAGL,gBAAA,8BAA8C,CAAA,eAC/C,IAAA,CAAK,CAAA,EAAG,IAAA;AAAA,KAGA,iBAAA,GAAoB,gBAAA,CAC9B,cAAA;AAAA,UAIe,gBAAA;EACf,KAAA;EACA,WAAA;EACA,WAAA;AAAA;AAAA,UAGe,WAAA;EACf,WAAA;EACA,GAAA;AAAA;AAAA,UAGe,cAAA;EACf,OAAA,GAAU,WAAA;EACV,QAAA,GAAW,WAAA;EACX,WAAA,GAAc,WAAA;AAAA;AAAA,UAGC,cAAA;EACf,SAAA,EAAW,QAAA;EACX,eAAA,EAAiB,qBAAA;EACjB,eAAA,EAAiB,iBAAA;EACjB,OAAA,EAAS,MAAA;EACT,MAAA,EAAQ,KAAA;EACR,QAAA,GAAW,cAAA;AAAA"}
|
|
@@ -116,6 +116,10 @@ interface AppAccessRequest {
|
|
|
116
116
|
*/
|
|
117
117
|
urls?: ApprovalUrl[];
|
|
118
118
|
}
|
|
119
|
+
/** @deprecated Use AppAccessRequest instead (same type, new canonical name) */
|
|
120
|
+
type AccessRequest = AppAccessRequest;
|
|
121
|
+
/** @deprecated Use AppRole instead (same type, new canonical name) */
|
|
122
|
+
type Role = AppRole;
|
|
119
123
|
//#endregion
|
|
120
|
-
export { AppAccessRequest, AppRole, ApprovalMethod, ApprovalMethodConfig, ApprovalMethodType, ApprovalUrl, CustomConfig, PersonTeamConfig, ServiceConfig };
|
|
124
|
+
export { AccessRequest, AppAccessRequest, AppRole, ApprovalMethod, ApprovalMethodConfig, ApprovalMethodType, ApprovalUrl, CustomConfig, PersonTeamConfig, Role, ServiceConfig };
|
|
121
125
|
//# sourceMappingURL=approvalMethodTypes.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approvalMethodTypes.d.mts","names":[],"sources":["../../../src/types/common/approvalMethodTypes.ts"],"mappings":";;AAWA;;;;;KAAY,kBAAA;;;;UAUK,aAAA;EACf,GAAA;EACA,IAAA;AAAA;;;AAcF;UARiB,gBAAA;EACf,WAAA;EACA,UAAA;AAAA;AAaF;;;AAAA,UAPiB,YAAA;;;;KAOL,oBAAA,GACR,aAAA,GACA,gBAAA,GACA,YAAA;;;;KAKQ,cAAA;EACV,IAAA;EADwB;;;EAKxB,WAAA;EAUY;;;EANZ,iBAAA;EACA,SAAA,GAAY,IAAA;EACZ,SAAA,GAAY,IAAA;AAAA;EAGR,IAAA;EACA,MAAA,EAAQ,aAAA;AAAA;EAGR,IAAA;EACA,MAAA,EAAQ,gBAAA;AAAA;EAGR,IAAA;EACA,MAAA,EAAQ,YAAA;AAAA;EAGR,IAAA;EACA,MAAA,EAAQ,YAAA;AAAA;EAGR,IAAA;EACA,MAAA,EAAQ,YAAA;AAAA;;;;UAWG,OAAA;EAZX;;;EAgBJ,WAAA;EAfwB;AAW1B;;EAQE,WAAA;EARsB;;;EAYtB,UAAA;AAAA;;AAMF;;UAAiB,WAAA;EACf,KAAA;EACA,GAAA;AAAA;;;;UAMe,gBAAA;EASf;;;EALA,kBAAA;EAkBQ;;;EAbR,QAAA;EAsBkB
|
|
1
|
+
{"version":3,"file":"approvalMethodTypes.d.mts","names":[],"sources":["../../../src/types/common/approvalMethodTypes.ts"],"mappings":";;AAWA;;;;;KAAY,kBAAA;;;;UAUK,aAAA;EACf,GAAA;EACA,IAAA;AAAA;;;AAcF;UARiB,gBAAA;EACf,WAAA;EACA,UAAA;AAAA;AAaF;;;AAAA,UAPiB,YAAA;;;;KAOL,oBAAA,GACR,aAAA,GACA,gBAAA,GACA,YAAA;;;;KAKQ,cAAA;EACV,IAAA;EADwB;;;EAKxB,WAAA;EAUY;;;EANZ,iBAAA;EACA,SAAA,GAAY,IAAA;EACZ,SAAA,GAAY,IAAA;AAAA;EAGR,IAAA;EACA,MAAA,EAAQ,aAAA;AAAA;EAGR,IAAA;EACA,MAAA,EAAQ,gBAAA;AAAA;EAGR,IAAA;EACA,MAAA,EAAQ,YAAA;AAAA;EAGR,IAAA;EACA,MAAA,EAAQ,YAAA;AAAA;EAGR,IAAA;EACA,MAAA,EAAQ,YAAA;AAAA;;;;UAWG,OAAA;EAZX;;;EAgBJ,WAAA;EAfwB;AAW1B;;EAQE,WAAA;EARsB;;;EAYtB,UAAA;AAAA;;AAMF;;UAAiB,WAAA;EACf,KAAA;EACA,GAAA;AAAA;;;;UAMe,gBAAA;EASf;;;EALA,kBAAA;EAkBQ;;;EAbR,QAAA;EAsBkB;;AAIpB;EAtBE,aAAA;;;;EAIA,wBAAA;EAqBc;;;EAhBd,KAAA,GAAQ,OAAA;;;;EAKR,mBAAA;;;;EAIA,IAAA,GAAO,WAAA;AAAA;;KAIG,aAAA,GAAgB,gBAAA;;KAGhB,IAAA,GAAO,OAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@igstack/app-catalog-backend-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Backend core library for App Catalog",
|
|
5
5
|
"homepage": "https://github.com/lislon/app-catalog",
|
|
6
6
|
"repository": {
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"tsyringe": "^4.10.0",
|
|
46
46
|
"yaml": "^2.8.0",
|
|
47
47
|
"zod": "^4.3.5",
|
|
48
|
-
"@igstack/app-catalog-shared-core": "0.
|
|
49
|
-
"@igstack/app-catalog-table-sync": "0.
|
|
48
|
+
"@igstack/app-catalog-shared-core": "0.4.0",
|
|
49
|
+
"@igstack/app-catalog-table-sync": "0.4.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@tanstack/vite-config": "^0.4.3",
|
package/prisma/schema.prisma
CHANGED
|
@@ -83,22 +83,22 @@ enum ApprovalMethodType {
|
|
|
83
83
|
// ========== Person & Group Models ==========
|
|
84
84
|
|
|
85
85
|
model DbPerson {
|
|
86
|
-
slug String
|
|
86
|
+
slug String @id // email or synthetic
|
|
87
87
|
firstName String
|
|
88
88
|
lastName String
|
|
89
|
-
email String?
|
|
89
|
+
email String? @unique
|
|
90
90
|
memberships DbGroupMembership[]
|
|
91
|
-
createdAt DateTime
|
|
92
|
-
updatedAt DateTime
|
|
91
|
+
createdAt DateTime @default(now())
|
|
92
|
+
updatedAt DateTime @updatedAt
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
model DbGroup {
|
|
96
|
-
slug String
|
|
96
|
+
slug String @id
|
|
97
97
|
displayName String?
|
|
98
|
-
email String?
|
|
98
|
+
email String? // team alias
|
|
99
99
|
memberships DbGroupMembership[]
|
|
100
|
-
createdAt DateTime
|
|
101
|
-
updatedAt DateTime
|
|
100
|
+
createdAt DateTime @default(now())
|
|
101
|
+
updatedAt DateTime @updatedAt
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
model DbGroupMembership {
|
|
@@ -124,62 +124,53 @@ model DbApprovalMethod {
|
|
|
124
124
|
@@unique([type, displayName])
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
model
|
|
128
|
-
id
|
|
129
|
-
slug
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
127
|
+
model DbResource {
|
|
128
|
+
id String @id @default(cuid())
|
|
129
|
+
slug String @unique // URL-friendly identifier for navigation
|
|
130
|
+
type String @default("application") // "application" | "sub-resource" | etc.
|
|
131
|
+
displayName String
|
|
132
|
+
abbreviation String? // Optional short abbreviation (e.g. K8s, ECR, LV)
|
|
133
|
+
nicknames String[] @default([]) // Alternative names / AKA
|
|
134
|
+
description String
|
|
134
135
|
/// [AccessMethod]
|
|
135
|
-
access
|
|
136
|
-
teams
|
|
137
|
-
/// [
|
|
138
|
-
accessRequest
|
|
139
|
-
notes
|
|
140
|
-
tags
|
|
141
|
-
appUrl
|
|
136
|
+
access Json?
|
|
137
|
+
teams String[] @default([])
|
|
138
|
+
/// [AccessRequest] - Per-resource approval configuration linking to ApprovalMethod
|
|
139
|
+
accessRequest Json?
|
|
140
|
+
notes String?
|
|
141
|
+
tags String[] @default([])
|
|
142
|
+
appUrl String?
|
|
142
143
|
/// [AppLink[]]
|
|
143
|
-
links
|
|
144
|
-
iconName
|
|
145
|
-
screenshotIds
|
|
144
|
+
links Json? // Array of {displayName?: string, url: string}
|
|
145
|
+
iconName String? // Optional icon identifier matching DbAsset.name
|
|
146
|
+
screenshotIds String[] @default([]) // Ordered array of DbAsset IDs
|
|
146
147
|
/// [AppDeprecation] - Deprecation info with optional replacement slug
|
|
147
|
-
deprecated
|
|
148
|
-
aiPrompt
|
|
149
|
-
urlIssues
|
|
150
|
-
/// [
|
|
151
|
-
tiers
|
|
152
|
-
sourceRefs
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
model DbSubResource {
|
|
163
|
-
id String @id @default(cuid())
|
|
164
|
-
slug String @unique
|
|
165
|
-
displayName String
|
|
166
|
-
description String?
|
|
167
|
-
appSlug String
|
|
168
|
-
app DbAppForCatalog @relation(fields: [appSlug], references: [slug], onDelete: Cascade)
|
|
169
|
-
familySlug String? // groups tier variants visually
|
|
170
|
-
tierSlug String?
|
|
171
|
-
aliases String[] @default([])
|
|
148
|
+
deprecated Json?
|
|
149
|
+
aiPrompt String?
|
|
150
|
+
urlIssues String[] @default([])
|
|
151
|
+
/// [TierVariant[]]
|
|
152
|
+
tiers Json?
|
|
153
|
+
sourceRefs SourceReference[]
|
|
154
|
+
// Self-referential tree: sub-resources point to their parent
|
|
155
|
+
parentSlug String?
|
|
156
|
+
parent DbResource? @relation("ResourceTree", fields: [parentSlug], references: [slug])
|
|
157
|
+
children DbResource[] @relation("ResourceTree")
|
|
158
|
+
// Fields merged from former DbSubResource
|
|
159
|
+
tier String? // e.g. "prod", "dev"
|
|
160
|
+
familySlug String? // groups tier variants visually
|
|
161
|
+
aliases String[] @default([])
|
|
172
162
|
ownerPersonSlug String?
|
|
173
|
-
accessMaintainerGroupSlugs String[]
|
|
174
|
-
/// [AppAccessRequest]
|
|
175
|
-
accessRequest Json?
|
|
163
|
+
accessMaintainerGroupSlugs String[] @default([])
|
|
176
164
|
accessComments String?
|
|
177
165
|
/// [SubResourceExtra]
|
|
178
166
|
extra Json?
|
|
179
|
-
createdAt DateTime
|
|
180
|
-
updatedAt DateTime
|
|
167
|
+
createdAt DateTime @default(now())
|
|
168
|
+
updatedAt DateTime @updatedAt
|
|
181
169
|
|
|
182
|
-
@@index([
|
|
170
|
+
@@index([displayName])
|
|
171
|
+
@@index([slug])
|
|
172
|
+
@@index([tags])
|
|
173
|
+
@@index([parentSlug])
|
|
183
174
|
}
|
|
184
175
|
|
|
185
176
|
model DbAppTagDefinition {
|
|
@@ -222,15 +213,15 @@ model Source {
|
|
|
222
213
|
}
|
|
223
214
|
|
|
224
215
|
model SourceReference {
|
|
225
|
-
id Int
|
|
216
|
+
id Int @id @default(autoincrement())
|
|
226
217
|
sourceSlug String
|
|
227
|
-
source Source
|
|
228
|
-
|
|
229
|
-
|
|
218
|
+
source Source @relation(fields: [sourceSlug], references: [slug], onDelete: Cascade)
|
|
219
|
+
resourceId String
|
|
220
|
+
resource DbResource @relation(fields: [resourceId], references: [id], onDelete: Cascade)
|
|
230
221
|
url String
|
|
231
222
|
parseDate DateTime?
|
|
232
|
-
excerpts String[]
|
|
223
|
+
excerpts String[] @default([])
|
|
233
224
|
userPrompt String?
|
|
234
225
|
|
|
235
|
-
@@unique([
|
|
226
|
+
@@unique([resourceId, url])
|
|
236
227
|
}
|