@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.
Files changed (72) hide show
  1. package/dist/db/syncAppCatalog.d.mts +3 -5
  2. package/dist/db/syncAppCatalog.d.mts.map +1 -1
  3. package/dist/db/syncAppCatalog.mjs +49 -57
  4. package/dist/db/syncAppCatalog.mjs.map +1 -1
  5. package/dist/db/tableSyncMagazine.d.mts +3 -7
  6. package/dist/db/tableSyncMagazine.d.mts.map +1 -1
  7. package/dist/db/tableSyncMagazine.mjs +3 -7
  8. package/dist/db/tableSyncMagazine.mjs.map +1 -1
  9. package/dist/db/tableSyncPrismaAdapter.mjs.map +1 -1
  10. package/dist/generated/prisma/client.mjs.map +1 -1
  11. package/dist/generated/prisma/internal/class.d.mts +5 -17
  12. package/dist/generated/prisma/internal/class.d.mts.map +1 -1
  13. package/dist/generated/prisma/internal/class.mjs +4 -4
  14. package/dist/generated/prisma/internal/class.mjs.map +1 -1
  15. package/dist/generated/prisma/internal/prismaNamespace.d.mts +46 -132
  16. package/dist/generated/prisma/internal/prismaNamespace.d.mts.map +1 -1
  17. package/dist/generated/prisma/models/DbResource.d.mts +2433 -0
  18. package/dist/generated/prisma/models/DbResource.d.mts.map +1 -0
  19. package/dist/generated/prisma/models/SourceReference.d.mts +90 -90
  20. package/dist/generated/prisma/models/SourceReference.d.mts.map +1 -1
  21. package/dist/generated/prisma/models.d.mts +2 -3
  22. package/dist/index.d.mts +3 -4
  23. package/dist/modules/appCatalog/checkLinks.mjs +1 -1
  24. package/dist/modules/appCatalog/checkLinks.mjs.map +1 -1
  25. package/dist/modules/appCatalog/service.mjs +26 -34
  26. package/dist/modules/appCatalog/service.mjs.map +1 -1
  27. package/dist/modules/assets/screenshotRestController.mjs +2 -2
  28. package/dist/modules/assets/screenshotRestController.mjs.map +1 -1
  29. package/dist/modules/assets/syncAssets.mjs +4 -4
  30. package/dist/modules/assets/syncAssets.mjs.map +1 -1
  31. package/dist/modules/lighthouseKeeper/tools.mjs +1 -1
  32. package/dist/modules/lighthouseKeeper/tools.mjs.map +1 -1
  33. package/dist/server/controller.d.mts +2 -2
  34. package/dist/server/controller.mjs.map +1 -1
  35. package/dist/types/common/appCatalogTypes.d.mts +26 -9
  36. package/dist/types/common/appCatalogTypes.d.mts.map +1 -1
  37. package/dist/types/common/approvalMethodTypes.d.mts +5 -1
  38. package/dist/types/common/approvalMethodTypes.d.mts.map +1 -1
  39. package/package.json +3 -3
  40. package/prisma/schema.prisma +53 -62
  41. package/src/db/syncAppCatalog.ts +68 -73
  42. package/src/db/tableSyncMagazine.ts +3 -7
  43. package/src/db/tableSyncPrismaAdapter.ts +1 -1
  44. package/src/generated/prisma/browser.ts +2 -7
  45. package/src/generated/prisma/client.ts +2 -7
  46. package/src/generated/prisma/internal/class.ts +8 -18
  47. package/src/generated/prisma/internal/prismaNamespace.ts +43 -131
  48. package/src/generated/prisma/internal/prismaNamespaceBrowser.ts +7 -20
  49. package/src/generated/prisma/models/DbResource.ts +2701 -0
  50. package/src/generated/prisma/models/SourceReference.ts +89 -89
  51. package/src/generated/prisma/models.ts +1 -2
  52. package/src/index.ts +1 -1
  53. package/src/modules/appCatalog/checkLinks.ts +7 -7
  54. package/src/modules/appCatalog/service.ts +51 -62
  55. package/src/modules/assets/screenshotRestController.ts +2 -2
  56. package/src/modules/assets/screenshotRouter.ts +2 -2
  57. package/src/modules/assets/syncAssets.ts +4 -4
  58. package/src/modules/lighthouseKeeper/tools.ts +1 -1
  59. package/src/prisma-json-types.d.ts +8 -8
  60. package/src/server/controller.ts +2 -2
  61. package/src/types/common/appCatalogTypes.ts +28 -9
  62. package/src/types/common/approvalMethodTypes.ts +6 -0
  63. package/src/types/index.ts +0 -1
  64. package/dist/generated/prisma/models/DbAppForCatalog.d.mts +0 -1778
  65. package/dist/generated/prisma/models/DbAppForCatalog.d.mts.map +0 -1
  66. package/dist/generated/prisma/models/DbSubResource.d.mts +0 -1468
  67. package/dist/generated/prisma/models/DbSubResource.d.mts.map +0 -1
  68. package/dist/types/common/subResourceTypes.d.mts +0 -24
  69. package/dist/types/common/subResourceTypes.d.mts.map +0 -1
  70. package/src/generated/prisma/models/DbAppForCatalog.ts +0 -2014
  71. package/src/generated/prisma/models/DbSubResource.ts +0 -1692
  72. package/src/types/common/subResourceTypes.ts +0 -20
@@ -1,6 +1,6 @@
1
1
  import type {
2
- AppForCatalog,
3
2
  GroupingTagDefinition,
3
+ Resource,
4
4
  } from '../types/common/appCatalogTypes'
5
5
  import { getDbClient } from './client'
6
6
  import { TABLE_SYNC_MAGAZINE } from './tableSyncMagazine'
@@ -8,7 +8,7 @@ import { tableSyncPrisma } from './tableSyncPrismaAdapter'
8
8
  import { readFile, readdir, stat } from 'node:fs/promises'
9
9
  import { group } from 'radashi'
10
10
  import { upsertAsset } from '../modules/assets/upsertAsset'
11
- import type { ApprovalMethod, Group, Person, SubResource } from '../types'
11
+ import type { ApprovalMethod, Group, Person } from '../types'
12
12
  import type { PrismaClient } from '../generated/prisma/client'
13
13
  import { naturalSort } from '../utils/naturalSort'
14
14
  import { parseSourceSlug } from '../utils/parseSourceSlug'
@@ -103,12 +103,12 @@ async function syncAppAssets(
103
103
  }
104
104
 
105
105
  async function syncAssetsFromFileSystem(
106
- apps: AppForCatalog[],
106
+ resources: Resource[],
107
107
  allAppsAssetsPath: string,
108
108
  ) {
109
109
  const appDirectories = await readdir(allAppsAssetsPath)
110
110
  const prisma = getDbClient()
111
- const bySlug = group(apps, (a) => a.slug)
111
+ const bySlug = group(resources, (a) => a.slug)
112
112
 
113
113
  for (const appDirName of appDirectories) {
114
114
  try {
@@ -150,7 +150,7 @@ async function syncAssetsFromFileSystem(
150
150
  }
151
151
 
152
152
  if (Object.keys(updateData).length > 0) {
153
- await prisma.dbAppForCatalog.update({
153
+ await prisma.dbResource.update({
154
154
  where: { slug: appSlug },
155
155
  data: updateData,
156
156
  })
@@ -171,20 +171,19 @@ async function syncAssetsFromFileSystem(
171
171
  export interface SyncAppCatalogOptions {
172
172
  persons?: Person[]
173
173
  groups?: Group[]
174
- subResources?: SubResource[]
175
174
  }
176
175
 
177
176
  /**
178
177
  * Syncs app catalog data to the database using table sync.
179
- * This will create new apps, update existing ones, and delete any that are no longer in the input.
178
+ * This will create new resources, update existing ones, and delete any that are no longer in the input.
180
179
  *
181
180
  * Note: Call connectDb() before and disconnectDb() after if running in a script.
182
181
  */
183
182
  export async function syncAppCatalog(
184
- apps: AppForCatalog[],
183
+ resources: Resource[],
185
184
  tagsDefinitions: GroupingTagDefinition[],
186
185
  approvalMethods: ApprovalMethod[],
187
- sreenshotsPath?: string,
186
+ screenshotsPath?: string,
188
187
  options?: SyncAppCatalogOptions,
189
188
  ): Promise<SyncAppCatalogResult> {
190
189
  try {
@@ -236,7 +235,7 @@ export async function syncAppCatalog(
236
235
 
237
236
  const sync = tableSyncPrisma({
238
237
  prisma,
239
- ...TABLE_SYNC_MAGAZINE.DbAppForCatalog,
238
+ ...TABLE_SYNC_MAGAZINE.DbResource,
240
239
  })
241
240
 
242
241
  await tableSyncPrisma({
@@ -246,8 +245,8 @@ export async function syncAppCatalog(
246
245
 
247
246
  // Collect all unique source slugs for sync
248
247
  const uniqueSourceSlugs = new Set<string>()
249
- for (const app of apps) {
250
- for (const source of app.sources ?? []) {
248
+ for (const resource of resources) {
249
+ for (const source of resource.sources ?? []) {
251
250
  const url = typeof source === 'string' ? source : source.url
252
251
  const sourceSlug = parseSourceSlug(url)
253
252
  uniqueSourceSlugs.add(sourceSlug)
@@ -264,67 +263,84 @@ export async function syncAppCatalog(
264
263
  ...TABLE_SYNC_MAGAZINE.Source,
265
264
  }).sync(sources)
266
265
 
267
- // Transform AppForCatalog to DbAppForCatalog format (scalar fields only)
268
- const dbApps = apps.map((app) => {
266
+ // Sort resources: parents first (no parentSlug), then children — for FK integrity
267
+ const sortedResources = [...resources].sort((a, b) => {
268
+ const aIsChild = a.parentSlug ? 1 : 0
269
+ const bIsChild = b.parentSlug ? 1 : 0
270
+ return aIsChild - bIsChild
271
+ })
272
+
273
+ // Transform Resource to DbResource format (scalar fields only)
274
+ const dbResources = sortedResources.map((resource) => {
269
275
  const slug =
270
- app.slug ||
271
- app.displayName
276
+ resource.slug ||
277
+ resource.displayName
272
278
  .toLowerCase()
273
279
  .replace(/[^a-z0-9]+/g, '-')
274
280
  .replace(/^-+|-+$/g, '')
275
281
 
276
282
  return {
277
283
  slug,
278
- displayName: app.displayName,
279
- abbreviation: app.abbreviation ?? null,
280
- nicknames: app.nicknames ?? [],
281
- description: app.description,
282
- teams: app.teams ?? [],
283
- accessRequest: app.accessRequest ?? null,
284
- notes: app.notes ?? null,
285
- tags: app.tags ?? [],
286
- appUrl: app.appUrl ?? null,
287
- links: app.links ?? null,
288
- iconName: app.iconName ?? null,
289
- screenshotIds: app.screenshotIds ?? [],
290
- deprecated: app.deprecated ?? null,
291
- aiPrompt: app.aiPrompt ?? null,
292
- urlIssues: app.urlIssues ?? [],
293
- tiers: app.tiers ?? null,
284
+ type: resource.type ?? 'application',
285
+ displayName: resource.displayName,
286
+ abbreviation: resource.abbreviation ?? null,
287
+ nicknames: resource.nicknames ?? [],
288
+ description: resource.description,
289
+ teams: resource.teams ?? [],
290
+ accessRequest: resource.accessRequest ?? null,
291
+ notes: resource.notes ?? null,
292
+ tags: resource.tags ?? [],
293
+ appUrl: resource.appUrl ?? null,
294
+ links: resource.links ?? null,
295
+ iconName: resource.iconName ?? null,
296
+ screenshotIds: resource.screenshotIds ?? [],
297
+ deprecated: resource.deprecated ?? null,
298
+ aiPrompt: resource.aiPrompt ?? null,
299
+ urlIssues: resource.urlIssues ?? [],
300
+ tiers: resource.tiers ?? null,
301
+ // Fields from former SubResource
302
+ parentSlug: resource.parentSlug ?? null,
303
+ tier: resource.tier ?? null,
304
+ familySlug: resource.familySlug ?? null,
305
+ aliases: resource.aliases ?? [],
306
+ ownerPersonSlug: resource.ownerPersonSlug ?? null,
307
+ accessMaintainerGroupSlugs: resource.accessMaintainerGroupSlugs ?? [],
308
+ accessComments: resource.accessComments ?? null,
309
+ extra: resource.extra ?? null,
294
310
  }
295
311
  })
296
312
 
297
- // Sync apps first
298
- const result = await sync.sync(dbApps)
313
+ // Sync resources
314
+ const result = await sync.sync(dbResources)
299
315
 
300
- // Resolve slug -> id for synced apps so SourceReference can reference by appId
301
- const slugs = dbApps.map((a) => a.slug)
302
- const appRows = await prisma.dbAppForCatalog.findMany({
316
+ // Resolve slug -> id for synced resources so SourceReference can reference by resourceId
317
+ const slugs = dbResources.map((a) => a.slug)
318
+ const resourceRows = await prisma.dbResource.findMany({
303
319
  where: { slug: { in: slugs } },
304
320
  select: { slug: true, id: true },
305
321
  })
306
- const slugToId = Object.fromEntries(appRows.map((r) => [r.slug, r.id]))
322
+ const slugToId = Object.fromEntries(resourceRows.map((r) => [r.slug, r.id]))
307
323
 
308
- // Build allSourceRefs with appId (slug already resolved to id)
309
- const allSourceRefs = apps.flatMap((app) => {
310
- const appSlug =
311
- app.slug ||
312
- app.displayName
324
+ // Build allSourceRefs with resourceId (slug already resolved to id)
325
+ const allSourceRefs = sortedResources.flatMap((resource) => {
326
+ const resourceSlug =
327
+ resource.slug ||
328
+ resource.displayName
313
329
  .toLowerCase()
314
330
  .replace(/[^a-z0-9]+/g, '-')
315
331
  .replace(/^-+|-+$/g, '')
316
- const appId = slugToId[appSlug]
317
- if (!appId) {
332
+ const resourceId = slugToId[resourceSlug]
333
+ if (!resourceId) {
318
334
  throw new Error(
319
- `App '${appSlug}' has no id after sync. Existing slugs: ${Object.keys(slugToId).join(', ')}`,
335
+ `Resource '${resourceSlug}' has no id after sync. Existing slugs: ${Object.keys(slugToId).join(', ')}`,
320
336
  )
321
337
  }
322
338
 
323
- return (app.sources ?? []).map((source) => {
339
+ return (resource.sources ?? []).map((source) => {
324
340
  const url = typeof source === 'string' ? source : source.url
325
341
  const sourceSlug = parseSourceSlug(url)
326
342
  return {
327
- appId,
343
+ resourceId,
328
344
  sourceSlug,
329
345
  url,
330
346
  parseDate: null,
@@ -340,39 +356,18 @@ export async function syncAppCatalog(
340
356
  ...TABLE_SYNC_MAGAZINE.SourceReference,
341
357
  }).sync(allSourceRefs)
342
358
 
343
- // Sync SubResources (after apps, since they reference appSlug)
344
- if (options?.subResources) {
345
- const dbSubResources = options.subResources.map((sr) => ({
346
- slug: sr.slug,
347
- displayName: sr.displayName,
348
- description: sr.description ?? null,
349
- appSlug: sr.appSlug,
350
- familySlug: sr.familySlug ?? null,
351
- tierSlug: sr.tierSlug ?? null,
352
- aliases: sr.aliases,
353
- ownerPersonSlug: sr.ownerPersonSlug ?? null,
354
- accessMaintainerGroupSlugs: sr.accessMaintainerGroupSlugs,
355
- accessRequest: sr.accessRequest ?? null,
356
- accessComments: sr.accessComments ?? null,
357
- extra: sr.extra ?? null,
358
- }))
359
- await tableSyncPrisma({
360
- prisma,
361
- ...TABLE_SYNC_MAGAZINE.DbSubResource,
362
- }).sync(dbSubResources)
363
- }
364
-
365
359
  // Get actual synced data to calculate stats
366
360
  const actual = result.getActual()
367
361
 
368
- if (sreenshotsPath) {
369
- await syncAssetsFromFileSystem(apps, sreenshotsPath)
362
+ if (screenshotsPath) {
363
+ await syncAssetsFromFileSystem(resources, screenshotsPath)
370
364
  } else {
371
365
  console.warn('Do not sync screenhots')
372
366
  }
373
367
 
374
368
  return {
375
- created: actual.length - apps.length + (apps.length - actual.length),
369
+ created:
370
+ actual.length - resources.length + (resources.length - actual.length),
376
371
  updated: 0, // TableSync doesn't expose this directly
377
372
  deleted: 0, // TableSync doesn't expose this directly
378
373
  total: actual.length,
@@ -27,8 +27,8 @@ export const TABLE_SYNC_MAGAZINE = {
27
27
  prismaModelName: 'DbGroupMembership',
28
28
  uniqColumns: ['groupSlug', 'personSlug'],
29
29
  },
30
- DbAppForCatalog: {
31
- prismaModelName: 'DbAppForCatalog',
30
+ DbResource: {
31
+ prismaModelName: 'DbResource',
32
32
  uniqColumns: ['slug'],
33
33
  },
34
34
  DbAppTagDefinition: {
@@ -45,13 +45,9 @@ export const TABLE_SYNC_MAGAZINE = {
45
45
  prismaModelName: 'Source',
46
46
  uniqColumns: ['slug'],
47
47
  },
48
- DbSubResource: {
49
- prismaModelName: 'DbSubResource',
50
- uniqColumns: ['slug'],
51
- },
52
48
  SourceReference: {
53
49
  prismaModelName: 'SourceReference',
54
- uniqColumns: ['appId', 'url'],
50
+ uniqColumns: ['resourceId', 'url'],
55
51
  },
56
52
  } as const satisfies TableSyncMagazineType
57
53
 
@@ -12,7 +12,7 @@ export type ScalarFilter<TPrismaModelName extends Prisma.ModelName> = Partial<
12
12
  >
13
13
 
14
14
  export type GetOperationFns<TModel extends Prisma.ModelName> = {
15
- [TOperation in keyof Prisma.TypeMap['model']['DbAppForCatalog']['operations']]: (
15
+ [TOperation in keyof Prisma.TypeMap['model']['DbResource']['operations']]: (
16
16
  args: Prisma.TypeMap['model'][TModel]['operations'][TOperation]['args'],
17
17
  ) => Promise<
18
18
  Prisma.TypeMap['model'][TModel]['operations'][TOperation]['result']
@@ -58,15 +58,10 @@ export type DbGroupMembership = Prisma.DbGroupMembershipModel
58
58
  */
59
59
  export type DbApprovalMethod = Prisma.DbApprovalMethodModel
60
60
  /**
61
- * Model DbAppForCatalog
61
+ * Model DbResource
62
62
  *
63
63
  */
64
- export type DbAppForCatalog = Prisma.DbAppForCatalogModel
65
- /**
66
- * Model DbSubResource
67
- *
68
- */
69
- export type DbSubResource = Prisma.DbSubResourceModel
64
+ export type DbResource = Prisma.DbResourceModel
70
65
  /**
71
66
  * Model DbAppTagDefinition
72
67
  *
@@ -82,15 +82,10 @@ export type DbGroupMembership = Prisma.DbGroupMembershipModel
82
82
  */
83
83
  export type DbApprovalMethod = Prisma.DbApprovalMethodModel
84
84
  /**
85
- * Model DbAppForCatalog
85
+ * Model DbResource
86
86
  *
87
87
  */
88
- export type DbAppForCatalog = Prisma.DbAppForCatalogModel
89
- /**
90
- * Model DbSubResource
91
- *
92
- */
93
- export type DbSubResource = Prisma.DbSubResourceModel
88
+ export type DbResource = Prisma.DbResourceModel
94
89
  /**
95
90
  * Model DbAppTagDefinition
96
91
  *