mirror of
				https://github.com/zoriya/Kyoo.git
				synced 2025-10-30 18:22:41 -04:00 
			
		
		
		
	Add collections in v5 (#821)
This commit is contained in:
		
						commit
						9b1bd69eae
					
				| @ -8,7 +8,7 @@ The many-to-many relation between entries (episodes/movies) & videos is NOT a mi | ||||
| erDiagram | ||||
|     shows { | ||||
|         guid id PK | ||||
|         kind kind "serie|movie" | ||||
|         kind kind "serie|movie|collection" | ||||
|         string(128) slug UK | ||||
|         genre[] genres | ||||
|         int rating "From 0 to 100" | ||||
| @ -20,6 +20,7 @@ erDiagram | ||||
|         jsonb external_id | ||||
|         guid studio_id FK | ||||
|         string original_language | ||||
| 		guid collection_id FK | ||||
|     } | ||||
|     show_translations { | ||||
|         guid id PK, FK | ||||
| @ -37,6 +38,7 @@ erDiagram | ||||
|     } | ||||
|     shows ||--|{ show_translations : has | ||||
|     shows |o--|| entries : has | ||||
|     shows |o--|| shows : has_collection | ||||
| 
 | ||||
|     entries { | ||||
|         guid id PK | ||||
| @ -70,23 +72,6 @@ erDiagram | ||||
|     } | ||||
|     video }|--|{ entries : for | ||||
| 
 | ||||
|     collections { | ||||
|         guid id PK | ||||
|         string(256) slug UK | ||||
|         datetime added_date | ||||
|         datetime next_refresh | ||||
|     } | ||||
| 
 | ||||
|     collection_translations { | ||||
|         guid id PK, FK | ||||
|         string language PK | ||||
|         string name "NN" | ||||
|         jsonb poster | ||||
|         jsonb thumbnail | ||||
|     } | ||||
|     collections ||--|{ collection_translations : has | ||||
|     collections |o--|{ shows : has | ||||
| 
 | ||||
|     seasons { | ||||
|         guid id PK | ||||
|         string(256) slug UK | ||||
|  | ||||
							
								
								
									
										3
									
								
								api/drizzle/0009_collections.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								api/drizzle/0009_collections.sql
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| ALTER TYPE "kyoo"."show_kind" ADD VALUE 'collection';--> statement-breakpoint | ||||
| ALTER TABLE "kyoo"."shows" ADD COLUMN "collection_pk" integer;--> statement-breakpoint | ||||
| ALTER TABLE "kyoo"."shows" ADD CONSTRAINT "shows_collection_pk_shows_pk_fk" FOREIGN KEY ("collection_pk") REFERENCES "kyoo"."shows"("pk") ON DELETE set null ON UPDATE no action; | ||||
							
								
								
									
										969
									
								
								api/drizzle/meta/0009_snapshot.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										969
									
								
								api/drizzle/meta/0009_snapshot.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,969 @@ | ||||
| { | ||||
| 	"id": "7a04670c-5fb9-4535-b6be-dc291b8b0b09", | ||||
| 	"prevId": "5c17dd71-409a-4c80-870d-f12386676738", | ||||
| 	"version": "7", | ||||
| 	"dialect": "postgresql", | ||||
| 	"tables": { | ||||
| 		"kyoo.entries": { | ||||
| 			"name": "entries", | ||||
| 			"schema": "kyoo", | ||||
| 			"columns": { | ||||
| 				"pk": { | ||||
| 					"name": "pk", | ||||
| 					"type": "integer", | ||||
| 					"primaryKey": true, | ||||
| 					"notNull": true, | ||||
| 					"identity": { | ||||
| 						"type": "always", | ||||
| 						"name": "entries_pk_seq", | ||||
| 						"schema": "kyoo", | ||||
| 						"increment": "1", | ||||
| 						"startWith": "1", | ||||
| 						"minValue": "1", | ||||
| 						"maxValue": "2147483647", | ||||
| 						"cache": "1", | ||||
| 						"cycle": false | ||||
| 					} | ||||
| 				}, | ||||
| 				"id": { | ||||
| 					"name": "id", | ||||
| 					"type": "uuid", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true, | ||||
| 					"default": "gen_random_uuid()" | ||||
| 				}, | ||||
| 				"slug": { | ||||
| 					"name": "slug", | ||||
| 					"type": "varchar(255)", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				}, | ||||
| 				"show_pk": { | ||||
| 					"name": "show_pk", | ||||
| 					"type": "integer", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				}, | ||||
| 				"order": { | ||||
| 					"name": "order", | ||||
| 					"type": "real", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"season_number": { | ||||
| 					"name": "season_number", | ||||
| 					"type": "integer", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"episode_number": { | ||||
| 					"name": "episode_number", | ||||
| 					"type": "integer", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"kind": { | ||||
| 					"name": "kind", | ||||
| 					"type": "entry_type", | ||||
| 					"typeSchema": "kyoo", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				}, | ||||
| 				"extra_kind": { | ||||
| 					"name": "extra_kind", | ||||
| 					"type": "text", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"air_date": { | ||||
| 					"name": "air_date", | ||||
| 					"type": "date", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"runtime": { | ||||
| 					"name": "runtime", | ||||
| 					"type": "integer", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"thumbnail": { | ||||
| 					"name": "thumbnail", | ||||
| 					"type": "jsonb", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"external_id": { | ||||
| 					"name": "external_id", | ||||
| 					"type": "jsonb", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true, | ||||
| 					"default": "'{}'::jsonb" | ||||
| 				}, | ||||
| 				"created_at": { | ||||
| 					"name": "created_at", | ||||
| 					"type": "timestamp with time zone", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true, | ||||
| 					"default": "now()" | ||||
| 				}, | ||||
| 				"next_refresh": { | ||||
| 					"name": "next_refresh", | ||||
| 					"type": "timestamp with time zone", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				} | ||||
| 			}, | ||||
| 			"indexes": {}, | ||||
| 			"foreignKeys": { | ||||
| 				"entries_show_pk_shows_pk_fk": { | ||||
| 					"name": "entries_show_pk_shows_pk_fk", | ||||
| 					"tableFrom": "entries", | ||||
| 					"tableTo": "shows", | ||||
| 					"schemaTo": "kyoo", | ||||
| 					"columnsFrom": ["show_pk"], | ||||
| 					"columnsTo": ["pk"], | ||||
| 					"onDelete": "cascade", | ||||
| 					"onUpdate": "no action" | ||||
| 				} | ||||
| 			}, | ||||
| 			"compositePrimaryKeys": {}, | ||||
| 			"uniqueConstraints": { | ||||
| 				"entries_id_unique": { | ||||
| 					"name": "entries_id_unique", | ||||
| 					"nullsNotDistinct": false, | ||||
| 					"columns": ["id"] | ||||
| 				}, | ||||
| 				"entries_slug_unique": { | ||||
| 					"name": "entries_slug_unique", | ||||
| 					"nullsNotDistinct": false, | ||||
| 					"columns": ["slug"] | ||||
| 				}, | ||||
| 				"entries_showPk_seasonNumber_episodeNumber_unique": { | ||||
| 					"name": "entries_showPk_seasonNumber_episodeNumber_unique", | ||||
| 					"nullsNotDistinct": false, | ||||
| 					"columns": ["show_pk", "season_number", "episode_number"] | ||||
| 				} | ||||
| 			}, | ||||
| 			"policies": {}, | ||||
| 			"checkConstraints": { | ||||
| 				"order_positive": { | ||||
| 					"name": "order_positive", | ||||
| 					"value": "\"kyoo\".\"entries\".\"order\" >= 0" | ||||
| 				} | ||||
| 			}, | ||||
| 			"isRLSEnabled": false | ||||
| 		}, | ||||
| 		"kyoo.entry_translations": { | ||||
| 			"name": "entry_translations", | ||||
| 			"schema": "kyoo", | ||||
| 			"columns": { | ||||
| 				"pk": { | ||||
| 					"name": "pk", | ||||
| 					"type": "integer", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				}, | ||||
| 				"language": { | ||||
| 					"name": "language", | ||||
| 					"type": "varchar(255)", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				}, | ||||
| 				"name": { | ||||
| 					"name": "name", | ||||
| 					"type": "text", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"description": { | ||||
| 					"name": "description", | ||||
| 					"type": "text", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"tagline": { | ||||
| 					"name": "tagline", | ||||
| 					"type": "text", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"poster": { | ||||
| 					"name": "poster", | ||||
| 					"type": "jsonb", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				} | ||||
| 			}, | ||||
| 			"indexes": {}, | ||||
| 			"foreignKeys": { | ||||
| 				"entry_translations_pk_entries_pk_fk": { | ||||
| 					"name": "entry_translations_pk_entries_pk_fk", | ||||
| 					"tableFrom": "entry_translations", | ||||
| 					"tableTo": "entries", | ||||
| 					"schemaTo": "kyoo", | ||||
| 					"columnsFrom": ["pk"], | ||||
| 					"columnsTo": ["pk"], | ||||
| 					"onDelete": "cascade", | ||||
| 					"onUpdate": "no action" | ||||
| 				} | ||||
| 			}, | ||||
| 			"compositePrimaryKeys": { | ||||
| 				"entry_translations_pk_language_pk": { | ||||
| 					"name": "entry_translations_pk_language_pk", | ||||
| 					"columns": ["pk", "language"] | ||||
| 				} | ||||
| 			}, | ||||
| 			"uniqueConstraints": {}, | ||||
| 			"policies": {}, | ||||
| 			"checkConstraints": {}, | ||||
| 			"isRLSEnabled": false | ||||
| 		}, | ||||
| 		"kyoo.season_translations": { | ||||
| 			"name": "season_translations", | ||||
| 			"schema": "kyoo", | ||||
| 			"columns": { | ||||
| 				"pk": { | ||||
| 					"name": "pk", | ||||
| 					"type": "integer", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				}, | ||||
| 				"language": { | ||||
| 					"name": "language", | ||||
| 					"type": "varchar(255)", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				}, | ||||
| 				"name": { | ||||
| 					"name": "name", | ||||
| 					"type": "text", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"description": { | ||||
| 					"name": "description", | ||||
| 					"type": "text", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"poster": { | ||||
| 					"name": "poster", | ||||
| 					"type": "jsonb", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"thumbnail": { | ||||
| 					"name": "thumbnail", | ||||
| 					"type": "jsonb", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"banner": { | ||||
| 					"name": "banner", | ||||
| 					"type": "jsonb", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				} | ||||
| 			}, | ||||
| 			"indexes": {}, | ||||
| 			"foreignKeys": { | ||||
| 				"season_translations_pk_seasons_pk_fk": { | ||||
| 					"name": "season_translations_pk_seasons_pk_fk", | ||||
| 					"tableFrom": "season_translations", | ||||
| 					"tableTo": "seasons", | ||||
| 					"schemaTo": "kyoo", | ||||
| 					"columnsFrom": ["pk"], | ||||
| 					"columnsTo": ["pk"], | ||||
| 					"onDelete": "cascade", | ||||
| 					"onUpdate": "no action" | ||||
| 				} | ||||
| 			}, | ||||
| 			"compositePrimaryKeys": { | ||||
| 				"season_translations_pk_language_pk": { | ||||
| 					"name": "season_translations_pk_language_pk", | ||||
| 					"columns": ["pk", "language"] | ||||
| 				} | ||||
| 			}, | ||||
| 			"uniqueConstraints": {}, | ||||
| 			"policies": {}, | ||||
| 			"checkConstraints": {}, | ||||
| 			"isRLSEnabled": false | ||||
| 		}, | ||||
| 		"kyoo.seasons": { | ||||
| 			"name": "seasons", | ||||
| 			"schema": "kyoo", | ||||
| 			"columns": { | ||||
| 				"pk": { | ||||
| 					"name": "pk", | ||||
| 					"type": "integer", | ||||
| 					"primaryKey": true, | ||||
| 					"notNull": true, | ||||
| 					"identity": { | ||||
| 						"type": "always", | ||||
| 						"name": "seasons_pk_seq", | ||||
| 						"schema": "kyoo", | ||||
| 						"increment": "1", | ||||
| 						"startWith": "1", | ||||
| 						"minValue": "1", | ||||
| 						"maxValue": "2147483647", | ||||
| 						"cache": "1", | ||||
| 						"cycle": false | ||||
| 					} | ||||
| 				}, | ||||
| 				"id": { | ||||
| 					"name": "id", | ||||
| 					"type": "uuid", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true, | ||||
| 					"default": "gen_random_uuid()" | ||||
| 				}, | ||||
| 				"slug": { | ||||
| 					"name": "slug", | ||||
| 					"type": "varchar(255)", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				}, | ||||
| 				"show_pk": { | ||||
| 					"name": "show_pk", | ||||
| 					"type": "integer", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"season_number": { | ||||
| 					"name": "season_number", | ||||
| 					"type": "integer", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				}, | ||||
| 				"start_air": { | ||||
| 					"name": "start_air", | ||||
| 					"type": "date", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"end_air": { | ||||
| 					"name": "end_air", | ||||
| 					"type": "date", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"external_id": { | ||||
| 					"name": "external_id", | ||||
| 					"type": "jsonb", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true, | ||||
| 					"default": "'{}'::jsonb" | ||||
| 				}, | ||||
| 				"created_at": { | ||||
| 					"name": "created_at", | ||||
| 					"type": "timestamp with time zone", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true, | ||||
| 					"default": "now()" | ||||
| 				}, | ||||
| 				"next_refresh": { | ||||
| 					"name": "next_refresh", | ||||
| 					"type": "timestamp with time zone", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				} | ||||
| 			}, | ||||
| 			"indexes": { | ||||
| 				"show_fk": { | ||||
| 					"name": "show_fk", | ||||
| 					"columns": [ | ||||
| 						{ | ||||
| 							"expression": "show_pk", | ||||
| 							"isExpression": false, | ||||
| 							"asc": true, | ||||
| 							"nulls": "last" | ||||
| 						} | ||||
| 					], | ||||
| 					"isUnique": false, | ||||
| 					"concurrently": false, | ||||
| 					"method": "hash", | ||||
| 					"with": {} | ||||
| 				} | ||||
| 			}, | ||||
| 			"foreignKeys": { | ||||
| 				"seasons_show_pk_shows_pk_fk": { | ||||
| 					"name": "seasons_show_pk_shows_pk_fk", | ||||
| 					"tableFrom": "seasons", | ||||
| 					"tableTo": "shows", | ||||
| 					"schemaTo": "kyoo", | ||||
| 					"columnsFrom": ["show_pk"], | ||||
| 					"columnsTo": ["pk"], | ||||
| 					"onDelete": "cascade", | ||||
| 					"onUpdate": "no action" | ||||
| 				} | ||||
| 			}, | ||||
| 			"compositePrimaryKeys": {}, | ||||
| 			"uniqueConstraints": { | ||||
| 				"seasons_id_unique": { | ||||
| 					"name": "seasons_id_unique", | ||||
| 					"nullsNotDistinct": false, | ||||
| 					"columns": ["id"] | ||||
| 				}, | ||||
| 				"seasons_slug_unique": { | ||||
| 					"name": "seasons_slug_unique", | ||||
| 					"nullsNotDistinct": false, | ||||
| 					"columns": ["slug"] | ||||
| 				}, | ||||
| 				"seasons_showPk_seasonNumber_unique": { | ||||
| 					"name": "seasons_showPk_seasonNumber_unique", | ||||
| 					"nullsNotDistinct": false, | ||||
| 					"columns": ["show_pk", "season_number"] | ||||
| 				} | ||||
| 			}, | ||||
| 			"policies": {}, | ||||
| 			"checkConstraints": {}, | ||||
| 			"isRLSEnabled": false | ||||
| 		}, | ||||
| 		"kyoo.show_translations": { | ||||
| 			"name": "show_translations", | ||||
| 			"schema": "kyoo", | ||||
| 			"columns": { | ||||
| 				"pk": { | ||||
| 					"name": "pk", | ||||
| 					"type": "integer", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				}, | ||||
| 				"language": { | ||||
| 					"name": "language", | ||||
| 					"type": "varchar(255)", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				}, | ||||
| 				"name": { | ||||
| 					"name": "name", | ||||
| 					"type": "text", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				}, | ||||
| 				"description": { | ||||
| 					"name": "description", | ||||
| 					"type": "text", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"tagline": { | ||||
| 					"name": "tagline", | ||||
| 					"type": "text", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"aliases": { | ||||
| 					"name": "aliases", | ||||
| 					"type": "text[]", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				}, | ||||
| 				"tags": { | ||||
| 					"name": "tags", | ||||
| 					"type": "text[]", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				}, | ||||
| 				"poster": { | ||||
| 					"name": "poster", | ||||
| 					"type": "jsonb", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"thumbnail": { | ||||
| 					"name": "thumbnail", | ||||
| 					"type": "jsonb", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"banner": { | ||||
| 					"name": "banner", | ||||
| 					"type": "jsonb", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"logo": { | ||||
| 					"name": "logo", | ||||
| 					"type": "jsonb", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"trailer_url": { | ||||
| 					"name": "trailer_url", | ||||
| 					"type": "text", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				} | ||||
| 			}, | ||||
| 			"indexes": { | ||||
| 				"name_trgm": { | ||||
| 					"name": "name_trgm", | ||||
| 					"columns": [ | ||||
| 						{ | ||||
| 							"expression": "\"name\" gin_trgm_ops", | ||||
| 							"asc": true, | ||||
| 							"isExpression": true, | ||||
| 							"nulls": "last" | ||||
| 						} | ||||
| 					], | ||||
| 					"isUnique": false, | ||||
| 					"concurrently": false, | ||||
| 					"method": "gin", | ||||
| 					"with": {} | ||||
| 				}, | ||||
| 				"tags": { | ||||
| 					"name": "tags", | ||||
| 					"columns": [ | ||||
| 						{ | ||||
| 							"expression": "tags", | ||||
| 							"isExpression": false, | ||||
| 							"asc": true, | ||||
| 							"nulls": "last" | ||||
| 						} | ||||
| 					], | ||||
| 					"isUnique": false, | ||||
| 					"concurrently": false, | ||||
| 					"method": "btree", | ||||
| 					"with": {} | ||||
| 				} | ||||
| 			}, | ||||
| 			"foreignKeys": { | ||||
| 				"show_translations_pk_shows_pk_fk": { | ||||
| 					"name": "show_translations_pk_shows_pk_fk", | ||||
| 					"tableFrom": "show_translations", | ||||
| 					"tableTo": "shows", | ||||
| 					"schemaTo": "kyoo", | ||||
| 					"columnsFrom": ["pk"], | ||||
| 					"columnsTo": ["pk"], | ||||
| 					"onDelete": "cascade", | ||||
| 					"onUpdate": "no action" | ||||
| 				} | ||||
| 			}, | ||||
| 			"compositePrimaryKeys": { | ||||
| 				"show_translations_pk_language_pk": { | ||||
| 					"name": "show_translations_pk_language_pk", | ||||
| 					"columns": ["pk", "language"] | ||||
| 				} | ||||
| 			}, | ||||
| 			"uniqueConstraints": {}, | ||||
| 			"policies": {}, | ||||
| 			"checkConstraints": {}, | ||||
| 			"isRLSEnabled": false | ||||
| 		}, | ||||
| 		"kyoo.shows": { | ||||
| 			"name": "shows", | ||||
| 			"schema": "kyoo", | ||||
| 			"columns": { | ||||
| 				"pk": { | ||||
| 					"name": "pk", | ||||
| 					"type": "integer", | ||||
| 					"primaryKey": true, | ||||
| 					"notNull": true, | ||||
| 					"identity": { | ||||
| 						"type": "always", | ||||
| 						"name": "shows_pk_seq", | ||||
| 						"schema": "kyoo", | ||||
| 						"increment": "1", | ||||
| 						"startWith": "1", | ||||
| 						"minValue": "1", | ||||
| 						"maxValue": "2147483647", | ||||
| 						"cache": "1", | ||||
| 						"cycle": false | ||||
| 					} | ||||
| 				}, | ||||
| 				"id": { | ||||
| 					"name": "id", | ||||
| 					"type": "uuid", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true, | ||||
| 					"default": "gen_random_uuid()" | ||||
| 				}, | ||||
| 				"slug": { | ||||
| 					"name": "slug", | ||||
| 					"type": "varchar(255)", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				}, | ||||
| 				"kind": { | ||||
| 					"name": "kind", | ||||
| 					"type": "show_kind", | ||||
| 					"typeSchema": "kyoo", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				}, | ||||
| 				"genres": { | ||||
| 					"name": "genres", | ||||
| 					"type": "genres[]", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				}, | ||||
| 				"rating": { | ||||
| 					"name": "rating", | ||||
| 					"type": "smallint", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"runtime": { | ||||
| 					"name": "runtime", | ||||
| 					"type": "integer", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"status": { | ||||
| 					"name": "status", | ||||
| 					"type": "show_status", | ||||
| 					"typeSchema": "kyoo", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				}, | ||||
| 				"start_air": { | ||||
| 					"name": "start_air", | ||||
| 					"type": "date", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"end_air": { | ||||
| 					"name": "end_air", | ||||
| 					"type": "date", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"original_language": { | ||||
| 					"name": "original_language", | ||||
| 					"type": "varchar(255)", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"collection_pk": { | ||||
| 					"name": "collection_pk", | ||||
| 					"type": "integer", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"external_id": { | ||||
| 					"name": "external_id", | ||||
| 					"type": "jsonb", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true, | ||||
| 					"default": "'{}'::jsonb" | ||||
| 				}, | ||||
| 				"created_at": { | ||||
| 					"name": "created_at", | ||||
| 					"type": "timestamp with time zone", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true, | ||||
| 					"default": "now()" | ||||
| 				}, | ||||
| 				"next_refresh": { | ||||
| 					"name": "next_refresh", | ||||
| 					"type": "timestamp with time zone", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				} | ||||
| 			}, | ||||
| 			"indexes": { | ||||
| 				"kind": { | ||||
| 					"name": "kind", | ||||
| 					"columns": [ | ||||
| 						{ | ||||
| 							"expression": "kind", | ||||
| 							"isExpression": false, | ||||
| 							"asc": true, | ||||
| 							"nulls": "last" | ||||
| 						} | ||||
| 					], | ||||
| 					"isUnique": false, | ||||
| 					"concurrently": false, | ||||
| 					"method": "hash", | ||||
| 					"with": {} | ||||
| 				}, | ||||
| 				"rating": { | ||||
| 					"name": "rating", | ||||
| 					"columns": [ | ||||
| 						{ | ||||
| 							"expression": "rating", | ||||
| 							"isExpression": false, | ||||
| 							"asc": true, | ||||
| 							"nulls": "last" | ||||
| 						} | ||||
| 					], | ||||
| 					"isUnique": false, | ||||
| 					"concurrently": false, | ||||
| 					"method": "btree", | ||||
| 					"with": {} | ||||
| 				}, | ||||
| 				"startAir": { | ||||
| 					"name": "startAir", | ||||
| 					"columns": [ | ||||
| 						{ | ||||
| 							"expression": "start_air", | ||||
| 							"isExpression": false, | ||||
| 							"asc": true, | ||||
| 							"nulls": "last" | ||||
| 						} | ||||
| 					], | ||||
| 					"isUnique": false, | ||||
| 					"concurrently": false, | ||||
| 					"method": "btree", | ||||
| 					"with": {} | ||||
| 				} | ||||
| 			}, | ||||
| 			"foreignKeys": { | ||||
| 				"shows_collection_pk_shows_pk_fk": { | ||||
| 					"name": "shows_collection_pk_shows_pk_fk", | ||||
| 					"tableFrom": "shows", | ||||
| 					"tableTo": "shows", | ||||
| 					"schemaTo": "kyoo", | ||||
| 					"columnsFrom": ["collection_pk"], | ||||
| 					"columnsTo": ["pk"], | ||||
| 					"onDelete": "set null", | ||||
| 					"onUpdate": "no action" | ||||
| 				} | ||||
| 			}, | ||||
| 			"compositePrimaryKeys": {}, | ||||
| 			"uniqueConstraints": { | ||||
| 				"shows_id_unique": { | ||||
| 					"name": "shows_id_unique", | ||||
| 					"nullsNotDistinct": false, | ||||
| 					"columns": ["id"] | ||||
| 				}, | ||||
| 				"shows_slug_unique": { | ||||
| 					"name": "shows_slug_unique", | ||||
| 					"nullsNotDistinct": false, | ||||
| 					"columns": ["slug"] | ||||
| 				} | ||||
| 			}, | ||||
| 			"policies": {}, | ||||
| 			"checkConstraints": { | ||||
| 				"rating_valid": { | ||||
| 					"name": "rating_valid", | ||||
| 					"value": "\"kyoo\".\"shows\".\"rating\" between 0 and 100" | ||||
| 				}, | ||||
| 				"runtime_valid": { | ||||
| 					"name": "runtime_valid", | ||||
| 					"value": "\"kyoo\".\"shows\".\"runtime\" >= 0" | ||||
| 				} | ||||
| 			}, | ||||
| 			"isRLSEnabled": false | ||||
| 		}, | ||||
| 		"kyoo.entry_video_join": { | ||||
| 			"name": "entry_video_join", | ||||
| 			"schema": "kyoo", | ||||
| 			"columns": { | ||||
| 				"entry": { | ||||
| 					"name": "entry", | ||||
| 					"type": "integer", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				}, | ||||
| 				"video": { | ||||
| 					"name": "video", | ||||
| 					"type": "integer", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				}, | ||||
| 				"slug": { | ||||
| 					"name": "slug", | ||||
| 					"type": "varchar(255)", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				} | ||||
| 			}, | ||||
| 			"indexes": {}, | ||||
| 			"foreignKeys": { | ||||
| 				"entry_video_join_entry_entries_pk_fk": { | ||||
| 					"name": "entry_video_join_entry_entries_pk_fk", | ||||
| 					"tableFrom": "entry_video_join", | ||||
| 					"tableTo": "entries", | ||||
| 					"schemaTo": "kyoo", | ||||
| 					"columnsFrom": ["entry"], | ||||
| 					"columnsTo": ["pk"], | ||||
| 					"onDelete": "cascade", | ||||
| 					"onUpdate": "no action" | ||||
| 				}, | ||||
| 				"entry_video_join_video_videos_pk_fk": { | ||||
| 					"name": "entry_video_join_video_videos_pk_fk", | ||||
| 					"tableFrom": "entry_video_join", | ||||
| 					"tableTo": "videos", | ||||
| 					"schemaTo": "kyoo", | ||||
| 					"columnsFrom": ["video"], | ||||
| 					"columnsTo": ["pk"], | ||||
| 					"onDelete": "cascade", | ||||
| 					"onUpdate": "no action" | ||||
| 				} | ||||
| 			}, | ||||
| 			"compositePrimaryKeys": { | ||||
| 				"entry_video_join_entry_video_pk": { | ||||
| 					"name": "entry_video_join_entry_video_pk", | ||||
| 					"columns": ["entry", "video"] | ||||
| 				} | ||||
| 			}, | ||||
| 			"uniqueConstraints": { | ||||
| 				"entry_video_join_slug_unique": { | ||||
| 					"name": "entry_video_join_slug_unique", | ||||
| 					"nullsNotDistinct": false, | ||||
| 					"columns": ["slug"] | ||||
| 				} | ||||
| 			}, | ||||
| 			"policies": {}, | ||||
| 			"checkConstraints": {}, | ||||
| 			"isRLSEnabled": false | ||||
| 		}, | ||||
| 		"kyoo.videos": { | ||||
| 			"name": "videos", | ||||
| 			"schema": "kyoo", | ||||
| 			"columns": { | ||||
| 				"pk": { | ||||
| 					"name": "pk", | ||||
| 					"type": "integer", | ||||
| 					"primaryKey": true, | ||||
| 					"notNull": true, | ||||
| 					"identity": { | ||||
| 						"type": "always", | ||||
| 						"name": "videos_pk_seq", | ||||
| 						"schema": "kyoo", | ||||
| 						"increment": "1", | ||||
| 						"startWith": "1", | ||||
| 						"minValue": "1", | ||||
| 						"maxValue": "2147483647", | ||||
| 						"cache": "1", | ||||
| 						"cycle": false | ||||
| 					} | ||||
| 				}, | ||||
| 				"id": { | ||||
| 					"name": "id", | ||||
| 					"type": "uuid", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true, | ||||
| 					"default": "gen_random_uuid()" | ||||
| 				}, | ||||
| 				"path": { | ||||
| 					"name": "path", | ||||
| 					"type": "text", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				}, | ||||
| 				"rendering": { | ||||
| 					"name": "rendering", | ||||
| 					"type": "text", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true | ||||
| 				}, | ||||
| 				"part": { | ||||
| 					"name": "part", | ||||
| 					"type": "integer", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": false | ||||
| 				}, | ||||
| 				"version": { | ||||
| 					"name": "version", | ||||
| 					"type": "integer", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true, | ||||
| 					"default": 1 | ||||
| 				}, | ||||
| 				"guess": { | ||||
| 					"name": "guess", | ||||
| 					"type": "jsonb", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true, | ||||
| 					"default": "'{}'::jsonb" | ||||
| 				}, | ||||
| 				"created_at": { | ||||
| 					"name": "created_at", | ||||
| 					"type": "timestamp with time zone", | ||||
| 					"primaryKey": false, | ||||
| 					"notNull": true, | ||||
| 					"default": "now()" | ||||
| 				} | ||||
| 			}, | ||||
| 			"indexes": {}, | ||||
| 			"foreignKeys": {}, | ||||
| 			"compositePrimaryKeys": {}, | ||||
| 			"uniqueConstraints": { | ||||
| 				"videos_id_unique": { | ||||
| 					"name": "videos_id_unique", | ||||
| 					"nullsNotDistinct": false, | ||||
| 					"columns": ["id"] | ||||
| 				}, | ||||
| 				"videos_path_unique": { | ||||
| 					"name": "videos_path_unique", | ||||
| 					"nullsNotDistinct": false, | ||||
| 					"columns": ["path"] | ||||
| 				} | ||||
| 			}, | ||||
| 			"policies": {}, | ||||
| 			"checkConstraints": { | ||||
| 				"part_pos": { | ||||
| 					"name": "part_pos", | ||||
| 					"value": "\"kyoo\".\"videos\".\"part\" >= 0" | ||||
| 				}, | ||||
| 				"version_pos": { | ||||
| 					"name": "version_pos", | ||||
| 					"value": "\"kyoo\".\"videos\".\"version\" >= 0" | ||||
| 				} | ||||
| 			}, | ||||
| 			"isRLSEnabled": false | ||||
| 		} | ||||
| 	}, | ||||
| 	"enums": { | ||||
| 		"kyoo.entry_type": { | ||||
| 			"name": "entry_type", | ||||
| 			"schema": "kyoo", | ||||
| 			"values": ["unknown", "episode", "movie", "special", "extra"] | ||||
| 		}, | ||||
| 		"kyoo.genres": { | ||||
| 			"name": "genres", | ||||
| 			"schema": "kyoo", | ||||
| 			"values": [ | ||||
| 				"action", | ||||
| 				"adventure", | ||||
| 				"animation", | ||||
| 				"comedy", | ||||
| 				"crime", | ||||
| 				"documentary", | ||||
| 				"drama", | ||||
| 				"family", | ||||
| 				"fantasy", | ||||
| 				"history", | ||||
| 				"horror", | ||||
| 				"music", | ||||
| 				"mystery", | ||||
| 				"romance", | ||||
| 				"science-fiction", | ||||
| 				"thriller", | ||||
| 				"war", | ||||
| 				"western", | ||||
| 				"kids", | ||||
| 				"reality", | ||||
| 				"politics", | ||||
| 				"soap", | ||||
| 				"talk" | ||||
| 			] | ||||
| 		}, | ||||
| 		"kyoo.show_kind": { | ||||
| 			"name": "show_kind", | ||||
| 			"schema": "kyoo", | ||||
| 			"values": ["serie", "movie", "collection"] | ||||
| 		}, | ||||
| 		"kyoo.show_status": { | ||||
| 			"name": "show_status", | ||||
| 			"schema": "kyoo", | ||||
| 			"values": ["unknown", "finished", "airing", "planned"] | ||||
| 		} | ||||
| 	}, | ||||
| 	"schemas": { | ||||
| 		"kyoo": "kyoo" | ||||
| 	}, | ||||
| 	"sequences": {}, | ||||
| 	"roles": {}, | ||||
| 	"policies": {}, | ||||
| 	"views": {}, | ||||
| 	"_meta": { | ||||
| 		"columns": {}, | ||||
| 		"schemas": {}, | ||||
| 		"tables": {} | ||||
| 	} | ||||
| } | ||||
| @ -64,6 +64,13 @@ | ||||
| 			"when": 1738064522937, | ||||
| 			"tag": "0008_entries", | ||||
| 			"breakpoints": true | ||||
| 		}, | ||||
| 		{ | ||||
| 			"idx": 9, | ||||
| 			"version": "7", | ||||
| 			"when": 1740872363604, | ||||
| 			"tag": "0009_collections", | ||||
| 			"breakpoints": true | ||||
| 		} | ||||
| 	] | ||||
| } | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| import { type SQL, and, eq, exists, sql } from "drizzle-orm"; | ||||
| import { Elysia, t } from "elysia"; | ||||
| import { db } from "~/db"; | ||||
| import { entries, entryVideoJoin, showTranslations, shows } from "~/db/schema"; | ||||
| import { getColumns, sqlarr } from "~/db/utils"; | ||||
| import { KError } from "~/models/error"; | ||||
| @ -25,7 +26,6 @@ import { | ||||
| 	sortToSql, | ||||
| } from "~/models/utils"; | ||||
| import { desc } from "~/models/utils/descriptions"; | ||||
| import { db } from "../db"; | ||||
| 
 | ||||
| const movieFilters: FilterDef = { | ||||
| 	genres: { | ||||
|  | ||||
							
								
								
									
										69
									
								
								api/src/controllers/seed/insert/collection.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								api/src/controllers/seed/insert/collection.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,69 @@ | ||||
| import { sql } from "drizzle-orm"; | ||||
| import { db } from "~/db"; | ||||
| import { showTranslations, shows } from "~/db/schema"; | ||||
| import { conflictUpdateAllExcept } from "~/db/utils"; | ||||
| import type { SeedCollection } from "~/models/collections"; | ||||
| import type { SeedMovie } from "~/models/movie"; | ||||
| import type { SeedSerie } from "~/models/serie"; | ||||
| import { processOptImage } from "../images"; | ||||
| 
 | ||||
| type ShowTrans = typeof showTranslations.$inferInsert; | ||||
| 
 | ||||
| export const insertCollection = async ( | ||||
| 	collection: SeedCollection | undefined, | ||||
| 	show: (({ kind: "movie" } & SeedMovie) | ({ kind: "serie" } & SeedSerie)) & { | ||||
| 		nextRefresh: string; | ||||
| 	}, | ||||
| ) => { | ||||
| 	if (!collection) return null; | ||||
| 	const { translations, ...col } = collection; | ||||
| 
 | ||||
| 	return await db.transaction(async (tx) => { | ||||
| 		const [ret] = await tx | ||||
| 			.insert(shows) | ||||
| 			.values({ | ||||
| 				kind: "collection", | ||||
| 				status: "unknown", | ||||
| 				startAir: show.kind === "movie" ? show.airDate : show.startAir, | ||||
| 				endAir: show.kind === "movie" ? show.airDate : show.endAir, | ||||
| 				nextRefresh: show.nextRefresh, | ||||
| 				...col, | ||||
| 			}) | ||||
| 			.onConflictDoUpdate({ | ||||
| 				target: shows.slug, | ||||
| 				set: { | ||||
| 					...conflictUpdateAllExcept(shows, [ | ||||
| 						"pk", | ||||
| 						"id", | ||||
| 						"slug", | ||||
| 						"createdAt", | ||||
| 						"startAir", | ||||
| 						"endAir", | ||||
| 					]), | ||||
| 					startAir: sql`least(${shows.startAir}, excluded.start_air)`, | ||||
| 					endAir: sql`greatest(${shows.endAir}, excluded.end_air)`, | ||||
| 				}, | ||||
| 			}) | ||||
| 			.returning({ pk: shows.pk, id: shows.id, slug: shows.slug }); | ||||
| 
 | ||||
| 		const trans: ShowTrans[] = Object.entries(translations).map( | ||||
| 			([lang, tr]) => ({ | ||||
| 				pk: ret.pk, | ||||
| 				language: lang, | ||||
| 				...tr, | ||||
| 				poster: processOptImage(tr.poster), | ||||
| 				thumbnail: processOptImage(tr.thumbnail), | ||||
| 				logo: processOptImage(tr.logo), | ||||
| 				banner: processOptImage(tr.banner), | ||||
| 			}), | ||||
| 		); | ||||
| 		await tx | ||||
| 			.insert(showTranslations) | ||||
| 			.values(trans) | ||||
| 			.onConflictDoUpdate({ | ||||
| 				target: [showTranslations.pk, showTranslations.language], | ||||
| 				set: conflictUpdateAllExcept(showTranslations, ["pk", "language"]), | ||||
| 			}); | ||||
| 		return ret; | ||||
| 	}); | ||||
| }; | ||||
| @ -2,6 +2,7 @@ import { eq, sql } from "drizzle-orm"; | ||||
| import { db } from "~/db"; | ||||
| import { showTranslations, shows } from "~/db/schema"; | ||||
| import { conflictUpdateAllExcept } from "~/db/utils"; | ||||
| import type { SeedCollection } from "~/models/collections"; | ||||
| import type { SeedMovie } from "~/models/movie"; | ||||
| import type { SeedSerie } from "~/models/serie"; | ||||
| import { getYear } from "~/utils"; | ||||
| @ -12,7 +13,10 @@ type ShowTrans = typeof showTranslations.$inferInsert; | ||||
| 
 | ||||
| export const insertShow = async ( | ||||
| 	show: Show, | ||||
| 	translations: SeedMovie["translations"] | SeedSerie["translations"], | ||||
| 	translations: | ||||
| 		| SeedMovie["translations"] | ||||
| 		| SeedSerie["translations"] | ||||
| 		| SeedCollection["translations"], | ||||
| ) => { | ||||
| 	return await db.transaction(async (tx) => { | ||||
| 		const ret = await insertBaseShow(tx, show); | ||||
| @ -77,13 +81,14 @@ async function insertBaseShow( | ||||
| 
 | ||||
| 	// if at this point ret is still undefined, we could not reconciliate.
 | ||||
| 	// simply bail and let the caller handle this.
 | ||||
| 	const [{ id }] = await db | ||||
| 		.select({ id: shows.id }) | ||||
| 	const [{ pk, id }] = await db | ||||
| 		.select({ pk: shows.pk, id: shows.id }) | ||||
| 		.from(shows) | ||||
| 		.where(eq(shows.slug, show.slug)) | ||||
| 		.limit(1); | ||||
| 	return { | ||||
| 		status: 409 as const, | ||||
| 		pk, | ||||
| 		id, | ||||
| 		slug: show.slug, | ||||
| 	}; | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| import { t } from "elysia"; | ||||
| import type { SeedMovie } from "~/models/movie"; | ||||
| import { getYear } from "~/utils"; | ||||
| import { insertCollection } from "./insert/collection"; | ||||
| import { insertEntries } from "./insert/entries"; | ||||
| import { insertShow } from "./insert/shows"; | ||||
| import { guessNextRefresh } from "./refresh"; | ||||
| @ -11,6 +12,12 @@ export const SeedMovieResponse = t.Object({ | ||||
| 	videos: t.Array( | ||||
| 		t.Object({ slug: t.String({ format: "slug", examples: ["bubble-v2"] }) }), | ||||
| 	), | ||||
| 	collection: t.Nullable( | ||||
| 		t.Object({ | ||||
| 			id: t.String({ format: "uuid" }), | ||||
| 			slug: t.String({ format: "slug", examples: ["sawano-collection"] }), | ||||
| 		}), | ||||
| 	), | ||||
| }); | ||||
| export type SeedMovieResponse = typeof SeedMovieResponse.static; | ||||
| 
 | ||||
| @ -31,14 +38,21 @@ export const seedMovie = async ( | ||||
| 		seed.slug = `random-${getYear(seed.airDate)}`; | ||||
| 	} | ||||
| 
 | ||||
| 	const { translations, videos, ...bMovie } = seed; | ||||
| 	const { translations, videos, collection, ...bMovie } = seed; | ||||
| 	const nextRefresh = guessNextRefresh(bMovie.airDate ?? new Date()); | ||||
| 
 | ||||
| 	const col = await insertCollection(collection, { | ||||
| 		kind: "movie", | ||||
| 		nextRefresh, | ||||
| 		...seed, | ||||
| 	}); | ||||
| 
 | ||||
| 	const show = await insertShow( | ||||
| 		{ | ||||
| 			kind: "movie", | ||||
| 			startAir: bMovie.airDate, | ||||
| 			nextRefresh, | ||||
| 			collectionPk: col?.pk, | ||||
| 			...bMovie, | ||||
| 		}, | ||||
| 		translations, | ||||
| @ -65,5 +79,6 @@ export const seedMovie = async ( | ||||
| 		id: show.id, | ||||
| 		slug: show.slug, | ||||
| 		videos: entry.videos, | ||||
| 		collection: col, | ||||
| 	}; | ||||
| }; | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| import { t } from "elysia"; | ||||
| import type { SeedSerie } from "~/models/serie"; | ||||
| import { getYear } from "~/utils"; | ||||
| import { insertCollection } from "./insert/collection"; | ||||
| import { insertEntries } from "./insert/entries"; | ||||
| import { insertSeasons } from "./insert/seasons"; | ||||
| import { insertShow } from "./insert/shows"; | ||||
| @ -35,6 +36,15 @@ export const SeedSerieResponse = t.Object({ | ||||
| 			slug: t.String({ format: "slug", examples: ["made-in-abyss-s1e1"] }), | ||||
| 		}), | ||||
| 	), | ||||
| 	collection: t.Nullable( | ||||
| 		t.Object({ | ||||
| 			id: t.String({ format: "uuid" }), | ||||
| 			slug: t.String({ | ||||
| 				format: "slug", | ||||
| 				examples: ["made-in-abyss-collection"], | ||||
| 			}), | ||||
| 		}), | ||||
| 	), | ||||
| }); | ||||
| export type SeedSerieResponse = typeof SeedSerieResponse.static; | ||||
| 
 | ||||
| @ -55,13 +65,20 @@ export const seedSerie = async ( | ||||
| 		seed.slug = `random-${getYear(seed.startAir)}`; | ||||
| 	} | ||||
| 
 | ||||
| 	const { translations, seasons, entries, extras, ...serie } = seed; | ||||
| 	const { translations, seasons, entries, extras, collection, ...serie } = seed; | ||||
| 	const nextRefresh = guessNextRefresh(serie.startAir ?? new Date()); | ||||
| 
 | ||||
| 	const col = await insertCollection(collection, { | ||||
| 		kind: "serie", | ||||
| 		nextRefresh, | ||||
| 		...seed, | ||||
| 	}); | ||||
| 
 | ||||
| 	const show = await insertShow( | ||||
| 		{ | ||||
| 			kind: "serie", | ||||
| 			nextRefresh, | ||||
| 			collectionPk: col?.pk, | ||||
| 			...serie, | ||||
| 		}, | ||||
| 		translations, | ||||
| @ -82,5 +99,6 @@ export const seedSerie = async ( | ||||
| 		seasons: retSeasons, | ||||
| 		entries: retEntries, | ||||
| 		extras: retExtras, | ||||
| 		collection: col, | ||||
| 	}; | ||||
| }; | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| import { relations, sql } from "drizzle-orm"; | ||||
| import { | ||||
| 	type AnyPgColumn, | ||||
| 	check, | ||||
| 	date, | ||||
| 	index, | ||||
| @ -16,7 +17,11 @@ import { entries } from "./entries"; | ||||
| import { seasons } from "./seasons"; | ||||
| import { image, language, schema } from "./utils"; | ||||
| 
 | ||||
| export const showKind = schema.enum("show_kind", ["serie", "movie"]); | ||||
| export const showKind = schema.enum("show_kind", [ | ||||
| 	"serie", | ||||
| 	"movie", | ||||
| 	"collection", | ||||
| ]); | ||||
| export const showStatus = schema.enum("show_status", [ | ||||
| 	"unknown", | ||||
| 	"finished", | ||||
| @ -78,6 +83,10 @@ export const shows = schema.table( | ||||
| 		endAir: date(), | ||||
| 		originalLanguage: language(), | ||||
| 
 | ||||
| 		collectionPk: integer().references((): AnyPgColumn => shows.pk, { | ||||
| 			onDelete: "set null", | ||||
| 		}), | ||||
| 
 | ||||
| 		externalId: externalid(), | ||||
| 
 | ||||
| 		createdAt: timestamp({ withTimezone: true, mode: "string" }) | ||||
|  | ||||
							
								
								
									
										83
									
								
								api/src/models/collections.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								api/src/models/collections.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,83 @@ | ||||
| import { t } from "elysia"; | ||||
| import type { Prettify } from "elysia/dist/types"; | ||||
| import { bubbleImages, duneCollection, registerExamples } from "./examples"; | ||||
| import { | ||||
| 	ExternalId, | ||||
| 	Genre, | ||||
| 	Image, | ||||
| 	Resource, | ||||
| 	SeedImage, | ||||
| 	TranslationRecord, | ||||
| } from "./utils"; | ||||
| 
 | ||||
| const BaseCollection = t.Object({ | ||||
| 	genres: t.Array(Genre), | ||||
| 	rating: t.Nullable(t.Integer({ minimum: 0, maximum: 100 })), | ||||
| 	startAir: t.Nullable( | ||||
| 		t.String({ | ||||
| 			format: "date", | ||||
| 			descrpition: "Date of the first item of the collection", | ||||
| 		}), | ||||
| 	), | ||||
| 	endAir: t.Nullable( | ||||
| 		t.String({ | ||||
| 			format: "date", | ||||
| 			descrpition: "Date of the last item of the collection", | ||||
| 		}), | ||||
| 	), | ||||
| 
 | ||||
| 	createdAt: t.String({ format: "date-time" }), | ||||
| 	nextRefresh: t.String({ format: "date-time" }), | ||||
| 
 | ||||
| 	externalId: ExternalId, | ||||
| }); | ||||
| 
 | ||||
| export const CollectionTranslation = t.Object({ | ||||
| 	name: t.String(), | ||||
| 	description: t.Nullable(t.String()), | ||||
| 	tagline: t.Nullable(t.String()), | ||||
| 	aliases: t.Array(t.String()), | ||||
| 	tags: t.Array(t.String()), | ||||
| 
 | ||||
| 	poster: t.Nullable(Image), | ||||
| 	thumbnail: t.Nullable(Image), | ||||
| 	banner: t.Nullable(Image), | ||||
| 	logo: t.Nullable(Image), | ||||
| }); | ||||
| 
 | ||||
| export const Collection = t.Intersect([ | ||||
| 	Resource(), | ||||
| 	CollectionTranslation, | ||||
| 	BaseCollection, | ||||
| ]); | ||||
| export type Collection = Prettify<typeof Collection.static>; | ||||
| 
 | ||||
| export const SeedCollection = t.Intersect([ | ||||
| 	t.Omit(BaseCollection, ["startAir", "endAir", "createdAt", "nextRefresh"]), | ||||
| 	t.Object({ | ||||
| 		slug: t.String({ format: "slug" }), | ||||
| 		translations: TranslationRecord( | ||||
| 			t.Intersect([ | ||||
| 				t.Omit(CollectionTranslation, [ | ||||
| 					"poster", | ||||
| 					"thumbnail", | ||||
| 					"banner", | ||||
| 					"logo", | ||||
| 				]), | ||||
| 				t.Object({ | ||||
| 					poster: t.Nullable(SeedImage), | ||||
| 					thumbnail: t.Nullable(SeedImage), | ||||
| 					banner: t.Nullable(SeedImage), | ||||
| 					logo: t.Nullable(SeedImage), | ||||
| 				}), | ||||
| 			]), | ||||
| 		), | ||||
| 	}), | ||||
| ]); | ||||
| export type SeedCollection = Prettify<typeof SeedCollection.static>; | ||||
| 
 | ||||
| registerExamples(Collection, { | ||||
| 	...duneCollection, | ||||
| 	...duneCollection.translations.en, | ||||
| 	...bubbleImages, | ||||
| }); | ||||
| @ -1,5 +1,5 @@ | ||||
| import type { SeedMovie } from "../movie"; | ||||
| import type { Video } from "../video"; | ||||
| import type { SeedMovie } from "~/models/movie"; | ||||
| import type { Video } from "~/models/video"; | ||||
| 
 | ||||
| export const duneVideo: Video = { | ||||
| 	id: "c9a0d02e-6b8e-4ac1-b431-45b022ec0708", | ||||
| @ -38,8 +38,8 @@ export const dune: SeedMovie = { | ||||
| 	originalLanguage: "en", | ||||
| 	externalId: { | ||||
| 		themoviedatabase: { | ||||
| 			dataId: "496243", | ||||
| 			link: "https://www.themoviedb.org/movie/496243", | ||||
| 			dataId: "438631", | ||||
| 			link: "https://www.themoviedb.org/movie/438631-dune", | ||||
| 		}, | ||||
| 		imdb: { | ||||
| 			dataId: "tt1160419", | ||||
|  | ||||
							
								
								
									
										29
									
								
								api/src/models/examples/dune-collection.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								api/src/models/examples/dune-collection.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| import type { SeedCollection } from "~/models/collections"; | ||||
| 
 | ||||
| export const duneCollection: SeedCollection = { | ||||
| 	slug: "dune-collection", | ||||
| 	translations: { | ||||
| 		en: { | ||||
| 			name: " Dune Collection", | ||||
| 			tagline: "A mythic and emotionally charged hero's journey.", | ||||
| 			description: | ||||
| 				"The saga of Paul Atreides and his rise to power on the deadly planet Arrakis.", | ||||
| 			aliases: [], | ||||
| 			tags: ["sci-fi", "adventure", "drama", "action", "epic"], | ||||
| 			poster: | ||||
| 				"https://image.tmdb.org/t/p/original/wD57HqZ6fXwwDdfQLo4hXLRwGV1.jpg", | ||||
| 			thumbnail: | ||||
| 				"https://image.tmdb.org/t/p/original/k2ocXnNkmvE6rJomRkExIStFq3v.jpg", | ||||
| 			banner: null, | ||||
| 			logo: "https://image.tmdb.org/t/p/original/5nDsd3u1c6kDphbtIqkHseLg7HL.png", | ||||
| 		}, | ||||
| 	}, | ||||
| 	genres: ["adventure", "science-fiction"], | ||||
| 	rating: 80, | ||||
| 	externalId: { | ||||
| 		themoviedatabase: { | ||||
| 			dataId: "726871", | ||||
| 			link: "https://www.themoviedb.org/collection/726871-dune-collection", | ||||
| 		}, | ||||
| 	}, | ||||
| }; | ||||
| @ -32,3 +32,7 @@ export const registerExamples = <T extends TSchema>( | ||||
| 
 | ||||
| export * from "./bubble"; | ||||
| export * from "./made-in-abyss"; | ||||
| export * from "./dune-1984"; | ||||
| export * from "./dune-2021"; | ||||
| export * from "./dune-collection"; | ||||
| export * from "./others"; | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| import { t } from "elysia"; | ||||
| import type { Prettify } from "~/utils"; | ||||
| import { SeedCollection } from "./collections"; | ||||
| import { bubble, registerExamples } from "./examples"; | ||||
| import { bubbleImages } from "./examples/bubble"; | ||||
| import { | ||||
| @ -85,6 +86,7 @@ export const SeedMovie = t.Intersect([ | ||||
| 			]), | ||||
| 		), | ||||
| 		videos: t.Optional(t.Array(t.String({ format: "uuid" }))), | ||||
| 		collection: t.Optional(SeedCollection), | ||||
| 	}), | ||||
| ]); | ||||
| export type SeedMovie = Prettify<typeof SeedMovie.static>; | ||||
|  | ||||
| @ -1,4 +1,6 @@ | ||||
| import { t } from "elysia"; | ||||
| import type { Prettify } from "~/utils"; | ||||
| import { SeedCollection } from "./collections"; | ||||
| import { SeedEntry, SeedExtra } from "./entry"; | ||||
| import { bubbleImages, madeInAbyss, registerExamples } from "./examples"; | ||||
| import { SeedSeason } from "./season"; | ||||
| @ -57,7 +59,7 @@ export const SerieTranslation = t.Object({ | ||||
| export type SerieTranslation = typeof SerieTranslation.static; | ||||
| 
 | ||||
| export const Serie = t.Intersect([Resource(), SerieTranslation, BaseSerie]); | ||||
| export type Serie = typeof Serie.static; | ||||
| export type Serie = Prettify<typeof Serie.static>; | ||||
| 
 | ||||
| export const SeedSerie = t.Intersect([ | ||||
| 	t.Omit(BaseSerie, ["createdAt", "nextRefresh"]), | ||||
| @ -77,6 +79,7 @@ export const SeedSerie = t.Intersect([ | ||||
| 		seasons: t.Array(SeedSeason), | ||||
| 		entries: t.Array(SeedEntry), | ||||
| 		extras: t.Optional(t.Array(SeedExtra)), | ||||
| 		collection: t.Optional(SeedCollection), | ||||
| 	}), | ||||
| ]); | ||||
| export type SeedSerie = typeof SeedSerie.static; | ||||
|  | ||||
							
								
								
									
										24
									
								
								api/tests/collection/seed-collection.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								api/tests/collection/seed-collection.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| import { beforeAll, describe, expect, it } from "bun:test"; | ||||
| import { createMovie } from "tests/helpers"; | ||||
| import { expectStatus } from "tests/utils"; | ||||
| import { db } from "~/db"; | ||||
| import { shows } from "~/db/schema"; | ||||
| import { dune } from "~/models/examples/dune-2021"; | ||||
| import { duneCollection } from "~/models/examples/dune-collection"; | ||||
| 
 | ||||
| beforeAll(async () => { | ||||
| 	await db.delete(shows); | ||||
| }); | ||||
| 
 | ||||
| describe("Collection seeding", () => { | ||||
| 	it("Can create a movie with a collection", async () => { | ||||
| 		const [resp, body] = await createMovie({ | ||||
| 			...dune, | ||||
| 			collection: duneCollection, | ||||
| 		}); | ||||
| 		expectStatus(resp, body).toBe(201); | ||||
| 		expect(body.id).toBeString(); | ||||
| 		expect(body.slug).toBe("dune"); | ||||
| 		expect(body.collection.slug).toBe("dune-collection"); | ||||
| 	}); | ||||
| }); | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user