From a73baf555bd84543c8164651b4b26eca8f4ee909 Mon Sep 17 00:00:00 2001 From: William Oldham Date: Sat, 18 Nov 2023 19:21:52 +0000 Subject: [PATCH] Add progress importing endpoint --- src/routes/users/progress.ts | 99 ++++++++++++++++++++++++++++++++---- 1 file changed, 90 insertions(+), 9 deletions(-) diff --git a/src/routes/users/progress.ts b/src/routes/users/progress.ts index 37f5797..1ff5221 100644 --- a/src/routes/users/progress.ts +++ b/src/routes/users/progress.ts @@ -6,8 +6,20 @@ import { import { StatusError } from '@/services/error'; import { handle } from '@/services/handler'; import { makeRouter } from '@/services/router'; +import { randomUUID } from 'crypto'; import { z } from 'zod'; +const progressItemSchema = z.object({ + meta: progressMetaSchema, + tmdbId: z.string(), + duration: z.number(), + watched: z.number(), + seasonId: z.string().optional(), + episodeId: z.string().optional(), + seasonNumber: z.number().optional(), + episodeNumber: z.number().optional(), +}); + export const userProgressRouter = makeRouter((app) => { app.put( '/users/:uid/progress/:tmdbid', @@ -17,15 +29,7 @@ export const userProgressRouter = makeRouter((app) => { uid: z.string(), tmdbid: z.string(), }), - body: z.object({ - meta: progressMetaSchema, - duration: z.number(), - watched: z.number(), - seasonId: z.string().optional(), - episodeId: z.string().optional(), - seasonNumber: z.number().optional(), - episodeNumber: z.number().optional(), - }), + body: progressItemSchema, }, }, handle(async ({ auth, params, body, em }) => { @@ -63,6 +67,83 @@ export const userProgressRouter = makeRouter((app) => { }), ); + app.put( + '/users/:uid/progress/import', + { + schema: { + params: z.object({ + uid: z.string(), + }), + body: z.array(progressItemSchema), + }, + }, + handle(async ({ auth, params, body: newItems, em, req, limiter }) => { + await auth.assert(); + + if (auth.user.id !== params.uid) + throw new StatusError('Cannot modify user other than yourself', 403); + + const existingItems = await em.find(ProgressItem, { userId: params.uid }); + + for (const newItem of newItems) { + const existingItem = existingItems.find( + (item) => + item.tmdbId == newItem.tmdbId && + item.seasonId == newItem.seasonId && + item.episodeId == newItem.episodeId, + ); + + if (existingItem) { + if (existingItem.watched < newItem.watched) { + existingItem.updatedAt = new Date(); + existingItem.watched = newItem.watched; + } + continue; + } + + existingItems.push({ + id: randomUUID(), + duration: newItem.duration, + episodeId: newItem.episodeId, + episodeNumber: newItem.episodeNumber, + meta: newItem.meta, + seasonId: newItem.seasonId, + seasonNumber: newItem.seasonNumber, + tmdbId: newItem.tmdbId, + userId: params.uid, + watched: newItem.watched, + updatedAt: new Date(), + }); + } + + const progressItems = await em.upsertMany(ProgressItem, existingItems); + + await em.flush(); + + await limiter?.assertAndBump(req, { + id: 'progress_import', + max: 5, + window: '10m', + }); + + // Construct a response that only has the items that were requested to be updated in the same order + // ! is used on find as the item *should* always exist if the code above works correctly + const newItemResponses = newItems + .map( + (newItem) => + progressItems.find( + (item) => + item.tmdbId == newItem.tmdbId && + item.seasonId == newItem.seasonId && + item.episodeId == newItem.episodeId, + )!, + ) + .map(formatProgressItem); + + return newItemResponses; + }), + ); + app.delete( '/users/:uid/progress/:tmdbid', {