try to deploy

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-07-13 21:27:37 +08:00
parent f5254f7571
commit c8dc2628a6
9 changed files with 120 additions and 46 deletions
+10 -2
View File
@@ -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 } });
+14 -28
View File
@@ -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 = [];
+3 -3
View File
@@ -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);