chore: relocate backend into apps/api ahead of monorepo merge

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-24 12:20:40 +08:00
co-authored by Claude Sonnet 5
parent af44598df6
commit eaa6d2276a
388 changed files with 0 additions and 13998 deletions
@@ -0,0 +1,65 @@
/***********************************************************************************************************************************************************************
* File Name : retry_stuck_transcodes.cron.js
* Type : Cron Job
* Description : Safety net for the .mov/.mkv -> faststart .mp4 background
* remux (see services/assetTranscode.service.js). Picks up:
* - "pending" — the fire-and-forget call in
* assets.controller.js#finalizeAssetFromStorage
* never actually started (e.g. this process
* crashed between the DB commit and the call).
* - "processing" for over 30 minutes — the job itself was
* running when the process restarted/crashed
* mid-remux and never got to flip the status.
*
* Processes at most 3 per run, sequentially — this runs on a
* small droplet, and remuxing is disk/CPU-bound; no reason to
* pile up concurrent ffmpeg processes for a background sweep.
*
* Schedule : Every 10 minutes ("*\/10 * * * *"). Registered by
* cron/admin.cron.js.
*
* Author: Kenneth Obsequio
* Date Created: Aug. 1, 2026
***********************************************************************************************************************************************************************/
const { Op } = require('sequelize');
const mdl_Assets = require('../../models/assets/assets.mdl');
const { transcodeAsset } = require('../../services/assetTranscode.service');
const MAX_PER_RUN = 3;
const STUCK_PROCESSING_MINUTES = 30;
async function run() {
try {
const stuckSince = new Date(Date.now() - STUCK_PROCESSING_MINUTES * 60 * 1000);
// Demote stale "processing" rows back to "pending" so transcodeAsset()'s
// own claim step (pending/failed -> processing) can pick them up again —
// it never claims an in-progress "processing" row, by design (avoids
// double-processing a job that's actually still running elsewhere).
await mdl_Assets.update(
{ transcode_status: 'pending' },
{ where: { transcode_status: 'processing', updatedAt: { [Op.lt]: stuckSince } } },
);
const candidates = await mdl_Assets.findAll({
where: { deletedAt: null, transcode_status: 'pending' },
limit: MAX_PER_RUN,
});
if (!candidates.length) return;
for (const asset of candidates) {
await transcodeAsset(asset);
}
console.log(`[CRON][RETRY STUCK TRANSCODES] Processed ${candidates.length} asset(s).`);
} catch (err) {
console.error('[CRON][RETRY STUCK TRANSCODES] Failed:', err);
}
}
module.exports = {
name: 'retryStuckTranscodes',
schedule: '*/10 * * * *',
run,
};