mirror of
				https://github.com/zoriya/Kyoo.git
				synced 2025-11-03 19:17:16 -05:00 
			
		
		
		
	Add studios (#824)
This commit is contained in:
		
						commit
						250c9c8ff9
					
				
							
								
								
									
										293
									
								
								api/README.md
									
									
									
									
									
								
							
							
						
						
									
										293
									
								
								api/README.md
									
									
									
									
									
								
							@ -6,159 +6,168 @@ The many-to-many relation between entries (episodes/movies) & videos is NOT a mi
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
```mermaid
 | 
					```mermaid
 | 
				
			||||||
erDiagram
 | 
					erDiagram
 | 
				
			||||||
    shows {
 | 
						shows {
 | 
				
			||||||
        guid id PK
 | 
							guid id PK
 | 
				
			||||||
        kind kind "serie|movie|collection"
 | 
							kind kind "serie|movie|collection"
 | 
				
			||||||
        string(128) slug UK
 | 
							string(128) slug UK
 | 
				
			||||||
        genre[] genres
 | 
							genre[] genres
 | 
				
			||||||
        int rating "From 0 to 100"
 | 
							int rating "From 0 to 100"
 | 
				
			||||||
        status status "NN"
 | 
							status status "NN"
 | 
				
			||||||
        datetime added_date
 | 
							datetime added_date
 | 
				
			||||||
        date start_air
 | 
							date start_air
 | 
				
			||||||
        date end_air "null for movies"
 | 
							date end_air "null for movies"
 | 
				
			||||||
        datetime next_refresh
 | 
							datetime next_refresh
 | 
				
			||||||
        jsonb external_id
 | 
							jsonb external_id
 | 
				
			||||||
        guid studio_id FK
 | 
							guid studio_id FK
 | 
				
			||||||
        string original_language
 | 
							string original_language
 | 
				
			||||||
		guid collection_id FK
 | 
							guid collection_id FK
 | 
				
			||||||
    }
 | 
						}
 | 
				
			||||||
    show_translations {
 | 
						show_translations {
 | 
				
			||||||
        guid id PK, FK
 | 
							guid id PK, FK
 | 
				
			||||||
        string language PK
 | 
							string language PK
 | 
				
			||||||
        string name "NN"
 | 
							string name "NN"
 | 
				
			||||||
        string tagline
 | 
							string tagline
 | 
				
			||||||
        string[] aliases
 | 
							string[] aliases
 | 
				
			||||||
        string description
 | 
							string description
 | 
				
			||||||
        string[] tags
 | 
							string[] tags
 | 
				
			||||||
        string trailerUrl
 | 
							string trailerUrl
 | 
				
			||||||
        jsonb poster
 | 
							jsonb poster
 | 
				
			||||||
        jsonb banner
 | 
							jsonb banner
 | 
				
			||||||
        jsonb logo
 | 
							jsonb logo
 | 
				
			||||||
        jsonb thumbnail
 | 
							jsonb thumbnail
 | 
				
			||||||
    }
 | 
						}
 | 
				
			||||||
    shows ||--|{ show_translations : has
 | 
						shows ||--|{ show_translations : has
 | 
				
			||||||
    shows |o--|| entries : has
 | 
						shows |o--|| entries : has
 | 
				
			||||||
    shows |o--|| shows : has_collection
 | 
						shows |o--|| shows : has_collection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    entries {
 | 
						entries {
 | 
				
			||||||
        guid id PK
 | 
							guid id PK
 | 
				
			||||||
        string(256) slug UK
 | 
							string(256) slug UK
 | 
				
			||||||
        guid show_id FK, UK
 | 
							guid show_id FK, UK
 | 
				
			||||||
        %% Order is absolute number.
 | 
							%% Order is absolute number.
 | 
				
			||||||
        uint order "NN"
 | 
							uint order "NN"
 | 
				
			||||||
        uint season_number UK
 | 
							uint season_number UK
 | 
				
			||||||
        uint episode_number UK "NN"
 | 
							uint episode_number UK "NN"
 | 
				
			||||||
        type type "episode|movie|special|extra"
 | 
							type type "episode|movie|special|extra"
 | 
				
			||||||
        date air_date
 | 
							date air_date
 | 
				
			||||||
        uint runtime
 | 
							uint runtime
 | 
				
			||||||
        jsonb thumbnail
 | 
							jsonb thumbnail
 | 
				
			||||||
        datetime next_refresh
 | 
							datetime next_refresh
 | 
				
			||||||
        jsonb external_id
 | 
							jsonb external_id
 | 
				
			||||||
    }
 | 
						}
 | 
				
			||||||
    entry_translations {
 | 
						entry_translations {
 | 
				
			||||||
        guid id PK, FK
 | 
							guid id PK, FK
 | 
				
			||||||
        string language PK
 | 
							string language PK
 | 
				
			||||||
        string name
 | 
							string name
 | 
				
			||||||
        string description
 | 
							string description
 | 
				
			||||||
    }
 | 
						}
 | 
				
			||||||
    entries ||--|{ entry_translations : has
 | 
						entries ||--|{ entry_translations : has
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    video {
 | 
						video {
 | 
				
			||||||
        guid id PK
 | 
							guid id PK
 | 
				
			||||||
        string path "NN"
 | 
							string path "NN"
 | 
				
			||||||
        uint rendering "dedup for duplicates part1/2"
 | 
							uint rendering "dedup for duplicates part1/2"
 | 
				
			||||||
        uint part
 | 
							uint part
 | 
				
			||||||
        uint version "max version is preferred rendering"
 | 
							uint version "max version is preferred rendering"
 | 
				
			||||||
    }
 | 
						}
 | 
				
			||||||
    video }|--|{ entries : for
 | 
						video }|--|{ entries : for
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    seasons {
 | 
						seasons {
 | 
				
			||||||
        guid id PK
 | 
							guid id PK
 | 
				
			||||||
        string(256) slug UK
 | 
							string(256) slug UK
 | 
				
			||||||
        guid show_id FK
 | 
							guid show_id FK
 | 
				
			||||||
        uint season_number "NN"
 | 
							uint season_number "NN"
 | 
				
			||||||
        datetime added_date
 | 
							datetime added_date
 | 
				
			||||||
        date start_air
 | 
							date start_air
 | 
				
			||||||
        date end_air
 | 
							date end_air
 | 
				
			||||||
        datetime next_refresh
 | 
							datetime next_refresh
 | 
				
			||||||
        jsonb external_id
 | 
							jsonb external_id
 | 
				
			||||||
    }
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    season_translations {
 | 
						season_translations {
 | 
				
			||||||
        guid id PK,FK
 | 
							guid id PK,FK
 | 
				
			||||||
        string language PK
 | 
							string language PK
 | 
				
			||||||
        string name
 | 
							string name
 | 
				
			||||||
        string description
 | 
							string description
 | 
				
			||||||
        jsonb poster
 | 
							jsonb poster
 | 
				
			||||||
        jsonb banner
 | 
							jsonb banner
 | 
				
			||||||
        jsonb logo
 | 
							jsonb logo
 | 
				
			||||||
        jsonb thumbnail
 | 
							jsonb thumbnail
 | 
				
			||||||
    }
 | 
						}
 | 
				
			||||||
    seasons ||--|{ season_translations : has
 | 
						seasons ||--|{ season_translations : has
 | 
				
			||||||
    seasons ||--o{ entries : has
 | 
						seasons ||--o{ entries : has
 | 
				
			||||||
    shows ||--|{ seasons : has
 | 
						shows ||--|{ seasons : has
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    watched_shows {
 | 
						users {
 | 
				
			||||||
        guid show_id PK, FK
 | 
							guid id PK
 | 
				
			||||||
        guid user_id PK, FK
 | 
						}
 | 
				
			||||||
        status status "completed|watching|droped|planned"
 | 
					 | 
				
			||||||
        uint seen_entry_count "NN"
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    shows ||--|{ watched_shows : has
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    watched_entries {
 | 
						watched_shows {
 | 
				
			||||||
        guid entry_id PK, FK
 | 
							guid show_id PK, FK
 | 
				
			||||||
        guid user_id PK, FK
 | 
							guid user_id PK, FK
 | 
				
			||||||
        uint time "in seconds, null of finished"
 | 
							status status "completed|watching|dropped|planned"
 | 
				
			||||||
        uint progress "NN, from 0 to 100"
 | 
							uint seen_entry_count "NN"
 | 
				
			||||||
        datetime played_date
 | 
							guid next_entry FK
 | 
				
			||||||
    }
 | 
						}
 | 
				
			||||||
    entries ||--|{ watched_entries : has
 | 
						shows ||--|{ watched_shows : has
 | 
				
			||||||
 | 
						users ||--|{ watched_shows : has
 | 
				
			||||||
 | 
						watched_shows ||--|o entries : next_entry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    roles {
 | 
						history {
 | 
				
			||||||
        guid show_id PK, FK
 | 
							int id PK
 | 
				
			||||||
        guid staff_id PK, FK
 | 
							guid entry_id FK
 | 
				
			||||||
        uint order
 | 
							guid user_id FK
 | 
				
			||||||
        type type "actor|director|writer|producer|music|other"
 | 
							uint time "in seconds, null of finished"
 | 
				
			||||||
        jsonb character_image
 | 
							uint progress "NN, from 0 to 100"
 | 
				
			||||||
    }
 | 
							datetime played_date
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						entries ||--|{ history : part_of
 | 
				
			||||||
 | 
						users ||--|{ history : has
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    role_translations {
 | 
						roles {
 | 
				
			||||||
        string language PK
 | 
							guid show_id PK, FK
 | 
				
			||||||
        string character_name
 | 
							guid staff_id PK, FK
 | 
				
			||||||
    }
 | 
							uint order
 | 
				
			||||||
    roles||--o{ role_translations : has
 | 
							type type "actor|director|writer|producer|music|other"
 | 
				
			||||||
    shows ||--|{ roles : has
 | 
							jsonb character_image
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    staff {
 | 
						role_translations {
 | 
				
			||||||
        guid id PK
 | 
							string language PK
 | 
				
			||||||
        string(256) slug UK
 | 
							string character_name
 | 
				
			||||||
        jsonb image
 | 
						}
 | 
				
			||||||
        datetime next_refresh
 | 
						roles||--o{ role_translations : has
 | 
				
			||||||
        jsonb external_id
 | 
						shows ||--|{ roles : has
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    staff_translations {
 | 
						staff {
 | 
				
			||||||
        guid id PK,FK
 | 
							guid id PK
 | 
				
			||||||
        string language PK
 | 
							string(256) slug UK
 | 
				
			||||||
        string name "NN"
 | 
							jsonb image
 | 
				
			||||||
    }
 | 
							datetime next_refresh
 | 
				
			||||||
    staff ||--|{ staff_translations : has
 | 
							jsonb external_id
 | 
				
			||||||
    staff ||--|{ roles : has
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    studios {
 | 
						staff_translations {
 | 
				
			||||||
      guid id PK
 | 
							guid id PK,FK
 | 
				
			||||||
      string(128) slug UK
 | 
							string language PK
 | 
				
			||||||
      jsonb logo
 | 
							string name "NN"
 | 
				
			||||||
      datetime next_refresh
 | 
						}
 | 
				
			||||||
      jsonb external_id
 | 
						staff ||--|{ staff_translations : has
 | 
				
			||||||
    }
 | 
						staff ||--|{ roles : has
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    studio_translations {
 | 
						studios {
 | 
				
			||||||
      guid id PK,FK
 | 
							guid id PK
 | 
				
			||||||
      string language PK
 | 
							string(128) slug UK
 | 
				
			||||||
      string name
 | 
							jsonb logo
 | 
				
			||||||
    }
 | 
							datetime next_refresh
 | 
				
			||||||
    studios ||--|{ studio_translations : has
 | 
							jsonb external_id
 | 
				
			||||||
    shows ||--|{ studios : has
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						studio_translations {
 | 
				
			||||||
 | 
							guid id PK,FK
 | 
				
			||||||
 | 
							string language PK
 | 
				
			||||||
 | 
							string name
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						studios ||--|{ studio_translations : has
 | 
				
			||||||
 | 
						shows }|--|{ studios : has
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										38
									
								
								api/drizzle/0010_studios.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								api/drizzle/0010_studios.sql
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					CREATE TABLE "kyoo"."show_studio_join" (
 | 
				
			||||||
 | 
						"show" integer NOT NULL,
 | 
				
			||||||
 | 
						"studio" integer NOT NULL,
 | 
				
			||||||
 | 
						CONSTRAINT "show_studio_join_show_studio_pk" PRIMARY KEY("show","studio")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					--> statement-breakpoint
 | 
				
			||||||
 | 
					CREATE TABLE "kyoo"."studio_translations" (
 | 
				
			||||||
 | 
						"pk" integer NOT NULL,
 | 
				
			||||||
 | 
						"language" varchar(255) NOT NULL,
 | 
				
			||||||
 | 
						"name" text NOT NULL,
 | 
				
			||||||
 | 
						"logo" jsonb,
 | 
				
			||||||
 | 
						CONSTRAINT "studio_translations_pk_language_pk" PRIMARY KEY("pk","language")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					--> statement-breakpoint
 | 
				
			||||||
 | 
					CREATE TABLE "kyoo"."studios" (
 | 
				
			||||||
 | 
						"pk" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "kyoo"."studios_pk_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
 | 
				
			||||||
 | 
						"id" uuid DEFAULT gen_random_uuid() NOT NULL,
 | 
				
			||||||
 | 
						"slug" varchar(255) NOT NULL,
 | 
				
			||||||
 | 
						"external_id" jsonb DEFAULT '{}'::jsonb NOT NULL,
 | 
				
			||||||
 | 
						"created_at" timestamp with time zone DEFAULT now() NOT NULL,
 | 
				
			||||||
 | 
						"updated_at" timestamp with time zone NOT NULL,
 | 
				
			||||||
 | 
						CONSTRAINT "studios_id_unique" UNIQUE("id"),
 | 
				
			||||||
 | 
						CONSTRAINT "studios_slug_unique" UNIQUE("slug")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					--> statement-breakpoint
 | 
				
			||||||
 | 
					ALTER TABLE "kyoo"."entries" ADD COLUMN "updated_at" timestamp with time zone NOT NULL;--> statement-breakpoint
 | 
				
			||||||
 | 
					ALTER TABLE "kyoo"."seasons" ADD COLUMN "updated_at" timestamp with time zone NOT NULL;--> statement-breakpoint
 | 
				
			||||||
 | 
					ALTER TABLE "kyoo"."shows" ADD COLUMN "updated_at" timestamp with time zone NOT NULL;--> statement-breakpoint
 | 
				
			||||||
 | 
					ALTER TABLE "kyoo"."videos" ADD COLUMN "updated_at" timestamp with time zone NOT NULL;--> statement-breakpoint
 | 
				
			||||||
 | 
					ALTER TABLE "kyoo"."show_studio_join" ADD CONSTRAINT "show_studio_join_show_shows_pk_fk" FOREIGN KEY ("show") REFERENCES "kyoo"."shows"("pk") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
 | 
				
			||||||
 | 
					ALTER TABLE "kyoo"."show_studio_join" ADD CONSTRAINT "show_studio_join_studio_studios_pk_fk" FOREIGN KEY ("studio") REFERENCES "kyoo"."studios"("pk") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
 | 
				
			||||||
 | 
					ALTER TABLE "kyoo"."studio_translations" ADD CONSTRAINT "studio_translations_pk_studios_pk_fk" FOREIGN KEY ("pk") REFERENCES "kyoo"."studios"("pk") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
 | 
				
			||||||
 | 
					CREATE INDEX "studio_name_trgm" ON "kyoo"."studio_translations" USING gin ("name" gin_trgm_ops);--> statement-breakpoint
 | 
				
			||||||
 | 
					CREATE INDEX "entry_kind" ON "kyoo"."entries" USING hash ("kind");--> statement-breakpoint
 | 
				
			||||||
 | 
					CREATE INDEX "entry_order" ON "kyoo"."entries" USING btree ("order");--> statement-breakpoint
 | 
				
			||||||
 | 
					CREATE INDEX "entry_name_trgm" ON "kyoo"."entry_translations" USING gin ("name" gin_trgm_ops);--> statement-breakpoint
 | 
				
			||||||
 | 
					CREATE INDEX "season_name_trgm" ON "kyoo"."season_translations" USING gin ("name" gin_trgm_ops);--> statement-breakpoint
 | 
				
			||||||
 | 
					CREATE INDEX "season_nbr" ON "kyoo"."seasons" USING btree ("season_number");
 | 
				
			||||||
							
								
								
									
										20
									
								
								api/drizzle/0011_join_rename.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								api/drizzle/0011_join_rename.sql
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					ALTER TABLE "kyoo"."show_studio_join" RENAME COLUMN "show" TO "show_pk";--> statement-breakpoint
 | 
				
			||||||
 | 
					ALTER TABLE "kyoo"."show_studio_join" RENAME COLUMN "studio" TO "studio_pk";--> statement-breakpoint
 | 
				
			||||||
 | 
					ALTER TABLE "kyoo"."entry_video_join" RENAME COLUMN "entry" TO "entry_pk";--> statement-breakpoint
 | 
				
			||||||
 | 
					ALTER TABLE "kyoo"."entry_video_join" RENAME COLUMN "video" TO "video_pk";--> statement-breakpoint
 | 
				
			||||||
 | 
					ALTER TABLE "kyoo"."show_studio_join" DROP CONSTRAINT "show_studio_join_show_shows_pk_fk";
 | 
				
			||||||
 | 
					--> statement-breakpoint
 | 
				
			||||||
 | 
					ALTER TABLE "kyoo"."show_studio_join" DROP CONSTRAINT "show_studio_join_studio_studios_pk_fk";
 | 
				
			||||||
 | 
					--> statement-breakpoint
 | 
				
			||||||
 | 
					ALTER TABLE "kyoo"."entry_video_join" DROP CONSTRAINT "entry_video_join_entry_entries_pk_fk";
 | 
				
			||||||
 | 
					--> statement-breakpoint
 | 
				
			||||||
 | 
					ALTER TABLE "kyoo"."entry_video_join" DROP CONSTRAINT "entry_video_join_video_videos_pk_fk";
 | 
				
			||||||
 | 
					--> statement-breakpoint
 | 
				
			||||||
 | 
					ALTER TABLE "kyoo"."show_studio_join" DROP CONSTRAINT "show_studio_join_show_studio_pk";--> statement-breakpoint
 | 
				
			||||||
 | 
					ALTER TABLE "kyoo"."entry_video_join" DROP CONSTRAINT "entry_video_join_entry_video_pk";--> statement-breakpoint
 | 
				
			||||||
 | 
					ALTER TABLE "kyoo"."show_studio_join" ADD CONSTRAINT "show_studio_join_show_pk_studio_pk_pk" PRIMARY KEY("show_pk","studio_pk");--> statement-breakpoint
 | 
				
			||||||
 | 
					ALTER TABLE "kyoo"."entry_video_join" ADD CONSTRAINT "entry_video_join_entry_pk_video_pk_pk" PRIMARY KEY("entry_pk","video_pk");--> statement-breakpoint
 | 
				
			||||||
 | 
					ALTER TABLE "kyoo"."show_studio_join" ADD CONSTRAINT "show_studio_join_show_pk_shows_pk_fk" FOREIGN KEY ("show_pk") REFERENCES "kyoo"."shows"("pk") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
 | 
				
			||||||
 | 
					ALTER TABLE "kyoo"."show_studio_join" ADD CONSTRAINT "show_studio_join_studio_pk_studios_pk_fk" FOREIGN KEY ("studio_pk") REFERENCES "kyoo"."studios"("pk") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
 | 
				
			||||||
 | 
					ALTER TABLE "kyoo"."entry_video_join" ADD CONSTRAINT "entry_video_join_entry_pk_entries_pk_fk" FOREIGN KEY ("entry_pk") REFERENCES "kyoo"."entries"("pk") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
 | 
				
			||||||
 | 
					ALTER TABLE "kyoo"."entry_video_join" ADD CONSTRAINT "entry_video_join_video_pk_videos_pk_fk" FOREIGN KEY ("video_pk") REFERENCES "kyoo"."videos"("pk") ON DELETE cascade ON UPDATE no action;
 | 
				
			||||||
							
								
								
									
										1265
									
								
								api/drizzle/meta/0010_snapshot.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1265
									
								
								api/drizzle/meta/0010_snapshot.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1265
									
								
								api/drizzle/meta/0011_snapshot.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1265
									
								
								api/drizzle/meta/0011_snapshot.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -71,6 +71,20 @@
 | 
				
			|||||||
			"when": 1740872363604,
 | 
								"when": 1740872363604,
 | 
				
			||||||
			"tag": "0009_collections",
 | 
								"tag": "0009_collections",
 | 
				
			||||||
			"breakpoints": true
 | 
								"breakpoints": true
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"idx": 10,
 | 
				
			||||||
 | 
								"version": "7",
 | 
				
			||||||
 | 
								"when": 1740950531468,
 | 
				
			||||||
 | 
								"tag": "0010_studios",
 | 
				
			||||||
 | 
								"breakpoints": true
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"idx": 11,
 | 
				
			||||||
 | 
								"version": "7",
 | 
				
			||||||
 | 
								"when": 1741014917375,
 | 
				
			||||||
 | 
								"tag": "0011_join_rename",
 | 
				
			||||||
 | 
								"breakpoints": true
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	]
 | 
						]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -141,8 +141,8 @@ export const insertEntries = async (
 | 
				
			|||||||
		.select(
 | 
							.select(
 | 
				
			||||||
			db
 | 
								db
 | 
				
			||||||
				.select({
 | 
									.select({
 | 
				
			||||||
					entry: sql<number>`vids.entryPk::integer`.as("entry"),
 | 
										entryPk: sql<number>`vids.entryPk::integer`.as("entry"),
 | 
				
			||||||
					video: sql`${videos.pk}`.as("video"),
 | 
										videoPk: sql`${videos.pk}`.as("video"),
 | 
				
			||||||
					slug: computeVideoSlug(
 | 
										slug: computeVideoSlug(
 | 
				
			||||||
						sql`${show.slug}::text`,
 | 
											sql`${show.slug}::text`,
 | 
				
			||||||
						sql`vids.needRendering::boolean`,
 | 
											sql`vids.needRendering::boolean`,
 | 
				
			||||||
@ -154,7 +154,7 @@ export const insertEntries = async (
 | 
				
			|||||||
		.onConflictDoNothing()
 | 
							.onConflictDoNothing()
 | 
				
			||||||
		.returning({
 | 
							.returning({
 | 
				
			||||||
			slug: entryVideoJoin.slug,
 | 
								slug: entryVideoJoin.slug,
 | 
				
			||||||
			entryPk: entryVideoJoin.entry,
 | 
								entryPk: entryVideoJoin.entryPk,
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return retEntries.map((entry) => ({
 | 
						return retEntries.map((entry) => ({
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										55
									
								
								api/src/controllers/seed/insert/studios.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								api/src/controllers/seed/insert/studios.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					import { db } from "~/db";
 | 
				
			||||||
 | 
					import { showStudioJoin, studioTranslations, studios } from "~/db/schema";
 | 
				
			||||||
 | 
					import { conflictUpdateAllExcept } from "~/db/utils";
 | 
				
			||||||
 | 
					import type { SeedStudio } from "~/models/studio";
 | 
				
			||||||
 | 
					import { processOptImage } from "../images";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type StudioI = typeof studios.$inferInsert;
 | 
				
			||||||
 | 
					type StudioTransI = typeof studioTranslations.$inferInsert;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const insertStudios = async (seed: SeedStudio[], showPk: number) => {
 | 
				
			||||||
 | 
						if (!seed.length) return [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return await db.transaction(async (tx) => {
 | 
				
			||||||
 | 
							const vals: StudioI[] = seed.map((x) => {
 | 
				
			||||||
 | 
								const { translations, ...item } = x;
 | 
				
			||||||
 | 
								return item;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const ret = await tx
 | 
				
			||||||
 | 
								.insert(studios)
 | 
				
			||||||
 | 
								.values(vals)
 | 
				
			||||||
 | 
								.onConflictDoUpdate({
 | 
				
			||||||
 | 
									target: studios.slug,
 | 
				
			||||||
 | 
									set: conflictUpdateAllExcept(studios, [
 | 
				
			||||||
 | 
										"pk",
 | 
				
			||||||
 | 
										"id",
 | 
				
			||||||
 | 
										"slug",
 | 
				
			||||||
 | 
										"createdAt",
 | 
				
			||||||
 | 
									]),
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								.returning({ pk: studios.pk, id: studios.id, slug: studios.slug });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const trans: StudioTransI[] = seed.flatMap((x, i) =>
 | 
				
			||||||
 | 
								Object.entries(x.translations).map(([lang, tr]) => ({
 | 
				
			||||||
 | 
									pk: ret[i].pk,
 | 
				
			||||||
 | 
									language: lang,
 | 
				
			||||||
 | 
									name: tr.name,
 | 
				
			||||||
 | 
									logo: processOptImage(tr.logo),
 | 
				
			||||||
 | 
								})),
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
							await tx
 | 
				
			||||||
 | 
								.insert(studioTranslations)
 | 
				
			||||||
 | 
								.values(trans)
 | 
				
			||||||
 | 
								.onConflictDoUpdate({
 | 
				
			||||||
 | 
									target: [studioTranslations.pk, studioTranslations.language],
 | 
				
			||||||
 | 
									set: conflictUpdateAllExcept(studioTranslations, ["pk", "language"]),
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							await tx
 | 
				
			||||||
 | 
								.insert(showStudioJoin)
 | 
				
			||||||
 | 
								.values(ret.map((studio) => ({ showPk: showPk, studioPk: studio.pk })))
 | 
				
			||||||
 | 
								.onConflictDoNothing();
 | 
				
			||||||
 | 
							return ret;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -4,6 +4,7 @@ import { getYear } from "~/utils";
 | 
				
			|||||||
import { insertCollection } from "./insert/collection";
 | 
					import { insertCollection } from "./insert/collection";
 | 
				
			||||||
import { insertEntries } from "./insert/entries";
 | 
					import { insertEntries } from "./insert/entries";
 | 
				
			||||||
import { insertShow } from "./insert/shows";
 | 
					import { insertShow } from "./insert/shows";
 | 
				
			||||||
 | 
					import { insertStudios } from "./insert/studios";
 | 
				
			||||||
import { guessNextRefresh } from "./refresh";
 | 
					import { guessNextRefresh } from "./refresh";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const SeedMovieResponse = t.Object({
 | 
					export const SeedMovieResponse = t.Object({
 | 
				
			||||||
@ -18,6 +19,12 @@ export const SeedMovieResponse = t.Object({
 | 
				
			|||||||
			slug: t.String({ format: "slug", examples: ["sawano-collection"] }),
 | 
								slug: t.String({ format: "slug", examples: ["sawano-collection"] }),
 | 
				
			||||||
		}),
 | 
							}),
 | 
				
			||||||
	),
 | 
						),
 | 
				
			||||||
 | 
						studios: t.Array(
 | 
				
			||||||
 | 
							t.Object({
 | 
				
			||||||
 | 
								id: t.String({ format: "uuid" }),
 | 
				
			||||||
 | 
								slug: t.String({ format: "slug", examples: ["disney"] }),
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
						),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
export type SeedMovieResponse = typeof SeedMovieResponse.static;
 | 
					export type SeedMovieResponse = typeof SeedMovieResponse.static;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -38,7 +45,7 @@ export const seedMovie = async (
 | 
				
			|||||||
		seed.slug = `random-${getYear(seed.airDate)}`;
 | 
							seed.slug = `random-${getYear(seed.airDate)}`;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const { translations, videos, collection, ...bMovie } = seed;
 | 
						const { translations, videos, collection, studios, ...bMovie } = seed;
 | 
				
			||||||
	const nextRefresh = guessNextRefresh(bMovie.airDate ?? new Date());
 | 
						const nextRefresh = guessNextRefresh(bMovie.airDate ?? new Date());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const col = await insertCollection(collection, {
 | 
						const col = await insertCollection(collection, {
 | 
				
			||||||
@ -74,11 +81,14 @@ export const seedMovie = async (
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
	]);
 | 
						]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const retStudios = await insertStudios(studios, show.pk);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return {
 | 
						return {
 | 
				
			||||||
		updated: show.updated,
 | 
							updated: show.updated,
 | 
				
			||||||
		id: show.id,
 | 
							id: show.id,
 | 
				
			||||||
		slug: show.slug,
 | 
							slug: show.slug,
 | 
				
			||||||
		videos: entry.videos,
 | 
							videos: entry.videos,
 | 
				
			||||||
		collection: col,
 | 
							collection: col,
 | 
				
			||||||
 | 
							studios: retStudios,
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,7 @@ import { insertCollection } from "./insert/collection";
 | 
				
			|||||||
import { insertEntries } from "./insert/entries";
 | 
					import { insertEntries } from "./insert/entries";
 | 
				
			||||||
import { insertSeasons } from "./insert/seasons";
 | 
					import { insertSeasons } from "./insert/seasons";
 | 
				
			||||||
import { insertShow } from "./insert/shows";
 | 
					import { insertShow } from "./insert/shows";
 | 
				
			||||||
 | 
					import { insertStudios } from "./insert/studios";
 | 
				
			||||||
import { guessNextRefresh } from "./refresh";
 | 
					import { guessNextRefresh } from "./refresh";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const SeedSerieResponse = t.Object({
 | 
					export const SeedSerieResponse = t.Object({
 | 
				
			||||||
@ -45,6 +46,12 @@ export const SeedSerieResponse = t.Object({
 | 
				
			|||||||
			}),
 | 
								}),
 | 
				
			||||||
		}),
 | 
							}),
 | 
				
			||||||
	),
 | 
						),
 | 
				
			||||||
 | 
						studios: t.Array(
 | 
				
			||||||
 | 
							t.Object({
 | 
				
			||||||
 | 
								id: t.String({ format: "uuid" }),
 | 
				
			||||||
 | 
								slug: t.String({ format: "slug", examples: ["mappa"] }),
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
						),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
export type SeedSerieResponse = typeof SeedSerieResponse.static;
 | 
					export type SeedSerieResponse = typeof SeedSerieResponse.static;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -65,7 +72,15 @@ export const seedSerie = async (
 | 
				
			|||||||
		seed.slug = `random-${getYear(seed.startAir)}`;
 | 
							seed.slug = `random-${getYear(seed.startAir)}`;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const { translations, seasons, entries, extras, collection, ...serie } = seed;
 | 
						const {
 | 
				
			||||||
 | 
							translations,
 | 
				
			||||||
 | 
							seasons,
 | 
				
			||||||
 | 
							entries,
 | 
				
			||||||
 | 
							extras,
 | 
				
			||||||
 | 
							collection,
 | 
				
			||||||
 | 
							studios,
 | 
				
			||||||
 | 
							...serie
 | 
				
			||||||
 | 
						} = seed;
 | 
				
			||||||
	const nextRefresh = guessNextRefresh(serie.startAir ?? new Date());
 | 
						const nextRefresh = guessNextRefresh(serie.startAir ?? new Date());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const col = await insertCollection(collection, {
 | 
						const col = await insertCollection(collection, {
 | 
				
			||||||
@ -92,6 +107,8 @@ export const seedSerie = async (
 | 
				
			|||||||
		(extras ?? []).map((x) => ({ ...x, kind: "extra", extraKind: x.kind })),
 | 
							(extras ?? []).map((x) => ({ ...x, kind: "extra", extraKind: x.kind })),
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const retStudios = await insertStudios(studios, show.pk);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return {
 | 
						return {
 | 
				
			||||||
		updated: show.updated,
 | 
							updated: show.updated,
 | 
				
			||||||
		id: show.id,
 | 
							id: show.id,
 | 
				
			||||||
@ -100,5 +117,6 @@ export const seedSerie = async (
 | 
				
			|||||||
		entries: retEntries,
 | 
							entries: retEntries,
 | 
				
			||||||
		extras: retExtras,
 | 
							extras: retExtras,
 | 
				
			||||||
		collection: col,
 | 
							collection: col,
 | 
				
			||||||
 | 
							studios: retStudios,
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +0,0 @@
 | 
				
			|||||||
import { Elysia, t } from "elysia";
 | 
					 | 
				
			||||||
import { Serie } from "~/models/serie";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const series = new Elysia({ prefix: "/series" })
 | 
					 | 
				
			||||||
	.model({
 | 
					 | 
				
			||||||
		serie: Serie,
 | 
					 | 
				
			||||||
		error: t.Object({}),
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
	.get("/:id", () => "hello" as unknown as Serie, {
 | 
					 | 
				
			||||||
		response: { 200: "serie" },
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
@ -10,6 +10,8 @@ import {
 | 
				
			|||||||
import { KError } from "~/models/error";
 | 
					import { KError } from "~/models/error";
 | 
				
			||||||
import { duneCollection } from "~/models/examples";
 | 
					import { duneCollection } from "~/models/examples";
 | 
				
			||||||
import { Movie } from "~/models/movie";
 | 
					import { Movie } from "~/models/movie";
 | 
				
			||||||
 | 
					import { Serie } from "~/models/serie";
 | 
				
			||||||
 | 
					import { Show } from "~/models/show";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
	AcceptLanguage,
 | 
						AcceptLanguage,
 | 
				
			||||||
	Filter,
 | 
						Filter,
 | 
				
			||||||
@ -171,6 +173,34 @@ export const collections = new Elysia({
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
						.guard({
 | 
				
			||||||
 | 
							params: t.Object({
 | 
				
			||||||
 | 
								id: t.String({
 | 
				
			||||||
 | 
									description: "The id or slug of the collection.",
 | 
				
			||||||
 | 
									example: duneCollection.slug,
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
							query: t.Object({
 | 
				
			||||||
 | 
								sort: showSort,
 | 
				
			||||||
 | 
								filter: t.Optional(Filter({ def: showFilters })),
 | 
				
			||||||
 | 
								query: t.Optional(t.String({ description: desc.query })),
 | 
				
			||||||
 | 
								limit: t.Integer({
 | 
				
			||||||
 | 
									minimum: 1,
 | 
				
			||||||
 | 
									maximum: 250,
 | 
				
			||||||
 | 
									default: 50,
 | 
				
			||||||
 | 
									description: "Max page size.",
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
								after: t.Optional(t.String({ description: desc.after })),
 | 
				
			||||||
 | 
								preferOriginal: t.Optional(
 | 
				
			||||||
 | 
									t.Boolean({
 | 
				
			||||||
 | 
										description: desc.preferOriginal,
 | 
				
			||||||
 | 
									}),
 | 
				
			||||||
 | 
								),
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
							headers: t.Object({
 | 
				
			||||||
 | 
								"accept-language": AcceptLanguage({ autoFallback: true }),
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
	.get(
 | 
						.get(
 | 
				
			||||||
		"/:id/movies",
 | 
							"/:id/movies",
 | 
				
			||||||
		async ({
 | 
							async ({
 | 
				
			||||||
@ -216,32 +246,6 @@ export const collections = new Elysia({
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			detail: { description: "Get all movies in a collection" },
 | 
								detail: { description: "Get all movies in a collection" },
 | 
				
			||||||
			params: t.Object({
 | 
					 | 
				
			||||||
				id: t.String({
 | 
					 | 
				
			||||||
					description: "The id or slug of the collection.",
 | 
					 | 
				
			||||||
					example: duneCollection.slug,
 | 
					 | 
				
			||||||
				}),
 | 
					 | 
				
			||||||
			}),
 | 
					 | 
				
			||||||
			query: t.Object({
 | 
					 | 
				
			||||||
				sort: showSort,
 | 
					 | 
				
			||||||
				filter: t.Optional(Filter({ def: showFilters })),
 | 
					 | 
				
			||||||
				query: t.Optional(t.String({ description: desc.query })),
 | 
					 | 
				
			||||||
				limit: t.Integer({
 | 
					 | 
				
			||||||
					minimum: 1,
 | 
					 | 
				
			||||||
					maximum: 250,
 | 
					 | 
				
			||||||
					default: 50,
 | 
					 | 
				
			||||||
					description: "Max page size.",
 | 
					 | 
				
			||||||
				}),
 | 
					 | 
				
			||||||
				after: t.Optional(t.String({ description: desc.after })),
 | 
					 | 
				
			||||||
				preferOriginal: t.Optional(
 | 
					 | 
				
			||||||
					t.Boolean({
 | 
					 | 
				
			||||||
						description: desc.preferOriginal,
 | 
					 | 
				
			||||||
					}),
 | 
					 | 
				
			||||||
				),
 | 
					 | 
				
			||||||
			}),
 | 
					 | 
				
			||||||
			headers: t.Object({
 | 
					 | 
				
			||||||
				"accept-language": AcceptLanguage({ autoFallback: true }),
 | 
					 | 
				
			||||||
			}),
 | 
					 | 
				
			||||||
			response: {
 | 
								response: {
 | 
				
			||||||
				200: Page(Movie),
 | 
									200: Page(Movie),
 | 
				
			||||||
				404: {
 | 
									404: {
 | 
				
			||||||
@ -297,34 +301,8 @@ export const collections = new Elysia({
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			detail: { description: "Get all series in a collection" },
 | 
								detail: { description: "Get all series in a collection" },
 | 
				
			||||||
			params: t.Object({
 | 
					 | 
				
			||||||
				id: t.String({
 | 
					 | 
				
			||||||
					description: "The id or slug of the collection.",
 | 
					 | 
				
			||||||
					example: duneCollection.slug,
 | 
					 | 
				
			||||||
				}),
 | 
					 | 
				
			||||||
			}),
 | 
					 | 
				
			||||||
			query: t.Object({
 | 
					 | 
				
			||||||
				sort: showSort,
 | 
					 | 
				
			||||||
				filter: t.Optional(Filter({ def: showFilters })),
 | 
					 | 
				
			||||||
				query: t.Optional(t.String({ description: desc.query })),
 | 
					 | 
				
			||||||
				limit: t.Integer({
 | 
					 | 
				
			||||||
					minimum: 1,
 | 
					 | 
				
			||||||
					maximum: 250,
 | 
					 | 
				
			||||||
					default: 50,
 | 
					 | 
				
			||||||
					description: "Max page size.",
 | 
					 | 
				
			||||||
				}),
 | 
					 | 
				
			||||||
				after: t.Optional(t.String({ description: desc.after })),
 | 
					 | 
				
			||||||
				preferOriginal: t.Optional(
 | 
					 | 
				
			||||||
					t.Boolean({
 | 
					 | 
				
			||||||
						description: desc.preferOriginal,
 | 
					 | 
				
			||||||
					}),
 | 
					 | 
				
			||||||
				),
 | 
					 | 
				
			||||||
			}),
 | 
					 | 
				
			||||||
			headers: t.Object({
 | 
					 | 
				
			||||||
				"accept-language": AcceptLanguage({ autoFallback: true }),
 | 
					 | 
				
			||||||
			}),
 | 
					 | 
				
			||||||
			response: {
 | 
								response: {
 | 
				
			||||||
				200: Page(Movie),
 | 
									200: Page(Serie),
 | 
				
			||||||
				404: {
 | 
									404: {
 | 
				
			||||||
					...KError,
 | 
										...KError,
 | 
				
			||||||
					description: "No collection found with the given id or slug.",
 | 
										description: "No collection found with the given id or slug.",
 | 
				
			||||||
@ -374,34 +352,8 @@ export const collections = new Elysia({
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			detail: { description: "Get all series & movies in a collection" },
 | 
								detail: { description: "Get all series & movies in a collection" },
 | 
				
			||||||
			params: t.Object({
 | 
					 | 
				
			||||||
				id: t.String({
 | 
					 | 
				
			||||||
					description: "The id or slug of the collection.",
 | 
					 | 
				
			||||||
					example: duneCollection.slug,
 | 
					 | 
				
			||||||
				}),
 | 
					 | 
				
			||||||
			}),
 | 
					 | 
				
			||||||
			query: t.Object({
 | 
					 | 
				
			||||||
				sort: showSort,
 | 
					 | 
				
			||||||
				filter: t.Optional(Filter({ def: showFilters })),
 | 
					 | 
				
			||||||
				query: t.Optional(t.String({ description: desc.query })),
 | 
					 | 
				
			||||||
				limit: t.Integer({
 | 
					 | 
				
			||||||
					minimum: 1,
 | 
					 | 
				
			||||||
					maximum: 250,
 | 
					 | 
				
			||||||
					default: 50,
 | 
					 | 
				
			||||||
					description: "Max page size.",
 | 
					 | 
				
			||||||
				}),
 | 
					 | 
				
			||||||
				after: t.Optional(t.String({ description: desc.after })),
 | 
					 | 
				
			||||||
				preferOriginal: t.Optional(
 | 
					 | 
				
			||||||
					t.Boolean({
 | 
					 | 
				
			||||||
						description: desc.preferOriginal,
 | 
					 | 
				
			||||||
					}),
 | 
					 | 
				
			||||||
				),
 | 
					 | 
				
			||||||
			}),
 | 
					 | 
				
			||||||
			headers: t.Object({
 | 
					 | 
				
			||||||
				"accept-language": AcceptLanguage({ autoFallback: true }),
 | 
					 | 
				
			||||||
			}),
 | 
					 | 
				
			||||||
			response: {
 | 
								response: {
 | 
				
			||||||
				200: Page(Movie),
 | 
									200: Page(Show),
 | 
				
			||||||
				404: {
 | 
									404: {
 | 
				
			||||||
					...KError,
 | 
										...KError,
 | 
				
			||||||
					description: "No collection found with the given id or slug.",
 | 
										description: "No collection found with the given id or slug.",
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import type { StaticDecode } from "@sinclair/typebox";
 | 
					import type { StaticDecode } from "@sinclair/typebox";
 | 
				
			||||||
import { type SQL, and, eq, sql } from "drizzle-orm";
 | 
					import { type SQL, and, eq, sql } from "drizzle-orm";
 | 
				
			||||||
import { db } from "~/db";
 | 
					import { db } from "~/db";
 | 
				
			||||||
import { showTranslations, shows } from "~/db/schema";
 | 
					import { showTranslations, shows, studioTranslations } from "~/db/schema";
 | 
				
			||||||
import { getColumns, sqlarr } from "~/db/utils";
 | 
					import { getColumns, sqlarr } from "~/db/utils";
 | 
				
			||||||
import type { MovieStatus } from "~/models/movie";
 | 
					import type { MovieStatus } from "~/models/movie";
 | 
				
			||||||
import { SerieStatus } from "~/models/serie";
 | 
					import { SerieStatus } from "~/models/serie";
 | 
				
			||||||
@ -12,6 +12,7 @@ import {
 | 
				
			|||||||
	Sort,
 | 
						Sort,
 | 
				
			||||||
	isUuid,
 | 
						isUuid,
 | 
				
			||||||
	keysetPaginate,
 | 
						keysetPaginate,
 | 
				
			||||||
 | 
						selectTranslationQuery,
 | 
				
			||||||
	sortToSql,
 | 
						sortToSql,
 | 
				
			||||||
} from "~/models/utils";
 | 
					} from "~/models/utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -130,7 +131,7 @@ export async function getShow(
 | 
				
			|||||||
	}: {
 | 
						}: {
 | 
				
			||||||
		languages: string[];
 | 
							languages: string[];
 | 
				
			||||||
		preferOriginal: boolean | undefined;
 | 
							preferOriginal: boolean | undefined;
 | 
				
			||||||
		relations: ("translations" | "videos")[];
 | 
							relations: ("translations" | "studios" | "videos")[];
 | 
				
			||||||
		filters: SQL | undefined;
 | 
							filters: SQL | undefined;
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
@ -141,18 +142,7 @@ export async function getShow(
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
		where: and(isUuid(id) ? eq(shows.id, id) : eq(shows.slug, id), filters),
 | 
							where: and(isUuid(id) ? eq(shows.id, id) : eq(shows.slug, id), filters),
 | 
				
			||||||
		with: {
 | 
							with: {
 | 
				
			||||||
			selectedTranslation: {
 | 
								selectedTranslation: selectTranslationQuery(showTranslations, languages),
 | 
				
			||||||
				columns: {
 | 
					 | 
				
			||||||
					pk: false,
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				where: !languages.includes("*")
 | 
					 | 
				
			||||||
					? eq(showTranslations.language, sql`any(${sqlarr(languages)})`)
 | 
					 | 
				
			||||||
					: undefined,
 | 
					 | 
				
			||||||
				orderBy: [
 | 
					 | 
				
			||||||
					sql`array_position(${sqlarr(languages)}, ${showTranslations.language})`,
 | 
					 | 
				
			||||||
				],
 | 
					 | 
				
			||||||
				limit: 1,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			originalTranslation: {
 | 
								originalTranslation: {
 | 
				
			||||||
				columns: {
 | 
									columns: {
 | 
				
			||||||
					poster: true,
 | 
										poster: true,
 | 
				
			||||||
@ -175,6 +165,23 @@ export async function getShow(
 | 
				
			|||||||
					},
 | 
										},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			}),
 | 
								}),
 | 
				
			||||||
 | 
								...(relations.includes("studios") && {
 | 
				
			||||||
 | 
									studios: {
 | 
				
			||||||
 | 
										with: {
 | 
				
			||||||
 | 
											studio: {
 | 
				
			||||||
 | 
												columns: {
 | 
				
			||||||
 | 
													pk: false,
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												with: {
 | 
				
			||||||
 | 
													selectedTranslation: selectTranslationQuery(
 | 
				
			||||||
 | 
														studioTranslations,
 | 
				
			||||||
 | 
														languages,
 | 
				
			||||||
 | 
													),
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
	if (!ret) return null;
 | 
						if (!ret) return null;
 | 
				
			||||||
@ -184,6 +191,7 @@ export async function getShow(
 | 
				
			|||||||
	const show = {
 | 
						const show = {
 | 
				
			||||||
		...ret,
 | 
							...ret,
 | 
				
			||||||
		...translation,
 | 
							...translation,
 | 
				
			||||||
 | 
							kind: ret.kind as any,
 | 
				
			||||||
		...(ot?.preferOriginal && {
 | 
							...(ot?.preferOriginal && {
 | 
				
			||||||
			...(ot.poster && { poster: ot.poster }),
 | 
								...(ot.poster && { poster: ot.poster }),
 | 
				
			||||||
			...(ot.thumbnail && { thumbnail: ot.thumbnail }),
 | 
								...(ot.thumbnail && { thumbnail: ot.thumbnail }),
 | 
				
			||||||
@ -197,6 +205,12 @@ export async function getShow(
 | 
				
			|||||||
				),
 | 
									),
 | 
				
			||||||
			),
 | 
								),
 | 
				
			||||||
		}),
 | 
							}),
 | 
				
			||||||
 | 
							...(ret.studios && {
 | 
				
			||||||
 | 
								studios: ret.studios.map((x: any) => ({
 | 
				
			||||||
 | 
									...x.studio,
 | 
				
			||||||
 | 
									...x.studio.selectedTranslation[0],
 | 
				
			||||||
 | 
								})),
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	return { show, language: translation.language };
 | 
						return { show, language: translation.language };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -65,7 +65,7 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] })
 | 
				
			|||||||
				preferOriginal: t.Optional(
 | 
									preferOriginal: t.Optional(
 | 
				
			||||||
					t.Boolean({ description: desc.preferOriginal }),
 | 
										t.Boolean({ description: desc.preferOriginal }),
 | 
				
			||||||
				),
 | 
									),
 | 
				
			||||||
				with: t.Array(t.UnionEnum(["translations", "videos"]), {
 | 
									with: t.Array(t.UnionEnum(["translations", "studios", "videos"]), {
 | 
				
			||||||
					default: [],
 | 
										default: [],
 | 
				
			||||||
					description: "Include related resources in the response.",
 | 
										description: "Include related resources in the response.",
 | 
				
			||||||
				}),
 | 
									}),
 | 
				
			||||||
 | 
				
			|||||||
@ -65,7 +65,7 @@ export const series = new Elysia({ prefix: "/series", tags: ["series"] })
 | 
				
			|||||||
				preferOriginal: t.Optional(
 | 
									preferOriginal: t.Optional(
 | 
				
			||||||
					t.Boolean({ description: desc.preferOriginal }),
 | 
										t.Boolean({ description: desc.preferOriginal }),
 | 
				
			||||||
				),
 | 
									),
 | 
				
			||||||
				with: t.Array(t.UnionEnum(["translations"]), {
 | 
									with: t.Array(t.UnionEnum(["translations", "studios"]), {
 | 
				
			||||||
					default: [],
 | 
										default: [],
 | 
				
			||||||
					description: "Include related resources in the response.",
 | 
										description: "Include related resources in the response.",
 | 
				
			||||||
				}),
 | 
									}),
 | 
				
			||||||
 | 
				
			|||||||
@ -2,10 +2,8 @@ import { and, isNull, sql } from "drizzle-orm";
 | 
				
			|||||||
import { Elysia, t } from "elysia";
 | 
					import { Elysia, t } from "elysia";
 | 
				
			||||||
import { db } from "~/db";
 | 
					import { db } from "~/db";
 | 
				
			||||||
import { shows } from "~/db/schema";
 | 
					import { shows } from "~/db/schema";
 | 
				
			||||||
import { Collection } from "~/models/collections";
 | 
					 | 
				
			||||||
import { KError } from "~/models/error";
 | 
					import { KError } from "~/models/error";
 | 
				
			||||||
import { Movie } from "~/models/movie";
 | 
					import { Show } from "~/models/show";
 | 
				
			||||||
import { Serie } from "~/models/serie";
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
	AcceptLanguage,
 | 
						AcceptLanguage,
 | 
				
			||||||
	Filter,
 | 
						Filter,
 | 
				
			||||||
@ -16,8 +14,6 @@ import {
 | 
				
			|||||||
import { desc } from "~/models/utils/descriptions";
 | 
					import { desc } from "~/models/utils/descriptions";
 | 
				
			||||||
import { getShows, showFilters, showSort } from "./logic";
 | 
					import { getShows, showFilters, showSort } from "./logic";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Show = t.Union([Movie, Serie, Collection]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const showsH = new Elysia({ prefix: "/shows", tags: ["shows"] })
 | 
					export const showsH = new Elysia({ prefix: "/shows", tags: ["shows"] })
 | 
				
			||||||
	.model({
 | 
						.model({
 | 
				
			||||||
		show: Show,
 | 
							show: Show,
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										412
									
								
								api/src/controllers/studios.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										412
									
								
								api/src/controllers/studios.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,412 @@
 | 
				
			|||||||
 | 
					import { and, eq, exists, sql } from "drizzle-orm";
 | 
				
			||||||
 | 
					import Elysia, { t } from "elysia";
 | 
				
			||||||
 | 
					import { db } from "~/db";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
						showStudioJoin,
 | 
				
			||||||
 | 
						shows,
 | 
				
			||||||
 | 
						studioTranslations,
 | 
				
			||||||
 | 
						studios,
 | 
				
			||||||
 | 
					} from "~/db/schema";
 | 
				
			||||||
 | 
					import { getColumns, sqlarr } from "~/db/utils";
 | 
				
			||||||
 | 
					import { KError } from "~/models/error";
 | 
				
			||||||
 | 
					import { Movie } from "~/models/movie";
 | 
				
			||||||
 | 
					import { Serie } from "~/models/serie";
 | 
				
			||||||
 | 
					import { Show } from "~/models/show";
 | 
				
			||||||
 | 
					import { Studio, StudioTranslation } from "~/models/studio";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
						AcceptLanguage,
 | 
				
			||||||
 | 
						Filter,
 | 
				
			||||||
 | 
						Page,
 | 
				
			||||||
 | 
						Sort,
 | 
				
			||||||
 | 
						createPage,
 | 
				
			||||||
 | 
						isUuid,
 | 
				
			||||||
 | 
						keysetPaginate,
 | 
				
			||||||
 | 
						processLanguages,
 | 
				
			||||||
 | 
						selectTranslationQuery,
 | 
				
			||||||
 | 
						sortToSql,
 | 
				
			||||||
 | 
					} from "~/models/utils";
 | 
				
			||||||
 | 
					import { desc } from "~/models/utils/descriptions";
 | 
				
			||||||
 | 
					import { getShows, showFilters, showSort } from "./shows/logic";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const studioSort = Sort(["slug", "createdAt"], { default: ["slug"] });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const studiosH = new Elysia({ prefix: "/studios", tags: ["studios"] })
 | 
				
			||||||
 | 
						.model({
 | 
				
			||||||
 | 
							studio: Studio,
 | 
				
			||||||
 | 
							"studio-translation": StudioTranslation,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						.get(
 | 
				
			||||||
 | 
							"/:id",
 | 
				
			||||||
 | 
							async ({
 | 
				
			||||||
 | 
								params: { id },
 | 
				
			||||||
 | 
								headers: { "accept-language": languages },
 | 
				
			||||||
 | 
								query: { with: relations },
 | 
				
			||||||
 | 
								error,
 | 
				
			||||||
 | 
								set,
 | 
				
			||||||
 | 
							}) => {
 | 
				
			||||||
 | 
								const langs = processLanguages(languages);
 | 
				
			||||||
 | 
								const ret = await db.query.studios.findFirst({
 | 
				
			||||||
 | 
									where: isUuid(id) ? eq(studios.id, id) : eq(studios.slug, id),
 | 
				
			||||||
 | 
									with: {
 | 
				
			||||||
 | 
										selectedTranslation: selectTranslationQuery(
 | 
				
			||||||
 | 
											studioTranslations,
 | 
				
			||||||
 | 
											langs,
 | 
				
			||||||
 | 
										),
 | 
				
			||||||
 | 
										...(relations.includes("translations") && {
 | 
				
			||||||
 | 
											translations: {
 | 
				
			||||||
 | 
												columns: {
 | 
				
			||||||
 | 
													pk: false,
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										}),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
								if (!ret) {
 | 
				
			||||||
 | 
									return error(404, {
 | 
				
			||||||
 | 
										status: 404,
 | 
				
			||||||
 | 
										message: `No studio with the id or slug: '${id}'`,
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								const tr = ret.selectedTranslation[0];
 | 
				
			||||||
 | 
								set.headers["content-language"] = tr.language;
 | 
				
			||||||
 | 
								return {
 | 
				
			||||||
 | 
									...ret,
 | 
				
			||||||
 | 
									...tr,
 | 
				
			||||||
 | 
									...(ret.translations && {
 | 
				
			||||||
 | 
										translations: Object.fromEntries(
 | 
				
			||||||
 | 
											ret.translations.map(
 | 
				
			||||||
 | 
												({ language, ...translation }) =>
 | 
				
			||||||
 | 
													[language, translation] as const,
 | 
				
			||||||
 | 
											),
 | 
				
			||||||
 | 
										),
 | 
				
			||||||
 | 
									}),
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								detail: {
 | 
				
			||||||
 | 
									description: "Get a studio by id or slug",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								params: t.Object({
 | 
				
			||||||
 | 
									id: t.String({
 | 
				
			||||||
 | 
										description: "The id or slug of the collection to retrieve.",
 | 
				
			||||||
 | 
										example: "mappa",
 | 
				
			||||||
 | 
									}),
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
								query: t.Object({
 | 
				
			||||||
 | 
									with: t.Array(t.UnionEnum(["translations"]), {
 | 
				
			||||||
 | 
										default: [],
 | 
				
			||||||
 | 
										description: "Include related resources in the response.",
 | 
				
			||||||
 | 
									}),
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
								headers: t.Object({
 | 
				
			||||||
 | 
									"accept-language": AcceptLanguage(),
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
								response: {
 | 
				
			||||||
 | 
									200: "studio",
 | 
				
			||||||
 | 
									404: {
 | 
				
			||||||
 | 
										...KError,
 | 
				
			||||||
 | 
										description: "No collection found with the given id or slug.",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									422: KError,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						.get(
 | 
				
			||||||
 | 
							"random",
 | 
				
			||||||
 | 
							async ({ error, redirect }) => {
 | 
				
			||||||
 | 
								const [studio] = await db
 | 
				
			||||||
 | 
									.select({ slug: studios.slug })
 | 
				
			||||||
 | 
									.from(studios)
 | 
				
			||||||
 | 
									.orderBy(sql`random()`)
 | 
				
			||||||
 | 
									.limit(1);
 | 
				
			||||||
 | 
								if (!studio)
 | 
				
			||||||
 | 
									return error(404, {
 | 
				
			||||||
 | 
										status: 404,
 | 
				
			||||||
 | 
										message: "No studios in the database.",
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								return redirect(`/studios/${studio.slug}`);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								detail: {
 | 
				
			||||||
 | 
									description: "Get a random studio.",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								response: {
 | 
				
			||||||
 | 
									302: t.Void({
 | 
				
			||||||
 | 
										description:
 | 
				
			||||||
 | 
											"Redirected to the [/studios/{id}](#tag/studios/GET/studios/{id}) route.",
 | 
				
			||||||
 | 
									}),
 | 
				
			||||||
 | 
									404: {
 | 
				
			||||||
 | 
										...KError,
 | 
				
			||||||
 | 
										description: "No studios in the database.",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						.get(
 | 
				
			||||||
 | 
							"",
 | 
				
			||||||
 | 
							async ({
 | 
				
			||||||
 | 
								query: { limit, after, query, sort },
 | 
				
			||||||
 | 
								headers: { "accept-language": languages },
 | 
				
			||||||
 | 
								request: { url },
 | 
				
			||||||
 | 
							}) => {
 | 
				
			||||||
 | 
								const langs = processLanguages(languages);
 | 
				
			||||||
 | 
								const transQ = db
 | 
				
			||||||
 | 
									.selectDistinctOn([studioTranslations.pk])
 | 
				
			||||||
 | 
									.from(studioTranslations)
 | 
				
			||||||
 | 
									.orderBy(
 | 
				
			||||||
 | 
										studioTranslations.pk,
 | 
				
			||||||
 | 
										sql`array_position(${sqlarr(langs)}, ${studioTranslations.language}`,
 | 
				
			||||||
 | 
									)
 | 
				
			||||||
 | 
									.as("t");
 | 
				
			||||||
 | 
								const { pk, ...transCol } = getColumns(transQ);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const items = await db
 | 
				
			||||||
 | 
									.select({
 | 
				
			||||||
 | 
										...getColumns(studios),
 | 
				
			||||||
 | 
										...transCol,
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
									.from(studios)
 | 
				
			||||||
 | 
									.where(
 | 
				
			||||||
 | 
										and(
 | 
				
			||||||
 | 
											query ? sql`${transQ.name} %> ${query}::text` : undefined,
 | 
				
			||||||
 | 
											keysetPaginate({ table: studios, after, sort }),
 | 
				
			||||||
 | 
										),
 | 
				
			||||||
 | 
									)
 | 
				
			||||||
 | 
									.orderBy(
 | 
				
			||||||
 | 
										...(query
 | 
				
			||||||
 | 
											? [sql`word_similarity(${query}::text, ${transQ.name})`]
 | 
				
			||||||
 | 
											: sortToSql(sort, studios)),
 | 
				
			||||||
 | 
										studios.pk,
 | 
				
			||||||
 | 
									)
 | 
				
			||||||
 | 
									.limit(limit);
 | 
				
			||||||
 | 
								return createPage(items, { url, sort, limit });
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								detail: { description: "Get all studios" },
 | 
				
			||||||
 | 
								query: t.Object({
 | 
				
			||||||
 | 
									sort: studioSort,
 | 
				
			||||||
 | 
									query: t.Optional(t.String({ description: desc.query })),
 | 
				
			||||||
 | 
									limit: t.Integer({
 | 
				
			||||||
 | 
										minimum: 1,
 | 
				
			||||||
 | 
										maximum: 250,
 | 
				
			||||||
 | 
										default: 50,
 | 
				
			||||||
 | 
										description: "Max page size.",
 | 
				
			||||||
 | 
									}),
 | 
				
			||||||
 | 
									after: t.Optional(t.String({ description: desc.after })),
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
								headers: t.Object({
 | 
				
			||||||
 | 
									"accept-language": AcceptLanguage({ autoFallback: true }),
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
								response: {
 | 
				
			||||||
 | 
									200: Page(Studio),
 | 
				
			||||||
 | 
									422: KError,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						.guard({
 | 
				
			||||||
 | 
							params: t.Object({
 | 
				
			||||||
 | 
								id: t.String({
 | 
				
			||||||
 | 
									description: "The id or slug of the studio.",
 | 
				
			||||||
 | 
									example: "mappa",
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
							query: t.Object({
 | 
				
			||||||
 | 
								sort: showSort,
 | 
				
			||||||
 | 
								filter: t.Optional(Filter({ def: showFilters })),
 | 
				
			||||||
 | 
								query: t.Optional(t.String({ description: desc.query })),
 | 
				
			||||||
 | 
								limit: t.Integer({
 | 
				
			||||||
 | 
									minimum: 1,
 | 
				
			||||||
 | 
									maximum: 250,
 | 
				
			||||||
 | 
									default: 50,
 | 
				
			||||||
 | 
									description: "Max page size.",
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
								after: t.Optional(t.String({ description: desc.after })),
 | 
				
			||||||
 | 
								preferOriginal: t.Optional(
 | 
				
			||||||
 | 
									t.Boolean({
 | 
				
			||||||
 | 
										description: desc.preferOriginal,
 | 
				
			||||||
 | 
									}),
 | 
				
			||||||
 | 
								),
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
							headers: t.Object({
 | 
				
			||||||
 | 
								"accept-language": AcceptLanguage({ autoFallback: true }),
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						.get(
 | 
				
			||||||
 | 
							"/:id/shows",
 | 
				
			||||||
 | 
							async ({
 | 
				
			||||||
 | 
								params: { id },
 | 
				
			||||||
 | 
								query: { limit, after, query, sort, filter, preferOriginal },
 | 
				
			||||||
 | 
								headers: { "accept-language": languages },
 | 
				
			||||||
 | 
								request: { url },
 | 
				
			||||||
 | 
								error,
 | 
				
			||||||
 | 
							}) => {
 | 
				
			||||||
 | 
								const [studio] = await db
 | 
				
			||||||
 | 
									.select({ pk: studios.pk })
 | 
				
			||||||
 | 
									.from(studios)
 | 
				
			||||||
 | 
									.where(isUuid(id) ? eq(studios.id, id) : eq(studios.slug, id))
 | 
				
			||||||
 | 
									.limit(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!studio) {
 | 
				
			||||||
 | 
									return error(404, {
 | 
				
			||||||
 | 
										status: 404,
 | 
				
			||||||
 | 
										message: `No studios with the id or slug: '${id}'.`,
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const langs = processLanguages(languages);
 | 
				
			||||||
 | 
								const items = await getShows({
 | 
				
			||||||
 | 
									limit,
 | 
				
			||||||
 | 
									after,
 | 
				
			||||||
 | 
									query,
 | 
				
			||||||
 | 
									sort,
 | 
				
			||||||
 | 
									filter: and(
 | 
				
			||||||
 | 
										exists(
 | 
				
			||||||
 | 
											db
 | 
				
			||||||
 | 
												.select()
 | 
				
			||||||
 | 
												.from(showStudioJoin)
 | 
				
			||||||
 | 
												.where(
 | 
				
			||||||
 | 
													and(
 | 
				
			||||||
 | 
														eq(showStudioJoin.studioPk, studio.pk),
 | 
				
			||||||
 | 
														eq(showStudioJoin.showPk, shows.pk),
 | 
				
			||||||
 | 
													),
 | 
				
			||||||
 | 
												),
 | 
				
			||||||
 | 
										),
 | 
				
			||||||
 | 
										filter,
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
									languages: langs,
 | 
				
			||||||
 | 
									preferOriginal,
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
								return createPage(items, { url, sort, limit });
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								detail: { description: "Get all series & movies made by a studio." },
 | 
				
			||||||
 | 
								response: {
 | 
				
			||||||
 | 
									200: Page(Show),
 | 
				
			||||||
 | 
									404: {
 | 
				
			||||||
 | 
										...KError,
 | 
				
			||||||
 | 
										description: "No collection found with the given id or slug.",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									422: KError,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						.get(
 | 
				
			||||||
 | 
							"/:id/movies",
 | 
				
			||||||
 | 
							async ({
 | 
				
			||||||
 | 
								params: { id },
 | 
				
			||||||
 | 
								query: { limit, after, query, sort, filter, preferOriginal },
 | 
				
			||||||
 | 
								headers: { "accept-language": languages },
 | 
				
			||||||
 | 
								request: { url },
 | 
				
			||||||
 | 
								error,
 | 
				
			||||||
 | 
							}) => {
 | 
				
			||||||
 | 
								const [studio] = await db
 | 
				
			||||||
 | 
									.select({ pk: studios.pk })
 | 
				
			||||||
 | 
									.from(studios)
 | 
				
			||||||
 | 
									.where(isUuid(id) ? eq(studios.id, id) : eq(studios.slug, id))
 | 
				
			||||||
 | 
									.limit(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!studio) {
 | 
				
			||||||
 | 
									return error(404, {
 | 
				
			||||||
 | 
										status: 404,
 | 
				
			||||||
 | 
										message: `No studios with the id or slug: '${id}'.`,
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const langs = processLanguages(languages);
 | 
				
			||||||
 | 
								const items = await getShows({
 | 
				
			||||||
 | 
									limit,
 | 
				
			||||||
 | 
									after,
 | 
				
			||||||
 | 
									query,
 | 
				
			||||||
 | 
									sort,
 | 
				
			||||||
 | 
									filter: and(
 | 
				
			||||||
 | 
										eq(shows.kind, "movie"),
 | 
				
			||||||
 | 
										exists(
 | 
				
			||||||
 | 
											db
 | 
				
			||||||
 | 
												.select()
 | 
				
			||||||
 | 
												.from(showStudioJoin)
 | 
				
			||||||
 | 
												.where(
 | 
				
			||||||
 | 
													and(
 | 
				
			||||||
 | 
														eq(showStudioJoin.studioPk, studio.pk),
 | 
				
			||||||
 | 
														eq(showStudioJoin.showPk, shows.pk),
 | 
				
			||||||
 | 
													),
 | 
				
			||||||
 | 
												),
 | 
				
			||||||
 | 
										),
 | 
				
			||||||
 | 
										filter,
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
									languages: langs,
 | 
				
			||||||
 | 
									preferOriginal,
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
								return createPage(items, { url, sort, limit });
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								detail: { description: "Get all movies made by a studio." },
 | 
				
			||||||
 | 
								response: {
 | 
				
			||||||
 | 
									200: Page(Movie),
 | 
				
			||||||
 | 
									404: {
 | 
				
			||||||
 | 
										...KError,
 | 
				
			||||||
 | 
										description: "No collection found with the given id or slug.",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									422: KError,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						.get(
 | 
				
			||||||
 | 
							"/:id/series",
 | 
				
			||||||
 | 
							async ({
 | 
				
			||||||
 | 
								params: { id },
 | 
				
			||||||
 | 
								query: { limit, after, query, sort, filter, preferOriginal },
 | 
				
			||||||
 | 
								headers: { "accept-language": languages },
 | 
				
			||||||
 | 
								request: { url },
 | 
				
			||||||
 | 
								error,
 | 
				
			||||||
 | 
							}) => {
 | 
				
			||||||
 | 
								const [studio] = await db
 | 
				
			||||||
 | 
									.select({ pk: studios.pk })
 | 
				
			||||||
 | 
									.from(studios)
 | 
				
			||||||
 | 
									.where(isUuid(id) ? eq(studios.id, id) : eq(studios.slug, id))
 | 
				
			||||||
 | 
									.limit(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!studio) {
 | 
				
			||||||
 | 
									return error(404, {
 | 
				
			||||||
 | 
										status: 404,
 | 
				
			||||||
 | 
										message: `No studios with the id or slug: '${id}'.`,
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const langs = processLanguages(languages);
 | 
				
			||||||
 | 
								const items = await getShows({
 | 
				
			||||||
 | 
									limit,
 | 
				
			||||||
 | 
									after,
 | 
				
			||||||
 | 
									query,
 | 
				
			||||||
 | 
									sort,
 | 
				
			||||||
 | 
									filter: and(
 | 
				
			||||||
 | 
										eq(shows.kind, "serie"),
 | 
				
			||||||
 | 
										exists(
 | 
				
			||||||
 | 
											db
 | 
				
			||||||
 | 
												.select()
 | 
				
			||||||
 | 
												.from(showStudioJoin)
 | 
				
			||||||
 | 
												.where(
 | 
				
			||||||
 | 
													and(
 | 
				
			||||||
 | 
														eq(showStudioJoin.studioPk, studio.pk),
 | 
				
			||||||
 | 
														eq(showStudioJoin.showPk, shows.pk),
 | 
				
			||||||
 | 
													),
 | 
				
			||||||
 | 
												),
 | 
				
			||||||
 | 
										),
 | 
				
			||||||
 | 
										filter,
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
									languages: langs,
 | 
				
			||||||
 | 
									preferOriginal,
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
								return createPage(items, { url, sort, limit });
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								detail: { description: "Get all series made by a studio." },
 | 
				
			||||||
 | 
								response: {
 | 
				
			||||||
 | 
									200: Page(Serie),
 | 
				
			||||||
 | 
									404: {
 | 
				
			||||||
 | 
										...KError,
 | 
				
			||||||
 | 
										description: "No collection found with the given id or slug.",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									422: KError,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
@ -42,78 +42,77 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
 | 
				
			|||||||
			return error(201, oldRet);
 | 
								return error(201, oldRet);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// TODO: this is a huge untested wip
 | 
								// TODO: this is a huge untested wip
 | 
				
			||||||
			// biome-ignore lint/correctness/noUnreachable: leave me alone
 | 
								// const vidsI = db.$with("vidsI").as(
 | 
				
			||||||
			const vidsI = db.$with("vidsI").as(
 | 
								// 	db.insert(videos).values(body).onConflictDoNothing().returning({
 | 
				
			||||||
				db.insert(videos).values(body).onConflictDoNothing().returning({
 | 
								// 		pk: videos.pk,
 | 
				
			||||||
					pk: videos.pk,
 | 
								// 		id: videos.id,
 | 
				
			||||||
					id: videos.id,
 | 
								// 		path: videos.path,
 | 
				
			||||||
					path: videos.path,
 | 
								// 		guess: videos.guess,
 | 
				
			||||||
					guess: videos.guess,
 | 
								// 	}),
 | 
				
			||||||
				}),
 | 
								// );
 | 
				
			||||||
			);
 | 
								//
 | 
				
			||||||
 | 
								// const findEntriesQ = db
 | 
				
			||||||
			const findEntriesQ = db
 | 
								// 	.select({
 | 
				
			||||||
				.select({
 | 
								// 		guess: videos.guess,
 | 
				
			||||||
					guess: videos.guess,
 | 
								// 		entryPk: entries.pk,
 | 
				
			||||||
					entryPk: entries.pk,
 | 
								// 		showSlug: shows.slug,
 | 
				
			||||||
					showSlug: shows.slug,
 | 
								// 		// TODO: handle extras here
 | 
				
			||||||
					// TODO: handle extras here
 | 
								// 		// guessit can't know if an episode is a special or not. treat specials like a normal episode.
 | 
				
			||||||
					// guessit can't know if an episode is a special or not. treat specials like a normal episode.
 | 
								// 		kind: sql`
 | 
				
			||||||
					kind: sql`
 | 
								// 			case when ${entries.kind} = 'movie' then 'movie' else 'episode' end
 | 
				
			||||||
						case when ${entries.kind} = 'movie' then 'movie' else 'episode' end
 | 
								// 		`.as("kind"),
 | 
				
			||||||
					`.as("kind"),
 | 
								// 		season: entries.seasonNumber,
 | 
				
			||||||
					season: entries.seasonNumber,
 | 
								// 		episode: entries.episodeNumber,
 | 
				
			||||||
					episode: entries.episodeNumber,
 | 
								// 	})
 | 
				
			||||||
				})
 | 
								// 	.from(entries)
 | 
				
			||||||
				.from(entries)
 | 
								// 	.leftJoin(entryVideoJoin, eq(entryVideoJoin.entry, entries.pk))
 | 
				
			||||||
				.leftJoin(entryVideoJoin, eq(entryVideoJoin.entry, entries.pk))
 | 
								// 	.leftJoin(videos, eq(videos.pk, entryVideoJoin.video))
 | 
				
			||||||
				.leftJoin(videos, eq(videos.pk, entryVideoJoin.video))
 | 
								// 	.leftJoin(shows, eq(shows.pk, entries.showPk))
 | 
				
			||||||
				.leftJoin(shows, eq(shows.pk, entries.showPk))
 | 
								// 	.as("find_entries");
 | 
				
			||||||
				.as("find_entries");
 | 
								//
 | 
				
			||||||
 | 
								// const hasRenderingQ = db
 | 
				
			||||||
			const hasRenderingQ = db
 | 
								// 	.select()
 | 
				
			||||||
				.select()
 | 
								// 	.from(entryVideoJoin)
 | 
				
			||||||
				.from(entryVideoJoin)
 | 
								// 	.where(eq(entryVideoJoin.entry, findEntriesQ.entryPk));
 | 
				
			||||||
				.where(eq(entryVideoJoin.entry, findEntriesQ.entryPk));
 | 
								//
 | 
				
			||||||
 | 
								// const ret = await db
 | 
				
			||||||
			const ret = await db
 | 
								// 	.with(vidsI)
 | 
				
			||||||
				.with(vidsI)
 | 
								// 	.insert(entryVideoJoin)
 | 
				
			||||||
				.insert(entryVideoJoin)
 | 
								// 	.select(
 | 
				
			||||||
				.select(
 | 
								// 		db
 | 
				
			||||||
					db
 | 
								// 			.select({
 | 
				
			||||||
						.select({
 | 
								// 				entry: findEntriesQ.entryPk,
 | 
				
			||||||
							entry: findEntriesQ.entryPk,
 | 
								// 				video: vidsI.pk,
 | 
				
			||||||
							video: vidsI.pk,
 | 
								// 				slug: computeVideoSlug(
 | 
				
			||||||
							slug: computeVideoSlug(
 | 
								// 					findEntriesQ.showSlug,
 | 
				
			||||||
								findEntriesQ.showSlug,
 | 
								// 					sql`exists(${hasRenderingQ})`,
 | 
				
			||||||
								sql`exists(${hasRenderingQ})`,
 | 
								// 				),
 | 
				
			||||||
							),
 | 
								// 			})
 | 
				
			||||||
						})
 | 
								// 			.from(vidsI)
 | 
				
			||||||
						.from(vidsI)
 | 
								// 			.leftJoin(
 | 
				
			||||||
						.leftJoin(
 | 
								// 				findEntriesQ,
 | 
				
			||||||
							findEntriesQ,
 | 
								// 				and(
 | 
				
			||||||
							and(
 | 
								// 					eq(
 | 
				
			||||||
								eq(
 | 
								// 						sql`${findEntriesQ.guess}->'title'`,
 | 
				
			||||||
									sql`${findEntriesQ.guess}->'title'`,
 | 
								// 						sql`${vidsI.guess}->'title'`,
 | 
				
			||||||
									sql`${vidsI.guess}->'title'`,
 | 
								// 					),
 | 
				
			||||||
								),
 | 
								// 					// TODO: find if @> with a jsonb created on the fly is
 | 
				
			||||||
								// TODO: find if @> with a jsonb created on the fly is
 | 
								// 					// better than multiples checks
 | 
				
			||||||
								// better than multiples checks
 | 
								// 					sql`${vidsI.guess} @> {"kind": }::jsonb`,
 | 
				
			||||||
								sql`${vidsI.guess} @> {"kind": }::jsonb`,
 | 
								// 					inArray(findEntriesQ.kind, sql`${vidsI.guess}->'type'`),
 | 
				
			||||||
								inArray(findEntriesQ.kind, sql`${vidsI.guess}->'type'`),
 | 
								// 					inArray(findEntriesQ.episode, sql`${vidsI.guess}->'episode'`),
 | 
				
			||||||
								inArray(findEntriesQ.episode, sql`${vidsI.guess}->'episode'`),
 | 
								// 					inArray(findEntriesQ.season, sql`${vidsI.guess}->'season'`),
 | 
				
			||||||
								inArray(findEntriesQ.season, sql`${vidsI.guess}->'season'`),
 | 
								// 				),
 | 
				
			||||||
							),
 | 
								// 			),
 | 
				
			||||||
						),
 | 
								// 	)
 | 
				
			||||||
				)
 | 
								// 	.onConflictDoNothing()
 | 
				
			||||||
				.onConflictDoNothing()
 | 
								// 	.returning({
 | 
				
			||||||
				.returning({
 | 
								// 		slug: entryVideoJoin.slug,
 | 
				
			||||||
					slug: entryVideoJoin.slug,
 | 
								// 		entryPk: entryVideoJoin.entry,
 | 
				
			||||||
					entryPk: entryVideoJoin.entry,
 | 
								// 		id: vidsI.id,
 | 
				
			||||||
					id: vidsI.id,
 | 
								// 		path: vidsI.path,
 | 
				
			||||||
					path: vidsI.path,
 | 
								// 	});
 | 
				
			||||||
				});
 | 
								// return error(201, ret as any);
 | 
				
			||||||
			return error(201, ret as any);
 | 
					 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			body: t.Array(SeedVideo),
 | 
								body: t.Array(SeedVideo),
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@ import { relations, sql } from "drizzle-orm";
 | 
				
			|||||||
import {
 | 
					import {
 | 
				
			||||||
	check,
 | 
						check,
 | 
				
			||||||
	date,
 | 
						date,
 | 
				
			||||||
 | 
						index,
 | 
				
			||||||
	integer,
 | 
						integer,
 | 
				
			||||||
	jsonb,
 | 
						jsonb,
 | 
				
			||||||
	primaryKey,
 | 
						primaryKey,
 | 
				
			||||||
@ -70,11 +71,17 @@ export const entries = schema.table(
 | 
				
			|||||||
		createdAt: timestamp({ withTimezone: true, mode: "string" })
 | 
							createdAt: timestamp({ withTimezone: true, mode: "string" })
 | 
				
			||||||
			.notNull()
 | 
								.notNull()
 | 
				
			||||||
			.defaultNow(),
 | 
								.defaultNow(),
 | 
				
			||||||
 | 
							updatedAt: timestamp({ withTimezone: true, mode: "string" })
 | 
				
			||||||
 | 
								.notNull()
 | 
				
			||||||
 | 
								.$onUpdate(() => sql`now()`),
 | 
				
			||||||
		nextRefresh: timestamp({ withTimezone: true, mode: "string" }).notNull(),
 | 
							nextRefresh: timestamp({ withTimezone: true, mode: "string" }).notNull(),
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	(t) => [
 | 
						(t) => [
 | 
				
			||||||
		unique().on(t.showPk, t.seasonNumber, t.episodeNumber),
 | 
							unique().on(t.showPk, t.seasonNumber, t.episodeNumber),
 | 
				
			||||||
		check("order_positive", sql`${t.order} >= 0`),
 | 
							check("order_positive", sql`${t.order} >= 0`),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							index("entry_kind").using("hash", t.kind),
 | 
				
			||||||
 | 
							index("entry_order").on(t.order),
 | 
				
			||||||
	],
 | 
						],
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -91,7 +98,10 @@ export const entryTranslations = schema.table(
 | 
				
			|||||||
		tagline: text(),
 | 
							tagline: text(),
 | 
				
			||||||
		poster: image(),
 | 
							poster: image(),
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	(t) => [primaryKey({ columns: [t.pk, t.language] })],
 | 
						(t) => [
 | 
				
			||||||
 | 
							primaryKey({ columns: [t.pk, t.language] }),
 | 
				
			||||||
 | 
							index("entry_name_trgm").using("gin", sql`${t.name} gin_trgm_ops`),
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const entryRelations = relations(entries, ({ one, many }) => ({
 | 
					export const entryRelations = relations(entries, ({ one, many }) => ({
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
export * from "./entries";
 | 
					export * from "./entries";
 | 
				
			||||||
export * from "./seasons";
 | 
					export * from "./seasons";
 | 
				
			||||||
export * from "./shows";
 | 
					export * from "./shows";
 | 
				
			||||||
 | 
					export * from "./studios";
 | 
				
			||||||
export * from "./videos";
 | 
					export * from "./videos";
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { relations } from "drizzle-orm";
 | 
					import { relations, sql } from "drizzle-orm";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
	date,
 | 
						date,
 | 
				
			||||||
	index,
 | 
						index,
 | 
				
			||||||
@ -45,11 +45,15 @@ export const seasons = schema.table(
 | 
				
			|||||||
		createdAt: timestamp({ withTimezone: true, mode: "string" })
 | 
							createdAt: timestamp({ withTimezone: true, mode: "string" })
 | 
				
			||||||
			.notNull()
 | 
								.notNull()
 | 
				
			||||||
			.defaultNow(),
 | 
								.defaultNow(),
 | 
				
			||||||
 | 
							updatedAt: timestamp({ withTimezone: true, mode: "string" })
 | 
				
			||||||
 | 
								.notNull()
 | 
				
			||||||
 | 
								.$onUpdate(() => sql`now()`),
 | 
				
			||||||
		nextRefresh: timestamp({ withTimezone: true, mode: "string" }).notNull(),
 | 
							nextRefresh: timestamp({ withTimezone: true, mode: "string" }).notNull(),
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	(t) => [
 | 
						(t) => [
 | 
				
			||||||
		unique().on(t.showPk, t.seasonNumber),
 | 
							unique().on(t.showPk, t.seasonNumber),
 | 
				
			||||||
		index("show_fk").using("hash", t.showPk),
 | 
							index("show_fk").using("hash", t.showPk),
 | 
				
			||||||
 | 
							index("season_nbr").on(t.seasonNumber),
 | 
				
			||||||
	],
 | 
						],
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -66,7 +70,10 @@ export const seasonTranslations = schema.table(
 | 
				
			|||||||
		thumbnail: image(),
 | 
							thumbnail: image(),
 | 
				
			||||||
		banner: image(),
 | 
							banner: image(),
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	(t) => [primaryKey({ columns: [t.pk, t.language] })],
 | 
						(t) => [
 | 
				
			||||||
 | 
							primaryKey({ columns: [t.pk, t.language] }),
 | 
				
			||||||
 | 
							index("season_name_trgm").using("gin", sql`${t.name} gin_trgm_ops`),
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const seasonRelations = relations(seasons, ({ one, many }) => ({
 | 
					export const seasonRelations = relations(seasons, ({ one, many }) => ({
 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,6 @@ import {
 | 
				
			|||||||
	date,
 | 
						date,
 | 
				
			||||||
	index,
 | 
						index,
 | 
				
			||||||
	integer,
 | 
						integer,
 | 
				
			||||||
	jsonb,
 | 
					 | 
				
			||||||
	primaryKey,
 | 
						primaryKey,
 | 
				
			||||||
	smallint,
 | 
						smallint,
 | 
				
			||||||
	text,
 | 
						text,
 | 
				
			||||||
@ -15,7 +14,8 @@ import {
 | 
				
			|||||||
} from "drizzle-orm/pg-core";
 | 
					} from "drizzle-orm/pg-core";
 | 
				
			||||||
import { entries } from "./entries";
 | 
					import { entries } from "./entries";
 | 
				
			||||||
import { seasons } from "./seasons";
 | 
					import { seasons } from "./seasons";
 | 
				
			||||||
import { image, language, schema } from "./utils";
 | 
					import { showStudioJoin } from "./studios";
 | 
				
			||||||
 | 
					import { externalid, image, language, schema } from "./utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const showKind = schema.enum("show_kind", [
 | 
					export const showKind = schema.enum("show_kind", [
 | 
				
			||||||
	"serie",
 | 
						"serie",
 | 
				
			||||||
@ -54,20 +54,6 @@ export const genres = schema.enum("genres", [
 | 
				
			|||||||
	"talk",
 | 
						"talk",
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const externalid = () =>
 | 
					 | 
				
			||||||
	jsonb()
 | 
					 | 
				
			||||||
		.$type<
 | 
					 | 
				
			||||||
			Record<
 | 
					 | 
				
			||||||
				string,
 | 
					 | 
				
			||||||
				{
 | 
					 | 
				
			||||||
					dataId: string;
 | 
					 | 
				
			||||||
					link: string | null;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			>
 | 
					 | 
				
			||||||
		>()
 | 
					 | 
				
			||||||
		.notNull()
 | 
					 | 
				
			||||||
		.default({});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const shows = schema.table(
 | 
					export const shows = schema.table(
 | 
				
			||||||
	"shows",
 | 
						"shows",
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
@ -92,6 +78,9 @@ export const shows = schema.table(
 | 
				
			|||||||
		createdAt: timestamp({ withTimezone: true, mode: "string" })
 | 
							createdAt: timestamp({ withTimezone: true, mode: "string" })
 | 
				
			||||||
			.notNull()
 | 
								.notNull()
 | 
				
			||||||
			.defaultNow(),
 | 
								.defaultNow(),
 | 
				
			||||||
 | 
							updatedAt: timestamp({ withTimezone: true, mode: "string" })
 | 
				
			||||||
 | 
								.notNull()
 | 
				
			||||||
 | 
								.$onUpdate(() => sql`now()`),
 | 
				
			||||||
		nextRefresh: timestamp({ withTimezone: true, mode: "string" }).notNull(),
 | 
							nextRefresh: timestamp({ withTimezone: true, mode: "string" }).notNull(),
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	(t) => [
 | 
						(t) => [
 | 
				
			||||||
@ -141,6 +130,7 @@ export const showsRelations = relations(shows, ({ many, one }) => ({
 | 
				
			|||||||
	}),
 | 
						}),
 | 
				
			||||||
	entries: many(entries, { relationName: "show_entries" }),
 | 
						entries: many(entries, { relationName: "show_entries" }),
 | 
				
			||||||
	seasons: many(seasons, { relationName: "show_seasons" }),
 | 
						seasons: many(seasons, { relationName: "show_seasons" }),
 | 
				
			||||||
 | 
						studios: many(showStudioJoin, { relationName: "ssj_show" }),
 | 
				
			||||||
}));
 | 
					}));
 | 
				
			||||||
export const showsTrRelations = relations(showTranslations, ({ one }) => ({
 | 
					export const showsTrRelations = relations(showTranslations, ({ one }) => ({
 | 
				
			||||||
	show: one(shows, {
 | 
						show: one(shows, {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										89
									
								
								api/src/db/schema/studios.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								api/src/db/schema/studios.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,89 @@
 | 
				
			|||||||
 | 
					import { relations, sql } from "drizzle-orm";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
						index,
 | 
				
			||||||
 | 
						integer,
 | 
				
			||||||
 | 
						primaryKey,
 | 
				
			||||||
 | 
						text,
 | 
				
			||||||
 | 
						timestamp,
 | 
				
			||||||
 | 
						uuid,
 | 
				
			||||||
 | 
						varchar,
 | 
				
			||||||
 | 
					} from "drizzle-orm/pg-core";
 | 
				
			||||||
 | 
					import { shows } from "./shows";
 | 
				
			||||||
 | 
					import { externalid, image, language, schema } from "./utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const studios = schema.table("studios", {
 | 
				
			||||||
 | 
						pk: integer().primaryKey().generatedAlwaysAsIdentity(),
 | 
				
			||||||
 | 
						id: uuid().notNull().unique().defaultRandom(),
 | 
				
			||||||
 | 
						slug: varchar({ length: 255 }).notNull().unique(),
 | 
				
			||||||
 | 
						externalId: externalid(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						createdAt: timestamp({ withTimezone: true, mode: "string" })
 | 
				
			||||||
 | 
							.notNull()
 | 
				
			||||||
 | 
							.defaultNow(),
 | 
				
			||||||
 | 
						updatedAt: timestamp({ withTimezone: true, mode: "string" })
 | 
				
			||||||
 | 
							.notNull()
 | 
				
			||||||
 | 
							.$onUpdate(() => sql`now()`),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const studioTranslations = schema.table(
 | 
				
			||||||
 | 
						"studio_translations",
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							pk: integer()
 | 
				
			||||||
 | 
								.notNull()
 | 
				
			||||||
 | 
								.references(() => studios.pk, { onDelete: "cascade" }),
 | 
				
			||||||
 | 
							language: language().notNull(),
 | 
				
			||||||
 | 
							name: text().notNull(),
 | 
				
			||||||
 | 
							logo: image(),
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						(t) => [
 | 
				
			||||||
 | 
							primaryKey({ columns: [t.pk, t.language] }),
 | 
				
			||||||
 | 
							index("studio_name_trgm").using("gin", sql`${t.name} gin_trgm_ops`),
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const showStudioJoin = schema.table(
 | 
				
			||||||
 | 
						"show_studio_join",
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							showPk: integer()
 | 
				
			||||||
 | 
								.notNull()
 | 
				
			||||||
 | 
								.references(() => shows.pk, { onDelete: "cascade" }),
 | 
				
			||||||
 | 
							studioPk: integer()
 | 
				
			||||||
 | 
								.notNull()
 | 
				
			||||||
 | 
								.references(() => studios.pk, { onDelete: "cascade" }),
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						(t) => [primaryKey({ columns: [t.showPk, t.studioPk] })],
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const studioRelations = relations(studios, ({ many }) => ({
 | 
				
			||||||
 | 
						translations: many(studioTranslations, {
 | 
				
			||||||
 | 
							relationName: "studio_translations",
 | 
				
			||||||
 | 
						}),
 | 
				
			||||||
 | 
						selectedTranslation: many(studioTranslations, {
 | 
				
			||||||
 | 
							relationName: "studio_selected_translation",
 | 
				
			||||||
 | 
						}),
 | 
				
			||||||
 | 
						showsJoin: many(showStudioJoin, { relationName: "ssj_studio" }),
 | 
				
			||||||
 | 
					}));
 | 
				
			||||||
 | 
					export const studioTrRelations = relations(studioTranslations, ({ one }) => ({
 | 
				
			||||||
 | 
						studio: one(studios, {
 | 
				
			||||||
 | 
							relationName: "studio_translations",
 | 
				
			||||||
 | 
							fields: [studioTranslations.pk],
 | 
				
			||||||
 | 
							references: [studios.pk],
 | 
				
			||||||
 | 
						}),
 | 
				
			||||||
 | 
						selectedTranslation: one(studios, {
 | 
				
			||||||
 | 
							relationName: "studio_selected_translation",
 | 
				
			||||||
 | 
							fields: [studioTranslations.pk],
 | 
				
			||||||
 | 
							references: [studios.pk],
 | 
				
			||||||
 | 
						}),
 | 
				
			||||||
 | 
					}));
 | 
				
			||||||
 | 
					export const ssjRelations = relations(showStudioJoin, ({ one }) => ({
 | 
				
			||||||
 | 
						show: one(shows, {
 | 
				
			||||||
 | 
							relationName: "ssj_show",
 | 
				
			||||||
 | 
							fields: [showStudioJoin.showPk],
 | 
				
			||||||
 | 
							references: [shows.pk],
 | 
				
			||||||
 | 
						}),
 | 
				
			||||||
 | 
						studio: one(studios, {
 | 
				
			||||||
 | 
							relationName: "ssj_studio",
 | 
				
			||||||
 | 
							fields: [showStudioJoin.studioPk],
 | 
				
			||||||
 | 
							references: [studios.pk],
 | 
				
			||||||
 | 
						}),
 | 
				
			||||||
 | 
					}));
 | 
				
			||||||
@ -6,3 +6,17 @@ export const language = () => varchar({ length: 255 });
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export const image = () =>
 | 
					export const image = () =>
 | 
				
			||||||
	jsonb().$type<{ id: string; source: string; blurhash: string }>();
 | 
						jsonb().$type<{ id: string; source: string; blurhash: string }>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const externalid = () =>
 | 
				
			||||||
 | 
						jsonb()
 | 
				
			||||||
 | 
							.$type<
 | 
				
			||||||
 | 
								Record<
 | 
				
			||||||
 | 
									string,
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										dataId: string;
 | 
				
			||||||
 | 
										link: string | null;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								>
 | 
				
			||||||
 | 
							>()
 | 
				
			||||||
 | 
							.notNull()
 | 
				
			||||||
 | 
							.default({});
 | 
				
			||||||
 | 
				
			|||||||
@ -26,6 +26,9 @@ export const videos = schema.table(
 | 
				
			|||||||
		createdAt: timestamp({ withTimezone: true, mode: "string" })
 | 
							createdAt: timestamp({ withTimezone: true, mode: "string" })
 | 
				
			||||||
			.notNull()
 | 
								.notNull()
 | 
				
			||||||
			.defaultNow(),
 | 
								.defaultNow(),
 | 
				
			||||||
 | 
							updatedAt: timestamp({ withTimezone: true, mode: "string" })
 | 
				
			||||||
 | 
								.notNull()
 | 
				
			||||||
 | 
								.$onUpdate(() => sql`now()`),
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	(t) => [
 | 
						(t) => [
 | 
				
			||||||
		check("part_pos", sql`${t.part} >= 0`),
 | 
							check("part_pos", sql`${t.part} >= 0`),
 | 
				
			||||||
@ -36,15 +39,15 @@ export const videos = schema.table(
 | 
				
			|||||||
export const entryVideoJoin = schema.table(
 | 
					export const entryVideoJoin = schema.table(
 | 
				
			||||||
	"entry_video_join",
 | 
						"entry_video_join",
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		entry: integer()
 | 
							entryPk: integer()
 | 
				
			||||||
			.notNull()
 | 
								.notNull()
 | 
				
			||||||
			.references(() => entries.pk, { onDelete: "cascade" }),
 | 
								.references(() => entries.pk, { onDelete: "cascade" }),
 | 
				
			||||||
		video: integer()
 | 
							videoPk: integer()
 | 
				
			||||||
			.notNull()
 | 
								.notNull()
 | 
				
			||||||
			.references(() => videos.pk, { onDelete: "cascade" }),
 | 
								.references(() => videos.pk, { onDelete: "cascade" }),
 | 
				
			||||||
		slug: varchar({ length: 255 }).notNull().unique(),
 | 
							slug: varchar({ length: 255 }).notNull().unique(),
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	(t) => [primaryKey({ columns: [t.entry, t.video] })],
 | 
						(t) => [primaryKey({ columns: [t.entryPk, t.videoPk] })],
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const videosRelations = relations(videos, ({ many }) => ({
 | 
					export const videosRelations = relations(videos, ({ many }) => ({
 | 
				
			||||||
@ -56,12 +59,12 @@ export const videosRelations = relations(videos, ({ many }) => ({
 | 
				
			|||||||
export const evjRelations = relations(entryVideoJoin, ({ one }) => ({
 | 
					export const evjRelations = relations(entryVideoJoin, ({ one }) => ({
 | 
				
			||||||
	video: one(videos, {
 | 
						video: one(videos, {
 | 
				
			||||||
		relationName: "evj_video",
 | 
							relationName: "evj_video",
 | 
				
			||||||
		fields: [entryVideoJoin.video],
 | 
							fields: [entryVideoJoin.videoPk],
 | 
				
			||||||
		references: [videos.pk],
 | 
							references: [videos.pk],
 | 
				
			||||||
	}),
 | 
						}),
 | 
				
			||||||
	entry: one(entries, {
 | 
						entry: one(entries, {
 | 
				
			||||||
		relationName: "evj_entry",
 | 
							relationName: "evj_entry",
 | 
				
			||||||
		fields: [entryVideoJoin.entry],
 | 
							fields: [entryVideoJoin.entryPk],
 | 
				
			||||||
		references: [entries.pk],
 | 
							references: [entries.pk],
 | 
				
			||||||
	}),
 | 
						}),
 | 
				
			||||||
}));
 | 
					}));
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,7 @@ import { collections } from "./controllers/shows/collections";
 | 
				
			|||||||
import { movies } from "./controllers/shows/movies";
 | 
					import { movies } from "./controllers/shows/movies";
 | 
				
			||||||
import { series } from "./controllers/shows/series";
 | 
					import { series } from "./controllers/shows/series";
 | 
				
			||||||
import { showsH } from "./controllers/shows/shows";
 | 
					import { showsH } from "./controllers/shows/shows";
 | 
				
			||||||
 | 
					import { studiosH } from "./controllers/studios";
 | 
				
			||||||
import { videosH } from "./controllers/videos";
 | 
					import { videosH } from "./controllers/videos";
 | 
				
			||||||
import type { KError } from "./models/error";
 | 
					import type { KError } from "./models/error";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -48,4 +49,5 @@ export const app = new Elysia()
 | 
				
			|||||||
	.use(entriesH)
 | 
						.use(entriesH)
 | 
				
			||||||
	.use(seasonsH)
 | 
						.use(seasonsH)
 | 
				
			||||||
	.use(videosH)
 | 
						.use(videosH)
 | 
				
			||||||
 | 
						.use(studiosH)
 | 
				
			||||||
	.use(seed);
 | 
						.use(seed);
 | 
				
			||||||
 | 
				
			|||||||
@ -63,6 +63,7 @@ app
 | 
				
			|||||||
							Can be used for administration or third party apps.
 | 
												Can be used for administration or third party apps.
 | 
				
			||||||
						`,
 | 
											`,
 | 
				
			||||||
					},
 | 
										},
 | 
				
			||||||
 | 
										{ name: "studios", description: "Routes about studios" },
 | 
				
			||||||
				],
 | 
									],
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		}),
 | 
							}),
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@ import { t } from "elysia";
 | 
				
			|||||||
import type { Prettify } from "elysia/dist/types";
 | 
					import type { Prettify } from "elysia/dist/types";
 | 
				
			||||||
import { bubbleImages, duneCollection, registerExamples } from "./examples";
 | 
					import { bubbleImages, duneCollection, registerExamples } from "./examples";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
						DbMetadata,
 | 
				
			||||||
	ExternalId,
 | 
						ExternalId,
 | 
				
			||||||
	Genre,
 | 
						Genre,
 | 
				
			||||||
	Image,
 | 
						Image,
 | 
				
			||||||
@ -33,10 +34,9 @@ const BaseCollection = t.Object({
 | 
				
			|||||||
		}),
 | 
							}),
 | 
				
			||||||
	),
 | 
						),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	createdAt: t.String({ format: "date-time" }),
 | 
					 | 
				
			||||||
	nextRefresh: t.String({ format: "date-time" }),
 | 
						nextRefresh: t.String({ format: "date-time" }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	externalId: ExternalId,
 | 
						externalId: ExternalId(),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const CollectionTranslation = t.Object({
 | 
					export const CollectionTranslation = t.Object({
 | 
				
			||||||
@ -56,6 +56,7 @@ export const Collection = t.Intersect([
 | 
				
			|||||||
	Resource(),
 | 
						Resource(),
 | 
				
			||||||
	CollectionTranslation,
 | 
						CollectionTranslation,
 | 
				
			||||||
	BaseCollection,
 | 
						BaseCollection,
 | 
				
			||||||
 | 
						DbMetadata,
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
export type Collection = Prettify<typeof Collection.static>;
 | 
					export type Collection = Prettify<typeof Collection.static>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -68,13 +69,7 @@ export const FullCollection = t.Intersect([
 | 
				
			|||||||
export type FullCollection = Prettify<typeof FullCollection.static>;
 | 
					export type FullCollection = Prettify<typeof FullCollection.static>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const SeedCollection = t.Intersect([
 | 
					export const SeedCollection = t.Intersect([
 | 
				
			||||||
	t.Omit(BaseCollection, [
 | 
						t.Omit(BaseCollection, ["kind", "startAir", "endAir", "nextRefresh"]),
 | 
				
			||||||
		"kind",
 | 
					 | 
				
			||||||
		"startAir",
 | 
					 | 
				
			||||||
		"endAir",
 | 
					 | 
				
			||||||
		"createdAt",
 | 
					 | 
				
			||||||
		"nextRefresh",
 | 
					 | 
				
			||||||
	]),
 | 
					 | 
				
			||||||
	t.Object({
 | 
						t.Object({
 | 
				
			||||||
		slug: t.String({ format: "slug" }),
 | 
							slug: t.String({ format: "slug" }),
 | 
				
			||||||
		translations: TranslationRecord(
 | 
							translations: TranslationRecord(
 | 
				
			||||||
 | 
				
			|||||||
@ -12,7 +12,6 @@ export const BaseEntry = () =>
 | 
				
			|||||||
		),
 | 
							),
 | 
				
			||||||
		thumbnail: t.Nullable(Image),
 | 
							thumbnail: t.Nullable(Image),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		createdAt: t.String({ format: "date-time" }),
 | 
					 | 
				
			||||||
		nextRefresh: t.String({ format: "date-time" }),
 | 
							nextRefresh: t.String({ format: "date-time" }),
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,13 @@
 | 
				
			|||||||
import { t } from "elysia";
 | 
					import { t } from "elysia";
 | 
				
			||||||
import type { Prettify } from "~/utils";
 | 
					import type { Prettify } from "~/utils";
 | 
				
			||||||
import { bubbleImages, madeInAbyss, registerExamples } from "../examples";
 | 
					import { bubbleImages, madeInAbyss, registerExamples } from "../examples";
 | 
				
			||||||
import { EpisodeId, Resource, SeedImage, TranslationRecord } from "../utils";
 | 
					import {
 | 
				
			||||||
 | 
						DbMetadata,
 | 
				
			||||||
 | 
						EpisodeId,
 | 
				
			||||||
 | 
						Resource,
 | 
				
			||||||
 | 
						SeedImage,
 | 
				
			||||||
 | 
						TranslationRecord,
 | 
				
			||||||
 | 
					} from "../utils";
 | 
				
			||||||
import { BaseEntry, EntryTranslation } from "./base-entry";
 | 
					import { BaseEntry, EntryTranslation } from "./base-entry";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const BaseEpisode = t.Intersect([
 | 
					export const BaseEpisode = t.Intersect([
 | 
				
			||||||
@ -19,11 +25,12 @@ export const Episode = t.Intersect([
 | 
				
			|||||||
	Resource(),
 | 
						Resource(),
 | 
				
			||||||
	EntryTranslation(),
 | 
						EntryTranslation(),
 | 
				
			||||||
	BaseEpisode,
 | 
						BaseEpisode,
 | 
				
			||||||
 | 
						DbMetadata,
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
export type Episode = Prettify<typeof Episode.static>;
 | 
					export type Episode = Prettify<typeof Episode.static>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const SeedEpisode = t.Intersect([
 | 
					export const SeedEpisode = t.Intersect([
 | 
				
			||||||
	t.Omit(BaseEpisode, ["thumbnail", "createdAt", "nextRefresh"]),
 | 
						t.Omit(BaseEpisode, ["thumbnail", "nextRefresh"]),
 | 
				
			||||||
	t.Object({
 | 
						t.Object({
 | 
				
			||||||
		thumbnail: t.Nullable(SeedImage),
 | 
							thumbnail: t.Nullable(SeedImage),
 | 
				
			||||||
		translations: TranslationRecord(EntryTranslation()),
 | 
							translations: TranslationRecord(EntryTranslation()),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import { t } from "elysia";
 | 
					import { t } from "elysia";
 | 
				
			||||||
import { type Prettify, comment } from "~/utils";
 | 
					import { type Prettify, comment } from "~/utils";
 | 
				
			||||||
import { madeInAbyss, registerExamples } from "../examples";
 | 
					import { madeInAbyss, registerExamples } from "../examples";
 | 
				
			||||||
import { SeedImage } from "../utils";
 | 
					import { DbMetadata, SeedImage } from "../utils";
 | 
				
			||||||
import { Resource } from "../utils/resource";
 | 
					import { Resource } from "../utils/resource";
 | 
				
			||||||
import { BaseEntry } from "./base-entry";
 | 
					import { BaseEntry } from "./base-entry";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -31,11 +31,11 @@ export const BaseExtra = t.Intersect(
 | 
				
			|||||||
	},
 | 
						},
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const Extra = t.Intersect([Resource(), BaseExtra]);
 | 
					export const Extra = t.Intersect([Resource(), BaseExtra, DbMetadata]);
 | 
				
			||||||
export type Extra = Prettify<typeof Extra.static>;
 | 
					export type Extra = Prettify<typeof Extra.static>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const SeedExtra = t.Intersect([
 | 
					export const SeedExtra = t.Intersect([
 | 
				
			||||||
	t.Omit(BaseExtra, ["thumbnail", "createdAt"]),
 | 
						t.Omit(BaseExtra, ["thumbnail"]),
 | 
				
			||||||
	t.Object({
 | 
						t.Object({
 | 
				
			||||||
		slug: t.String({ format: "slug" }),
 | 
							slug: t.String({ format: "slug" }),
 | 
				
			||||||
		thumbnail: t.Nullable(SeedImage),
 | 
							thumbnail: t.Nullable(SeedImage),
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@ import { t } from "elysia";
 | 
				
			|||||||
import { type Prettify, comment } from "~/utils";
 | 
					import { type Prettify, comment } from "~/utils";
 | 
				
			||||||
import { bubbleImages, madeInAbyss, registerExamples } from "../examples";
 | 
					import { bubbleImages, madeInAbyss, registerExamples } from "../examples";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
						DbMetadata,
 | 
				
			||||||
	ExternalId,
 | 
						ExternalId,
 | 
				
			||||||
	Image,
 | 
						Image,
 | 
				
			||||||
	Resource,
 | 
						Resource,
 | 
				
			||||||
@ -18,7 +19,7 @@ export const BaseMovieEntry = t.Intersect(
 | 
				
			|||||||
				minimum: 1,
 | 
									minimum: 1,
 | 
				
			||||||
				description: "Absolute playback order. Can be mixed with episodes.",
 | 
									description: "Absolute playback order. Can be mixed with episodes.",
 | 
				
			||||||
			}),
 | 
								}),
 | 
				
			||||||
			externalId: ExternalId,
 | 
								externalId: ExternalId(),
 | 
				
			||||||
		}),
 | 
							}),
 | 
				
			||||||
		BaseEntry(),
 | 
							BaseEntry(),
 | 
				
			||||||
	],
 | 
						],
 | 
				
			||||||
@ -42,11 +43,12 @@ export const MovieEntry = t.Intersect([
 | 
				
			|||||||
	Resource(),
 | 
						Resource(),
 | 
				
			||||||
	MovieEntryTranslation,
 | 
						MovieEntryTranslation,
 | 
				
			||||||
	BaseMovieEntry,
 | 
						BaseMovieEntry,
 | 
				
			||||||
 | 
						DbMetadata,
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
export type MovieEntry = Prettify<typeof MovieEntry.static>;
 | 
					export type MovieEntry = Prettify<typeof MovieEntry.static>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const SeedMovieEntry = t.Intersect([
 | 
					export const SeedMovieEntry = t.Intersect([
 | 
				
			||||||
	t.Omit(BaseMovieEntry, ["thumbnail", "createdAt", "nextRefresh"]),
 | 
						t.Omit(BaseMovieEntry, ["thumbnail", "nextRefresh"]),
 | 
				
			||||||
	t.Object({
 | 
						t.Object({
 | 
				
			||||||
		slug: t.Optional(t.String({ format: "slug" })),
 | 
							slug: t.Optional(t.String({ format: "slug" })),
 | 
				
			||||||
		thumbnail: t.Nullable(SeedImage),
 | 
							thumbnail: t.Nullable(SeedImage),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,13 @@
 | 
				
			|||||||
import { t } from "elysia";
 | 
					import { t } from "elysia";
 | 
				
			||||||
import { type Prettify, comment } from "~/utils";
 | 
					import { type Prettify, comment } from "~/utils";
 | 
				
			||||||
import { bubbleImages, madeInAbyss, registerExamples } from "../examples";
 | 
					import { bubbleImages, madeInAbyss, registerExamples } from "../examples";
 | 
				
			||||||
import { EpisodeId, Resource, SeedImage, TranslationRecord } from "../utils";
 | 
					import {
 | 
				
			||||||
 | 
						DbMetadata,
 | 
				
			||||||
 | 
						EpisodeId,
 | 
				
			||||||
 | 
						Resource,
 | 
				
			||||||
 | 
						SeedImage,
 | 
				
			||||||
 | 
						TranslationRecord,
 | 
				
			||||||
 | 
					} from "../utils";
 | 
				
			||||||
import { BaseEntry, EntryTranslation } from "./base-entry";
 | 
					import { BaseEntry, EntryTranslation } from "./base-entry";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const BaseSpecial = t.Intersect(
 | 
					export const BaseSpecial = t.Intersect(
 | 
				
			||||||
@ -29,11 +35,12 @@ export const Special = t.Intersect([
 | 
				
			|||||||
	Resource(),
 | 
						Resource(),
 | 
				
			||||||
	EntryTranslation(),
 | 
						EntryTranslation(),
 | 
				
			||||||
	BaseSpecial,
 | 
						BaseSpecial,
 | 
				
			||||||
 | 
						DbMetadata,
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
export type Special = Prettify<typeof Special.static>;
 | 
					export type Special = Prettify<typeof Special.static>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const SeedSpecial = t.Intersect([
 | 
					export const SeedSpecial = t.Intersect([
 | 
				
			||||||
	t.Omit(BaseSpecial, ["thumbnail", "createdAt", "nextRefresh"]),
 | 
						t.Omit(BaseSpecial, ["thumbnail", "nextRefresh"]),
 | 
				
			||||||
	t.Object({
 | 
						t.Object({
 | 
				
			||||||
		thumbnail: t.Nullable(SeedImage),
 | 
							thumbnail: t.Nullable(SeedImage),
 | 
				
			||||||
		translations: TranslationRecord(EntryTranslation()),
 | 
							translations: TranslationRecord(EntryTranslation()),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,7 @@
 | 
				
			|||||||
import { t } from "elysia";
 | 
					import { t } from "elysia";
 | 
				
			||||||
import { type Prettify, comment } from "~/utils";
 | 
					import { type Prettify, comment } from "~/utils";
 | 
				
			||||||
import { bubbleImages, registerExamples } from "../examples";
 | 
					import { bubbleImages, registerExamples, youtubeExample } from "../examples";
 | 
				
			||||||
import { youtubeExample } from "../examples/others";
 | 
					import { DbMetadata, Resource } from "../utils";
 | 
				
			||||||
import { Resource } from "../utils/resource";
 | 
					 | 
				
			||||||
import { BaseEntry, EntryTranslation } from "./base-entry";
 | 
					import { BaseEntry, EntryTranslation } from "./base-entry";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const BaseUnknownEntry = t.Intersect(
 | 
					export const BaseUnknownEntry = t.Intersect(
 | 
				
			||||||
@ -28,6 +27,7 @@ export const UnknownEntry = t.Intersect([
 | 
				
			|||||||
	Resource(),
 | 
						Resource(),
 | 
				
			||||||
	UnknownEntryTranslation,
 | 
						UnknownEntryTranslation,
 | 
				
			||||||
	BaseUnknownEntry,
 | 
						BaseUnknownEntry,
 | 
				
			||||||
 | 
						DbMetadata,
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
export type UnknownEntry = Prettify<typeof UnknownEntry.static>;
 | 
					export type UnknownEntry = Prettify<typeof UnknownEntry.static>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,7 @@ export const bubbleVideo: Video = {
 | 
				
			|||||||
	part: null,
 | 
						part: null,
 | 
				
			||||||
	version: 1,
 | 
						version: 1,
 | 
				
			||||||
	createdAt: "2024-11-23T15:01:24.968Z",
 | 
						createdAt: "2024-11-23T15:01:24.968Z",
 | 
				
			||||||
 | 
						updatedAt: "2024-11-23T15:01:24.968Z",
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const bubble: SeedMovie = {
 | 
					export const bubble: SeedMovie = {
 | 
				
			||||||
@ -60,6 +61,7 @@ export const bubble: SeedMovie = {
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	videos: [bubbleVideo.id],
 | 
						videos: [bubbleVideo.id],
 | 
				
			||||||
 | 
						studios: [],
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const bubbleImages = {
 | 
					export const bubbleImages = {
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,7 @@ export const dune1984Video: Video = {
 | 
				
			|||||||
	part: null,
 | 
						part: null,
 | 
				
			||||||
	version: 1,
 | 
						version: 1,
 | 
				
			||||||
	createdAt: "2024-12-02T11:45:12.968Z",
 | 
						createdAt: "2024-12-02T11:45:12.968Z",
 | 
				
			||||||
 | 
						updatedAt: "2024-12-02T11:45:12.968Z",
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const dune1984: SeedMovie = {
 | 
					export const dune1984: SeedMovie = {
 | 
				
			||||||
@ -47,6 +48,7 @@ export const dune1984: SeedMovie = {
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	videos: [dune1984Video.id],
 | 
						videos: [dune1984Video.id],
 | 
				
			||||||
 | 
						studios: [],
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const dune1984Images = {
 | 
					export const dune1984Images = {
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,7 @@ export const duneVideo: Video = {
 | 
				
			|||||||
	part: null,
 | 
						part: null,
 | 
				
			||||||
	version: 1,
 | 
						version: 1,
 | 
				
			||||||
	createdAt: "2024-12-02T10:10:24.968Z",
 | 
						createdAt: "2024-12-02T10:10:24.968Z",
 | 
				
			||||||
 | 
						updatedAt: "2024-12-02T10:10:24.968Z",
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const dune: SeedMovie = {
 | 
					export const dune: SeedMovie = {
 | 
				
			||||||
@ -47,6 +48,7 @@ export const dune: SeedMovie = {
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	videos: [duneVideo.id],
 | 
						videos: [duneVideo.id],
 | 
				
			||||||
 | 
						studios: [],
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const duneImages = {
 | 
					export const duneImages = {
 | 
				
			||||||
 | 
				
			|||||||
@ -16,6 +16,7 @@ export const madeInAbyssVideo: Video = {
 | 
				
			|||||||
		from: "guessit",
 | 
							from: "guessit",
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	createdAt: "2024-11-23T15:01:24.968Z",
 | 
						createdAt: "2024-11-23T15:01:24.968Z",
 | 
				
			||||||
 | 
						updatedAt: "2024-11-23T15:01:24.968Z",
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const madeInAbyss = {
 | 
					export const madeInAbyss = {
 | 
				
			||||||
@ -242,4 +243,21 @@ export const madeInAbyss = {
 | 
				
			|||||||
			video: "3cd436ee-01ff-4f45-ba98-654282531234",
 | 
								video: "3cd436ee-01ff-4f45-ba98-654282531234",
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	],
 | 
						],
 | 
				
			||||||
 | 
						studios: [
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								slug: "kinema-citrus",
 | 
				
			||||||
 | 
								translations: {
 | 
				
			||||||
 | 
									en: {
 | 
				
			||||||
 | 
										name: "Kinema Citrus",
 | 
				
			||||||
 | 
										logo: "https://image.tmdb.org/t/p/original/Lf0udeB7OwHoFJ0XIxVwfyGOqE.png",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								externalId: {
 | 
				
			||||||
 | 
									themoviedatabase: {
 | 
				
			||||||
 | 
										dataId: "16738",
 | 
				
			||||||
 | 
										link: "https://www.themoviedb.org/company/16738",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
} satisfies SeedSerie;
 | 
					} satisfies SeedSerie;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,10 @@
 | 
				
			|||||||
import { t } from "elysia";
 | 
					import { t } from "elysia";
 | 
				
			||||||
import type { Prettify } from "~/utils";
 | 
					import type { Prettify } from "~/utils";
 | 
				
			||||||
import { SeedCollection } from "./collections";
 | 
					import { SeedCollection } from "./collections";
 | 
				
			||||||
import { bubble, registerExamples } from "./examples";
 | 
					import { bubble, bubbleImages, registerExamples } from "./examples";
 | 
				
			||||||
import { bubbleImages } from "./examples/bubble";
 | 
					import { SeedStudio, Studio } from "./studio";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
						DbMetadata,
 | 
				
			||||||
	ExternalId,
 | 
						ExternalId,
 | 
				
			||||||
	Genre,
 | 
						Genre,
 | 
				
			||||||
	Image,
 | 
						Image,
 | 
				
			||||||
@ -33,10 +34,9 @@ const BaseMovie = t.Object({
 | 
				
			|||||||
		}),
 | 
							}),
 | 
				
			||||||
	),
 | 
						),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	createdAt: t.String({ format: "date-time" }),
 | 
					 | 
				
			||||||
	nextRefresh: t.String({ format: "date-time" }),
 | 
						nextRefresh: t.String({ format: "date-time" }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	externalId: ExternalId,
 | 
						externalId: ExternalId(),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const MovieTranslation = t.Object({
 | 
					export const MovieTranslation = t.Object({
 | 
				
			||||||
@ -58,6 +58,7 @@ export const Movie = t.Intersect([
 | 
				
			|||||||
	Resource(),
 | 
						Resource(),
 | 
				
			||||||
	MovieTranslation,
 | 
						MovieTranslation,
 | 
				
			||||||
	BaseMovie,
 | 
						BaseMovie,
 | 
				
			||||||
 | 
						DbMetadata,
 | 
				
			||||||
	// t.Object({ isAvailable: t.Boolean() }),
 | 
						// t.Object({ isAvailable: t.Boolean() }),
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
export type Movie = Prettify<typeof Movie.static>;
 | 
					export type Movie = Prettify<typeof Movie.static>;
 | 
				
			||||||
@ -67,12 +68,13 @@ export const FullMovie = t.Intersect([
 | 
				
			|||||||
	t.Object({
 | 
						t.Object({
 | 
				
			||||||
		translations: t.Optional(TranslationRecord(MovieTranslation)),
 | 
							translations: t.Optional(TranslationRecord(MovieTranslation)),
 | 
				
			||||||
		videos: t.Optional(t.Array(Video)),
 | 
							videos: t.Optional(t.Array(Video)),
 | 
				
			||||||
 | 
							studios: t.Optional(t.Array(Studio)),
 | 
				
			||||||
	}),
 | 
						}),
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
export type FullMovie = Prettify<typeof FullMovie.static>;
 | 
					export type FullMovie = Prettify<typeof FullMovie.static>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const SeedMovie = t.Intersect([
 | 
					export const SeedMovie = t.Intersect([
 | 
				
			||||||
	t.Omit(BaseMovie, ["kind", "createdAt", "nextRefresh"]),
 | 
						t.Omit(BaseMovie, ["kind", "nextRefresh"]),
 | 
				
			||||||
	t.Object({
 | 
						t.Object({
 | 
				
			||||||
		slug: t.String({ format: "slug", examples: ["bubble"] }),
 | 
							slug: t.String({ format: "slug", examples: ["bubble"] }),
 | 
				
			||||||
		translations: TranslationRecord(
 | 
							translations: TranslationRecord(
 | 
				
			||||||
@ -88,6 +90,7 @@ export const SeedMovie = t.Intersect([
 | 
				
			|||||||
		),
 | 
							),
 | 
				
			||||||
		videos: t.Optional(t.Array(t.String({ format: "uuid" }))),
 | 
							videos: t.Optional(t.Array(t.String({ format: "uuid" }))),
 | 
				
			||||||
		collection: t.Optional(SeedCollection),
 | 
							collection: t.Optional(SeedCollection),
 | 
				
			||||||
 | 
							studios: t.Array(SeedStudio),
 | 
				
			||||||
	}),
 | 
						}),
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
export type SeedMovie = Prettify<typeof SeedMovie.static>;
 | 
					export type SeedMovie = Prettify<typeof SeedMovie.static>;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
import { t } from "elysia";
 | 
					import { t } from "elysia";
 | 
				
			||||||
import type { Prettify } from "~/utils";
 | 
					import type { Prettify } from "~/utils";
 | 
				
			||||||
import { bubbleImages, madeInAbyss, registerExamples } from "./examples";
 | 
					import { bubbleImages, madeInAbyss, registerExamples } from "./examples";
 | 
				
			||||||
 | 
					import { DbMetadata } from "./utils";
 | 
				
			||||||
import { SeasonId } from "./utils/external-id";
 | 
					import { SeasonId } from "./utils/external-id";
 | 
				
			||||||
import { Image, SeedImage } from "./utils/image";
 | 
					import { Image, SeedImage } from "./utils/image";
 | 
				
			||||||
import { TranslationRecord } from "./utils/language";
 | 
					import { TranslationRecord } from "./utils/language";
 | 
				
			||||||
@ -11,7 +12,6 @@ export const BaseSeason = t.Object({
 | 
				
			|||||||
	startAir: t.Nullable(t.String({ format: "date" })),
 | 
						startAir: t.Nullable(t.String({ format: "date" })),
 | 
				
			||||||
	endAir: t.Nullable(t.String({ format: "date" })),
 | 
						endAir: t.Nullable(t.String({ format: "date" })),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	createdAt: t.String({ format: "date-time" }),
 | 
					 | 
				
			||||||
	nextRefresh: t.String({ format: "date-time" }),
 | 
						nextRefresh: t.String({ format: "date-time" }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	externalId: SeasonId,
 | 
						externalId: SeasonId,
 | 
				
			||||||
@ -27,11 +27,16 @@ export const SeasonTranslation = t.Object({
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
export type SeasonTranslation = typeof SeasonTranslation.static;
 | 
					export type SeasonTranslation = typeof SeasonTranslation.static;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const Season = t.Intersect([Resource(), SeasonTranslation, BaseSeason]);
 | 
					export const Season = t.Intersect([
 | 
				
			||||||
export type Season = typeof Season.static;
 | 
						Resource(),
 | 
				
			||||||
 | 
						SeasonTranslation,
 | 
				
			||||||
 | 
						BaseSeason,
 | 
				
			||||||
 | 
						DbMetadata,
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					export type Season = Prettify<typeof Season.static>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const SeedSeason = t.Intersect([
 | 
					export const SeedSeason = t.Intersect([
 | 
				
			||||||
	t.Omit(BaseSeason, ["createdAt", "nextRefresh"]),
 | 
						t.Omit(BaseSeason, ["nextRefresh"]),
 | 
				
			||||||
	t.Object({
 | 
						t.Object({
 | 
				
			||||||
		translations: TranslationRecord(
 | 
							translations: TranslationRecord(
 | 
				
			||||||
			t.Intersect([
 | 
								t.Intersect([
 | 
				
			||||||
 | 
				
			|||||||
@ -4,11 +4,17 @@ import { SeedCollection } from "./collections";
 | 
				
			|||||||
import { SeedEntry, SeedExtra } from "./entry";
 | 
					import { SeedEntry, SeedExtra } from "./entry";
 | 
				
			||||||
import { bubbleImages, madeInAbyss, registerExamples } from "./examples";
 | 
					import { bubbleImages, madeInAbyss, registerExamples } from "./examples";
 | 
				
			||||||
import { SeedSeason } from "./season";
 | 
					import { SeedSeason } from "./season";
 | 
				
			||||||
import { ExternalId } from "./utils/external-id";
 | 
					import { SeedStudio, Studio } from "./studio";
 | 
				
			||||||
import { Genre } from "./utils/genres";
 | 
					import {
 | 
				
			||||||
import { Image, SeedImage } from "./utils/image";
 | 
						DbMetadata,
 | 
				
			||||||
import { Language, TranslationRecord } from "./utils/language";
 | 
						ExternalId,
 | 
				
			||||||
import { Resource } from "./utils/resource";
 | 
						Genre,
 | 
				
			||||||
 | 
						Image,
 | 
				
			||||||
 | 
						Language,
 | 
				
			||||||
 | 
						Resource,
 | 
				
			||||||
 | 
						SeedImage,
 | 
				
			||||||
 | 
						TranslationRecord,
 | 
				
			||||||
 | 
					} from "./utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const SerieStatus = t.UnionEnum([
 | 
					export const SerieStatus = t.UnionEnum([
 | 
				
			||||||
	"unknown",
 | 
						"unknown",
 | 
				
			||||||
@ -18,7 +24,7 @@ export const SerieStatus = t.UnionEnum([
 | 
				
			|||||||
]);
 | 
					]);
 | 
				
			||||||
export type SerieStatus = typeof SerieStatus.static;
 | 
					export type SerieStatus = typeof SerieStatus.static;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const BaseSerie = t.Object({
 | 
					const BaseSerie = t.Object({
 | 
				
			||||||
	kind: t.Literal("serie"),
 | 
						kind: t.Literal("serie"),
 | 
				
			||||||
	genres: t.Array(Genre),
 | 
						genres: t.Array(Genre),
 | 
				
			||||||
	rating: t.Nullable(t.Integer({ minimum: 0, maximum: 100 })),
 | 
						rating: t.Nullable(t.Integer({ minimum: 0, maximum: 100 })),
 | 
				
			||||||
@ -38,10 +44,9 @@ export const BaseSerie = t.Object({
 | 
				
			|||||||
		}),
 | 
							}),
 | 
				
			||||||
	),
 | 
						),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	createdAt: t.String({ format: "date-time" }),
 | 
					 | 
				
			||||||
	nextRefresh: t.String({ format: "date-time" }),
 | 
						nextRefresh: t.String({ format: "date-time" }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	externalId: ExternalId,
 | 
						externalId: ExternalId(),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const SerieTranslation = t.Object({
 | 
					export const SerieTranslation = t.Object({
 | 
				
			||||||
@ -59,19 +64,25 @@ export const SerieTranslation = t.Object({
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
export type SerieTranslation = typeof SerieTranslation.static;
 | 
					export type SerieTranslation = typeof SerieTranslation.static;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const Serie = t.Intersect([Resource(), SerieTranslation, BaseSerie]);
 | 
					export const Serie = t.Intersect([
 | 
				
			||||||
 | 
						Resource(),
 | 
				
			||||||
 | 
						SerieTranslation,
 | 
				
			||||||
 | 
						BaseSerie,
 | 
				
			||||||
 | 
						DbMetadata,
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
export type Serie = Prettify<typeof Serie.static>;
 | 
					export type Serie = Prettify<typeof Serie.static>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const FullSerie = t.Intersect([
 | 
					export const FullSerie = t.Intersect([
 | 
				
			||||||
	Serie,
 | 
						Serie,
 | 
				
			||||||
	t.Object({
 | 
						t.Object({
 | 
				
			||||||
		translations: t.Optional(TranslationRecord(SerieTranslation)),
 | 
							translations: t.Optional(TranslationRecord(SerieTranslation)),
 | 
				
			||||||
 | 
							studios: t.Optional(t.Array(Studio)),
 | 
				
			||||||
	}),
 | 
						}),
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
export type FullMovie = Prettify<typeof FullSerie.static>;
 | 
					export type FullMovie = Prettify<typeof FullSerie.static>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const SeedSerie = t.Intersect([
 | 
					export const SeedSerie = t.Intersect([
 | 
				
			||||||
	t.Omit(BaseSerie, ["kind", "createdAt", "nextRefresh"]),
 | 
						t.Omit(BaseSerie, ["kind", "nextRefresh"]),
 | 
				
			||||||
	t.Object({
 | 
						t.Object({
 | 
				
			||||||
		slug: t.String({ format: "slug" }),
 | 
							slug: t.String({ format: "slug" }),
 | 
				
			||||||
		translations: TranslationRecord(
 | 
							translations: TranslationRecord(
 | 
				
			||||||
@ -89,6 +100,7 @@ export const SeedSerie = t.Intersect([
 | 
				
			|||||||
		entries: t.Array(SeedEntry),
 | 
							entries: t.Array(SeedEntry),
 | 
				
			||||||
		extras: t.Optional(t.Array(SeedExtra)),
 | 
							extras: t.Optional(t.Array(SeedExtra)),
 | 
				
			||||||
		collection: t.Optional(SeedCollection),
 | 
							collection: t.Optional(SeedCollection),
 | 
				
			||||||
 | 
							studios: t.Array(SeedStudio),
 | 
				
			||||||
	}),
 | 
						}),
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
export type SeedSerie = typeof SeedSerie.static;
 | 
					export type SeedSerie = typeof SeedSerie.static;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										6
									
								
								api/src/models/show.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								api/src/models/show.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					import { t } from "elysia";
 | 
				
			||||||
 | 
					import { Collection } from "./collections";
 | 
				
			||||||
 | 
					import { Movie } from "./movie";
 | 
				
			||||||
 | 
					import { Serie } from "./serie";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Show = t.Union([Movie, Serie, Collection]);
 | 
				
			||||||
							
								
								
									
										41
									
								
								api/src/models/studio.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								api/src/models/studio.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					import { t } from "elysia";
 | 
				
			||||||
 | 
					import type { Prettify } from "elysia/dist/types";
 | 
				
			||||||
 | 
					import { bubbleImages, madeInAbyss, registerExamples } from "./examples";
 | 
				
			||||||
 | 
					import { DbMetadata, ExternalId, Resource, TranslationRecord } from "./utils";
 | 
				
			||||||
 | 
					import { Image, SeedImage } from "./utils/image";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const BaseStudio = t.Object({
 | 
				
			||||||
 | 
						externalId: ExternalId(),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const StudioTranslation = t.Object({
 | 
				
			||||||
 | 
						name: t.String(),
 | 
				
			||||||
 | 
						logo: t.Nullable(Image),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Studio = t.Intersect([
 | 
				
			||||||
 | 
						Resource(),
 | 
				
			||||||
 | 
						StudioTranslation,
 | 
				
			||||||
 | 
						BaseStudio,
 | 
				
			||||||
 | 
						DbMetadata,
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					export type Studio = Prettify<typeof Studio.static>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const SeedStudio = t.Intersect([
 | 
				
			||||||
 | 
						BaseStudio,
 | 
				
			||||||
 | 
						t.Object({
 | 
				
			||||||
 | 
							slug: t.String({ format: "slug" }),
 | 
				
			||||||
 | 
							translations: TranslationRecord(
 | 
				
			||||||
 | 
								t.Intersect([
 | 
				
			||||||
 | 
									t.Omit(StudioTranslation, ["logo"]),
 | 
				
			||||||
 | 
									t.Object({
 | 
				
			||||||
 | 
										logo: t.Nullable(SeedImage),
 | 
				
			||||||
 | 
									}),
 | 
				
			||||||
 | 
								]),
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
						}),
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					export type SeedStudio = Prettify<typeof SeedStudio.static>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ex = madeInAbyss.studios[0];
 | 
				
			||||||
 | 
					registerExamples(Studio, { ...ex, ...ex.translations.en, ...bubbleImages });
 | 
				
			||||||
							
								
								
									
										6
									
								
								api/src/models/utils/db-metadata.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								api/src/models/utils/db-metadata.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					import { t } from "elysia";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const DbMetadata = t.Object({
 | 
				
			||||||
 | 
						createdAt: t.String({ format: "date-time" }),
 | 
				
			||||||
 | 
						updatedAt: t.String({ format: "date-time" }),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@ -1,14 +1,14 @@
 | 
				
			|||||||
import { t } from "elysia";
 | 
					import { t } from "elysia";
 | 
				
			||||||
import { comment } from "../../utils";
 | 
					import { comment } from "../../utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const ExternalId = t.Record(
 | 
					export const ExternalId = () =>
 | 
				
			||||||
	t.String(),
 | 
						t.Record(
 | 
				
			||||||
	t.Object({
 | 
							t.String(),
 | 
				
			||||||
		dataId: t.String(),
 | 
							t.Object({
 | 
				
			||||||
		link: t.Nullable(t.String({ format: "uri" })),
 | 
								dataId: t.String(),
 | 
				
			||||||
	}),
 | 
								link: t.Nullable(t.String({ format: "uri" })),
 | 
				
			||||||
);
 | 
							}),
 | 
				
			||||||
export type ExternalId = typeof ExternalId.static;
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const EpisodeId = t.Record(
 | 
					export const EpisodeId = t.Record(
 | 
				
			||||||
	t.String(),
 | 
						t.String(),
 | 
				
			||||||
 | 
				
			|||||||
@ -7,3 +7,4 @@ export * from "./filters";
 | 
				
			|||||||
export * from "./page";
 | 
					export * from "./page";
 | 
				
			||||||
export * from "./sort";
 | 
					export * from "./sort";
 | 
				
			||||||
export * from "./keyset-paginate";
 | 
					export * from "./keyset-paginate";
 | 
				
			||||||
 | 
					export * from "./db-metadata";
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,9 @@ import {
 | 
				
			|||||||
	type TSchema,
 | 
						type TSchema,
 | 
				
			||||||
	type TString,
 | 
						type TString,
 | 
				
			||||||
} from "@sinclair/typebox";
 | 
					} from "@sinclair/typebox";
 | 
				
			||||||
 | 
					import { type Column, type Table, eq, sql } from "drizzle-orm";
 | 
				
			||||||
import { t } from "elysia";
 | 
					import { t } from "elysia";
 | 
				
			||||||
 | 
					import { sqlarr } from "~/db/utils";
 | 
				
			||||||
import { comment } from "../../utils";
 | 
					import { comment } from "../../utils";
 | 
				
			||||||
import { KErrorT } from "../error";
 | 
					import { KErrorT } from "../error";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -106,3 +108,19 @@ export const AcceptLanguage = ({
 | 
				
			|||||||
		`
 | 
							`
 | 
				
			||||||
				: ""),
 | 
									: ""),
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const selectTranslationQuery = (
 | 
				
			||||||
 | 
						translationTable: Table & { language: Column },
 | 
				
			||||||
 | 
						languages: string[],
 | 
				
			||||||
 | 
					) => ({
 | 
				
			||||||
 | 
						columns: {
 | 
				
			||||||
 | 
							pk: false,
 | 
				
			||||||
 | 
						} as const,
 | 
				
			||||||
 | 
						where: !languages.includes("*")
 | 
				
			||||||
 | 
							? eq(translationTable.language, sql`any(${sqlarr(languages)})`)
 | 
				
			||||||
 | 
							: undefined,
 | 
				
			||||||
 | 
						orderBy: [
 | 
				
			||||||
 | 
							sql`array_position(${sqlarr(languages)}, ${translationTable.language})`,
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
						limit: 1,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,9 @@
 | 
				
			|||||||
import { type TSchema, t } from "elysia";
 | 
					import { t } from "elysia";
 | 
				
			||||||
import { comment } from "../utils";
 | 
					import { type Prettify, comment } from "~/utils";
 | 
				
			||||||
import { bubbleVideo, registerExamples } from "./examples";
 | 
					import { bubbleVideo, registerExamples } from "./examples";
 | 
				
			||||||
 | 
					import { DbMetadata, Resource } from "./utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const Video = t.Object({
 | 
					export const SeedVideo = t.Object({
 | 
				
			||||||
	id: t.String({ format: "uuid" }),
 | 
					 | 
				
			||||||
	slug: t.String({ format: "slug" }),
 | 
					 | 
				
			||||||
	path: t.String(),
 | 
						path: t.String(),
 | 
				
			||||||
	rendering: t.String({
 | 
						rendering: t.String({
 | 
				
			||||||
		description: comment`
 | 
							description: comment`
 | 
				
			||||||
@ -30,8 +29,6 @@ export const Video = t.Object({
 | 
				
			|||||||
			"Kyoo will prefer playing back the highest `version` number if there are multiples rendering.",
 | 
								"Kyoo will prefer playing back the highest `version` number if there are multiples rendering.",
 | 
				
			||||||
	}),
 | 
						}),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	createdAt: t.String({ format: "date-time" }),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	guess: t.Optional(
 | 
						guess: t.Optional(
 | 
				
			||||||
		t.Recursive((Self) =>
 | 
							t.Recursive((Self) =>
 | 
				
			||||||
			t.Object(
 | 
								t.Object(
 | 
				
			||||||
@ -69,8 +66,9 @@ export const Video = t.Object({
 | 
				
			|||||||
		),
 | 
							),
 | 
				
			||||||
	),
 | 
						),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
export type Video = typeof Video.static;
 | 
					 | 
				
			||||||
registerExamples(Video, bubbleVideo);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const SeedVideo = t.Omit(Video, ["id", "slug", "createdAt"]);
 | 
					 | 
				
			||||||
export type SeedVideo = typeof SeedVideo.static;
 | 
					export type SeedVideo = typeof SeedVideo.static;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Video = t.Intersect([Resource(), SeedVideo, DbMetadata]);
 | 
				
			||||||
 | 
					export type Video = Prettify<typeof Video.static>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					registerExamples(Video, bubbleVideo);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
export * from "./movies-helper";
 | 
					export * from "./movies-helper";
 | 
				
			||||||
export * from "./series-helper";
 | 
					export * from "./series-helper";
 | 
				
			||||||
 | 
					export * from "./studio-helper";
 | 
				
			||||||
export * from "./videos-helper";
 | 
					export * from "./videos-helper";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export * from "~/elysia";
 | 
					export * from "~/elysia";
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,10 @@ import type { SeedMovie } from "~/models/movie";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export const getMovie = async (
 | 
					export const getMovie = async (
 | 
				
			||||||
	id: string,
 | 
						id: string,
 | 
				
			||||||
	{ langs, ...query }: { langs?: string; preferOriginal?: boolean },
 | 
						{
 | 
				
			||||||
 | 
							langs,
 | 
				
			||||||
 | 
							...query
 | 
				
			||||||
 | 
						}: { langs?: string; preferOriginal?: boolean; with?: string[] },
 | 
				
			||||||
) => {
 | 
					) => {
 | 
				
			||||||
	const resp = await app.handle(
 | 
						const resp = await app.handle(
 | 
				
			||||||
		new Request(buildUrl(`movies/${id}`, query), {
 | 
							new Request(buildUrl(`movies/${id}`, query), {
 | 
				
			||||||
 | 
				
			|||||||
@ -16,6 +16,27 @@ export const createSerie = async (serie: SeedSerie) => {
 | 
				
			|||||||
	return [resp, body] as const;
 | 
						return [resp, body] as const;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getSerie = async (
 | 
				
			||||||
 | 
						id: string,
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							langs,
 | 
				
			||||||
 | 
							...query
 | 
				
			||||||
 | 
						}: { langs?: string; preferOriginal?: boolean; with?: string[] },
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
 | 
						const resp = await app.handle(
 | 
				
			||||||
 | 
							new Request(buildUrl(`series/${id}`, query), {
 | 
				
			||||||
 | 
								method: "GET",
 | 
				
			||||||
 | 
								headers: langs
 | 
				
			||||||
 | 
									? {
 | 
				
			||||||
 | 
											"Accept-Language": langs,
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									: {},
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
						const body = await resp.json();
 | 
				
			||||||
 | 
						return [resp, body] as const;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getSeasons = async (
 | 
					export const getSeasons = async (
 | 
				
			||||||
	serie: string,
 | 
						serie: string,
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										49
									
								
								api/tests/helpers/studio-helper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								api/tests/helpers/studio-helper.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					import { buildUrl } from "tests/utils";
 | 
				
			||||||
 | 
					import { app } from "~/elysia";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getStudio = async (
 | 
				
			||||||
 | 
						id: string,
 | 
				
			||||||
 | 
						{ langs, ...query }: { langs?: string; preferOriginal?: boolean },
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
 | 
						const resp = await app.handle(
 | 
				
			||||||
 | 
							new Request(buildUrl(`studios/${id}`, query), {
 | 
				
			||||||
 | 
								method: "GET",
 | 
				
			||||||
 | 
								headers: langs
 | 
				
			||||||
 | 
									? {
 | 
				
			||||||
 | 
											"Accept-Language": langs,
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									: {},
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
						const body = await resp.json();
 | 
				
			||||||
 | 
						return [resp, body] as const;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getShowsByStudio = async (
 | 
				
			||||||
 | 
						studio: string,
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							langs,
 | 
				
			||||||
 | 
							...opts
 | 
				
			||||||
 | 
						}: {
 | 
				
			||||||
 | 
							filter?: string;
 | 
				
			||||||
 | 
							limit?: number;
 | 
				
			||||||
 | 
							after?: string;
 | 
				
			||||||
 | 
							sort?: string | string[];
 | 
				
			||||||
 | 
							query?: string;
 | 
				
			||||||
 | 
							langs?: string;
 | 
				
			||||||
 | 
							preferOriginal?: boolean;
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
 | 
						const resp = await app.handle(
 | 
				
			||||||
 | 
							new Request(buildUrl(`studios/${studio}/shows`, opts), {
 | 
				
			||||||
 | 
								method: "GET",
 | 
				
			||||||
 | 
								headers: langs
 | 
				
			||||||
 | 
									? {
 | 
				
			||||||
 | 
											"Accept-Language": langs,
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									: {},
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
						const body = await resp.json();
 | 
				
			||||||
 | 
						return [resp, body] as const;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -41,6 +41,7 @@ describe("with a null value", () => {
 | 
				
			|||||||
			airDate: null,
 | 
								airDate: null,
 | 
				
			||||||
			originalLanguage: null,
 | 
								originalLanguage: null,
 | 
				
			||||||
			externalId: {},
 | 
								externalId: {},
 | 
				
			||||||
 | 
								studios: [],
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										64
									
								
								api/tests/series/studios.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								api/tests/series/studios.test.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
				
			|||||||
 | 
					import { beforeAll, describe, expect, it } from "bun:test";
 | 
				
			||||||
 | 
					import { getSerie, getShowsByStudio, getStudio } from "tests/helpers";
 | 
				
			||||||
 | 
					import { expectStatus } from "tests/utils";
 | 
				
			||||||
 | 
					import { seedSerie } from "~/controllers/seed/series";
 | 
				
			||||||
 | 
					import { madeInAbyss } from "~/models/examples";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					beforeAll(async () => {
 | 
				
			||||||
 | 
						await seedSerie(madeInAbyss);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe("Get by studio", () => {
 | 
				
			||||||
 | 
						it("Invalid slug", async () => {
 | 
				
			||||||
 | 
							const [resp, body] = await getShowsByStudio("sotneuhn", { langs: "en" });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							expectStatus(resp, body).toBe(404);
 | 
				
			||||||
 | 
							expect(body).toMatchObject({
 | 
				
			||||||
 | 
								status: 404,
 | 
				
			||||||
 | 
								message: expect.any(String),
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						it("Get serie from studio", async () => {
 | 
				
			||||||
 | 
							const [resp, body] = await getShowsByStudio(madeInAbyss.studios[0].slug, {
 | 
				
			||||||
 | 
								langs: "en",
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							expectStatus(resp, body).toBe(200);
 | 
				
			||||||
 | 
							expect(body.items).toBeArrayOfSize(1);
 | 
				
			||||||
 | 
							expect(body.items[0].slug).toBe(madeInAbyss.slug);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe("Get a studio", () => {
 | 
				
			||||||
 | 
						it("Invalid slug", async () => {
 | 
				
			||||||
 | 
							const [resp, body] = await getStudio("sotneuhn", { langs: "en" });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							expectStatus(resp, body).toBe(404);
 | 
				
			||||||
 | 
							expect(body).toMatchObject({
 | 
				
			||||||
 | 
								status: 404,
 | 
				
			||||||
 | 
								message: expect.any(String),
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						it("Get by id", async () => {
 | 
				
			||||||
 | 
							const slug = madeInAbyss.studios[0].slug;
 | 
				
			||||||
 | 
							const [resp, body] = await getStudio(slug, { langs: "en" });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							expectStatus(resp, body).toBe(200);
 | 
				
			||||||
 | 
							expect(body.slug).toBe(slug);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						it("Get using /shows?with=", async () => {
 | 
				
			||||||
 | 
							const [resp, body] = await getSerie(madeInAbyss.slug, {
 | 
				
			||||||
 | 
								langs: "en",
 | 
				
			||||||
 | 
								with: ["studios"],
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							expectStatus(resp, body).toBe(200);
 | 
				
			||||||
 | 
							expect(body.slug).toBe(madeInAbyss.slug);
 | 
				
			||||||
 | 
							expect(body.studios).toBeArrayOfSize(1);
 | 
				
			||||||
 | 
							const studio = madeInAbyss.studios[0];
 | 
				
			||||||
 | 
							expect(body.studios[0]).toMatchObject({
 | 
				
			||||||
 | 
								slug: studio.slug,
 | 
				
			||||||
 | 
								name: studio.translations.en.name,
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user