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
+98
-67
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 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';
|
||||
import {renderComposition} from './render-composition.js';
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} RenderGifResult
|
||||
* @property {Buffer} buffer
|
||||
* @property {number} durationMs
|
||||
* @property {number} byteLength
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {WheelRenderRequest} inputProps
|
||||
* @param {RenderWheelGifOptions} options
|
||||
* @returns {Promise<RenderGifResult>}
|
||||
*/
|
||||
export const renderWheelGif = async (inputProps, options) => {
|
||||
const startedAt = Date.now();
|
||||
const tempDir = await createRenderTempDir();
|
||||
const outputLocation = path.join(tempDir, 'wheelofnames.gif');
|
||||
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: 'WheelGif',
|
||||
inputProps,
|
||||
serveUrl,
|
||||
timeoutInMilliseconds: rendererTimeout,
|
||||
export const renderWheelGif = (inputProps, options) =>
|
||||
renderComposition({
|
||||
compositionId: 'WheelGif',
|
||||
filename: 'wheelofnames.gif',
|
||||
inputProps: {...inputProps},
|
||||
media: {codec: 'gif', imageFormat: 'png'},
|
||||
timeoutInMilliseconds: options.timeoutInMilliseconds,
|
||||
});
|
||||
|
||||
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 {isAuthorized} from '../lib/bearer-auth.js';
|
||||
import {isRenderTimeoutError} from '../lib/render-errors.js';
|
||||
import {pickWinnerIndex} from '../lib/winner.js';
|
||||
import {parseWheelRequest} from '../schemas/wheel-request.js';
|
||||
@@ -30,12 +31,9 @@ const formatValidationError = (issues) => ({
|
||||
*/
|
||||
export const registerGifRoute = async (app, deps) => {
|
||||
app.post('/api/gif', async (request, reply) => {
|
||||
if (deps.config.apiToken) {
|
||||
const expected = `Bearer ${deps.config.apiToken}`;
|
||||
if (request.headers.authorization !== expected) {
|
||||
if (!isAuthorized(request, deps.config.apiToken)) {
|
||||
return reply.code(401).send({error: 'unauthorized'});
|
||||
}
|
||||
}
|
||||
|
||||
let wheelRequest;
|
||||
try {
|
||||
|
||||
Reference in new issue
Block a user