Files
AFFiNE-Mirror/packages/backend/server/src/models/blob.ts
T
DarkSky 0a422aa158 feat(server): blob reconciliation (#15165)
#### PR Dependency Tree


* **PR #15165** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added automated backend maintenance for missing blob metadata
backfill, document-to-blob reference rebuilding, and unreferenced blob
cleanup planning/execution.
* Introduced scheduled batch processing (workspace-paged) and paginated
object-storage listing.
* **Bug Fixes**
* Improved reliability of object-storage reads by treating expected “not
found” results as non-errors.
* Strengthened blob/expired cleanup flows with runtime-driven batching
and reduced coupling to metadata synchronization.
* **Tests**
* Expanded unit and e2e coverage for partial blob metadata and updated
runtime/job cleanup test assertions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-29 00:02:38 +08:00

113 lines
2.2 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { BaseModel } from './base';
export type CreateBlobInput = Prisma.BlobUncheckedCreateInput;
/**
* Blob Model
*/
@Injectable()
export class BlobModel extends BaseModel {
async upsert(blob: CreateBlobInput) {
return await this.db.blob.upsert({
where: {
workspaceId_key: {
workspaceId: blob.workspaceId,
key: blob.key,
},
},
update: {
mime: blob.mime,
size: blob.size,
status: blob.status,
uploadId: blob.uploadId,
},
create: {
workspaceId: blob.workspaceId,
key: blob.key,
mime: blob.mime,
size: blob.size,
status: blob.status,
uploadId: blob.uploadId,
},
});
}
async delete(workspaceId: string, key: string, permanently = false) {
if (permanently) {
await this.db.blob.deleteMany({
where: {
workspaceId,
key,
},
});
this.logger.log(`deleted blob ${workspaceId}/${key} permanently`);
return;
}
await this.db.blob.update({
where: {
workspaceId_key: {
workspaceId,
key,
},
},
data: {
deletedAt: new Date(),
},
});
}
async get(workspaceId: string, key: string) {
return await this.db.blob.findUnique({
where: {
workspaceId_key: {
workspaceId,
key,
},
},
});
}
async list(
workspaceId: string,
options?: { where: Prisma.BlobWhereInput; select?: Prisma.BlobSelect }
) {
return await this.db.blob.findMany({
where: {
...options?.where,
workspaceId,
deletedAt: null,
status: 'completed',
},
select: options?.select,
});
}
async hasAny(workspaceId: string) {
const count = await this.db.blob.count({
where: {
workspaceId,
deletedAt: null,
},
});
return count > 0;
}
async totalSize(workspaceId: string) {
const sum = await this.db.blob.aggregate({
where: {
workspaceId,
deletedAt: null,
},
_sum: {
size: true,
},
});
return sum._sum.size ?? 0;
}
}