mirror of
https://github.com/tiennm99/tiennm99bot.git
synced 2026-10-11 03:13:46 +00:00
refactor(render): share the render core and bearer check across routes
This commit is contained in:
1 parent
2fd362dd01
commit
61d0e74709
4 files changed
+100
-69
No files matched your search
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* @param {import('fastify').FastifyRequest} request
|
||||||
|
* @param {string | undefined} apiToken
|
||||||
|
* @returns {boolean} true when no token is configured or the request carries it.
|
||||||
|
*/
|
||||||
|
export const isAuthorized = (request, apiToken) =>
|
||||||
|
!apiToken || request.headers.authorization === `Bearer ${apiToken}`;
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import {readFile} from 'node:fs/promises';
|
||||||
|
import path from 'node:path';
|
||||||
|
import {makeCancelSignal, renderMedia, selectComposition} from '@remotion/renderer';
|
||||||
|
import {minRenderTimeoutMs} from '../config.js';
|
||||||
|
import {RenderTimeoutError} from '../lib/render-errors.js';
|
||||||
|
import {cleanupTempDir, createRenderTempDir} from '../lib/tmp-files.js';
|
||||||
|
import {getRemotionServeUrl} from './remotion-bundle.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} RenderCompositionOptions
|
||||||
|
* @property {string} compositionId
|
||||||
|
* @property {Record<string, unknown>} inputProps
|
||||||
|
* @property {string} filename
|
||||||
|
* @property {{codec: import('@remotion/renderer').Codec} & Partial<import('@remotion/renderer').RenderMediaOptions>} media Codec and encoder settings.
|
||||||
|
* @property {number} timeoutInMilliseconds
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} RenderResult
|
||||||
|
* @property {Buffer} buffer
|
||||||
|
* @property {number} durationMs
|
||||||
|
* @property {number} byteLength
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders one composition from the shared bundle into memory, cancelling the
|
||||||
|
* render once the total timeout elapses.
|
||||||
|
*
|
||||||
|
* @param {RenderCompositionOptions} options
|
||||||
|
* @returns {Promise<RenderResult>}
|
||||||
|
*/
|
||||||
|
export const renderComposition = async (options) => {
|
||||||
|
const startedAt = Date.now();
|
||||||
|
const tempDir = await createRenderTempDir();
|
||||||
|
const outputLocation = path.join(tempDir, options.filename);
|
||||||
|
const {cancel, cancelSignal} = makeCancelSignal();
|
||||||
|
let timedOut = false;
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
timedOut = true;
|
||||||
|
cancel();
|
||||||
|
}, options.timeoutInMilliseconds);
|
||||||
|
const rendererTimeout = Math.max(minRenderTimeoutMs, options.timeoutInMilliseconds);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const serveUrl = await getRemotionServeUrl();
|
||||||
|
const composition = await selectComposition({
|
||||||
|
id: options.compositionId,
|
||||||
|
inputProps: options.inputProps,
|
||||||
|
serveUrl,
|
||||||
|
timeoutInMilliseconds: rendererTimeout,
|
||||||
|
});
|
||||||
|
|
||||||
|
await renderMedia({
|
||||||
|
...options.media,
|
||||||
|
cancelSignal,
|
||||||
|
composition,
|
||||||
|
inputProps: options.inputProps,
|
||||||
|
outputLocation,
|
||||||
|
overwrite: true,
|
||||||
|
serveUrl,
|
||||||
|
timeoutInMilliseconds: rendererTimeout,
|
||||||
|
});
|
||||||
|
|
||||||
|
const buffer = await readFile(outputLocation);
|
||||||
|
return {
|
||||||
|
buffer,
|
||||||
|
byteLength: buffer.byteLength,
|
||||||
|
durationMs: Date.now() - startedAt,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (timedOut || Date.now() - startedAt >= options.timeoutInMilliseconds) {
|
||||||
|
throw new RenderTimeoutError(options.timeoutInMilliseconds, {cause: error});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
await cleanupTempDir(tempDir);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,13 +1,8 @@
|
|||||||
import {readFile} from 'node:fs/promises';
|
import {renderComposition} from './render-composition.js';
|
||||||
import path from 'node:path';
|
|
||||||
import {makeCancelSignal, renderMedia, selectComposition} from '@remotion/renderer';
|
|
||||||
import {minRenderTimeoutMs} from '../config.js';
|
|
||||||
import {RenderTimeoutError} from '../lib/render-errors.js';
|
|
||||||
import {cleanupTempDir, createRenderTempDir} from '../lib/tmp-files.js';
|
|
||||||
import {getRemotionServeUrl} from './remotion-bundle.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('../schemas/wheel-request.js').WheelRenderRequest} WheelRenderRequest
|
* @typedef {import('../schemas/wheel-request.js').WheelRenderRequest} WheelRenderRequest
|
||||||
|
* @typedef {import('./render-composition.js').RenderResult} RenderGifResult
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,65 +10,16 @@ import {getRemotionServeUrl} from './remotion-bundle.js';
|
|||||||
* @property {number} timeoutInMilliseconds
|
* @property {number} timeoutInMilliseconds
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} RenderGifResult
|
|
||||||
* @property {Buffer} buffer
|
|
||||||
* @property {number} durationMs
|
|
||||||
* @property {number} byteLength
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {WheelRenderRequest} inputProps
|
* @param {WheelRenderRequest} inputProps
|
||||||
* @param {RenderWheelGifOptions} options
|
* @param {RenderWheelGifOptions} options
|
||||||
* @returns {Promise<RenderGifResult>}
|
* @returns {Promise<RenderGifResult>}
|
||||||
*/
|
*/
|
||||||
export const renderWheelGif = async (inputProps, options) => {
|
export const renderWheelGif = (inputProps, options) =>
|
||||||
const startedAt = Date.now();
|
renderComposition({
|
||||||
const tempDir = await createRenderTempDir();
|
compositionId: 'WheelGif',
|
||||||
const outputLocation = path.join(tempDir, 'wheelofnames.gif');
|
filename: 'wheelofnames.gif',
|
||||||
const {cancel, cancelSignal} = makeCancelSignal();
|
inputProps: {...inputProps},
|
||||||
let timedOut = false;
|
media: {codec: 'gif', imageFormat: 'png'},
|
||||||
const timeoutId = setTimeout(() => {
|
timeoutInMilliseconds: options.timeoutInMilliseconds,
|
||||||
timedOut = true;
|
});
|
||||||
cancel();
|
|
||||||
}, options.timeoutInMilliseconds);
|
|
||||||
const rendererTimeout = Math.max(minRenderTimeoutMs, options.timeoutInMilliseconds);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const serveUrl = await getRemotionServeUrl();
|
|
||||||
const composition = await selectComposition({
|
|
||||||
id: 'WheelGif',
|
|
||||||
inputProps,
|
|
||||||
serveUrl,
|
|
||||||
timeoutInMilliseconds: rendererTimeout,
|
|
||||||
});
|
|
||||||
|
|
||||||
await renderMedia({
|
|
||||||
cancelSignal,
|
|
||||||
codec: 'gif',
|
|
||||||
composition,
|
|
||||||
imageFormat: 'png',
|
|
||||||
inputProps,
|
|
||||||
outputLocation,
|
|
||||||
overwrite: true,
|
|
||||||
serveUrl,
|
|
||||||
timeoutInMilliseconds: rendererTimeout,
|
|
||||||
});
|
|
||||||
|
|
||||||
const buffer = await readFile(outputLocation);
|
|
||||||
return {
|
|
||||||
buffer,
|
|
||||||
byteLength: buffer.byteLength,
|
|
||||||
durationMs: Date.now() - startedAt,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
if (timedOut || Date.now() - startedAt >= options.timeoutInMilliseconds) {
|
|
||||||
throw new RenderTimeoutError(options.timeoutInMilliseconds, {cause: error});
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
await cleanupTempDir(tempDir);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import {ZodError} from 'zod';
|
import {ZodError} from 'zod';
|
||||||
|
import {isAuthorized} from '../lib/bearer-auth.js';
|
||||||
import {isRenderTimeoutError} from '../lib/render-errors.js';
|
import {isRenderTimeoutError} from '../lib/render-errors.js';
|
||||||
import {pickWinnerIndex} from '../lib/winner.js';
|
import {pickWinnerIndex} from '../lib/winner.js';
|
||||||
import {parseWheelRequest} from '../schemas/wheel-request.js';
|
import {parseWheelRequest} from '../schemas/wheel-request.js';
|
||||||
@@ -30,11 +31,8 @@ const formatValidationError = (issues) => ({
|
|||||||
*/
|
*/
|
||||||
export const registerGifRoute = async (app, deps) => {
|
export const registerGifRoute = async (app, deps) => {
|
||||||
app.post('/api/gif', async (request, reply) => {
|
app.post('/api/gif', async (request, reply) => {
|
||||||
if (deps.config.apiToken) {
|
if (!isAuthorized(request, deps.config.apiToken)) {
|
||||||
const expected = `Bearer ${deps.config.apiToken}`;
|
return reply.code(401).send({error: 'unauthorized'});
|
||||||
if (request.headers.authorization !== expected) {
|
|
||||||
return reply.code(401).send({error: 'unauthorized'});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let wheelRequest;
|
let wheelRequest;
|
||||||
|
|||||||
Reference in new issue
Block a user