mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
@@ -315,7 +315,11 @@ exports.archiveAdvertisement = async (req, res) => {
|
||||
const advertisement = await Advertisement.findOne({ where: { advertisement_id: advertisementId, ...notDeleted } });
|
||||
if (!advertisement) return R.error(res, "Advertisement not found.", 404);
|
||||
|
||||
await advertisement.update({ deletedBy: req.body.deletedBy ?? null });
|
||||
// Freeze the derived status (e.g. "expired") onto the row before it goes
|
||||
// paranoid — the archived list trusts this stored value as-is and never
|
||||
// re-derives it, so "Remove Expired" would otherwise miss ads that had
|
||||
// already lapsed at the moment an admin manually archived them.
|
||||
await advertisement.update({ deletedBy: req.body.deletedBy ?? null, status: deriveStatus(advertisement) });
|
||||
await advertisement.destroy();
|
||||
logActivity(req.user?.user_id, 'archive_advertisement', { entityType: 'advertisement', entityId: Number(advertisementId) });
|
||||
return R.success(res, "Advertisement archived.");
|
||||
@@ -337,7 +341,11 @@ exports.archiveAdvertisements = async (req, res) => {
|
||||
|
||||
const activeIds = advertisements.map((a) => a.advertisement_id);
|
||||
|
||||
await Advertisement.update({ deletedBy: deletedBy ?? null }, { where: { advertisement_id: { [Op.in]: activeIds } } });
|
||||
// Same status-freeze as the single-archive path — resync each row's
|
||||
// status right before it goes paranoid so "Remove Expired" can trust it.
|
||||
await Promise.all(advertisements.map((a) =>
|
||||
a.update({ deletedBy: deletedBy ?? null, status: deriveStatus(a) })
|
||||
));
|
||||
await Advertisement.destroy({ where: { advertisement_id: { [Op.in]: activeIds } } });
|
||||
|
||||
logActivity(req.user?.user_id, 'bulk_archive_advertisements', { entityType: 'advertisement', details: { ids: activeIds, count: activeIds.length } });
|
||||
|
||||
@@ -267,8 +267,8 @@ exports.getAsset = async (req, res) => {
|
||||
// available below to sign a stream token — stripped before the response.
|
||||
attributes: { exclude: ["storage_bucket"] },
|
||||
include: [
|
||||
{ model: mdl_Users, as: "creator", attributes: ["user_id", "personal_info"], foreignKey: "createdBy" },
|
||||
{ model: mdl_Users, as: "updater", attributes: ["user_id", "personal_info"], foreignKey: "updatedBy" },
|
||||
{ model: mdl_Users, as: "creator", attributes: ["user_id", "email", "personal_info"], foreignKey: "createdBy" },
|
||||
{ model: mdl_Users, as: "updater", attributes: ["user_id", "email", "personal_info"], foreignKey: "updatedBy" },
|
||||
],
|
||||
});
|
||||
|
||||
@@ -276,16 +276,18 @@ exports.getAsset = async (req, res) => {
|
||||
|
||||
const json = asset.toJSON();
|
||||
|
||||
// Falls back to email when full_name hasn't been filled in — better than
|
||||
// surfacing the raw numeric user_id in the admin UI.
|
||||
if (json.creator) {
|
||||
json.creator = {
|
||||
user_id: json.creator.user_id,
|
||||
full_name: json.creator.personal_info?.name?.full_name ?? null,
|
||||
full_name: json.creator.personal_info?.name?.full_name || json.creator.email || null,
|
||||
};
|
||||
}
|
||||
if (json.updater) {
|
||||
json.updater = {
|
||||
user_id: json.updater.user_id,
|
||||
full_name: json.updater.personal_info?.name?.full_name ?? null,
|
||||
full_name: json.updater.personal_info?.name?.full_name || json.updater.email || null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -339,7 +341,9 @@ async function createAssetFromUpload({ file, thumbFile, body, user, requireVideo
|
||||
display_name,
|
||||
description,
|
||||
is_public = false,
|
||||
storage_provider = "chibisafe",
|
||||
// TEMPORARY: defaulting to "s3" instead of "chibisafe" — zrok tunnel is
|
||||
// dropping Chibisafe uploads (502s). Revert this default once off zrok/VPS migration.
|
||||
storage_provider = "s3",
|
||||
storage_bucket,
|
||||
storage_key,
|
||||
createdBy,
|
||||
@@ -406,8 +410,8 @@ async function createAssetFromUpload({ file, thumbFile, body, user, requireVideo
|
||||
let video_codec = null, audio_codec = null;
|
||||
let thumbnail_url = null;
|
||||
|
||||
if (file_type === "video") {
|
||||
const meta = await extractVideoMeta({ buffer: file.buffer, extension: extension || "mp4" });
|
||||
if (file_type === "video" || file_type === "audio") {
|
||||
const meta = await extractVideoMeta({ buffer: file.buffer, extension: extension || (file_type === "video" ? "mp4" : "mp3") });
|
||||
width = meta.width;
|
||||
height = meta.height;
|
||||
resolution = meta.resolution;
|
||||
@@ -436,26 +440,6 @@ async function createAssetFromUpload({ file, thumbFile, body, user, requireVideo
|
||||
thumbnail_url = thumbFile?.filename ? `/uploads/${thumbFile.filename}` : null;
|
||||
}
|
||||
|
||||
} else if (file_type === "audio" && thumbFile) {
|
||||
if (usesProvider) {
|
||||
if (!thumbFile.buffer) {
|
||||
await rollbackUploads(uploadedFiles);
|
||||
throw Object.assign(new Error("Thumbnail buffer is required."), { status: 400 });
|
||||
}
|
||||
const baseName = file.originalname.replace(/\.[^.]+$/, "");
|
||||
const svc = getProvider(storage_provider);
|
||||
const thumbResult = await svc.uploadFile({
|
||||
buffer: thumbFile.buffer,
|
||||
originalname: `thumb_${baseName}.${resolveExtension(thumbFile.originalname) || "jpg"}`,
|
||||
mimetype: thumbFile.mimetype,
|
||||
ownerType: "thumbnail",
|
||||
});
|
||||
thumbnail_url = thumbResult.url;
|
||||
uploadedFiles.push({ key: thumbResult.uuid, provider: storage_provider });
|
||||
} else {
|
||||
thumbnail_url = thumbFile?.filename ? `/uploads/${thumbFile.filename}` : null;
|
||||
}
|
||||
|
||||
} else {
|
||||
const parsedWidth = body.width ? parseInt(body.width) : null;
|
||||
const parsedHeight = body.height ? parseInt(body.height) : null;
|
||||
@@ -554,7 +538,9 @@ exports.uploadAssetsBatch = async (req, res) => {
|
||||
const files = req.files ?? [];
|
||||
if (!files.length) return R.error(res, "No files uploaded.", 400);
|
||||
|
||||
const { display_name, description, is_public = false, storage_provider = "chibisafe", createdBy } = req.body;
|
||||
// TEMPORARY: defaulting to "s3" instead of "chibisafe" — zrok tunnel is
|
||||
// dropping Chibisafe uploads (502s). Revert this default once off zrok/VPS migration.
|
||||
const { display_name, description, is_public = false, storage_provider = "s3", createdBy } = req.body;
|
||||
if (!createdBy) return R.error(res, "createdBy is required.", 400);
|
||||
|
||||
const results = [];
|
||||
|
||||
@@ -1320,12 +1320,12 @@ exports.getLessonByUuid = async (req, res) => {
|
||||
model: Unit,
|
||||
as: "units",
|
||||
where: notDeleted, required: false,
|
||||
attributes: ["unit_id", "uuid", "title"],
|
||||
attributes: ["unit_id", "uuid", "title", "duration_seconds"],
|
||||
through: { attributes: ["order_index"] },
|
||||
include: [{
|
||||
model: Course, as: "courses",
|
||||
where: notDeleted, required: false,
|
||||
attributes: ["course_id", "title", "subscription"],
|
||||
attributes: ["course_id", "title", "subscription", "duration_seconds"],
|
||||
through: { attributes: [] },
|
||||
}],
|
||||
},
|
||||
@@ -1360,7 +1360,7 @@ exports.getLessonByUuid = async (req, res) => {
|
||||
objectives: plain.objectives ?? [],
|
||||
status: progress?.status ?? "not_started",
|
||||
completed_at: progress?.completed_at ?? null,
|
||||
unit: firstUnit ? { unit_id: firstUnit.unit_id, uuid: firstUnit.uuid, title: firstUnit.title, course: firstUnit.courses?.[0] ?? null } : null, // back-compat singular field
|
||||
unit: firstUnit ? { unit_id: firstUnit.unit_id, uuid: firstUnit.uuid, title: firstUnit.title, duration_seconds: firstUnit.duration_seconds, course: firstUnit.courses?.[0] ?? null } : null, // back-compat singular field
|
||||
units: plain.units ?? [],
|
||||
};
|
||||
return R.success(res, "Lesson retrieved.", data);
|
||||
|
||||
Reference in New Issue
Block a user