proper asset creation/deletion, also raw file view in admin as well

This commit is contained in:
user
2026-03-01 18:22:49 +02:00
parent 6c2b917088
commit 9716deead7
10 changed files with 524 additions and 69 deletions

View File

@@ -5,11 +5,10 @@ import {
count,
desc,
eq,
inArray,
like,
or,
} from "@pkg/db";
import { file, mobileDevice, mobileMediaAsset, mobileSMS, user } from "@pkg/db/schema";
import { mobileDevice, mobileMediaAsset, mobileSMS, user } from "@pkg/db/schema";
import { ResultAsync, errAsync, okAsync } from "neverthrow";
import { FlowExecCtx } from "@core/flow.execution.context";
import type {
@@ -676,7 +675,7 @@ export class MobileRepository {
fctx: FlowExecCtx,
mediaAssetId: number,
ownerUserId: string,
): ResultAsync<boolean, Err> {
): ResultAsync<string, Err> {
const startedAt = Date.now();
return ResultAsync.fromPromise(
this.db
@@ -706,27 +705,32 @@ export class MobileRepository {
}
return ResultAsync.fromPromise(
this.db.transaction(async (tx) => {
await tx
.delete(mobileMediaAsset)
.where(eq(mobileMediaAsset.id, mediaAssetId));
await tx.delete(file).where(eq(file.id, target.fileId));
return true;
}),
this.db
.delete(mobileMediaAsset)
.where(eq(mobileMediaAsset.id, mediaAssetId))
.returning({ fileId: mobileMediaAsset.fileId }),
(error) =>
mobileErrors.deleteMediaFailed(
fctx,
error instanceof Error ? error.message : String(error),
),
);
}).map((deleted) => {
).andThen((deletedRows) => {
const deleted = deletedRows[0];
if (!deleted) {
return errAsync(
mobileErrors.mediaAssetNotFound(fctx, mediaAssetId),
);
}
return okAsync(deleted.fileId);
});
}).map((fileId) => {
logDomainEvent({
event: "mobile.media.delete.succeeded",
fctx,
durationMs: Date.now() - startedAt,
meta: { mediaAssetId, ownerUserId, deleted },
meta: { mediaAssetId, ownerUserId, fileId },
});
return deleted;
return fileId;
});
}
@@ -734,7 +738,7 @@ export class MobileRepository {
fctx: FlowExecCtx,
deviceId: number,
ownerUserId: string,
): ResultAsync<{ deleted: boolean; deletedFileCount: number }, Err> {
): ResultAsync<{ fileIds: string[] }, Err> {
const startedAt = Date.now();
return ResultAsync.fromPromise(
this.db
@@ -758,35 +762,54 @@ export class MobileRepository {
}
return ResultAsync.fromPromise(
this.db.transaction(async (tx) => {
const mediaFiles = await tx
.select({ fileId: mobileMediaAsset.fileId })
.from(mobileMediaAsset)
.where(eq(mobileMediaAsset.deviceId, deviceId));
const fileIds = mediaFiles.map((item) => item.fileId);
await tx.delete(mobileDevice).where(eq(mobileDevice.id, deviceId));
if (fileIds.length > 0) {
await tx.delete(file).where(inArray(file.id, fileIds));
}
return { deleted: true, deletedFileCount: fileIds.length };
}),
this.db
.select({ fileId: mobileMediaAsset.fileId })
.from(mobileMediaAsset)
.where(eq(mobileMediaAsset.deviceId, deviceId)),
(error) =>
mobileErrors.deleteDeviceFailed(
fctx,
error instanceof Error ? error.message : String(error),
),
);
).map((rows) => ({
fileIds: [...new Set(rows.map((item) => item.fileId))],
}));
}).map((result) => {
logDomainEvent({
event: "mobile.device.delete.succeeded",
event: "mobile.device.delete.prepared",
fctx,
durationMs: Date.now() - startedAt,
meta: { deviceId, deletedFileCount: result.deletedFileCount },
meta: { deviceId, deletedFileCount: result.fileIds.length },
});
return result;
});
}
finalizeDeleteDevice(
fctx: FlowExecCtx,
deviceId: number,
ownerUserId: string,
): ResultAsync<boolean, Err> {
return ResultAsync.fromPromise(
this.db
.delete(mobileDevice)
.where(
and(
eq(mobileDevice.id, deviceId),
eq(mobileDevice.ownerUserId, ownerUserId),
),
)
.returning({ id: mobileDevice.id }),
(error) =>
mobileErrors.deleteDeviceFailed(
fctx,
error instanceof Error ? error.message : String(error),
),
).andThen((rows) => {
if (!rows[0]) {
return errAsync(mobileErrors.deviceNotFoundById(fctx, deviceId));
}
return okAsync(true);
});
}
}