proper asset creation/deletion, also raw file view in admin as well
This commit is contained in:
@@ -13,12 +13,14 @@ import { traceResultAsync } from "@core/observability";
|
||||
import { MobileRepository } from "./repository";
|
||||
import { settings } from "@core/settings";
|
||||
import { mobileErrors } from "./errors";
|
||||
import { errAsync } from "neverthrow";
|
||||
import { getFileController, type FileController } from "@domains/files/controller";
|
||||
import { errAsync, okAsync } from "neverthrow";
|
||||
import { db } from "@pkg/db";
|
||||
|
||||
export class MobileController {
|
||||
constructor(
|
||||
private mobileRepo: MobileRepository,
|
||||
private fileController: FileController,
|
||||
private defaultAdminEmail?: string,
|
||||
) {}
|
||||
|
||||
@@ -201,11 +203,13 @@ export class MobileController {
|
||||
"app.mobile.media_asset_id": mediaAssetId,
|
||||
},
|
||||
fn: () =>
|
||||
this.mobileRepo.deleteMediaAsset(
|
||||
fctx,
|
||||
mediaAssetId,
|
||||
ownerUserId,
|
||||
),
|
||||
this.mobileRepo
|
||||
.deleteMediaAsset(fctx, mediaAssetId, ownerUserId)
|
||||
.andThen((fileId) =>
|
||||
this.fileController
|
||||
.deleteFiles(fctx, [fileId], ownerUserId)
|
||||
.map(() => true),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -217,7 +221,31 @@ export class MobileController {
|
||||
"app.user.id": ownerUserId,
|
||||
"app.mobile.device_id": deviceId,
|
||||
},
|
||||
fn: () => this.mobileRepo.deleteDevice(fctx, deviceId, ownerUserId),
|
||||
fn: () =>
|
||||
this.mobileRepo
|
||||
.deleteDevice(fctx, deviceId, ownerUserId)
|
||||
.andThen((result) => {
|
||||
const cleanup = result.fileIds.length
|
||||
? this.fileController.deleteFiles(
|
||||
fctx,
|
||||
result.fileIds,
|
||||
ownerUserId,
|
||||
)
|
||||
: okAsync(true);
|
||||
|
||||
return cleanup.andThen(() =>
|
||||
this.mobileRepo
|
||||
.finalizeDeleteDevice(
|
||||
fctx,
|
||||
deviceId,
|
||||
ownerUserId,
|
||||
)
|
||||
.map(() => ({
|
||||
deleted: true,
|
||||
deletedFileCount: result.fileIds.length,
|
||||
})),
|
||||
);
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -235,6 +263,7 @@ export class MobileController {
|
||||
export function getMobileController(): MobileController {
|
||||
return new MobileController(
|
||||
new MobileRepository(db),
|
||||
getFileController(),
|
||||
settings.defaultAdminEmail || undefined,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"@pkg/keystore": "workspace:*",
|
||||
"@pkg/logger": "workspace:*",
|
||||
"@pkg/result": "workspace:*",
|
||||
"@pkg/objectstorage ": "workspace:*",
|
||||
"@pkg/settings": "workspace:*",
|
||||
"@types/pdfkit": "^0.14.0",
|
||||
"argon2": "^0.43.0",
|
||||
|
||||
Reference in New Issue
Block a user