ratelimits & some more stuff

This commit is contained in:
localhost 2024-02-15 16:13:12 +01:00
parent 8a4602fbfd
commit 2b4e8d25f6
6 changed files with 195 additions and 125 deletions

View File

@ -1,9 +1,16 @@
const { PrismaClient } = require('@prisma/client')
const redis = require('../utils/redis.js')
const prisma = new PrismaClient()
exports.getLatest = async (req, res) => {
res.json(await prisma.videos.findMany({
take: 30,
let json
const cached = await redis.get('latest')
if (cached) {
json = JSON.parse(cached)
} else {
json = await prisma.videos.findMany({
take: 90,
orderBy: [
{
archived: 'desc'
@ -20,5 +27,9 @@ exports.getLatest = async (req, res) => {
channelAvatar: true,
channelVerified: true
}
}))
})
await redis.set('latest', JSON.stringify(json), 'EX', 3600)
}
res.json(json)
}

View File

@ -1,8 +1,22 @@
const crypto = require('node:crypto')
const validate = require('../utils/validate.js')
const redis = require('../utils/redis.js')
const { RedisRateLimiter } = require('rolling-rate-limiter')
const { PrismaClient } = require('@prisma/client')
const prisma = new PrismaClient()
const limiter = new RedisRateLimiter({
client: redis,
namespace: 'search:',
interval: 5 * 60 * 1000,
maxInInterval: 5
})
exports.searchVideo = async (req, res) => {
const ipHash = crypto.createHash('sha256').update(req.headers['x-userip'] || '0.0.0.0').digest('hex')
const isLimited = await limiter.limit(ipHash)
if (isLimited) return res.status(429).send('error-You have been ratelimited.')
const id = await validate.validateVideoInput(req.query.search)
if (id.fail) {
const videos = await prisma.videos.findMany({
@ -24,7 +38,7 @@ exports.searchPlaylist = async (req, res) => {
if (id.fail) {
res.status(500).send(id.message)
} else {
res.send(`<script>window.location.href = '${process.env.FRONTEND}/playlist?list=${id}'</script>`)
res.redirect(`${process.env.FRONTEND}/playlist?list=${id}`)
}
}
@ -33,6 +47,6 @@ exports.searchChannel = async (req, res) => {
if (id.fail) {
res.status(500).send(id.message)
} else {
res.send(`<script>window.location.href = '${process.env.FRONTEND}/channel/${id}'</script>`)
res.redirect(`${process.env.FRONTEND}/channel/${id}`)
}
}

View File

@ -1,14 +1,25 @@
const { PrismaClient } = require('@prisma/client')
const redis = require('../utils/redis.js')
const prisma = new PrismaClient()
exports.getReports = async (req, res) => {
res.json((await prisma.reports.findMany()).map(r => {
let json
const cached = await redis.get('transparency')
if (cached) {
json = JSON.parse(cached)
} else {
json = (await prisma.reports.findMany()).map(r => {
return {
...r,
details: (r.details).split('<').join('&lt;').split('>').join('&gt;'),
date: (r.date).toISOString().slice(0,10)
}
}))
})
await redis.set('transparency', JSON.stringify(json), 'EX', 3600)
}
res.json(json)
}
exports.getReport = async (req, res) => {

View File

@ -3,9 +3,16 @@ const prisma = new PrismaClient()
const DOMPurify = require('isomorphic-dompurify')
const metadata = require('../utils/metadata.js')
const redis = require('../utils/redis.js')
exports.getVideo = async (req, res) => {
const info = await prisma.videos.findFirst({
let info
const cached = await redis.get(`video:${req.params.id}`)
if (cached) {
info = JSON.parse(cached)
} else {
info = await prisma.videos.findFirst({
where: {
id: req.params.id
},
@ -25,6 +32,8 @@ exports.getVideo = async (req, res) => {
})
if (!info) return res.json({ error: '404' })
await redis.set(`video:${req.params.id}`, JSON.stringify(info), 'EX', 3600)
}
res.json({
...info,
@ -33,11 +42,17 @@ exports.getVideo = async (req, res) => {
}
exports.getChannel = async (req, res) => {
const videos = await metadata.getChannelVideos(req.params.id)
const channel = await metadata.getChannel(req.params.id)
const cached = await redis.get(`channel:${req.params.id}`)
if (cached) return res.json(JSON.parse(cached))
if (!videos || !channel) return res.json({ error: '500' })
if (videos.error) return res.json({ error: '404' })
const [videos, channel] = await Promise.all([
metadata.getChannelVideos(req.params.id),
metadata.getChannel(req.params.id)
])
if (!videos || !channel || videos.error) {
return res.json({ error: '404' });
}
const archived = await prisma.videos.findMany({
where: {
@ -52,43 +67,66 @@ exports.getChannel = async (req, res) => {
}
})
var allVideos = []
allVideos = allVideos.concat((videos).map(video => {
return {
const processedVideos = videos.map(video => ({
id: video.url.replace('/watch?v=', ''),
published: (new Date(video.uploaded)).toISOString().slice(0,10),
published: new Date(video.uploaded).toISOString().slice(0, 10),
...video
}
}))
}));
await Promise.all(archived.map(async (v) => {
const allVideo = allVideos.find(o => o.id == v.id)
if (allVideo) {
const index = allVideos.findIndex(o => o.id == v.id)
allVideos[index] = v
archived.forEach(v => {
const existingVideoIndex = processedVideos.findIndex(video => video.id === v.id);
if (existingVideoIndex !== -1) {
processedVideos[existingVideoIndex] = v;
} else {
allVideos.push({
...v,
deleted: undefined
})
processedVideos.push({ ...v, deleted: undefined });
}
}))
});
allVideos.sort((a, b) => new Date(b.published) - new Date(a.published))
processedVideos.sort((a, b) => new Date(b.published) - new Date(a.published));
res.json({
const json = {
name: channel.author,
avatar: channel.authorThumbnails[1].url,
verified: channel.authorVerified,
videos: allVideos
videos: processedVideos
}
await redis.set(`channel:${req.params.id}`, JSON.stringify(json), 'EX', 3600)
res.json(json)
}
exports.getOnlyChannelVideos = async (req, res) => {
const cached = await redis.get(`channelVideos:${req.params.id}`)
if (cached) return res.json(JSON.parse(cached))
const archived = await prisma.videos.findMany({
where: {
channelId: req.params.id
},
select: {
id: true,
title: true,
thumbnail: true,
published: true,
archived: true
},
orderBy: {
published: 'desc'
}
})
const json = {
videos: archived
}
await redis.set(`channelVideos:${req.params.id}`, JSON.stringify(json), 'EX', 3600)
res.json(json)
}
exports.getPlaylist = async (req, res) => {
const playlist = await metadata.getPlaylistVideos(req.params.id)
const cached = await redis.get(`playlist:${req.params.id}`)
if (cached) return res.json(JSON.parse(cached))
if (!playlist) return res.json({ error: '500' })
if (playlist.error) return res.json({ error: '404' })
const playlist = await metadata.getPlaylistVideos(req.params.id)
if (!playlist || playlist.error) return res.json({ error: '404' })
const playlistArchived = await prisma.videos.findMany({
where: {
@ -103,32 +141,27 @@ exports.getPlaylist = async (req, res) => {
}
})
var allVideos = []
allVideos = allVideos.concat((playlist.relatedStreams).map(video => {
return {
const allVideos = playlist.relatedStreams.map(video => ({
id: video.url.replace('/watch?v=', ''),
published: (new Date(video.uploaded)).toISOString().slice(0,10),
published: new Date(video.uploaded).toISOString().slice(0, 10),
...video
}
}))
}));
await Promise.all(playlistArchived.map(async (v) => {
const allVideo = allVideos.find(o => o.id == v.id)
const allVideo = allVideos.find(o => o.id == v.id);
if (allVideo) {
const index = allVideos.findIndex(o => o.id == v.id)
allVideos[index] = v
const index = allVideos.findIndex(o => o.id == v.id);
allVideos[index] = v;
} else {
const live = await metadata.getVideoMetadata(v.id)
const live = await metadata.getVideoMetadata(v.id);
allVideos.push({
...v,
deleted: live.error ? true : false
})
});
}
}))
}));
await Promise.all(allVideos.map(async (v) => {
if (!v.archived) {
await Promise.all(allVideos.filter(v => !v.archived).map(async (v) => {
const video = await prisma.videos.findFirst({
where: {
id: v.id
@ -140,22 +173,22 @@ exports.getPlaylist = async (req, res) => {
published: true,
archived: true
}
})
});
if (video) {
const index = allVideos.findIndex(o => o.id == v.id)
allVideos[index] = video
const index = allVideos.findIndex(o => o.id == v.id);
allVideos[index] = video;
}
}
}))
}));
allVideos.sort((a, b) => new Date(a.published) - new Date(b.published))
allVideos.sort((a, b) => new Date(b.published) - new Date(a.published));
res.json({
const json = {
name: playlist.name,
channel: playlist.uploader,
url: playlist.uploaderUrl,
avatar: playlist.uploaderAvatar,
videos: allVideos
})
}
await redis.set(`playlist:${req.params.id}`, JSON.stringify(json), 'EX', 3600)
res.json(json)
}

View File

@ -19,6 +19,7 @@ app.use(cors())
app.get('/latest', latestController.getLatest)
app.get('/video/:id', videoController.getVideo)
app.get('/channel/:id', videoController.getChannel)
app.get('/channel/:id/videos', videoController.getOnlyChannelVideos)
app.get('/playlist/:id', videoController.getPlaylist)
app.get('/search/video', searchController.searchVideo)

View File

@ -21,13 +21,13 @@ async function downloadVideo(url, ws) {
})
child.on("close", async (code, signal) => {
if (code == 2) {
if (code == 0) { // https://itsfoss.com/linux-exit-codes/
reject({
fail: true
fail: false
})
} else {
resolve({
fail: false
fail: true
})
}
})