Skip to content

Commit 0fcac5d

Browse files
committed
minor cleanups
1 parent a88d4f6 commit 0fcac5d

29 files changed

Lines changed: 345 additions & 270 deletions

.oxlintrc.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"plugins": ["typescript", "react", "import", "oxc"],
3+
"rules": {
4+
"no-unused-vars": "error",
5+
"no-console": "off",
6+
"eqeqeq": ["error", "always", { "null": "ignore" }]
7+
},
8+
"ignorePatterns": ["node_modules", "dist"]
9+
}

oxlintrc.json

Lines changed: 0 additions & 8 deletions
This file was deleted.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"tool:backup": "tsx tools/backupDb.ts",
1919
"tool:restore": "tsx tools/restore.ts",
2020
"tool:pruneBackups": "tsx tools/pruneBackups.ts",
21+
"tool:cleanupSessions": "tsx tools/cleanupSessions.ts",
2122
"tool:seed-mirror": "tsx tools/seedMirror.ts",
2223
"cli": "tsx src/cli/cli.ts",
2324
"test": "vitest run",

server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ app.get('/api/blog/:slug', (c) => {
233233
const slug = c.req.param('slug')
234234
const mdPath = resolve('content/blog', `${slug}.md`)
235235
if (!existsSync(mdPath)) {
236-
return c.json({ error: 'Not found' }, 404)
236+
return c.json({ error: 'Not found', statusCode: 404 }, 404)
237237
}
238238
const raw = readFileSync(mdPath, 'utf-8')
239239
// Strip frontmatter

src/api/accounts.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -344,8 +344,9 @@ const app = new Hono<AuthEnv>()
344344
try {
345345
const avatarKeys = await listS3Objects(`avatars/${defaultOrg.id}/`)
346346
if (avatarKeys.length > 0) await deleteS3Objects(avatarKeys)
347-
} catch {
348-
// Non-fatal
347+
} catch (err) {
348+
// Non-fatal — orphaned avatars are harmless
349+
console.error(`[accounts] Failed to delete avatars for org ${defaultOrg.id}:`, err)
349350
}
350351

351352
await db.delete(schema.apikey).where(eq(schema.apikey.referenceId, userId))
@@ -443,8 +444,9 @@ const app = new Hono<AuthEnv>()
443444
try {
444445
const avatarKeys = await listS3Objects(`avatars/${org.id}/`)
445446
if (avatarKeys.length > 0) await deleteS3Objects(avatarKeys)
446-
} catch {
447-
// Non-fatal
447+
} catch (err) {
448+
// Non-fatal — orphaned avatars are harmless
449+
console.error(`[accounts] Failed to delete avatars for org ${org.id}:`, err)
448450
}
449451

450452
await db.delete(schema.apikey).where(eq(schema.apikey.referenceId, org.id))

src/api/ark.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ import { type AuthEnv } from './auth.server.js'
2222

2323
export async function resolve(c: Context<AuthEnv>) {
2424
const path = c.req.query('path')
25-
if (!path) return c.json({ error: 'Missing path' }, 400)
25+
if (!path) return c.json({ error: 'Missing path', statusCode: 400 }, 400)
2626

2727
// path = "ark:NAAN/shoulder+collection..."
2828
const arkLabelIdx = path.indexOf('ark:')
29-
if (arkLabelIdx === -1) return c.json({ error: 'Invalid ARK path' }, 400)
29+
if (arkLabelIdx === -1) return c.json({ error: 'Invalid ARK path', statusCode: 400 }, 400)
3030

3131
const afterLabel = path.slice(arkLabelIdx + 4) // strip "ark:"
3232
const slashIdx = afterLabel.indexOf('/')
@@ -322,11 +322,11 @@ export async function getArk(c: Context<AuthEnv>) {
322322
.innerJoin(schema.organization, eq(schema.collections.organizationId, schema.organization.id))
323323
.where(and(eq(schema.organization.slug, owner), eq(schema.collections.slug, slug)))
324324
.limit(1)
325-
if (!coll) return c.json({ error: 'Collection not found' }, 404)
325+
if (!coll) return c.json({ error: 'Collection not found', statusCode: 404 }, 404)
326326

327327
// Must be owner/member
328328
const hasAccess = await checkCollectionAccess(coll.organizationId, c.get('userId')!)
329-
if (!hasAccess) return c.json({ error: 'Forbidden' }, 403)
329+
if (!hasAccess) return c.json({ error: 'Forbidden', statusCode: 403 }, 403)
330330

331331
const naan = coll.ownerNaan ?? DEFAULT_NAAN
332332

@@ -367,10 +367,10 @@ export async function updateArk(c: Context<AuthEnv>) {
367367
.innerJoin(schema.organization, eq(schema.collections.organizationId, schema.organization.id))
368368
.where(and(eq(schema.organization.slug, owner), eq(schema.collections.slug, slug)))
369369
.limit(1)
370-
if (!coll) return c.json({ error: 'Collection not found' }, 404)
370+
if (!coll) return c.json({ error: 'Collection not found', statusCode: 404 }, 404)
371371

372372
const hasAccess = await checkCollectionAccess(coll.organizationId, c.get('userId')!)
373-
if (!hasAccess) return c.json({ error: 'Forbidden' }, 403)
373+
if (!hasAccess) return c.json({ error: 'Forbidden', statusCode: 403 }, 403)
374374

375375
const [existing] = await db
376376
.select({ collectionId: schema.arkCollections.collectionId })
@@ -415,10 +415,10 @@ export async function getArkRecordTypes(c: Context<AuthEnv>) {
415415
.innerJoin(schema.organization, eq(schema.collections.organizationId, schema.organization.id))
416416
.where(and(eq(schema.organization.slug, owner), eq(schema.collections.slug, slug)))
417417
.limit(1)
418-
if (!coll) return c.json({ error: 'Collection not found' }, 404)
418+
if (!coll) return c.json({ error: 'Collection not found', statusCode: 404 }, 404)
419419

420420
const hasAccess = await checkCollectionAccess(coll.organizationId, c.get('userId')!)
421-
if (!hasAccess) return c.json({ error: 'Forbidden' }, 403)
421+
if (!hasAccess) return c.json({ error: 'Forbidden', statusCode: 403 }, 403)
422422

423423
const rows = await db
424424
.select({
@@ -436,18 +436,18 @@ export async function updateArkRecordTypes(c: Context<AuthEnv>) {
436436
const slug = c.req.param('slug')!
437437
const { recordType, redirectUrlField } = await c.req.json()
438438

439-
if (!recordType) return c.json({ error: 'recordType required' }, 400)
439+
if (!recordType) return c.json({ error: 'recordType required', statusCode: 400 }, 400)
440440

441441
const [coll] = await db
442442
.select({ id: schema.collections.id, organizationId: schema.collections.organizationId })
443443
.from(schema.collections)
444444
.innerJoin(schema.organization, eq(schema.collections.organizationId, schema.organization.id))
445445
.where(and(eq(schema.organization.slug, owner), eq(schema.collections.slug, slug)))
446446
.limit(1)
447-
if (!coll) return c.json({ error: 'Collection not found' }, 404)
447+
if (!coll) return c.json({ error: 'Collection not found', statusCode: 404 }, 404)
448448

449449
const hasAccess = await checkCollectionAccess(coll.organizationId, c.get('userId')!)
450-
if (!hasAccess) return c.json({ error: 'Forbidden' }, 403)
450+
if (!hasAccess) return c.json({ error: 'Forbidden', statusCode: 403 }, 403)
451451

452452
if (redirectUrlField === null) {
453453
await db
@@ -478,15 +478,15 @@ export async function updateAccountArk(c: Context<AuthEnv>) {
478478
const { naan } = await c.req.json()
479479

480480
if (naan !== null && !/^\d{1,16}$/.test(naan)) {
481-
return c.json({ error: 'NAAN must be numeric (up to 16 digits)' }, 400)
481+
return c.json({ error: 'NAAN must be numeric (up to 16 digits)', statusCode: 400 }, 400)
482482
}
483483

484484
const [org] = await db
485485
.select({ id: schema.organization.id })
486486
.from(schema.organization)
487487
.where(eq(schema.organization.slug, slug))
488488
.limit(1)
489-
if (!org) return c.json({ error: 'Org not found' }, 404)
489+
if (!org) return c.json({ error: 'Org not found', statusCode: 404 }, 404)
490490

491491
// Must be owner/admin of the org
492492
const [membership] = await db
@@ -497,7 +497,7 @@ export async function updateAccountArk(c: Context<AuthEnv>) {
497497
)
498498
.limit(1)
499499
if (!membership || (membership.role !== 'owner' && membership.role !== 'admin')) {
500-
return c.json({ error: 'Forbidden' }, 403)
500+
return c.json({ error: 'Forbidden', statusCode: 403 }, 403)
501501
}
502502

503503
await db

src/api/collections.ts

Lines changed: 17 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { z } from 'zod'
1010
import { db, schema } from '../db/client.server.js'
1111
import { buildArkUrl, collectionToArkId, DEFAULT_NAAN, getOrMintShoulder } from '../lib/ark.js'
1212
import { downloadFromS3 } from '../lib/s3.js'
13-
import { getOrgRole, hasOrgAccess } from '../lib/version-helpers.server.js'
13+
import { getLatestReadyVersion, getOrgRole, hasOrgAccess } from '../lib/version-helpers.server.js'
1414
import { type AuthEnv } from './auth.server.js'
1515
import { requireAuth } from './auth.server.js'
1616

@@ -206,8 +206,9 @@ const app = new Hono<AuthEnv>()
206206
const naan = org.arkNaan ?? DEFAULT_NAAN
207207
const arkUrl = buildArkUrl(naan, shoulder, arkId)
208208
return c.json({ id, owner, slug, name, ark: arkUrl }, 201)
209-
} catch {
209+
} catch (err) {
210210
// ARK minting failure is non-fatal
211+
console.error(`[ark] Failed to mint ARK for new collection ${owner}/${slug}:`, err)
211212
return c.json({ id, owner, slug, name }, 201)
212213
}
213214
},
@@ -276,25 +277,7 @@ const app = new Hono<AuthEnv>()
276277
}
277278

278279
// Get latest version info
279-
const [latestVersion] = await db
280-
.select({
281-
id: schema.versions.id,
282-
semver: schema.versions.semver,
283-
recordCount: schema.versions.recordCount,
284-
fileCount: schema.versions.fileCount,
285-
totalBytes: schema.versions.totalBytes,
286-
createdAt: schema.versions.createdAt,
287-
message: schema.versions.message,
288-
metadata: schema.versions.metadata,
289-
})
290-
.from(schema.versions)
291-
.where(
292-
and(eq(schema.versions.collectionId, result.id), eq(schema.versions.status, 'ready')),
293-
)
294-
.orderBy(
295-
sql`${schema.versions.major} desc, ${schema.versions.minor} desc, ${schema.versions.patch} desc`,
296-
)
297-
.limit(1)
280+
const latestVersion = await getLatestReadyVersion(result.id)
298281

299282
// Get per-type record counts for latest version
300283
let typeCounts: { type: string; count: number }[] = []
@@ -342,8 +325,9 @@ const app = new Hono<AuthEnv>()
342325
if (arkRow?.enabled) {
343326
ark = buildArkUrl(arkRow.ownerNaan ?? DEFAULT_NAAN, arkRow.shoulder, arkRow.arkId)
344327
}
345-
} catch {
346-
// Non-fatal
328+
} catch (err) {
329+
// Non-fatal — ARK URL is decorative here
330+
console.error(`[ark] Failed to load ARK info for collection ${owner}/${slug}:`, err)
347331
}
348332

349333
const [vcRow] = await db
@@ -758,7 +742,8 @@ const app = new Hono<AuthEnv>()
758742

759743
const schemasMap = Object.fromEntries(versionSchemaEntries.map((e) => [e.slug, e.schemaBody]))
760744

761-
// Add manifest.json
745+
// Build manifest.json (packed last, so it can report any files that
746+
// failed to download)
762747
const versionMeta = version.metadata as Record<string, unknown> | null
763748
const manifest = {
764749
collection: {
@@ -777,6 +762,7 @@ const app = new Hono<AuthEnv>()
777762
createdAt: version.createdAt,
778763
},
779764
schemas: schemasMap,
765+
files_missing: [] as string[],
780766
}
781767

782768
// Build tar.gz stream
@@ -785,10 +771,6 @@ const app = new Hono<AuthEnv>()
785771

786772
const filename = `${owner}-${slug}-${version.semver}.tar.gz`
787773

788-
// Add manifest
789-
const manifestBuf = Buffer.from(JSON.stringify(manifest, null, 2))
790-
pack.entry({ name: 'manifest.json', size: manifestBuf.length }, manifestBuf)
791-
792774
// Stream records per-type into tar — avoids loading all records at once
793775
const types = await db
794776
.selectDistinct({ type: schema.recordObjects.type })
@@ -843,11 +825,15 @@ const app = new Hono<AuthEnv>()
843825
try {
844826
const fileBuffer = await downloadFromS3(file.storageKey)
845827
pack.entry({ name: `files/${file.hash}`, size: fileBuffer.length }, fileBuffer)
846-
} catch {
847-
// Skip files that can't be downloaded (shouldn't happen in normal operation)
828+
} catch (err) {
829+
console.error(`[export] Failed to download file ${file.hash} (${file.storageKey}):`, err)
830+
manifest.files_missing.push(file.hash)
848831
}
849832
}
850833

834+
const manifestBuf = Buffer.from(JSON.stringify(manifest, null, 2))
835+
pack.entry({ name: 'manifest.json', size: manifestBuf.length }, manifestBuf)
836+
851837
pack.finalize()
852838

853839
// Pipe tar → gzip and collect into a ReadableStream
@@ -959,16 +945,7 @@ const app = new Hono<AuthEnv>()
959945
}
960946

961947
// Get latest version of source
962-
const [latestVersion] = await db
963-
.select()
964-
.from(schema.versions)
965-
.where(
966-
and(eq(schema.versions.collectionId, source.id), eq(schema.versions.status, 'ready')),
967-
)
968-
.orderBy(
969-
sql`${schema.versions.major} desc, ${schema.versions.minor} desc, ${schema.versions.patch} desc`,
970-
)
971-
.limit(1)
948+
const latestVersion = await getLatestReadyVersion(source.id)
972949

973950
if (!latestVersion) {
974951
return c.json({ error: 'Source collection has no versions', statusCode: 422 }, 422)

src/api/kf-summary.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ import { db, schema } from '../db/client.server.js'
1313
export async function summary(c: Context) {
1414
const kfOrgId = c.req.query('kf_org_id')
1515
if (!kfOrgId) {
16-
return c.json({ error: 'kf_org_id is required' }, 400)
16+
return c.json({ error: 'kf_org_id is required', statusCode: 400 }, 400)
1717
}
1818

1919
// Verify internal API key
2020
const authHeader = c.req.header('Authorization')
2121
const expectedKey = process.env.AUTH_INTERNAL_API_KEY
2222
if (!expectedKey || authHeader !== `Bearer ${expectedKey}`) {
23-
return c.json({ error: 'Unauthorized' }, 401)
23+
return c.json({ error: 'Unauthorized', statusCode: 401 }, 401)
2424
}
2525

2626
const APP_URL = process.env.APP_URL ?? 'http://localhost:4100'

0 commit comments

Comments
 (0)