diff --git a/apps/web/src/api/endpoints.schemas.ts b/apps/web/src/api/endpoints.schemas.ts index f6130087..636d9e31 100644 --- a/apps/web/src/api/endpoints.schemas.ts +++ b/apps/web/src/api/endpoints.schemas.ts @@ -11,6 +11,7 @@ export type PostUsersCreateBody = { email: string; /** @minLength 8 */ password: string; + displayName?: string; }; export type PostUsersCreate201UserSubscriptionType = typeof PostUsersCreate201UserSubscriptionType[keyof typeof PostUsersCreate201UserSubscriptionType]; @@ -27,8 +28,12 @@ export type PostUsersCreate201User = { id?: string; username: string; email: string; + /** @nullable */ + displayName?: string | null; createdAt?: string; /** @nullable */ + deletedAt?: string | null; + /** @nullable */ bannerUrl?: string | null; /** @nullable */ avatarUrl?: string | null; @@ -110,8 +115,12 @@ export type GetUsersUsername200User = { id: string; username: string; email: string; + /** @nullable */ + displayName: string | null; createdAt: string; /** @nullable */ + deletedAt?: string | null; + /** @nullable */ bannerUrl: string | null; /** @nullable */ avatarUrl: string | null; @@ -140,8 +149,12 @@ export type GetUserById200User = { id: string; username: string; email: string; + /** @nullable */ + displayName: string | null; createdAt: string; /** @nullable */ + deletedAt?: string | null; + /** @nullable */ bannerUrl: string | null; /** @nullable */ avatarUrl: string | null; @@ -170,8 +183,12 @@ export type GetMe200User = { id: string; username: string; email: string; + /** @nullable */ + displayName: string | null; createdAt: string; /** @nullable */ + deletedAt?: string | null; + /** @nullable */ bannerUrl: string | null; /** @nullable */ avatarUrl: string | null; @@ -187,19 +204,33 @@ export type GetMe200 = { }; export type PatchUserBody = { + displayName?: string; bannerUrl?: string; avatarUrl?: string; username?: string; biography?: string; }; +export type PatchUser200UserSubscriptionType = typeof PatchUser200UserSubscriptionType[keyof typeof PatchUser200UserSubscriptionType]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const PatchUser200UserSubscriptionType = { + MEMBER: 'MEMBER', + PRO: 'PRO', +} as const; + export type PatchUser200User = { /** @pattern ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$ */ id: string; username: string; email: string; + /** @nullable */ + displayName: string | null; createdAt: string; /** @nullable */ + deletedAt?: string | null; + /** @nullable */ bannerUrl: string | null; /** @nullable */ avatarUrl: string | null; @@ -207,12 +238,24 @@ export type PatchUser200User = { isLegacy: boolean | null; /** @nullable */ biography: string | null; + subscriptionType: PatchUser200UserSubscriptionType; }; export type PatchUser200 = { user: PatchUser200User; }; +export type DeleteUser200 = { + success: boolean; +}; + +/** + * User not found. + */ +export type DeleteUser404 = { + message: string; +}; + export type PatchUserPasswordBody = { password: string; token: string; @@ -231,8 +274,10 @@ export type PatchUserPassword200 = { }; export type UpdateUserPreferencesBody = { - watchProvidersIds: number[]; - watchRegion: string; + watchProvidersIds?: number[]; + watchRegion?: string; + mediaTypes?: string[]; + genreIds?: number[]; }; export type UpdateUserPreferences200UserPreferences = { @@ -244,6 +289,10 @@ export type UpdateUserPreferences200UserPreferences = { watchProvidersIds: number[] | null; /** @nullable */ watchRegion: string | null; + /** @nullable */ + mediaTypes: string[] | null; + /** @nullable */ + genreIds: number[] | null; }; export type UpdateUserPreferences200 = { @@ -262,6 +311,10 @@ export type GetUserPreferences200UserPreferences = { watchProvidersIds: number[] | null; /** @nullable */ watchRegion: string | null; + /** @nullable */ + mediaTypes: string[] | null; + /** @nullable */ + genreIds: number[] | null; } | null; export type GetUserPreferences200 = { @@ -1404,6 +1457,11 @@ export const PutUserItem201UserItemStatus = { DROPPED: 'DROPPED', } as const; +export type PutUserItem201UserItemWatchEntriesItem = { + id: string; + watchedAt: string; +}; + export type PutUserItem201UserItem = { /** @pattern ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$ */ id: string; @@ -1417,7 +1475,13 @@ export type PutUserItem201UserItem = { mediaType: PutUserItem201UserItemMediaType; status: PutUserItem201UserItemStatus; addedAt: string; + /** + * @minimum -2147483648 + * @maximum 2147483647 + */ + position: number; updatedAt: string; + watchEntries?: PutUserItem201UserItemWatchEntriesItem[]; }; export type PutUserItem201 = { @@ -1476,6 +1540,11 @@ export type GetUserItem200UserItem = { mediaType: GetUserItem200UserItemMediaType; status: GetUserItem200UserItemStatus; addedAt: string; + /** + * @minimum -2147483648 + * @maximum 2147483647 + */ + position: number; updatedAt: string; watchEntries?: GetUserItem200UserItemWatchEntriesItem[]; }; @@ -1577,6 +1646,11 @@ export type GetUserItems200UserItemsItem = { mediaType: GetUserItems200UserItemsItemMediaType; status: GetUserItems200UserItemsItemStatus; addedAt: string; + /** + * @minimum -2147483648 + * @maximum 2147483647 + */ + position: number; updatedAt: string; title: string; /** @nullable */ @@ -1626,12 +1700,34 @@ export type GetAllUserItems200UserItemsItem = { * @maximum 2147483647 */ tmdbId: number; + /** + * @minimum -2147483648 + * @maximum 2147483647 + */ + position: number; }; export type GetAllUserItems200 = { userItems: GetAllUserItems200UserItemsItem[]; }; +export type PutUserItemsReorderBodyStatus = typeof PutUserItemsReorderBodyStatus[keyof typeof PutUserItemsReorderBodyStatus]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const PutUserItemsReorderBodyStatus = { + WATCHLIST: 'WATCHLIST', + WATCHED: 'WATCHED', + WATCHING: 'WATCHING', + DROPPED: 'DROPPED', +} as const; + +export type PutUserItemsReorderBody = { + status: PutUserItemsReorderBodyStatus; + /** @minItems 1 */ + orderedIds: string[]; +}; + export type GetUserItemsCountParams = { userId: string; }; @@ -1954,8 +2050,16 @@ export type GetUserIdStats200 = { watchedSeriesCount: number; }; +export type GetUserIdTotalHours200MonthlyHoursItem = { + month: string; + hours: number; +}; + export type GetUserIdTotalHours200 = { totalHours: number; + movieHours: number; + seriesHours: number; + monthlyHours: GetUserIdTotalHours200MonthlyHoursItem[]; }; export type GetUserIdReviewsCount200 = { @@ -2083,6 +2187,11 @@ export type GetUserIdWatchedCountries200 = { export type GetUserIdBestReviewsParams = { language?: GetUserIdBestReviewsLanguage; +/** + * @minimum 1 + * @maximum 100 + */ +limit?: number; }; export type GetUserIdBestReviewsLanguage = typeof GetUserIdBestReviewsLanguage[keyof typeof GetUserIdBestReviewsLanguage]; @@ -2203,6 +2312,7 @@ export const PostImageFolder = { banner: 'banner', avatar: 'avatar', list: 'list', + feedback: 'feedback', } as const; export type PostImageBody = { @@ -3667,4 +3777,57 @@ export type PutWatchEntryId200WatchEntry = { export type PutWatchEntryId200 = { watchEntry: PutWatchEntryId200WatchEntry; -}; \ No newline at end of file +}; + +export type PostFeedbackBodyType = typeof PostFeedbackBodyType[keyof typeof PostFeedbackBodyType]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const PostFeedbackBodyType = { + bug: 'bug', + idea: 'idea', +} as const; + +export type PostFeedbackBody = { + type: PostFeedbackBodyType; + description: string; + /** @nullable */ + screenshotUrl?: string | null; + /** @nullable */ + appVersion?: string | null; + /** @nullable */ + deviceInfo?: string | null; +}; + +export type PostFeedback201FeedbackType = typeof PostFeedback201FeedbackType[keyof typeof PostFeedback201FeedbackType]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const PostFeedback201FeedbackType = { + bug: 'bug', + idea: 'idea', +} as const; + +export type PostFeedback201Feedback = { + /** @pattern ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$ */ + id?: string; + createdAt?: string; + /** @pattern ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$ */ + userId: string; + type: PostFeedback201FeedbackType; + description: string; + /** @nullable */ + screenshotUrl?: string | null; + /** @nullable */ + appVersion?: string | null; + /** @nullable */ + deviceInfo?: string | null; +}; + +/** + * Feedback created. + */ +export type PostFeedback201 = { + feedback: PostFeedback201Feedback; +}; + diff --git a/apps/web/src/api/feedback.ts b/apps/web/src/api/feedback.ts new file mode 100644 index 00000000..e9b5db32 --- /dev/null +++ b/apps/web/src/api/feedback.ts @@ -0,0 +1,89 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * Plotwist + * OpenAPI spec version: 0.1.0 + */ +import { + useMutation +} from '@tanstack/react-query'; +import type { + MutationFunction, + QueryClient, + UseMutationOptions, + UseMutationResult +} from '@tanstack/react-query'; + +import type { + PostFeedback201, + PostFeedbackBody +} from './endpoints.schemas'; + +import { axiosInstance } from '../services/axios-instance'; + + + + + +/** + * Submit user feedback + */ +export const postFeedback = ( + postFeedbackBody: PostFeedbackBody, + signal?: AbortSignal +) => { + + + return axiosInstance( + {url: `/feedback`, method: 'POST', + headers: {'Content-Type': 'application/json', }, + data: postFeedbackBody, signal + }, + ); + } + + + +export const getPostFeedbackMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{data: PostFeedbackBody}, TContext>, } +): UseMutationOptions>, TError,{data: PostFeedbackBody}, TContext> => { + +const mutationKey = ['postFeedback']; +const {mutation: mutationOptions} = options ? + options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ? + options + : {...options, mutation: {...options.mutation, mutationKey}} + : {mutation: { mutationKey, }}; + + + + + const mutationFn: MutationFunction>, {data: PostFeedbackBody}> = (props) => { + const {data} = props ?? {}; + + return postFeedback(data,) + } + + + + + return { mutationFn, ...mutationOptions }} + + export type PostFeedbackMutationResult = NonNullable>> + export type PostFeedbackMutationBody = PostFeedbackBody + export type PostFeedbackMutationError = unknown + + export const usePostFeedback = (options?: { mutation?:UseMutationOptions>, TError,{data: PostFeedbackBody}, TContext>, } + , queryClient?: QueryClient): UseMutationResult< + Awaited>, + TError, + {data: PostFeedbackBody}, + TContext + > => { + + const mutationOptions = getPostFeedbackMutationOptions(options); + + return useMutation(mutationOptions, queryClient); + } + \ No newline at end of file diff --git a/apps/web/src/api/tmdb-proxy.ts b/apps/web/src/api/tmdb-proxy.ts new file mode 100644 index 00000000..4b3e1cd2 --- /dev/null +++ b/apps/web/src/api/tmdb-proxy.ts @@ -0,0 +1,171 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * Plotwist + * OpenAPI spec version: 0.1.0 + */ +import { + useQuery, + useSuspenseQuery +} from '@tanstack/react-query'; +import type { + DataTag, + DefinedInitialDataOptions, + DefinedUseQueryResult, + QueryClient, + QueryFunction, + QueryKey, + UndefinedInitialDataOptions, + UseQueryOptions, + UseQueryResult, + UseSuspenseQueryOptions, + UseSuspenseQueryResult +} from '@tanstack/react-query'; + +import { axiosInstance } from '../services/axios-instance'; + + + + + +/** + * Proxy TMDB API requests with Redis caching + */ +export const getTmdb = ( + : string, + signal?: AbortSignal +) => { + + + return axiosInstance( + {url: `/tmdb/${}`, method: 'GET', signal + }, + ); + } + + + + +export const getGetTmdbQueryKey = (: string,) => { + return [ + `/tmdb/${}` + ] as const; + } + + +export const getGetTmdbQueryOptions = >, TError = unknown>(: string, options?: { query?:Partial>, TError, TData>>, } +) => { + +const {query: queryOptions} = options ?? {}; + + const queryKey = queryOptions?.queryKey ?? getGetTmdbQueryKey(); + + + + const queryFn: QueryFunction>> = ({ signal }) => getTmdb(signal); + + + + + + return { queryKey, queryFn, enabled: !!(), ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: DataTag } +} + +export type GetTmdbQueryResult = NonNullable>> +export type GetTmdbQueryError = unknown + + +export function useGetTmdb>, TError = unknown>( + : string, options: { query:Partial>, TError, TData>> & Pick< + DefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + > , 'initialData' + >, } + , queryClient?: QueryClient + ): DefinedUseQueryResult & { queryKey: DataTag } +export function useGetTmdb>, TError = unknown>( + : string, options?: { query?:Partial>, TError, TData>> & Pick< + UndefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + > , 'initialData' + >, } + , queryClient?: QueryClient + ): UseQueryResult & { queryKey: DataTag } +export function useGetTmdb>, TError = unknown>( + : string, options?: { query?:Partial>, TError, TData>>, } + , queryClient?: QueryClient + ): UseQueryResult & { queryKey: DataTag } + +export function useGetTmdb>, TError = unknown>( + : string, options?: { query?:Partial>, TError, TData>>, } + , queryClient?: QueryClient + ): UseQueryResult & { queryKey: DataTag } { + + const queryOptions = getGetTmdbQueryOptions(options) + + const query = useQuery(queryOptions, queryClient) as UseQueryResult & { queryKey: DataTag }; + + query.queryKey = queryOptions.queryKey ; + + return query; +} + + + + +export const getGetTmdbSuspenseQueryOptions = >, TError = unknown>(: string, options?: { query?:Partial>, TError, TData>>, } +) => { + +const {query: queryOptions} = options ?? {}; + + const queryKey = queryOptions?.queryKey ?? getGetTmdbQueryKey(); + + + + const queryFn: QueryFunction>> = ({ signal }) => getTmdb(signal); + + + + + + return { queryKey, queryFn, ...queryOptions} as UseSuspenseQueryOptions>, TError, TData> & { queryKey: DataTag } +} + +export type GetTmdbSuspenseQueryResult = NonNullable>> +export type GetTmdbSuspenseQueryError = unknown + + +export function useGetTmdbSuspense>, TError = unknown>( + : string, options: { query:Partial>, TError, TData>>, } + , queryClient?: QueryClient + ): UseSuspenseQueryResult & { queryKey: DataTag } +export function useGetTmdbSuspense>, TError = unknown>( + : string, options?: { query?:Partial>, TError, TData>>, } + , queryClient?: QueryClient + ): UseSuspenseQueryResult & { queryKey: DataTag } +export function useGetTmdbSuspense>, TError = unknown>( + : string, options?: { query?:Partial>, TError, TData>>, } + , queryClient?: QueryClient + ): UseSuspenseQueryResult & { queryKey: DataTag } + +export function useGetTmdbSuspense>, TError = unknown>( + : string, options?: { query?:Partial>, TError, TData>>, } + , queryClient?: QueryClient + ): UseSuspenseQueryResult & { queryKey: DataTag } { + + const queryOptions = getGetTmdbSuspenseQueryOptions(options) + + const query = useSuspenseQuery(queryOptions, queryClient) as UseSuspenseQueryResult & { queryKey: DataTag }; + + query.queryKey = queryOptions.queryKey ; + + return query; +} + + + + diff --git a/apps/web/src/api/user-items.ts b/apps/web/src/api/user-items.ts index 5dd13d9d..aeb0f630 100644 --- a/apps/web/src/api/user-items.ts +++ b/apps/web/src/api/user-items.ts @@ -41,7 +41,8 @@ import type { GetUserItemsCount200, GetUserItemsCountParams, PutUserItem201, - PutUserItemBody + PutUserItemBody, + PutUserItemsReorderBody } from './endpoints.schemas'; import { axiosInstance } from '../services/axios-instance'; @@ -666,6 +667,66 @@ export function useGetAllUserItemsSuspense { + + + return axiosInstance( + {url: `/user/items/reorder`, method: 'PUT', + headers: {'Content-Type': 'application/json', }, + data: putUserItemsReorderBody + }, + ); + } + + + +export const getPutUserItemsReorderMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{data: PutUserItemsReorderBody}, TContext>, } +): UseMutationOptions>, TError,{data: PutUserItemsReorderBody}, TContext> => { + +const mutationKey = ['putUserItemsReorder']; +const {mutation: mutationOptions} = options ? + options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ? + options + : {...options, mutation: {...options.mutation, mutationKey}} + : {mutation: { mutationKey, }}; + + + + + const mutationFn: MutationFunction>, {data: PutUserItemsReorderBody}> = (props) => { + const {data} = props ?? {}; + + return putUserItemsReorder(data,) + } + + + + + return { mutationFn, ...mutationOptions }} + + export type PutUserItemsReorderMutationResult = NonNullable>> + export type PutUserItemsReorderMutationBody = PutUserItemsReorderBody + export type PutUserItemsReorderMutationError = unknown + + export const usePutUserItemsReorder = (options?: { mutation?:UseMutationOptions>, TError,{data: PutUserItemsReorderBody}, TContext>, } + , queryClient?: QueryClient): UseMutationResult< + Awaited>, + TError, + {data: PutUserItemsReorderBody}, + TContext + > => { + + const mutationOptions = getPutUserItemsReorderMutationOptions(options); + + return useMutation(mutationOptions, queryClient); + } + /** * Get user items count */ export const getUserItemsCount = ( diff --git a/apps/web/src/api/users.ts b/apps/web/src/api/users.ts index b1d5a749..fb7a5bd8 100644 --- a/apps/web/src/api/users.ts +++ b/apps/web/src/api/users.ts @@ -27,6 +27,8 @@ import type { } from '@tanstack/react-query'; import type { + DeleteUser200, + DeleteUser404, GetMe200, GetUserById200, GetUserPreferences200, @@ -886,6 +888,64 @@ const {mutation: mutationOptions} = options ? return useMutation(mutationOptions, queryClient); } /** + * Delete user account and all associated data + */ +export const deleteUser = ( + + ) => { + + + return axiosInstance( + {url: `/user`, method: 'DELETE' + }, + ); + } + + + +export const getDeleteUserMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,void, TContext>, } +): UseMutationOptions>, TError,void, TContext> => { + +const mutationKey = ['deleteUser']; +const {mutation: mutationOptions} = options ? + options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ? + options + : {...options, mutation: {...options.mutation, mutationKey}} + : {mutation: { mutationKey, }}; + + + + + const mutationFn: MutationFunction>, void> = () => { + + + return deleteUser() + } + + + + + return { mutationFn, ...mutationOptions }} + + export type DeleteUserMutationResult = NonNullable>> + + export type DeleteUserMutationError = DeleteUser404 + + export const useDeleteUser = (options?: { mutation?:UseMutationOptions>, TError,void, TContext>, } + , queryClient?: QueryClient): UseMutationResult< + Awaited>, + TError, + void, + TContext + > => { + + const mutationOptions = getDeleteUserMutationOptions(options); + + return useMutation(mutationOptions, queryClient); + } + /** * Update user password */ export const patchUserPassword = (