mirror of
				https://github.com/zoriya/Kyoo.git
				synced 2025-10-31 02:27:11 -04:00 
			
		
		
		
	Put POST /videos in a transaction, handle dups
				
					
				
			This commit is contained in:
		
							parent
							
								
									379765b28f
								
							
						
					
					
						commit
						466b67afe5
					
				| @ -160,166 +160,174 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] }) | |||||||
| 	.post( | 	.post( | ||||||
| 		"", | 		"", | ||||||
| 		async ({ body, error }) => { | 		async ({ body, error }) => { | ||||||
| 			const vids = await db | 			return await db.transaction(async (tx) => { | ||||||
| 				.insert(videos) | 				const vids = await tx | ||||||
| 				.values(body) | 					.insert(videos) | ||||||
| 				.onConflictDoUpdate({ | 					.values(body) | ||||||
| 					target: [videos.path], | 					.onConflictDoUpdate({ | ||||||
| 					set: conflictUpdateAllExcept(videos, ["pk", "id", "createdAt"]), | 						target: [videos.path], | ||||||
| 				}) | 						set: conflictUpdateAllExcept(videos, ["pk", "id", "createdAt"]), | ||||||
| 				.returning({ | 					}) | ||||||
| 					pk: videos.pk, | 					.returning({ | ||||||
| 					id: videos.id, | 						pk: videos.pk, | ||||||
| 					path: videos.path, | 						id: videos.id, | ||||||
|  | 						path: videos.path, | ||||||
|  | 					}); | ||||||
|  | 
 | ||||||
|  | 				const vidEntries = body.flatMap((x) => { | ||||||
|  | 					if (!x.for) return []; | ||||||
|  | 					return x.for.map((e) => ({ | ||||||
|  | 						video: vids.find((v) => v.path === x.path)!.pk, | ||||||
|  | 						path: x.path, | ||||||
|  | 						entry: { | ||||||
|  | 							...e, | ||||||
|  | 							movie: | ||||||
|  | 								"movie" in e | ||||||
|  | 									? isUuid(e.movie) | ||||||
|  | 										? { id: e.movie } | ||||||
|  | 										: { slug: e.movie } | ||||||
|  | 									: undefined, | ||||||
|  | 							serie: | ||||||
|  | 								"serie" in e | ||||||
|  | 									? isUuid(e.serie) | ||||||
|  | 										? { id: e.serie } | ||||||
|  | 										: { slug: e.serie } | ||||||
|  | 									: undefined, | ||||||
|  | 						}, | ||||||
|  | 					})); | ||||||
| 				}); | 				}); | ||||||
| 
 | 
 | ||||||
| 			const vidEntries = body.flatMap((x) => { | 				if (!vidEntries.length) { | ||||||
| 				if (!x.for) return []; | 					return error( | ||||||
| 				return x.for.map((e) => ({ | 						201, | ||||||
| 					video: vids.find((v) => v.path === x.path)!.pk, | 						vids.map((x) => ({ id: x.id, path: x.path, entries: [] })), | ||||||
| 					path: x.path, | 					); | ||||||
| 					entry: { | 				} | ||||||
| 						...e, |  | ||||||
| 						movie: |  | ||||||
| 							"movie" in e |  | ||||||
| 								? isUuid(e.movie) |  | ||||||
| 									? { id: e.movie } |  | ||||||
| 									: { slug: e.movie } |  | ||||||
| 								: undefined, |  | ||||||
| 						serie: |  | ||||||
| 							"serie" in e |  | ||||||
| 								? isUuid(e.serie) |  | ||||||
| 									? { id: e.serie } |  | ||||||
| 									: { slug: e.serie } |  | ||||||
| 								: undefined, |  | ||||||
| 					}, |  | ||||||
| 				})); |  | ||||||
| 			}); |  | ||||||
| 
 | 
 | ||||||
| 			if (!vidEntries.length) { | 				const entriesQ = tx | ||||||
|  | 					.select({ | ||||||
|  | 						pk: entries.pk, | ||||||
|  | 						id: entries.id, | ||||||
|  | 						slug: entries.slug, | ||||||
|  | 						kind: entries.kind, | ||||||
|  | 						seasonNumber: entries.seasonNumber, | ||||||
|  | 						episodeNumber: entries.episodeNumber, | ||||||
|  | 						order: entries.order, | ||||||
|  | 						showId: sql`${shows.id}`.as("showId"), | ||||||
|  | 						showSlug: sql`${shows.slug}`.as("showSlug"), | ||||||
|  | 						externalId: entries.externalId, | ||||||
|  | 					}) | ||||||
|  | 					.from(entries) | ||||||
|  | 					.innerJoin(shows, eq(entries.showPk, shows.pk)) | ||||||
|  | 					.as("entriesQ"); | ||||||
|  | 
 | ||||||
|  | 				const hasRenderingQ = tx | ||||||
|  | 					.select() | ||||||
|  | 					.from(entryVideoJoin) | ||||||
|  | 					.where(eq(entryVideoJoin.entryPk, entriesQ.pk)); | ||||||
|  | 
 | ||||||
|  | 				const ret = await tx | ||||||
|  | 					.insert(entryVideoJoin) | ||||||
|  | 					.select( | ||||||
|  | 						tx | ||||||
|  | 							.selectDistinctOn([entriesQ.pk, videos.pk], { | ||||||
|  | 								entryPk: entriesQ.pk, | ||||||
|  | 								videoPk: videos.pk, | ||||||
|  | 								slug: computeVideoSlug( | ||||||
|  | 									entriesQ.slug, | ||||||
|  | 									sql`exists(${hasRenderingQ})`, | ||||||
|  | 								), | ||||||
|  | 							}) | ||||||
|  | 							.from( | ||||||
|  | 								values(vidEntries, { | ||||||
|  | 									video: "integer", | ||||||
|  | 									entry: "jsonb", | ||||||
|  | 								}).as("j"), | ||||||
|  | 							) | ||||||
|  | 							.innerJoin(videos, eq(videos.pk, sql`j.video`)) | ||||||
|  | 							.innerJoin( | ||||||
|  | 								entriesQ, | ||||||
|  | 								or( | ||||||
|  | 									and( | ||||||
|  | 										sql`j.entry ? 'slug'`, | ||||||
|  | 										eq(entriesQ.slug, sql`j.entry->>'slug'`), | ||||||
|  | 									), | ||||||
|  | 									and( | ||||||
|  | 										sql`j.entry ? 'movie'`, | ||||||
|  | 										or( | ||||||
|  | 											eq( | ||||||
|  | 												entriesQ.showId, | ||||||
|  | 												sql`(j.entry #>> '{movie, id}')::uuid`, | ||||||
|  | 											), | ||||||
|  | 											eq(entriesQ.showSlug, sql`j.entry #>> '{movie, slug}'`), | ||||||
|  | 										), | ||||||
|  | 										eq(entriesQ.kind, "movie"), | ||||||
|  | 									), | ||||||
|  | 									and( | ||||||
|  | 										sql`j.entry ? 'serie'`, | ||||||
|  | 										or( | ||||||
|  | 											eq( | ||||||
|  | 												entriesQ.showId, | ||||||
|  | 												sql`(j.entry #>> '{serie, id}')::uuid`, | ||||||
|  | 											), | ||||||
|  | 											eq(entriesQ.showSlug, sql`j.entry #>> '{serie, slug}'`), | ||||||
|  | 										), | ||||||
|  | 										or( | ||||||
|  | 											and( | ||||||
|  | 												sql`j.entry ?& array['season', 'episode']`, | ||||||
|  | 												eq( | ||||||
|  | 													entriesQ.seasonNumber, | ||||||
|  | 													sql`(j.entry->>'season')::integer`, | ||||||
|  | 												), | ||||||
|  | 												eq( | ||||||
|  | 													entriesQ.episodeNumber, | ||||||
|  | 													sql`(j.entry->>'episode')::integer`, | ||||||
|  | 												), | ||||||
|  | 											), | ||||||
|  | 											and( | ||||||
|  | 												sql`j.entry ? 'order'`, | ||||||
|  | 												eq(entriesQ.order, sql`(j.entry->>'order')::float`), | ||||||
|  | 											), | ||||||
|  | 											and( | ||||||
|  | 												sql`j.entry ? 'special'`, | ||||||
|  | 												eq( | ||||||
|  | 													entriesQ.episodeNumber, | ||||||
|  | 													sql`(j.entry->>'special')::integer`, | ||||||
|  | 												), | ||||||
|  | 												eq(entriesQ.kind, "special"), | ||||||
|  | 											), | ||||||
|  | 										), | ||||||
|  | 									), | ||||||
|  | 									and( | ||||||
|  | 										sql`j.entry ? 'externalId'`, | ||||||
|  | 										sql`j.entry->'externalId' <@ ${entriesQ.externalId}`, | ||||||
|  | 									), | ||||||
|  | 								), | ||||||
|  | 							), | ||||||
|  | 					) | ||||||
|  | 					.onConflictDoNothing() | ||||||
|  | 					.returning({ | ||||||
|  | 						slug: entryVideoJoin.slug, | ||||||
|  | 						entryPk: entryVideoJoin.entryPk, | ||||||
|  | 						videoPk: entryVideoJoin.videoPk, | ||||||
|  | 					}); | ||||||
|  | 				const entr = ret.reduce( | ||||||
|  | 					(acc, x) => { | ||||||
|  | 						acc[x.videoPk] ??= []; | ||||||
|  | 						acc[x.videoPk].push({ slug: x.slug }); | ||||||
|  | 						return acc; | ||||||
|  | 					}, | ||||||
|  | 					{} as Record<number, { slug: string }[]>, | ||||||
|  | 				); | ||||||
| 				return error( | 				return error( | ||||||
| 					201, | 					201, | ||||||
| 					vids.map((x) => ({ id: x.id, path: x.path, entries: [] })), | 					vids.map((x) => ({ | ||||||
|  | 						id: x.id, | ||||||
|  | 						path: x.path, | ||||||
|  | 						entries: entr[x.pk] ?? [], | ||||||
|  | 					})), | ||||||
| 				); | 				); | ||||||
| 			} | 			}); | ||||||
| 
 |  | ||||||
| 			const entriesQ = db |  | ||||||
| 				.select({ |  | ||||||
| 					pk: entries.pk, |  | ||||||
| 					id: entries.id, |  | ||||||
| 					slug: entries.slug, |  | ||||||
| 					kind: entries.kind, |  | ||||||
| 					seasonNumber: entries.seasonNumber, |  | ||||||
| 					episodeNumber: entries.episodeNumber, |  | ||||||
| 					order: entries.order, |  | ||||||
| 					showId: sql`${shows.id}`.as("showId"), |  | ||||||
| 					showSlug: sql`${shows.slug}`.as("showSlug"), |  | ||||||
| 					externalId: entries.externalId, |  | ||||||
| 				}) |  | ||||||
| 				.from(entries) |  | ||||||
| 				.innerJoin(shows, eq(entries.showPk, shows.pk)) |  | ||||||
| 				.as("entriesQ"); |  | ||||||
| 
 |  | ||||||
| 			const hasRenderingQ = db |  | ||||||
| 				.select() |  | ||||||
| 				.from(entryVideoJoin) |  | ||||||
| 				.where(eq(entryVideoJoin.entryPk, entriesQ.pk)); |  | ||||||
| 
 |  | ||||||
| 			const ret = await db |  | ||||||
| 				.insert(entryVideoJoin) |  | ||||||
| 				.select( |  | ||||||
| 					db |  | ||||||
| 						.select({ |  | ||||||
| 							entryPk: entriesQ.pk, |  | ||||||
| 							videoPk: videos.pk, |  | ||||||
| 							slug: computeVideoSlug( |  | ||||||
| 								entriesQ.slug, |  | ||||||
| 								sql`exists(${hasRenderingQ})`, |  | ||||||
| 							), |  | ||||||
| 						}) |  | ||||||
| 						.from( |  | ||||||
| 							values(vidEntries, { |  | ||||||
| 								video: "integer", |  | ||||||
| 								entry: "jsonb", |  | ||||||
| 							}).as("j"), |  | ||||||
| 						) |  | ||||||
| 						.innerJoin(videos, eq(videos.pk, sql`j.video`)) |  | ||||||
| 						.innerJoin( |  | ||||||
| 							entriesQ, |  | ||||||
| 							or( |  | ||||||
| 								and( |  | ||||||
| 									sql`j.entry ? 'slug'`, |  | ||||||
| 									eq(entriesQ.slug, sql`j.entry->>'slug'`), |  | ||||||
| 								), |  | ||||||
| 								and( |  | ||||||
| 									sql`j.entry ? 'movie'`, |  | ||||||
| 									or( |  | ||||||
| 										eq(entriesQ.showId, sql`(j.entry #>> '{movie, id}')::uuid`), |  | ||||||
| 										eq(entriesQ.showSlug, sql`j.entry #>> '{movie, slug}'`), |  | ||||||
| 									), |  | ||||||
| 									eq(entriesQ.kind, "movie"), |  | ||||||
| 								), |  | ||||||
| 								and( |  | ||||||
| 									sql`j.entry ? 'serie'`, |  | ||||||
| 									or( |  | ||||||
| 										eq(entriesQ.showId, sql`(j.entry #>> '{serie, id}')::uuid`), |  | ||||||
| 										eq(entriesQ.showSlug, sql`j.entry #>> '{serie, slug}'`), |  | ||||||
| 									), |  | ||||||
| 									or( |  | ||||||
| 										and( |  | ||||||
| 											sql`j.entry ?& array['season', 'episode']`, |  | ||||||
| 											eq( |  | ||||||
| 												entriesQ.seasonNumber, |  | ||||||
| 												sql`(j.entry->>'season')::integer`, |  | ||||||
| 											), |  | ||||||
| 											eq( |  | ||||||
| 												entriesQ.episodeNumber, |  | ||||||
| 												sql`(j.entry->>'episode')::integer`, |  | ||||||
| 											), |  | ||||||
| 										), |  | ||||||
| 										and( |  | ||||||
| 											sql`j.entry ? 'order'`, |  | ||||||
| 											eq(entriesQ.order, sql`(j.entry->>'order')::float`), |  | ||||||
| 										), |  | ||||||
| 										and( |  | ||||||
| 											sql`j.entry ? 'special'`, |  | ||||||
| 											eq( |  | ||||||
| 												entriesQ.episodeNumber, |  | ||||||
| 												sql`(j.entry->>'special')::integer`, |  | ||||||
| 											), |  | ||||||
| 											eq(entriesQ.kind, "special"), |  | ||||||
| 										), |  | ||||||
| 									), |  | ||||||
| 								), |  | ||||||
| 								and( |  | ||||||
| 									sql`j.entry ? 'externalId'`, |  | ||||||
| 									sql`j.entry->'externalId' <@ ${entriesQ.externalId}`, |  | ||||||
| 								), |  | ||||||
| 							), |  | ||||||
| 						), |  | ||||||
| 				) |  | ||||||
| 				.onConflictDoNothing() |  | ||||||
| 				.returning({ |  | ||||||
| 					slug: entryVideoJoin.slug, |  | ||||||
| 					entryPk: entryVideoJoin.entryPk, |  | ||||||
| 					videoPk: entryVideoJoin.videoPk, |  | ||||||
| 				}); |  | ||||||
| 			const entr = ret.reduce( |  | ||||||
| 				(acc, x) => { |  | ||||||
| 					acc[x.videoPk] ??= []; |  | ||||||
| 					acc[x.videoPk].push({ slug: x.slug }); |  | ||||||
| 					return acc; |  | ||||||
| 				}, |  | ||||||
| 				{} as Record<number, { slug: string }[]>, |  | ||||||
| 			); |  | ||||||
| 			return error( |  | ||||||
| 				201, |  | ||||||
| 				vids.map((x) => ({ |  | ||||||
| 					id: x.id, |  | ||||||
| 					path: x.path, |  | ||||||
| 					entries: entr[x.pk] ?? [], |  | ||||||
| 				})), |  | ||||||
| 			); |  | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			detail: { | 			detail: { | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import { PatternString } from "@sinclair/typebox"; | import { PatternStringExact } from "@sinclair/typebox"; | ||||||
| import { t } from "elysia"; | import { t } from "elysia"; | ||||||
| import { type Prettify, comment } from "~/utils"; | import { type Prettify, comment } from "~/utils"; | ||||||
| import { ExtraType } from "./entry/extra"; | import { ExtraType } from "./entry/extra"; | ||||||
| @ -9,12 +9,13 @@ const ExternalIds = t.Record( | |||||||
| 	t.String(), | 	t.String(), | ||||||
| 	t.Omit( | 	t.Omit( | ||||||
| 		t.Union([ | 		t.Union([ | ||||||
| 			EpisodeId.patternProperties[PatternString], | 			EpisodeId.patternProperties[PatternStringExact], | ||||||
| 			ExternalId().patternProperties[PatternString], | 			ExternalId().patternProperties[PatternStringExact], | ||||||
| 		]), | 		]), | ||||||
| 		["link"], | 		["link"], | ||||||
| 	), | 	), | ||||||
| ); | ); | ||||||
|  | type ExternalIds = typeof ExternalIds.static; | ||||||
| 
 | 
 | ||||||
| export const Guess = t.Recursive((Self) => | export const Guess = t.Recursive((Self) => | ||||||
| 	t.Object( | 	t.Object( | ||||||
|  | |||||||
| @ -109,6 +109,38 @@ describe("Video seeding", () => { | |||||||
| 		expect(vid!.evj[0].entry.slug).toBe(bubble.slug); | 		expect(vid!.evj[0].entry.slug).toBe(bubble.slug); | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
|  | 	it("Conflicting path", async () => { | ||||||
|  | 		const [resp, body] = await createVideo({ | ||||||
|  | 			guess: { title: "bubble", from: "test" }, | ||||||
|  | 			part: null, | ||||||
|  | 			path: "/video/bubble.mkv", | ||||||
|  | 			rendering: "sha", | ||||||
|  | 			version: 1, | ||||||
|  | 			for: [{ movie: bubble.slug }], | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		expectStatus(resp, body).toBe(201); | ||||||
|  | 		expect(body).toBeArrayOfSize(1); | ||||||
|  | 		expect(body[0].id).toBeString(); | ||||||
|  | 
 | ||||||
|  | 		const vid = await db.query.videos.findFirst({ | ||||||
|  | 			where: eq(videos.id, body[0].id), | ||||||
|  | 			with: { | ||||||
|  | 				evj: { with: { entry: true } }, | ||||||
|  | 			}, | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		expect(vid).not.toBeNil(); | ||||||
|  | 		expect(vid!.path).toBe("/video/bubble.mkv"); | ||||||
|  | 		expect(vid!.guess).toMatchObject({ title: "bubble", from: "test" }); | ||||||
|  | 
 | ||||||
|  | 		expect(body[0].entries).toBeArrayOfSize(1); | ||||||
|  | 		expect(vid!.evj).toBeArrayOfSize(1); | ||||||
|  | 
 | ||||||
|  | 		expect(vid!.evj[0].slug).toBe(bubble.slug); | ||||||
|  | 		expect(vid!.evj[0].entry.slug).toBe(bubble.slug); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
| 	it("With season/episode", async () => { | 	it("With season/episode", async () => { | ||||||
| 		const [resp, body] = await createVideo({ | 		const [resp, body] = await createVideo({ | ||||||
| 			guess: { title: "mia", season: [2], episode: [1], from: "test" }, | 			guess: { title: "mia", season: [2], episode: [1], from: "test" }, | ||||||
| @ -229,7 +261,7 @@ describe("Video seeding", () => { | |||||||
| 				episode: [3], | 				episode: [3], | ||||||
| 				from: "test", | 				from: "test", | ||||||
| 				externalId: { | 				externalId: { | ||||||
| 					themoviedb: { serieId: "72636", season: 1, episode: 13 }, | 					themoviedatabase: { serieId: "72636", season: 1, episode: 13 }, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 			part: null, | 			part: null, | ||||||
| @ -239,7 +271,7 @@ describe("Video seeding", () => { | |||||||
| 			for: [ | 			for: [ | ||||||
| 				{ | 				{ | ||||||
| 					externalId: { | 					externalId: { | ||||||
| 						themoviedb: { serieId: "72636", season: 1, episode: 13 }, | 						themoviedatabase: { serieId: "72636", season: 1, episode: 13 }, | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 			], | 			], | ||||||
| @ -273,7 +305,7 @@ describe("Video seeding", () => { | |||||||
| 				title: "bubble", | 				title: "bubble", | ||||||
| 				from: "test", | 				from: "test", | ||||||
| 				externalId: { | 				externalId: { | ||||||
| 					themoviedb: { dataId: "912598", season: 1, episode: 13 }, | 					themoviedatabase: { dataId: "912598" }, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 			part: null, | 			part: null, | ||||||
| @ -283,7 +315,7 @@ describe("Video seeding", () => { | |||||||
| 			for: [ | 			for: [ | ||||||
| 				{ | 				{ | ||||||
| 					externalId: { | 					externalId: { | ||||||
| 						themoviedb: { serieId: "912598", season: 1, episode: 13 }, | 						themoviedatabase: { dataId: "912598" }, | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 			], | 			], | ||||||
| @ -317,18 +349,18 @@ describe("Video seeding", () => { | |||||||
| 				title: "bubble", | 				title: "bubble", | ||||||
| 				from: "test", | 				from: "test", | ||||||
| 				externalId: { | 				externalId: { | ||||||
| 					themoviedb: { dataId: "912598", season: 1, episode: 13 }, | 					themoviedatabase: { dataId: "912598" }, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 			part: null, | 			part: null, | ||||||
| 			path: "/video/bubble [tmdb=912598].mkv", | 			path: "/video/bubble ue [tmdb=912598].mkv", | ||||||
| 			rendering: "cwhtn", | 			rendering: "aoeubnht", | ||||||
| 			version: 1, | 			version: 1, | ||||||
| 			for: [ | 			for: [ | ||||||
| 				{ movie: "bubble" }, | 				{ movie: "bubble" }, | ||||||
| 				{ | 				{ | ||||||
| 					externalId: { | 					externalId: { | ||||||
| 						themoviedb: { serieId: "912598", season: 1, episode: 13 }, | 						themoviedatabase: { dataId: "912598" }, | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 			], | 			], | ||||||
| @ -346,13 +378,13 @@ describe("Video seeding", () => { | |||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
| 		expect(vid).not.toBeNil(); | 		expect(vid).not.toBeNil(); | ||||||
| 		expect(vid!.path).toBe("/video/bubble [tmdb=912598].mkv"); | 		expect(vid!.path).toBe("/video/bubble ue [tmdb=912598].mkv"); | ||||||
| 		expect(vid!.guess).toMatchObject({ title: "bubble", from: "test" }); | 		expect(vid!.guess).toMatchObject({ title: "bubble", from: "test" }); | ||||||
| 
 | 
 | ||||||
| 		expect(body[0].entries).toBeArrayOfSize(1); | 		expect(body[0].entries).toBeArrayOfSize(1); | ||||||
| 		expect(vid!.evj).toBeArrayOfSize(1); | 		expect(vid!.evj).toBeArrayOfSize(1); | ||||||
| 
 | 
 | ||||||
| 		expect(vid!.evj[0].slug).toBe("bubble-cwhtn"); | 		expect(vid!.evj[0].slug).toBe("bubble-aoeubnht"); | ||||||
| 		expect(vid!.evj[0].entry.slug).toBe("bubble"); | 		expect(vid!.evj[0].entry.slug).toBe("bubble"); | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| @ -363,7 +395,7 @@ describe("Video seeding", () => { | |||||||
| 				title: "bubble", | 				title: "bubble", | ||||||
| 				from: "test", | 				from: "test", | ||||||
| 				externalId: { | 				externalId: { | ||||||
| 					themoviedb: { dataId: "912598", season: 1, episode: 13 }, | 					themoviedatabase: { dataId: "912598" }, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 			part: null, | 			part: null, | ||||||
| @ -374,7 +406,7 @@ describe("Video seeding", () => { | |||||||
| 				{ movie: "bubble" }, | 				{ movie: "bubble" }, | ||||||
| 				{ | 				{ | ||||||
| 					externalId: { | 					externalId: { | ||||||
| 						themoviedb: { serieId: "912598", season: 1, episode: 13 }, | 						themoviedatabase: { dataId: "912598" }, | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 			], | 			], | ||||||
| @ -401,4 +433,120 @@ describe("Video seeding", () => { | |||||||
| 		expect(vid!.evj[0].slug).toBe("bubble"); | 		expect(vid!.evj[0].slug).toBe("bubble"); | ||||||
| 		expect(vid!.evj[0].entry.slug).toBe("bubble"); | 		expect(vid!.evj[0].entry.slug).toBe("bubble"); | ||||||
| 	}); | 	}); | ||||||
|  | 
 | ||||||
|  | 	it("Multi part", async () => { | ||||||
|  | 		await db.delete(videos); | ||||||
|  | 		const [resp, body] = await createVideo([ | ||||||
|  | 			{ | ||||||
|  | 				guess: { | ||||||
|  | 					title: "bubble", | ||||||
|  | 					from: "test", | ||||||
|  | 					externalId: { | ||||||
|  | 						themoviedatabase: { dataId: "912598" }, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				part: 1, | ||||||
|  | 				path: "/video/bubble p1 [tmdb=912598].mkv", | ||||||
|  | 				rendering: "cwhtn", | ||||||
|  | 				version: 1, | ||||||
|  | 				for: [ | ||||||
|  | 					{ movie: "bubble" }, | ||||||
|  | 					{ | ||||||
|  | 						externalId: { | ||||||
|  | 							themoviedatabase: { dataId: "912598" }, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				], | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				guess: { | ||||||
|  | 					title: "bubble", | ||||||
|  | 					from: "test", | ||||||
|  | 					externalId: { | ||||||
|  | 						themoviedatabase: { dataId: "912598" }, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				part: 2, | ||||||
|  | 				path: "/video/bubble p2 [tmdb=912598].mkv", | ||||||
|  | 				rendering: "cwhtn", | ||||||
|  | 				version: 1, | ||||||
|  | 				for: [ | ||||||
|  | 					{ movie: "bubble" }, | ||||||
|  | 					{ | ||||||
|  | 						externalId: { | ||||||
|  | 							themoviedatabase: { dataId: "912598" }, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				], | ||||||
|  | 			}, | ||||||
|  | 		]); | ||||||
|  | 
 | ||||||
|  | 		expectStatus(resp, body).toBe(201); | ||||||
|  | 		expect(body).toBeArrayOfSize(2); | ||||||
|  | 		expect(body[0].id).toBeString(); | ||||||
|  | 		expect(body[1].id).toBeString(); | ||||||
|  | 		expect(body[0].entries).toBeArrayOfSize(1); | ||||||
|  | 		expect(body[1].entries).toBeArrayOfSize(1); | ||||||
|  | 
 | ||||||
|  | 		const entr = (await db.query.entries.findFirst({ | ||||||
|  | 			where: eq(entries.slug, bubble.slug), | ||||||
|  | 			with: { | ||||||
|  | 				evj: { with: { video: true } }, | ||||||
|  | 			}, | ||||||
|  | 		}))!; | ||||||
|  | 
 | ||||||
|  | 		expect(entr.evj).toBeArrayOfSize(2); | ||||||
|  | 		expect(entr.evj[0].video.path).toBe("/video/bubble p1 [tmdb=912598].mkv"); | ||||||
|  | 
 | ||||||
|  | 		expect(entr.evj[0].slug).toBe("bubble-p1"); | ||||||
|  | 		expect(entr.evj[1].slug).toBe("bubble-p2"); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	it("Multi entry", async () => { | ||||||
|  | 		await db.delete(videos); | ||||||
|  | 		const [resp, body] = await createVideo({ | ||||||
|  | 			guess: { | ||||||
|  | 				title: "mia", | ||||||
|  | 				season: [1, 2], | ||||||
|  | 				episode: [13, 1], | ||||||
|  | 				from: "test", | ||||||
|  | 			}, | ||||||
|  | 			part: null, | ||||||
|  | 			path: "/video/mia s1e13 & s2e1 [tmdb=72636].mkv", | ||||||
|  | 			rendering: "notehu", | ||||||
|  | 			version: 1, | ||||||
|  | 			for: [ | ||||||
|  | 				{ serie: madeInAbyss.slug, season: 1, episode: 13 }, | ||||||
|  | 				{ | ||||||
|  | 					externalId: { | ||||||
|  | 						themoviedatabase: { serieId: "72636", season: 1, episode: 13 }, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				{ serie: madeInAbyss.slug, season: 2, episode: 1 }, | ||||||
|  | 			], | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		expectStatus(resp, body).toBe(201); | ||||||
|  | 		expect(body).toBeArrayOfSize(1); | ||||||
|  | 		expect(body[0].id).toBeString(); | ||||||
|  | 
 | ||||||
|  | 		const vid = await db.query.videos.findFirst({ | ||||||
|  | 			where: eq(videos.id, body[0].id), | ||||||
|  | 			with: { | ||||||
|  | 				evj: { with: { entry: true } }, | ||||||
|  | 			}, | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		expect(vid).not.toBeNil(); | ||||||
|  | 		expect(vid!.path).toBe("/video/mia s1e13 & s2e1 [tmdb=72636].mkv"); | ||||||
|  | 		expect(vid!.guess).toMatchObject({ title: "mia", from: "test" }); | ||||||
|  | 
 | ||||||
|  | 		expect(body[0].entries).toBeArrayOfSize(2); | ||||||
|  | 		expect(vid!.evj).toBeArrayOfSize(2); | ||||||
|  | 
 | ||||||
|  | 		expect(vid!.evj[0].slug).toBe("made-in-abyss-s1e13"); | ||||||
|  | 		expect(vid!.evj[0].entry.slug).toBe("made-in-abyss-s1e13"); | ||||||
|  | 		expect(vid!.evj[1].slug).toBe("made-in-abyss-s2e1"); | ||||||
|  | 		expect(vid!.evj[1].entry.slug).toBe("made-in-abyss-s2e1"); | ||||||
|  | 	}); | ||||||
| }); | }); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user