A personal media tracker built on the AT Protocol opnshelf.xyz
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add credits and actors

+412 -62
+1 -3
apps/mobile/app/(tabs)/shelf.tsx
··· 16 16 CheckCircle2, 17 17 } from "lucide-react-native"; 18 18 import { useCallback } from "react"; 19 - import { Pressable, StyleSheet, Text, View, TouchableOpacity } from "react-native"; 19 + import { StyleSheet, Text, View, TouchableOpacity } from "react-native"; 20 20 import { SafeAreaView } from "react-native-safe-area-context"; 21 21 import { useAuth } from "@/contexts/auth"; 22 22 import { useToast } from "@/contexts/toast"; ··· 42 42 .trim() 43 43 .replace(/\s+/g, "-"); 44 44 } 45 - 46 - const AnimatedPressable = Animated.createAnimatedComponent(Pressable); 47 45 48 46 // Spinning loader component 49 47 const SpinningLoader = ({ size, color }: { size: number; color: string }) => {
+132 -1
apps/mobile/app/movie/[id].tsx
··· 9 9 moviesControllerMarkWatchedMutation, 10 10 moviesControllerUnmarkWatchedMutation, 11 11 } from "@opnshelf/api"; 12 - import type { TmdbMovieDetailDto, WatchHistoryItemDto } from "@opnshelf/api"; 12 + import type { TmdbCastDto, TmdbCrewDto, TmdbMovieDetailDto, WatchHistoryItemDto } from "@opnshelf/api"; 13 13 import DateTimePicker, { DateTimePickerEvent } from "@react-native-community/datetimepicker"; 14 14 import { useLocalSearchParams, useRouter } from "expo-router"; 15 15 import { Image } from "expo-image"; ··· 541 541 </View> 542 542 </View> 543 543 )} 544 + 545 + {/* Cast */} 546 + {movie?.credits?.cast && movie.credits.cast.length > 0 && ( 547 + <View style={styles.section}> 548 + <Text style={[styles.sectionTitle, { color: movieColors.primary }]}> 549 + Cast 550 + </Text> 551 + <ScrollView 552 + horizontal 553 + showsHorizontalScrollIndicator={false} 554 + contentContainerStyle={styles.castScrollContent} 555 + > 556 + {movie.credits.cast.map((person: TmdbCastDto) => ( 557 + <TouchableOpacity 558 + key={person.id} 559 + style={styles.castCard} 560 + activeOpacity={0.8} 561 + > 562 + <View style={styles.castImageContainer}> 563 + {person.profile_path ? ( 564 + <Image 565 + source={{ uri: `https://image.tmdb.org/t/p/w185${person.profile_path}` }} 566 + style={styles.castImage} 567 + contentFit="cover" 568 + /> 569 + ) : ( 570 + <View style={styles.castImagePlaceholder}> 571 + <Text style={styles.castImagePlaceholderText}>No photo</Text> 572 + </View> 573 + )} 574 + </View> 575 + <Text style={styles.castName} numberOfLines={2}> 576 + {person.name} 577 + </Text> 578 + {person.character && ( 579 + <Text style={[styles.castCharacter, { color: movieColors.muted }]}> 580 + as {person.character} 581 + </Text> 582 + )} 583 + </TouchableOpacity> 584 + ))} 585 + </ScrollView> 586 + </View> 587 + )} 588 + 589 + {/* Crew */} 590 + {movie?.credits?.crew && movie.credits.crew.length > 0 && ( 591 + <View style={styles.section}> 592 + <Text style={[styles.sectionTitle, { color: movieColors.primary }]}> 593 + Crew 594 + </Text> 595 + <View style={styles.crewGrid}> 596 + {movie.credits.crew.map((person: TmdbCrewDto) => ( 597 + <TouchableOpacity 598 + key={`${person.id}-${person.job}`} 599 + style={styles.crewCard} 600 + activeOpacity={0.8} 601 + > 602 + <Text style={styles.crewName} numberOfLines={1}> 603 + {person.name} 604 + </Text> 605 + <Text style={[styles.crewJob, { color: movieColors.muted }]}> 606 + {person.job} 607 + </Text> 608 + </TouchableOpacity> 609 + ))} 610 + </View> 611 + </View> 612 + )} 544 613 </View> 545 614 </ScrollView> 546 615 ··· 986 1055 textAlign: "center", 987 1056 color: "#6b7280", 988 1057 padding: 32, 1058 + }, 1059 + castScrollContent: { 1060 + paddingRight: 16, 1061 + gap: 12, 1062 + }, 1063 + castCard: { 1064 + width: 100, 1065 + }, 1066 + castImageContainer: { 1067 + borderRadius: borderRadius.md, 1068 + overflow: "hidden", 1069 + marginBottom: 8, 1070 + backgroundColor: "#1f2937", 1071 + }, 1072 + castImage: { 1073 + width: 100, 1074 + height: 140, 1075 + }, 1076 + castImagePlaceholder: { 1077 + width: 100, 1078 + height: 140, 1079 + backgroundColor: "#1f2937", 1080 + justifyContent: "center", 1081 + alignItems: "center", 1082 + }, 1083 + castImagePlaceholderText: { 1084 + fontSize: 12, 1085 + color: "#6b7280", 1086 + textAlign: "center", 1087 + paddingHorizontal: 8, 1088 + }, 1089 + castName: { 1090 + fontSize: 13, 1091 + fontWeight: "500", 1092 + color: "#e5e7eb", 1093 + marginBottom: 2, 1094 + }, 1095 + castCharacter: { 1096 + fontSize: 11, 1097 + color: "#6b7280", 1098 + }, 1099 + crewGrid: { 1100 + flexDirection: "row", 1101 + flexWrap: "wrap", 1102 + gap: 8, 1103 + }, 1104 + crewCard: { 1105 + backgroundColor: "#111827", 1106 + borderRadius: borderRadius.md, 1107 + padding: 12, 1108 + flex: 1, 1109 + minWidth: "45%", 1110 + }, 1111 + crewName: { 1112 + fontSize: 14, 1113 + fontWeight: "500", 1114 + color: "#e5e7eb", 1115 + marginBottom: 2, 1116 + }, 1117 + crewJob: { 1118 + fontSize: 12, 1119 + color: "#6b7280", 989 1120 }, 990 1121 });
+99 -9
apps/web/src/routes/movies.$movieId.$title.tsx
··· 7 7 moviesControllerGetUserMoviesQueryKey, 8 8 moviesControllerMarkWatchedMutation, 9 9 moviesControllerUnmarkWatchedMutation, 10 + type TmdbCastDto, 11 + type TmdbCrewDto, 10 12 type TmdbMovieDetailDto, 11 13 type TrackedMovieDto, 12 14 type WatchHistoryItemDto, ··· 341 343 <div className="container mx-auto max-w-6xl"> 342 344 <div className="flex items-end gap-4 md:gap-8"> 343 345 {/* Poster */} 344 - <div className="hidden md:block flex-shrink-0"> 346 + <div className="hidden md:block shrink-0"> 345 347 <div 346 348 className="w-48 lg:w-64 rounded-lg overflow-hidden shadow-2xl" 347 349 style={{ ··· 404 406 405 407 {/* Main Content */} 406 408 <div className="container mx-auto px-4 py-4 max-w-6xl"> 407 - <div className="grid grid-cols-1 md:grid-cols-[300px_1fr] gap-8"> 409 + <div className="grid grid-cols-1 md:grid-cols-[300px_1fr] gap-8 min-w-0"> 408 410 {/* Left Column - Poster (mobile) & Actions */} 409 - <div className="md:hidden"> 411 + <div className="md:hidden min-w-0"> 410 412 <div className="flex gap-4"> 411 413 {posterUrl && ( 412 414 <div 413 - className="w-32 flex-shrink-0 rounded-lg overflow-hidden" 415 + className="w-32 shrink-0 rounded-lg overflow-hidden" 414 416 style={{ 415 417 boxShadow: `0 20px 40px -10px ${colors.primary}40`, 416 418 }} ··· 548 550 </div> 549 551 550 552 {/* Desktop Actions */} 551 - <div className="hidden md:block space-y-4"> 553 + <div className="hidden md:block space-y-4 min-w-0"> 552 554 {user ? ( 553 555 !isWatched ? ( 554 556 <div className="space-y-3"> ··· 671 673 </div> 672 674 673 675 {/* Right Column - Details */} 674 - <div className="space-y-6"> 676 + <div className="space-y-6 min-w-0 w-full"> 675 677 {/* Overview */} 676 678 <section> 677 679 <h2 ··· 680 682 > 681 683 Overview 682 684 </h2> 683 - <p className="text-gray-300 leading-relaxed text-lg"> 685 + <p className="text-gray-300 leading-relaxed text-lg wrap-break-word"> 684 686 {movie?.overview || "No overview available."} 685 687 </p> 686 688 </section> 687 689 688 690 {/* Additional Info */} 689 - <section className="grid grid-cols-2 gap-4"> 691 + <section className="grid grid-cols-2 gap-4 min-w-0"> 690 692 {movie?.release_date && ( 691 693 <div className="p-4 rounded-lg bg-gray-900/50"> 692 694 <span className="text-gray-500 text-sm block mb-1"> ··· 775 777 </div> 776 778 </section> 777 779 )} 780 + 781 + {/* Cast */} 782 + {movie?.credits?.cast && movie.credits.cast.length > 0 && ( 783 + <section className="pt-4 min-w-0"> 784 + <h2 785 + className="text-xl font-semibold mb-4" 786 + style={{ color: colors.primary }} 787 + > 788 + Cast 789 + </h2> 790 + <div className="relative w-full overflow-hidden"> 791 + <div className="flex gap-4 overflow-x-auto pb-4 scrollbar-thin scrollbar-thumb-gray-700 scrollbar-track-transparent w-full pr-8"> 792 + {movie.credits.cast.map((person: TmdbCastDto) => ( 793 + <div 794 + key={person.id} 795 + className="shrink-0 w-32 group cursor-pointer" 796 + > 797 + <div className="relative overflow-hidden rounded-lg bg-gray-900/50 aspect-2/3 mb-2 transition-transform duration-300 group-hover:scale-[1.02]"> 798 + {person.profile_path ? ( 799 + <img 800 + src={`https://image.tmdb.org/t/p/w185${person.profile_path}`} 801 + alt={person.name} 802 + className="w-full h-full object-cover transition-opacity duration-300 group-hover:opacity-90" 803 + loading="lazy" 804 + /> 805 + ) : ( 806 + <div className="w-full h-full bg-gray-800 flex items-center justify-center"> 807 + <span className="text-gray-600 text-xs text-center px-2"> 808 + No photo 809 + </span> 810 + </div> 811 + )} 812 + </div> 813 + <div className="space-y-0.5"> 814 + <p className="text-sm font-medium text-gray-200 line-clamp-2 transition-colors duration-200 group-hover:text-white"> 815 + {person.name} 816 + </p> 817 + {person.character && ( 818 + <p 819 + className="text-xs line-clamp-2" 820 + style={{ color: colors.muted }} 821 + > 822 + as {person.character} 823 + </p> 824 + )} 825 + </div> 826 + </div> 827 + ))} 828 + </div> 829 + <div 830 + className="absolute right-0 top-0 bottom-4 w-16 pointer-events-none" 831 + style={{ 832 + background: `linear-gradient(to left, rgb(3, 7, 18), transparent)`, 833 + }} 834 + /> 835 + </div> 836 + </section> 837 + )} 838 + 839 + {/* Crew */} 840 + {movie?.credits?.crew && movie.credits.crew.length > 0 && ( 841 + <section className="pt-2"> 842 + <h2 843 + className="text-xl font-semibold mb-4" 844 + style={{ color: colors.primary }} 845 + > 846 + Crew 847 + </h2> 848 + <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3"> 849 + {movie.credits.crew.map((person: TmdbCrewDto) => ( 850 + <div 851 + key={`${person.id}-${person.job}`} 852 + className="group p-3 rounded-lg bg-gray-900/30 hover:bg-gray-900/60 transition-all duration-200 cursor-pointer" 853 + > 854 + <p className="text-sm font-medium text-gray-200 line-clamp-1 transition-colors duration-200 group-hover:text-white"> 855 + {person.name} 856 + </p> 857 + <p 858 + className="text-xs mt-0.5" 859 + style={{ color: colors.muted }} 860 + > 861 + {person.job} 862 + </p> 863 + </div> 864 + ))} 865 + </div> 866 + </section> 867 + )} 778 868 </div> 779 869 </div> 780 870 </div> ··· 811 901 }) 812 902 } 813 903 disabled={deleteWatchEntryMutation.isPending} 814 - className="flex-shrink-0 p-2 text-gray-400 hover:text-red-400 hover:bg-red-400/10 rounded-lg transition-colors disabled:opacity-50" 904 + className="shrink-0 p-2 text-gray-400 hover:text-red-400 hover:bg-red-400/10 rounded-lg transition-colors disabled:opacity-50" 815 905 title="Remove this watch" 816 906 > 817 907 {deleteWatchEntryMutation.isPending &&
+45
backend/src/movies/dto/movie.dto.ts
··· 131 131 name: string; 132 132 } 133 133 134 + export class TMDBCastDto { 135 + @ApiProperty() 136 + id: number; 137 + 138 + @ApiProperty() 139 + name: string; 140 + 141 + @ApiPropertyOptional() 142 + character?: string; 143 + 144 + @ApiPropertyOptional() 145 + profile_path?: string; 146 + 147 + @ApiProperty() 148 + order: number; 149 + } 150 + 151 + export class TMDBCrewDto { 152 + @ApiProperty() 153 + id: number; 154 + 155 + @ApiProperty() 156 + name: string; 157 + 158 + @ApiPropertyOptional() 159 + job?: string; 160 + 161 + @ApiPropertyOptional() 162 + department?: string; 163 + 164 + @ApiPropertyOptional() 165 + profile_path?: string; 166 + } 167 + 168 + export class TMDBCreditsDto { 169 + @ApiProperty({ type: [TMDBCastDto] }) 170 + cast: TMDBCastDto[]; 171 + 172 + @ApiProperty({ type: [TMDBCrewDto] }) 173 + crew: TMDBCrewDto[]; 174 + } 175 + 134 176 export class TMDBMovieDetailDto extends TMDBMovieResultDto { 135 177 @ApiPropertyOptional() 136 178 @IsOptional() ··· 149 191 @ApiPropertyOptional({ type: MovieColorsDto }) 150 192 @IsOptional() 151 193 colors?: MovieColorsDto; 194 + 195 + @ApiPropertyOptional({ type: TMDBCreditsDto }) 196 + credits?: TMDBCreditsDto; 152 197 } 153 198 154 199 export class SearchResultsDto {
+7 -1
backend/src/movies/movies.controller.ts
··· 27 27 MovieDto, 28 28 MarkWatchedDto, 29 29 TMDBMovieDetailDto, 30 + WatchHistoryItemDto, 30 31 } from './dto/movie.dto'; 31 32 import { AuthGuard } from '../auth/auth.guard'; 32 33 import type { AuthenticatedRequest } from '../auth/types'; ··· 57 58 // Ensure movie is in database with colors 58 59 const movie = await this.moviesService.upsertMovie(movieData); 59 60 60 - // Return combined data with colors 61 + // Get movie credits 62 + const credits = await this.moviesService.getMovieCredits(movieId); 63 + 64 + // Return combined data with colors and credits 61 65 return { 62 66 ...movieData, 63 67 colors: movie.colors ?? undefined, 68 + credits, 64 69 }; 65 70 } 66 71 ··· 203 208 @ApiResponse({ 204 209 status: 200, 205 210 description: 'Watch history retrieved successfully', 211 + type: [WatchHistoryItemDto], 206 212 }) 207 213 @ApiResponse({ status: 401, description: 'Not authenticated' }) 208 214 async getMovieWatchHistory(
+55
backend/src/movies/movies.service.ts
··· 26 26 total_pages: number; 27 27 } 28 28 29 + export interface TMDBCredits { 30 + cast: { 31 + id: number; 32 + name: string; 33 + character?: string; 34 + profile_path?: string; 35 + order: number; 36 + }[]; 37 + crew: { 38 + id: number; 39 + name: string; 40 + job?: string; 41 + department?: string; 42 + profile_path?: string; 43 + }[]; 44 + } 45 + 29 46 export interface ATSession { 30 47 did: string; 31 48 } ··· 69 86 } 70 87 71 88 return response.json() as Promise<TMDBMovie>; 89 + } 90 + 91 + async getMovieCredits(movieId: string): Promise<TMDBCredits | null> { 92 + const response = await fetch( 93 + `${this.tmdbBaseUrl}/movie/${movieId}/credits?api_key=${this.tmdbApiKey}`, 94 + ); 95 + 96 + if (!response.ok) { 97 + this.logger.warn(`Failed to fetch credits for movie ${movieId}`); 98 + return null; 99 + } 100 + 101 + const data = (await response.json()) as TMDBCredits; 102 + 103 + // Sort cast by order and limit to top 15 104 + const sortedCast = (data.cast || []) 105 + .sort((a, b) => (a.order || 0) - (b.order || 0)) 106 + .slice(0, 15); 107 + 108 + // Filter key crew roles 109 + const keyJobs = [ 110 + 'Director', 111 + 'Producer', 112 + 'Executive Producer', 113 + 'Screenplay', 114 + 'Writer', 115 + 'Director of Photography', 116 + 'Original Music Composer', 117 + 'Composer', 118 + ]; 119 + const filteredCrew = (data.crew || []) 120 + .filter((member) => keyJobs.includes(member.job || '')) 121 + .slice(0, 10); 122 + 123 + return { 124 + cast: sortedCast, 125 + crew: filteredCrew, 126 + }; 72 127 } 73 128 74 129 async getUserMovies(userDid: string) {
+17 -17
packages/api/src/generated/@tanstack/react-query.gen.ts
··· 127 127 return mutationOptions; 128 128 }; 129 129 130 - /** 131 - * Delete a specific watch history entry 132 - */ 133 - export const moviesControllerDeleteWatchHistoryEntryMutation = (options?: Partial<Options<MoviesControllerDeleteWatchHistoryEntryData>>): UseMutationOptions<MoviesControllerDeleteWatchHistoryEntryResponse, DefaultError, Options<MoviesControllerDeleteWatchHistoryEntryData>> => { 134 - const mutationOptions: UseMutationOptions<MoviesControllerDeleteWatchHistoryEntryResponse, DefaultError, Options<MoviesControllerDeleteWatchHistoryEntryData>> = { 135 - mutationFn: async (fnOptions) => { 136 - const { data } = await moviesControllerDeleteWatchHistoryEntry({ 137 - ...options, 138 - ...fnOptions, 139 - throwOnError: true 140 - }); 141 - return data; 142 - } 143 - }; 144 - return mutationOptions; 145 - }; 146 - 147 130 export const moviesControllerGetMovieQueryKey = (options: Options<MoviesControllerGetMovieData>) => createQueryKey('moviesControllerGetMovie', options); 148 131 149 132 /** ··· 179 162 }, 180 163 queryKey: moviesControllerGetMovieWatchHistoryQueryKey(options) 181 164 }); 165 + 166 + /** 167 + * Delete a specific watch history entry 168 + */ 169 + export const moviesControllerDeleteWatchHistoryEntryMutation = (options?: Partial<Options<MoviesControllerDeleteWatchHistoryEntryData>>): UseMutationOptions<MoviesControllerDeleteWatchHistoryEntryResponse, DefaultError, Options<MoviesControllerDeleteWatchHistoryEntryData>> => { 170 + const mutationOptions: UseMutationOptions<MoviesControllerDeleteWatchHistoryEntryResponse, DefaultError, Options<MoviesControllerDeleteWatchHistoryEntryData>> = { 171 + mutationFn: async (fnOptions) => { 172 + const { data } = await moviesControllerDeleteWatchHistoryEntry({ 173 + ...options, 174 + ...fnOptions, 175 + throwOnError: true 176 + }); 177 + return data; 178 + } 179 + }; 180 + return mutationOptions; 181 + }; 182 182 183 183 export const authControllerGetClientMetadataQueryKey = (options?: Options<AuthControllerGetClientMetadataData>) => createQueryKey('authControllerGetClientMetadata', options); 184 184
+1 -1
packages/api/src/generated/core/queryKeySerializer.gen.ts
··· 57 57 * Turns URLSearchParams into a sorted JSON object for deterministic keys. 58 58 */ 59 59 const serializeSearchParams = (params: URLSearchParams): JsonValue => { 60 - const entries = Array.from(params as Iterable<[string, string]>).sort((a, b) => a[0].localeCompare(b[0])); 60 + const entries = Array.from(params.entries()).sort(([a], [b]) => a.localeCompare(b)); 61 61 const result: Record<string, JsonValue> = {}; 62 62 63 63 for (const [key, value] of entries) {
+1 -1
packages/api/src/generated/index.ts
··· 1 1 // This file is auto-generated by @hey-api/openapi-ts 2 2 3 3 export { authControllerCallback, authControllerGetClientMetadata, authControllerLogin, authControllerLogout, authControllerMe, moviesControllerDeleteWatchHistoryEntry, moviesControllerGetMovie, moviesControllerGetMovieDetails, moviesControllerGetMovieWatchHistory, moviesControllerGetUserMovies, moviesControllerMarkWatched, moviesControllerSearchMovies, moviesControllerUnmarkWatched, type Options } from './sdk.gen'; 4 - export type { AuthControllerCallbackData, AuthControllerGetClientMetadataData, AuthControllerGetClientMetadataResponses, AuthControllerLoginData, AuthControllerLogoutData, AuthControllerLogoutResponses, AuthControllerMeData, AuthControllerMeErrors, AuthControllerMeResponse, AuthControllerMeResponses, ClientOptions, MarkWatchedDto, MovieColorsDto, MovieDto, MoviesControllerDeleteWatchHistoryEntryData, MoviesControllerDeleteWatchHistoryEntryErrors, MoviesControllerDeleteWatchHistoryEntryResponse, MoviesControllerDeleteWatchHistoryEntryResponses, MoviesControllerGetMovieData, MoviesControllerGetMovieDetailsData, MoviesControllerGetMovieDetailsResponse, MoviesControllerGetMovieDetailsResponses, MoviesControllerGetMovieResponse, MoviesControllerGetMovieResponses, MoviesControllerGetMovieWatchHistoryData, MoviesControllerGetMovieWatchHistoryErrors, MoviesControllerGetMovieWatchHistoryResponse, MoviesControllerGetMovieWatchHistoryResponses, MoviesControllerGetUserMoviesData, MoviesControllerGetUserMoviesResponse, MoviesControllerGetUserMoviesResponses, MoviesControllerMarkWatchedData, MoviesControllerMarkWatchedErrors, MoviesControllerMarkWatchedResponse, MoviesControllerMarkWatchedResponses, MoviesControllerSearchMoviesData, MoviesControllerSearchMoviesResponse, MoviesControllerSearchMoviesResponses, MoviesControllerUnmarkWatchedData, MoviesControllerUnmarkWatchedErrors, MoviesControllerUnmarkWatchedResponse, MoviesControllerUnmarkWatchedResponses, SearchResultsDto, TmdbGenreDto, TmdbMovieDetailDto, TmdbMovieResultDto, TrackedMovieDto, UserDto, WatchHistoryItemDto } from './types.gen'; 4 + export type { AuthControllerCallbackData, AuthControllerGetClientMetadataData, AuthControllerGetClientMetadataResponses, AuthControllerLoginData, AuthControllerLogoutData, AuthControllerLogoutResponses, AuthControllerMeData, AuthControllerMeErrors, AuthControllerMeResponse, AuthControllerMeResponses, ClientOptions, MarkWatchedDto, MovieColorsDto, MovieDto, MoviesControllerDeleteWatchHistoryEntryData, MoviesControllerDeleteWatchHistoryEntryErrors, MoviesControllerDeleteWatchHistoryEntryResponse, MoviesControllerDeleteWatchHistoryEntryResponses, MoviesControllerGetMovieData, MoviesControllerGetMovieDetailsData, MoviesControllerGetMovieDetailsResponse, MoviesControllerGetMovieDetailsResponses, MoviesControllerGetMovieResponse, MoviesControllerGetMovieResponses, MoviesControllerGetMovieWatchHistoryData, MoviesControllerGetMovieWatchHistoryErrors, MoviesControllerGetMovieWatchHistoryResponse, MoviesControllerGetMovieWatchHistoryResponses, MoviesControllerGetUserMoviesData, MoviesControllerGetUserMoviesResponse, MoviesControllerGetUserMoviesResponses, MoviesControllerMarkWatchedData, MoviesControllerMarkWatchedErrors, MoviesControllerMarkWatchedResponse, MoviesControllerMarkWatchedResponses, MoviesControllerSearchMoviesData, MoviesControllerSearchMoviesResponse, MoviesControllerSearchMoviesResponses, MoviesControllerUnmarkWatchedData, MoviesControllerUnmarkWatchedErrors, MoviesControllerUnmarkWatchedResponse, MoviesControllerUnmarkWatchedResponses, SearchResultsDto, TmdbCastDto, TmdbCreditsDto, TmdbCrewDto, TmdbGenreDto, TmdbMovieDetailDto, TmdbMovieResultDto, TrackedMovieDto, UserDto, WatchHistoryItemDto } from './types.gen';
+54 -29
packages/api/src/generated/types.gen.ts
··· 31 31 muted?: string; 32 32 }; 33 33 34 + export type TmdbCastDto = { 35 + id: number; 36 + name: string; 37 + character?: string; 38 + profile_path?: string; 39 + order: number; 40 + }; 41 + 42 + export type TmdbCrewDto = { 43 + id: number; 44 + name: string; 45 + job?: string; 46 + department?: string; 47 + profile_path?: string; 48 + }; 49 + 50 + export type TmdbCreditsDto = { 51 + cast: Array<TmdbCastDto>; 52 + crew: Array<TmdbCrewDto>; 53 + }; 54 + 34 55 export type TmdbMovieDetailDto = { 35 56 id: number; 36 57 title: string; ··· 43 64 vote_count?: number; 44 65 genres?: Array<TmdbGenreDto>; 45 66 colors?: MovieColorsDto; 67 + credits?: TmdbCreditsDto; 46 68 }; 47 69 48 70 export type MovieDto = { ··· 207 229 208 230 export type MoviesControllerUnmarkWatchedResponse = MoviesControllerUnmarkWatchedResponses[keyof MoviesControllerUnmarkWatchedResponses]; 209 231 210 - export type MoviesControllerDeleteWatchHistoryEntryData = { 211 - body?: never; 212 - path: { 213 - trackedMovieId: string; 214 - }; 215 - query?: never; 216 - url: '/movies/history/{trackedMovieId}'; 217 - }; 218 - 219 - export type MoviesControllerDeleteWatchHistoryEntryErrors = { 220 - /** 221 - * Not authenticated 222 - */ 223 - 401: unknown; 224 - /** 225 - * Tracked movie entry not found 226 - */ 227 - 404: unknown; 228 - }; 229 - 230 - export type MoviesControllerDeleteWatchHistoryEntryResponses = { 231 - /** 232 - * Watch history entry deleted 233 - */ 234 - 204: void; 235 - }; 236 - 237 - export type MoviesControllerDeleteWatchHistoryEntryResponse = MoviesControllerDeleteWatchHistoryEntryResponses[keyof MoviesControllerDeleteWatchHistoryEntryResponses]; 238 - 239 232 export type MoviesControllerGetMovieData = { 240 233 body?: never; 241 234 path: { ··· 282 275 }; 283 276 284 277 export type MoviesControllerGetMovieWatchHistoryResponse = MoviesControllerGetMovieWatchHistoryResponses[keyof MoviesControllerGetMovieWatchHistoryResponses]; 278 + 279 + export type MoviesControllerDeleteWatchHistoryEntryData = { 280 + body?: never; 281 + path: { 282 + /** 283 + * Tracked movie entry ID 284 + */ 285 + trackedMovieId: string; 286 + }; 287 + query?: never; 288 + url: '/movies/history/{trackedMovieId}'; 289 + }; 290 + 291 + export type MoviesControllerDeleteWatchHistoryEntryErrors = { 292 + /** 293 + * Not authenticated 294 + */ 295 + 401: unknown; 296 + /** 297 + * Tracked movie entry not found 298 + */ 299 + 404: unknown; 300 + }; 301 + 302 + export type MoviesControllerDeleteWatchHistoryEntryResponses = { 303 + /** 304 + * Watch history entry deleted 305 + */ 306 + 204: void; 307 + }; 308 + 309 + export type MoviesControllerDeleteWatchHistoryEntryResponse = MoviesControllerDeleteWatchHistoryEntryResponses[keyof MoviesControllerDeleteWatchHistoryEntryResponses]; 285 310 286 311 export type AuthControllerGetClientMetadataData = { 287 312 body?: never;