feat: assetId, fileAssetId columns on integrity reports

This commit is contained in:
izzy
2025-12-01 15:49:03 +00:00
parent 042af30bef
commit 806a2880ca
6 changed files with 166 additions and 48 deletions
+49 -14
View File
@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { Kysely } from 'kysely';
import { Kysely, sql } from 'kysely';
import { jsonArrayFrom } from 'kysely/helpers/postgres';
import { InjectKysely } from 'nestjs-kysely';
import { Asset, columns } from 'src/database';
@@ -282,24 +282,52 @@ export class AssetJobRepository {
.selectFrom((eb) =>
eb
.selectFrom('asset')
.select(['originalPath as path'])
.select(['asset.originalPath as path'])
.select((eb) => [
eb.ref('asset.id').$castTo<string | null>().as('assetId'),
sql<string | null>`null::uuid`.as('fileAssetId'),
])
.unionAll(
eb
.selectFrom('asset')
.select(['encodedVideoPath as path'])
.where('encodedVideoPath', 'is not', null)
.where('encodedVideoPath', '!=', '')
.$castTo<{ path: string }>(),
.select((eb) => [
eb.ref('asset.encodedVideoPath').$castTo<string>().as('path'),
eb.ref('asset.id').$castTo<string | null>().as('assetId'),
sql<string | null>`null::uuid`.as('fileAssetId'),
])
.where('asset.encodedVideoPath', 'is not', null)
.where('asset.encodedVideoPath', '!=', sql<string>`''`),
)
.unionAll(
eb
.selectFrom('asset_file')
.select(['path'])
.select((eb) => [
sql<string | null>`null::uuid`.as('assetId'),
eb.ref('asset_file.id').$castTo<string | null>().as('fileAssetId'),
]),
)
.unionAll(eb.selectFrom('asset_file').select(['path']))
.as('allPaths'),
)
.leftJoin('integrity_report', (join) =>
join
.onRef('integrity_report.path', '=', 'allPaths.path')
.on('integrity_report.type', '=', IntegrityReportType.OrphanFile),
.leftJoin(
'integrity_report',
(join) =>
join
.on('integrity_report.type', '=', IntegrityReportType.OrphanFile)
.on((eb) =>
eb.or([
eb('integrity_report.assetId', '=', eb.ref('allPaths.assetId')),
eb('integrity_report.fileAssetId', '=', eb.ref('allPaths.fileAssetId')),
]),
),
// .onRef('integrity_report.path', '=', 'allPaths.path')
)
.select(['allPaths.path as path', 'integrity_report.path as reportId'])
.select([
'allPaths.path as path',
'allPaths.assetId',
'allPaths.fileAssetId',
'integrity_report.path as reportId',
])
.stream();
}
@@ -309,10 +337,17 @@ export class AssetJobRepository {
.selectFrom('asset')
.leftJoin('integrity_report', (join) =>
join
.onRef('integrity_report.path', '=', 'asset.originalPath')
.onRef('integrity_report.assetId', '=', 'asset.id')
// .onRef('integrity_report.path', '=', 'asset.originalPath')
.on('integrity_report.type', '=', IntegrityReportType.ChecksumFail),
)
.select(['asset.originalPath', 'asset.checksum', 'asset.createdAt', 'integrity_report.id as reportId'])
.select([
'asset.originalPath',
'asset.checksum',
'asset.createdAt',
'asset.id as assetId',
'integrity_report.id as reportId',
])
.$if(startMarker !== undefined, (qb) => qb.where('createdAt', '>=', startMarker!))
.$if(endMarker !== undefined, (qb) => qb.where('createdAt', '<=', endMarker!))
.orderBy('createdAt', 'asc')
@@ -19,7 +19,12 @@ export class IntegrityReportRepository {
return this.db
.insertInto('integrity_report')
.values(dto)
.onConflict((oc) => oc.doNothing())
.onConflict((oc) =>
oc.columns(['path', 'type']).doUpdateSet({
assetId: (eb) => eb.ref('excluded.assetId'),
fileAssetId: (eb) => eb.ref('excluded.fileAssetId'),
}),
)
.returningAll()
.executeTakeFirst();
}
@@ -60,7 +65,7 @@ export class IntegrityReportRepository {
return {
items: await this.db
.selectFrom('integrity_report')
.select(['id', 'type', 'path'])
.select(['id', 'type', 'path', 'assetId', 'fileAssetId'])
.where('type', '=', dto.type)
.orderBy('createdAt', 'desc')
.execute(),
@@ -70,19 +75,19 @@ export class IntegrityReportRepository {
getIntegrityReportCsv(type: IntegrityReportType): Readable {
const items = this.db
.selectFrom('integrity_report')
.select(['id', 'type', 'path'])
.select(['id', 'type', 'path', 'assetId', 'fileAssetId'])
.where('type', '=', type)
.orderBy('createdAt', 'desc')
.stream();
// very rudimentary csv serialiser
async function* generator() {
yield 'id,type,path\n';
yield 'id,type,assetId,fileAssetId,path\n';
for await (const item of items) {
// no expectation of particularly bad filenames
// but they could potentially have a newline or quote character
yield `${item.id},${item.type},"${item.path.replace(/"/g, '\\"')}"\n`;
yield `${item.id},${item.type},${item.assetId},${item.fileAssetId},"${item.path.replace(/"/g, '\\"')}"\n`;
}
}