fp/services/worker/index.ts.old
2025-11-12 07:54:01 -08:00

199 lines
6.4 KiB
TypeScript

import { createBullBoard } from '@bull-board/api';
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
import { ElysiaAdapter } from '@bull-board/elysia';
import { Queue as QueueMQ, Worker } from 'bullmq';
import Elysia from 'elysia';
import { version } from './package.json';
import PocketBase from 'pocketbase';
import env from './env.ts';
import Mux from '@mux/mux-node';
const { subtle } = globalThis.crypto;
async function createToken(playbackId: string) {
// Set some base options we can use for a few different signing types
// Type can be either video, thumbnail, gif, or storyboard
let baseOptions = {
keyId: env.MUX_SIGNING_KEY_ID, // Enter your signing key id here
keySecret: env.MUX_SIGNING_KEY_PRIVATE_KEY, // Enter your base64 encoded private key here
expiration: '7d', // E.g 60, "2 days", "10h", "7d", numeric value interpreted as seconds
};
const token = await mux.jwt.signPlaybackId(playbackId, { ...baseOptions, type: 'video' });
console.log('video token', token);
// Now the signed playback url should look like this:
// https://stream.mux.com/${playbackId}.m3u8?token=${token}
// // If you wanted to pass in params for something like a gif, use the
// // params key in the options object
// const gifToken = await mux.jwt.signPlaybackId(playbackId, {
// ...baseOptions,
// type: 'gif',
// params: { time: '10' },
// });
// console.log('gif token', gifToken);
// // Then, use this token in a URL like this:
// // https://image.mux.com/${playbackId}/animated.gif?token=${gifToken}
// // A final example, if you wanted to sign a thumbnail url with a playback restriction
// const thumbnailToken = await mux.jwt.signPlaybackId(playbackId, {
// ...baseOptions,
// type: 'thumbnail',
// params: { playback_restriction_id: YOUR_PLAYBACK_RESTRICTION_ID },
// });
// console.log('thumbnail token', thumbnailToken);
// When used in a URL, it should look like this:
// https://image.mux.com/${playbackId}/thumbnail.png?token=${thumbnailToken}
return token
}
// function base64ToArrayBuffer(base64: string) {
// const binary = Buffer.from(base64, 'base64');
// return binary.buffer.slice(binary.byteOffset, binary.byteOffset + binary.byteLength);
// }
// async function importPrivateKey(base64Key: string): Promise<CryptoKey> {
// const keyData = base64ToArrayBuffer(base64Key);
// return await subtle.importKey(
// 'pkcs8', // format for private keys
// keyData,
// { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, // RS256
// false, // not extractable
// ['sign'] // usage
// );
// }
const mux = new Mux({
tokenId: env.MUX_TOKEN_ID,
tokenSecret: env.MUX_TOKEN_SECRET
});
const pb = new PocketBase(env.POCKETBASE_URL);
const sleep = (t: number) => new Promise((resolve) => setTimeout(resolve, t));
const redisOptions = {
port: 6379,
host: 'localhost',
password: '',
};
const createQueueMQ = (name: string) => new QueueMQ(name, { connection: redisOptions });
function setupBullMQProcessor(queueName: string) {
new Worker(
queueName,
async (job) => {
job.log(`the job ${job.data.title} is running`);
const userData = await pb.collection('_superusers').authWithPassword(env.POCKETBASE_USERNAME, env.POCKETBASE_PASSWORD);
job.log(`userData ${JSON.stringify(userData)}`);
// // @todo presign all mux assets
// // 1. for each VOD in pocketbase
// // 2. get the muxPlaybackId
const vods = await pb.collection('vods').getFullList({
sort: '-created',
});
job.log(`there are ${vods.length} vods`);
// job.log(JSON.stringify(vods, null, 2));
// 3. sign the muxPlaybackId
for (let i = 0; i < vods.length; i++) {
const vod = vods[i];
if (!vod) throw new Error(`vod ${i} missing`);
if (vod.muxPlaybackId) {
// const muxPlaybackToken = await mux.jwt.signPlaybackId(vod.muxPlaybackId, {
// expiration: "7d"
// })
const muxPlaybackToken = await createToken(vod.muxPlaybackId);
job.log(`muxPlaybackToken is ${muxPlaybackToken}`);
await pb.collection('vods').update(vod.id, {
muxPlaybackToken
});
}
// Calculate progress as a percentage
const progress = Math.round(((i + 1) / vods.length) * 100);
await job.updateProgress(progress);
}
// const record1 = await pb.collection('vods').getOne('RECORD_ID', {
// expand: 'relField1,relField2.subRelField',
// });
// // list and filter "example" collection records
// const result = await pb.collection('vods').getList(1, 20, {
// filter: 'status = true && created > "2022-08-01 10:00:00"'
// });
// // 4. update the VOD
// const record = await pb.collection('demo').update('YOUR_RECORD_ID', {
// title: 'Lorem ipsum',
// });
// for (let i = 0; i <= 100; i++) {
// await sleep(Math.random());
// await job.updateProgress(i);
// await job.log(`Processing job at interval ${i}`);
// if (Math.random() * 200 < 1) throw new Error(`Random error ${i}`);
// }
return { recordCount: 'ninja' };
},
{ connection: redisOptions }
);
}
const muxBullMq = createQueueMQ('PresignMux');
setupBullMQProcessor(muxBullMq.name);
const serverAdapter = new ElysiaAdapter('/ui');
createBullBoard({
queues: [new BullMQAdapter(muxBullMq)],
serverAdapter,
options: {
// This configuration fixes a build error on Bun caused by eval (https://github.com/oven-sh/bun/issues/5809#issuecomment-2065310008)
uiBasePath: 'node_modules/@bull-board/ui',
},
});
const app = new Elysia()
.onError(({ error, code, request }) => {
console.error(error, code, request.method, request.url);
if (code === 'NOT_FOUND') return 'NOT_FOUND';
})
.use(serverAdapter.registerPlugin())
.get('/', async () => {
return `futureporn worker version ${version}`
})
.get('/task', async ({ query }) => {
await muxBullMq.add('Add', { title: query.title });
return { ok: true };
});
app.listen(3000, ({ port, url }) => {
/* eslint-disable no-console */
console.log(`Running on ${url.hostname}:${port}...`);
console.log(`For the UI of instance1, open http://localhost:${port}/ui`);
console.log('Make sure Redis is running on port 6379 by default');
console.log('To populate the queue, run:');
console.log(` curl http://localhost:${port}/task?title=Example`);
/* eslint-enable no-console */
});