@igstack/app-catalog-backend-core 0.15.0 → 0.17.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/client.d.mts +54 -1
- package/dist/db/client.d.mts.map +1 -1
- package/dist/db/client.mjs +88 -7
- package/dist/db/client.mjs.map +1 -1
- package/dist/db/index.d.mts +1 -1
- package/dist/db/syncAppCatalog.mjs +2 -1
- package/dist/db/syncAppCatalog.mjs.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 +1 -0
- package/dist/generated/prisma/internal/prismaNamespace.d.mts.map +1 -1
- package/dist/generated/prisma/models/DbResource.d.mts +43 -1
- package/dist/generated/prisma/models/DbResource.d.mts.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/middleware/database.d.mts +1 -0
- package/dist/middleware/database.d.mts.map +1 -1
- package/dist/middleware/database.mjs +10 -13
- package/dist/middleware/database.mjs.map +1 -1
- package/dist/modules/appCatalog/freshness.mjs +5 -3
- package/dist/modules/appCatalog/freshness.mjs.map +1 -1
- package/dist/modules/appCatalog/service.mjs +5 -1
- package/dist/modules/appCatalog/service.mjs.map +1 -1
- package/dist/modules/assets/assetCache.mjs +25 -0
- package/dist/modules/assets/assetCache.mjs.map +1 -0
- package/dist/modules/assets/assetRestController.d.mts.map +1 -1
- package/dist/modules/assets/assetRestController.mjs +17 -6
- package/dist/modules/assets/assetRestController.mjs.map +1 -1
- package/dist/modules/assets/assetUtils.mjs +1 -0
- package/dist/modules/assets/assetUtils.mjs.map +1 -1
- package/dist/modules/assets/screenshotRestController.d.mts.map +1 -1
- package/dist/modules/assets/screenshotRestController.mjs +7 -2
- package/dist/modules/assets/screenshotRestController.mjs.map +1 -1
- package/dist/modules/assets/upsertAsset.mjs +7 -1
- package/dist/modules/assets/upsertAsset.mjs.map +1 -1
- package/dist/modules/icons/iconRestController.d.mts.map +1 -1
- package/dist/modules/icons/iconRestController.mjs +7 -2
- package/dist/modules/icons/iconRestController.mjs.map +1 -1
- package/dist/modules/lighthouseKeeper/tools.d.mts.map +1 -1
- package/dist/modules/lighthouseKeeper/tools.mjs +5 -5
- package/dist/modules/lighthouseKeeper/tools.mjs.map +1 -1
- package/dist/types/common/appCatalogTypes.d.mts +8 -0
- package/dist/types/common/appCatalogTypes.d.mts.map +1 -1
- package/package.json +4 -4
- package/prisma/schema.prisma +5 -2
- package/src/__tests__/assetMime.test.ts +74 -0
- package/src/__tests__/dbSchema.test.ts +129 -0
- package/src/__tests__/dbSchemaAdapter.test.ts +118 -0
- package/src/__tests__/freshness.test.ts +56 -17
- package/src/__tests__/iconCache.test.ts +81 -0
- package/src/db/client.ts +118 -12
- package/src/db/index.ts +10 -1
- package/src/db/syncAppCatalog.ts +3 -0
- package/src/generated/prisma/internal/class.ts +4 -4
- package/src/generated/prisma/internal/prismaNamespace.ts +1 -0
- package/src/generated/prisma/internal/prismaNamespaceBrowser.ts +1 -0
- package/src/generated/prisma/models/DbResource.ts +44 -1
- package/src/index.ts +4 -0
- package/src/middleware/database.ts +20 -19
- package/src/modules/admin/chat/createDatabaseTools.ts +6 -2
- package/src/modules/appCatalog/freshness.ts +29 -11
- package/src/modules/appCatalog/service.ts +9 -4
- package/src/modules/assets/assetCache.ts +30 -0
- package/src/modules/assets/assetRestController.ts +25 -2
- package/src/modules/assets/assetUtils.ts +1 -0
- package/src/modules/assets/screenshotRestController.ts +13 -1
- package/src/modules/assets/upsertAsset.ts +8 -0
- package/src/modules/icons/iconRestController.ts +11 -1
- package/src/modules/lighthouseKeeper/tools.ts +6 -7
- package/src/types/common/appCatalogTypes.ts +8 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Request, Response, Router } from 'express'
|
|
2
2
|
import sharp from 'sharp'
|
|
3
3
|
import { getDbClient } from '../../db'
|
|
4
|
+
import { isNotModified, setRevalidatingCacheHeaders } from './assetCache'
|
|
4
5
|
|
|
5
6
|
export interface ScreenshotRestControllerConfig {
|
|
6
7
|
/**
|
|
@@ -133,6 +134,7 @@ export function registerScreenshotRestController(
|
|
|
133
134
|
content: true,
|
|
134
135
|
mimeType: true,
|
|
135
136
|
name: true,
|
|
137
|
+
checksum: true,
|
|
136
138
|
},
|
|
137
139
|
})
|
|
138
140
|
|
|
@@ -141,6 +143,17 @@ export function registerScreenshotRestController(
|
|
|
141
143
|
return
|
|
142
144
|
}
|
|
143
145
|
|
|
146
|
+
// Answered before resizing, so an unchanged screenshot costs no image work.
|
|
147
|
+
const etag = setRevalidatingCacheHeaders(
|
|
148
|
+
res,
|
|
149
|
+
screenshot.checksum,
|
|
150
|
+
targetSize && targetSize > 0 ? `s${targetSize}` : undefined,
|
|
151
|
+
)
|
|
152
|
+
if (isNotModified(req, etag)) {
|
|
153
|
+
res.status(304).end()
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
|
|
144
157
|
let content: Uint8Array | Buffer = screenshot.content
|
|
145
158
|
|
|
146
159
|
// Resize if size parameter provided
|
|
@@ -164,7 +177,6 @@ export function registerScreenshotRestController(
|
|
|
164
177
|
'Content-Disposition',
|
|
165
178
|
`inline; filename="${screenshot.name}"`,
|
|
166
179
|
)
|
|
167
|
-
res.setHeader('Cache-Control', 'public, max-age=86400') // Cache for 1 day
|
|
168
180
|
|
|
169
181
|
// Send binary content
|
|
170
182
|
res.send(content)
|
|
@@ -27,6 +27,14 @@ export async function upsertAsset({
|
|
|
27
27
|
})
|
|
28
28
|
|
|
29
29
|
if (existing) {
|
|
30
|
+
// Reusing the stored binary must not keep a mimeType we no longer derive:
|
|
31
|
+
// rows written by an older, wrong derivation would never be corrected.
|
|
32
|
+
if (existing.mimeType !== mimeType) {
|
|
33
|
+
await prisma.dbAsset.update({
|
|
34
|
+
where: { id: existing.id },
|
|
35
|
+
data: { mimeType },
|
|
36
|
+
})
|
|
37
|
+
}
|
|
30
38
|
return existing.id
|
|
31
39
|
}
|
|
32
40
|
|
|
@@ -3,6 +3,10 @@ import multer from 'multer'
|
|
|
3
3
|
import { createHash } from 'node:crypto'
|
|
4
4
|
import { getDbClient } from '../../db'
|
|
5
5
|
import { getExtensionFromFilename, getExtensionFromMimeType } from './iconUtils'
|
|
6
|
+
import {
|
|
7
|
+
isNotModified,
|
|
8
|
+
setRevalidatingCacheHeaders,
|
|
9
|
+
} from '../assets/assetCache'
|
|
6
10
|
|
|
7
11
|
// Configure multer for memory storage
|
|
8
12
|
const upload = multer({
|
|
@@ -112,6 +116,7 @@ export function registerIconRestController(
|
|
|
112
116
|
content: true,
|
|
113
117
|
mimeType: true,
|
|
114
118
|
name: true,
|
|
119
|
+
checksum: true,
|
|
115
120
|
},
|
|
116
121
|
})
|
|
117
122
|
|
|
@@ -120,10 +125,15 @@ export function registerIconRestController(
|
|
|
120
125
|
return
|
|
121
126
|
}
|
|
122
127
|
|
|
128
|
+
const etag = setRevalidatingCacheHeaders(res, icon.checksum)
|
|
129
|
+
if (isNotModified(req, etag)) {
|
|
130
|
+
res.status(304).end()
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
123
134
|
// Set appropriate headers
|
|
124
135
|
res.setHeader('Content-Type', icon.mimeType)
|
|
125
136
|
res.setHeader('Content-Disposition', `inline; filename="${icon.name}"`)
|
|
126
|
-
res.setHeader('Cache-Control', 'public, max-age=86400') // Cache for 1 day
|
|
127
137
|
|
|
128
138
|
// Send binary content
|
|
129
139
|
res.send(icon.content)
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { Tool } from 'ai'
|
|
2
2
|
import { z } from 'zod'
|
|
3
|
-
import {
|
|
4
|
-
import { PrismaPg } from '@prisma/adapter-pg'
|
|
5
|
-
import pg from 'pg'
|
|
3
|
+
import { createCorePrismaClient } from '../../db/client'
|
|
6
4
|
import type { AcDatabaseConfig } from '../../middleware/types.js'
|
|
7
5
|
|
|
8
6
|
// ============================================================================
|
|
@@ -27,10 +25,11 @@ function getDatabaseUrl(config: AcDatabaseConfig): string {
|
|
|
27
25
|
export function createAppCatalogAITools(
|
|
28
26
|
databaseConfig: AcDatabaseConfig,
|
|
29
27
|
): Record<string, Tool> {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
const { client: prisma } = createCorePrismaClient({
|
|
29
|
+
connectionString: getDatabaseUrl(databaseConfig),
|
|
30
|
+
configuredSchema:
|
|
31
|
+
'url' in databaseConfig ? undefined : databaseConfig.schema,
|
|
32
|
+
})
|
|
34
33
|
|
|
35
34
|
const getAppCardSchema = z.object({
|
|
36
35
|
slug: z.string().describe('The app slug to fetch'),
|
|
@@ -46,6 +46,13 @@ export interface SourceReference {
|
|
|
46
46
|
export interface Freshness {
|
|
47
47
|
/** ISO-8601 timestamp the app's sources were last verified, or null if never. */
|
|
48
48
|
lastCheckedAt: string | null
|
|
49
|
+
/**
|
|
50
|
+
* ISO-8601 timestamp the source content last actually CHANGED — what the UI
|
|
51
|
+
* shows as "Updated". Distinct from `lastCheckedAt`: a source can be re-read
|
|
52
|
+
* repeatedly without changing. Null for entries scanned before this was
|
|
53
|
+
* tracked, in which case the frontend falls back to `lastCheckedAt`.
|
|
54
|
+
*/
|
|
55
|
+
lastContentChangeAt: string | null
|
|
49
56
|
/** True when the entry is overdue for a re-check by more than the grace period. */
|
|
50
57
|
isStale: boolean
|
|
51
58
|
}
|
|
@@ -118,6 +125,7 @@ export interface Resource {
|
|
|
118
125
|
*/
|
|
119
126
|
lastCheckedAt?: string | null
|
|
120
127
|
nextCheckAfter?: string | null
|
|
128
|
+
lastContentChangeAt?: string | null
|
|
121
129
|
/** ISO-8601 timestamp of when this entry was first created in the catalog DB. */
|
|
122
130
|
createdAt?: string
|
|
123
131
|
}
|