2025-08-12 21:43:26 -08:00

189 lines
4.3 KiB
TypeScript

import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'
import { env } from '../config/env'
import { constants } from '../config/constants'
import { type CS3Asset, type S3, uploadFile } from '../utils/s3'
import { S3Client } from '@aws-sdk/client-s3'
import { isModerator } from '../utils/privs'
import { PrismaClient } from '../../generated/prisma'
import { withAccelerate } from "@prisma/extension-accelerate"
const prisma = new PrismaClient().$extends(withAccelerate())
const s3Resource: S3 = {
useSSL: true,
port: 443,
bucket: env.S3_BUCKET,
region: env.S3_REGION,
endPoint: env.S3_ENDPOINT,
accessKey: env.S3_KEY_ID,
pathStyle: env.S3_FORCE_PATH_STYLE,
secretKey: env.S3_APPLICATION_KEY
}
export default async function vodsRoutes(
fastify: FastifyInstance,
): Promise<void> {
fastify.get('/vods', async function (request, reply) {
const { format } = request.query as { format: 'rss' | 'html' };
const userId = request.session.get('userId');
let user = null
if (userId !== undefined) {
user = await prisma.user.findUnique({
where: { id: userId },
include: { roles: true }
})
}
const { cursor: cursorRaw, search = '' } = request.query as {
cursor?: string;
search?: string;
};
const cursor = cursorRaw ? parseInt(cursorRaw, 10) : undefined;
if (cursorRaw && isNaN(cursor)) {
return reply.status(400).send({ error: 'Invalid cursor value' });
}
const vods = await prisma.vod.findMany({
where: {
status: {
in: ['approved', 'processed', 'processing'],
},
},
orderBy: { createdAt: 'desc' },
include: {
vtubers: true,
stream: true,
},
});
// RSS branch
if (format === 'rss') {
const items = vods.map(vod => {
const vtuberNames = vod.vtubers.map(v => v.displayName || v.slug).join(', ');
return {
title: `${vtuberNames} stream on ${vod.streamDate.toDateString()}`,
link: `${env.ORIGIN}/vods/${vod.id}`,
guid: `${env.ORIGIN}/vods/${vod.id}`,
pubDate: vod.streamDate.toUTCString(),
description: vod.notes
? `${vod.notes}\n\nFeaturing: ${vtuberNames}`
: `Featuring: ${vtuberNames}`,
};
});
return reply
.type('application/rss+xml')
.view('/feed.hbs', {
title: 'future.porn - VODs',
description: 'All VODs and their featured VTubers',
link: `${env.ORIGIN}/vods`,
items,
}, { layout: 'layouts/xml.hbs' });
}
// rss branch
return reply.viewAsync('vods.hbs', {
user,
vods,
site: constants.site,
}, { layout: 'layouts/main.hbs' });
});
fastify.get('/vods/:id', async function (request, reply) {
const { id } = request.params as { id: string };
const userId = request.session.get('userId')
let user = await prisma.user.findFirst({
where: {
id: userId
},
include: {
roles: true
}
})
if (!id) {
return reply.status(400).send({ error: 'Invalid VOD ID' });
}
const vod = await prisma.vod.findUnique({
where: { id },
include: {
vtubers: true,
stream: true,
},
});
if (!vod) {
return reply.status(404).send({ error: 'VOD not found' });
}
return reply.viewAsync('vod.hbs', {
vod,
site: constants.site,
user,
}, { layout: 'layouts/main.hbs' });
});
fastify.post('/vods/:id/process', async function (request, reply) {
const { id: vodId } = request.params as { id: string };
const userId = request.session.get('userId')
let user = await prisma.user.findFirstOrThrow({
where: {
id: userId
},
include: {
roles: true
}
})
if (!isModerator(user)) {
reply.status(401).send('This endpoint is for moderators only.');
}
fastify.graphileWorker.addJob('scheduleVodProcessing', { vodId })
reply.flash
const vod = await prisma.vod.findFirstOrThrow({
where: {
id: vodId
},
include: {
stream: true
}
})
return reply.viewAsync('vod.hbs', {
vod,
site: constants.site,
user,
message: 'Successfully scheduled vod processing.'
}, { layout: 'layouts/main.hbs' });
})
}