mirror of
https://github.com/movie-web/native-app.git
synced 2025-09-13 08:23:26 +00:00
Compare commits
2 Commits
bd8a4394ea
...
9694630cdf
Author | SHA1 | Date | |
---|---|---|---|
|
9694630cdf | ||
|
3fb2567ae1 |
@@ -1,17 +1,6 @@
|
|||||||
import { ofetch } from "ofetch";
|
import { ofetch } from "ofetch";
|
||||||
|
|
||||||
export interface SessionResponse {
|
import type { LoginResponse } from "./types";
|
||||||
id: string;
|
|
||||||
userId: string;
|
|
||||||
createdAt: string;
|
|
||||||
accessedAt: string;
|
|
||||||
device: string;
|
|
||||||
userAgent: string;
|
|
||||||
}
|
|
||||||
export interface LoginResponse {
|
|
||||||
session: SessionResponse;
|
|
||||||
token: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAuthHeaders(token: string): Record<string, string> {
|
export function getAuthHeaders(token: string): Record<string, string> {
|
||||||
return {
|
return {
|
||||||
|
55
packages/api/src/bookmarks.ts
Normal file
55
packages/api/src/bookmarks.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { ofetch } from "ofetch";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
AccountWithToken,
|
||||||
|
BookmarkInput,
|
||||||
|
BookmarkMediaItem,
|
||||||
|
BookmarkResponse,
|
||||||
|
} from "./types";
|
||||||
|
import { getAuthHeaders } from "./auth";
|
||||||
|
|
||||||
|
export function bookmarkMediaToInput(
|
||||||
|
tmdbId: string,
|
||||||
|
item: BookmarkMediaItem,
|
||||||
|
): BookmarkInput {
|
||||||
|
return {
|
||||||
|
meta: {
|
||||||
|
title: item.title,
|
||||||
|
type: item.type,
|
||||||
|
poster: item.poster,
|
||||||
|
year: item.year ?? 0,
|
||||||
|
},
|
||||||
|
tmdbId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addBookmark(
|
||||||
|
url: string,
|
||||||
|
account: AccountWithToken,
|
||||||
|
input: BookmarkInput,
|
||||||
|
) {
|
||||||
|
return ofetch<BookmarkResponse>(
|
||||||
|
`/users/${account.userId}/bookmarks/${input.tmdbId}`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: getAuthHeaders(account.token),
|
||||||
|
baseURL: url,
|
||||||
|
body: input,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeBookmark(
|
||||||
|
url: string,
|
||||||
|
account: AccountWithToken,
|
||||||
|
id: string,
|
||||||
|
) {
|
||||||
|
return ofetch<{ tmdbId: string }>(
|
||||||
|
`/users/${account.userId}/bookmarks/${id}`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
headers: getAuthHeaders(account.token),
|
||||||
|
baseURL: url,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
30
packages/api/src/import.ts
Normal file
30
packages/api/src/import.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { ofetch } from "ofetch";
|
||||||
|
|
||||||
|
import type { AccountWithToken, BookmarkInput, ProgressInput } from "./types";
|
||||||
|
import { getAuthHeaders } from "./auth";
|
||||||
|
|
||||||
|
export function importProgress(
|
||||||
|
url: string,
|
||||||
|
account: AccountWithToken,
|
||||||
|
progressItems: ProgressInput[],
|
||||||
|
) {
|
||||||
|
return ofetch<void>(`/users/${account.userId}/progress/import`, {
|
||||||
|
method: "PUT",
|
||||||
|
body: progressItems,
|
||||||
|
baseURL: url,
|
||||||
|
headers: getAuthHeaders(account.token),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function importBookmarks(
|
||||||
|
url: string,
|
||||||
|
account: AccountWithToken,
|
||||||
|
bookmarks: BookmarkInput[],
|
||||||
|
) {
|
||||||
|
return ofetch<void>(`/users/${account.userId}/bookmarks`, {
|
||||||
|
method: "PUT",
|
||||||
|
body: bookmarks,
|
||||||
|
baseURL: url,
|
||||||
|
headers: getAuthHeaders(account.token),
|
||||||
|
});
|
||||||
|
}
|
@@ -1,3 +1,13 @@
|
|||||||
export const name = "api";
|
export const name = "api";
|
||||||
export * from "./auth";
|
export * from "./auth";
|
||||||
|
export * from "./bookmarks";
|
||||||
export * from "./crypto";
|
export * from "./crypto";
|
||||||
|
export * from "./import";
|
||||||
|
export * from "./login";
|
||||||
|
export * from "./meta";
|
||||||
|
export * from "./progress";
|
||||||
|
export * from "./register";
|
||||||
|
export * from "./sessions";
|
||||||
|
export * from "./settings";
|
||||||
|
export * from "./types";
|
||||||
|
export * from "./user";
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import { ofetch } from "ofetch";
|
import { ofetch } from "ofetch";
|
||||||
|
|
||||||
import type { SessionResponse } from "./auth";
|
import type {
|
||||||
|
ChallengeTokenResponse,
|
||||||
export interface ChallengeTokenResponse {
|
LoginInput,
|
||||||
challenge: string;
|
LoginResponse,
|
||||||
}
|
} from "./types";
|
||||||
|
|
||||||
export async function getLoginChallengeToken(
|
export async function getLoginChallengeToken(
|
||||||
url: string,
|
url: string,
|
||||||
@@ -19,20 +19,6 @@ export async function getLoginChallengeToken(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoginResponse {
|
|
||||||
session: SessionResponse;
|
|
||||||
token: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LoginInput {
|
|
||||||
publicKey: string;
|
|
||||||
challenge: {
|
|
||||||
code: string;
|
|
||||||
signature: string;
|
|
||||||
};
|
|
||||||
device: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loginAccount(
|
export async function loginAccount(
|
||||||
url: string,
|
url: string,
|
||||||
data: LoginInput,
|
data: LoginInput,
|
||||||
|
@@ -1,12 +1,6 @@
|
|||||||
import { ofetch } from "ofetch";
|
import { ofetch } from "ofetch";
|
||||||
|
|
||||||
export interface MetaResponse {
|
import type { MetaResponse } from "./types";
|
||||||
version: string;
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
hasCaptcha: boolean;
|
|
||||||
captchaClientKey?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getBackendMeta(url: string): Promise<MetaResponse> {
|
export async function getBackendMeta(url: string): Promise<MetaResponse> {
|
||||||
return ofetch<MetaResponse>("/meta", {
|
return ofetch<MetaResponse>("/meta", {
|
||||||
|
102
packages/api/src/progress.ts
Normal file
102
packages/api/src/progress.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import { ofetch } from "ofetch";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
AccountWithToken,
|
||||||
|
ProgressInput,
|
||||||
|
ProgressMediaItem,
|
||||||
|
ProgressResponse,
|
||||||
|
ProgressUpdateItem,
|
||||||
|
} from "./types";
|
||||||
|
import { getAuthHeaders } from "./auth";
|
||||||
|
|
||||||
|
export function progressUpdateItemToInput(
|
||||||
|
item: ProgressUpdateItem,
|
||||||
|
): ProgressInput {
|
||||||
|
return {
|
||||||
|
duration: item.progress?.duration ?? 0,
|
||||||
|
watched: item.progress?.watched ?? 0,
|
||||||
|
tmdbId: item.tmdbId,
|
||||||
|
meta: {
|
||||||
|
title: item.title ?? "",
|
||||||
|
type: item.type ?? "",
|
||||||
|
year: item.year ?? NaN,
|
||||||
|
poster: item.poster,
|
||||||
|
},
|
||||||
|
episodeId: item.episodeId,
|
||||||
|
seasonId: item.seasonId,
|
||||||
|
episodeNumber: item.episodeNumber,
|
||||||
|
seasonNumber: item.seasonNumber,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function progressMediaItemToInputs(
|
||||||
|
tmdbId: string,
|
||||||
|
item: ProgressMediaItem,
|
||||||
|
): ProgressInput[] {
|
||||||
|
if (item.type === "show") {
|
||||||
|
return Object.entries(item.episodes).flatMap(([_, episode]) => ({
|
||||||
|
duration: item.progress?.duration ?? episode.progress.duration,
|
||||||
|
watched: item.progress?.watched ?? episode.progress.watched,
|
||||||
|
tmdbId,
|
||||||
|
meta: {
|
||||||
|
title: item.title ?? "",
|
||||||
|
type: item.type ?? "",
|
||||||
|
year: item.year ?? NaN,
|
||||||
|
poster: item.poster,
|
||||||
|
},
|
||||||
|
episodeId: episode.id,
|
||||||
|
seasonId: episode.seasonId,
|
||||||
|
episodeNumber: episode.number,
|
||||||
|
seasonNumber: item.seasons[episode.seasonId]?.number,
|
||||||
|
updatedAt: new Date(episode.updatedAt).toISOString(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
duration: item.progress?.duration ?? 0,
|
||||||
|
watched: item.progress?.watched ?? 0,
|
||||||
|
tmdbId,
|
||||||
|
updatedAt: new Date(item.updatedAt).toISOString(),
|
||||||
|
meta: {
|
||||||
|
title: item.title ?? "",
|
||||||
|
type: item.type ?? "",
|
||||||
|
year: item.year ?? NaN,
|
||||||
|
poster: item.poster,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setProgress(
|
||||||
|
url: string,
|
||||||
|
account: AccountWithToken,
|
||||||
|
input: ProgressInput,
|
||||||
|
) {
|
||||||
|
return ofetch<ProgressResponse>(
|
||||||
|
`/users/${account.userId}/progress/${input.tmdbId}`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: getAuthHeaders(account.token),
|
||||||
|
baseURL: url,
|
||||||
|
body: input,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeProgress(
|
||||||
|
url: string,
|
||||||
|
account: AccountWithToken,
|
||||||
|
id: string,
|
||||||
|
episodeId?: string,
|
||||||
|
seasonId?: string,
|
||||||
|
) {
|
||||||
|
await ofetch(`/users/${account.userId}/progress/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: getAuthHeaders(account.token),
|
||||||
|
baseURL: url,
|
||||||
|
body: {
|
||||||
|
episodeId,
|
||||||
|
seasonId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
41
packages/api/src/register.ts
Normal file
41
packages/api/src/register.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { ofetch } from "ofetch";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ChallengeTokenResponse,
|
||||||
|
RegisterInput,
|
||||||
|
SessionResponse,
|
||||||
|
UserResponse,
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
|
export async function getRegisterChallengeToken(
|
||||||
|
url: string,
|
||||||
|
captchaToken?: string,
|
||||||
|
): Promise<ChallengeTokenResponse> {
|
||||||
|
return ofetch<ChallengeTokenResponse>("/auth/register/start", {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
captchaToken,
|
||||||
|
},
|
||||||
|
baseURL: url,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RegisterResponse {
|
||||||
|
user: UserResponse;
|
||||||
|
session: SessionResponse;
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function registerAccount(
|
||||||
|
url: string,
|
||||||
|
data: RegisterInput,
|
||||||
|
): Promise<RegisterResponse> {
|
||||||
|
return ofetch<RegisterResponse>("/auth/register/complete", {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
namespace: "movie-web",
|
||||||
|
...data,
|
||||||
|
},
|
||||||
|
baseURL: url,
|
||||||
|
});
|
||||||
|
}
|
@@ -1,36 +1,8 @@
|
|||||||
import { ofetch } from "ofetch";
|
import { ofetch } from "ofetch";
|
||||||
|
|
||||||
|
import type { AccountWithToken, SessionResponse, SessionUpdate } from "./types";
|
||||||
import { getAuthHeaders } from "./auth";
|
import { getAuthHeaders } from "./auth";
|
||||||
|
|
||||||
export interface SessionResponse {
|
|
||||||
id: string;
|
|
||||||
userId: string;
|
|
||||||
createdAt: string;
|
|
||||||
accessedAt: string;
|
|
||||||
device: string;
|
|
||||||
userAgent: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SessionUpdate {
|
|
||||||
deviceName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Account {
|
|
||||||
profile: {
|
|
||||||
colorA: string;
|
|
||||||
colorB: string;
|
|
||||||
icon: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AccountWithToken = Account & {
|
|
||||||
sessionId: string;
|
|
||||||
userId: string;
|
|
||||||
token: string;
|
|
||||||
seed: string;
|
|
||||||
deviceName: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function getSessions(url: string, account: AccountWithToken) {
|
export async function getSessions(url: string, account: AccountWithToken) {
|
||||||
return ofetch<SessionResponse[]>(`/users/${account.userId}/sessions`, {
|
return ofetch<SessionResponse[]>(`/users/${account.userId}/sessions`, {
|
||||||
headers: getAuthHeaders(account.token),
|
headers: getAuthHeaders(account.token),
|
||||||
|
@@ -1,22 +1,12 @@
|
|||||||
import { ofetch } from "ofetch";
|
import { ofetch } from "ofetch";
|
||||||
|
|
||||||
import type { AccountWithToken } from "./sessions";
|
import type {
|
||||||
|
AccountWithToken,
|
||||||
|
SettingsInput,
|
||||||
|
SettingsResponse,
|
||||||
|
} from "./types";
|
||||||
import { getAuthHeaders } from "./auth";
|
import { getAuthHeaders } from "./auth";
|
||||||
|
|
||||||
export interface SettingsInput {
|
|
||||||
applicationLanguage?: string;
|
|
||||||
applicationTheme?: string | null;
|
|
||||||
defaultSubtitleLanguage?: string;
|
|
||||||
proxyUrls?: string[] | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SettingsResponse {
|
|
||||||
applicationTheme?: string | null;
|
|
||||||
applicationLanguage?: string | null;
|
|
||||||
defaultSubtitleLanguage?: string | null;
|
|
||||||
proxyUrls?: string[] | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateSettings(
|
export function updateSettings(
|
||||||
url: string,
|
url: string,
|
||||||
account: AccountWithToken,
|
account: AccountWithToken,
|
||||||
|
231
packages/api/src/types.ts
Normal file
231
packages/api/src/types.ts
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
export interface SessionResponse {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
createdAt: string;
|
||||||
|
accessedAt: string;
|
||||||
|
device: string;
|
||||||
|
userAgent: string;
|
||||||
|
}
|
||||||
|
export interface LoginResponse {
|
||||||
|
session: SessionResponse;
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BookmarkMetaInput {
|
||||||
|
title: string;
|
||||||
|
year: number;
|
||||||
|
poster?: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BookmarkInput {
|
||||||
|
tmdbId: string;
|
||||||
|
meta: BookmarkMetaInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChallengeTokenResponse {
|
||||||
|
challenge: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoginResponse {
|
||||||
|
session: SessionResponse;
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoginInput {
|
||||||
|
publicKey: string;
|
||||||
|
challenge: {
|
||||||
|
code: string;
|
||||||
|
signature: string;
|
||||||
|
};
|
||||||
|
device: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MetaResponse {
|
||||||
|
version: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
hasCaptcha: boolean;
|
||||||
|
captchaClientKey?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RegisterInput {
|
||||||
|
publicKey: string;
|
||||||
|
challenge: {
|
||||||
|
code: string;
|
||||||
|
signature: string;
|
||||||
|
};
|
||||||
|
device: string;
|
||||||
|
profile: {
|
||||||
|
colorA: string;
|
||||||
|
colorB: string;
|
||||||
|
icon: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProgressInput {
|
||||||
|
meta?: {
|
||||||
|
title: string;
|
||||||
|
year: number;
|
||||||
|
poster?: string;
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
tmdbId: string;
|
||||||
|
watched: number;
|
||||||
|
duration: number;
|
||||||
|
seasonId?: string;
|
||||||
|
episodeId?: string;
|
||||||
|
seasonNumber?: number;
|
||||||
|
episodeNumber?: number;
|
||||||
|
updatedAt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SessionResponse {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
createdAt: string;
|
||||||
|
accessedAt: string;
|
||||||
|
device: string;
|
||||||
|
userAgent: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SessionUpdate {
|
||||||
|
deviceName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Account {
|
||||||
|
profile: {
|
||||||
|
colorA: string;
|
||||||
|
colorB: string;
|
||||||
|
icon: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AccountWithToken = Account & {
|
||||||
|
sessionId: string;
|
||||||
|
userId: string;
|
||||||
|
token: string;
|
||||||
|
seed: string;
|
||||||
|
deviceName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface SettingsInput {
|
||||||
|
applicationLanguage?: string;
|
||||||
|
applicationTheme?: string | null;
|
||||||
|
defaultSubtitleLanguage?: string;
|
||||||
|
proxyUrls?: string[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SettingsResponse {
|
||||||
|
applicationTheme?: string | null;
|
||||||
|
applicationLanguage?: string | null;
|
||||||
|
defaultSubtitleLanguage?: string | null;
|
||||||
|
proxyUrls?: string[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserResponse {
|
||||||
|
id: string;
|
||||||
|
namespace: string;
|
||||||
|
name: string;
|
||||||
|
roles: string[];
|
||||||
|
createdAt: string;
|
||||||
|
profile: {
|
||||||
|
colorA: string;
|
||||||
|
colorB: string;
|
||||||
|
icon: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserEdit {
|
||||||
|
profile?: {
|
||||||
|
colorA: string;
|
||||||
|
colorB: string;
|
||||||
|
icon: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BookmarkMediaItem {
|
||||||
|
title: string;
|
||||||
|
year?: number;
|
||||||
|
poster?: string;
|
||||||
|
type: "show" | "movie";
|
||||||
|
updatedAt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BookmarkResponse {
|
||||||
|
tmdbId: string;
|
||||||
|
meta: {
|
||||||
|
title: string;
|
||||||
|
year: number;
|
||||||
|
poster?: string;
|
||||||
|
type: "show" | "movie";
|
||||||
|
};
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProgressItem {
|
||||||
|
watched: number;
|
||||||
|
duration: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProgressSeasonItem {
|
||||||
|
title: string;
|
||||||
|
number: number;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProgressEpisodeItem {
|
||||||
|
title: string;
|
||||||
|
number: number;
|
||||||
|
id: string;
|
||||||
|
seasonId: string;
|
||||||
|
updatedAt: number;
|
||||||
|
progress: ProgressItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProgressMediaItem {
|
||||||
|
title: string;
|
||||||
|
year?: number;
|
||||||
|
poster?: string;
|
||||||
|
type: "show" | "movie";
|
||||||
|
progress?: ProgressItem;
|
||||||
|
updatedAt: number;
|
||||||
|
seasons: Record<string, ProgressSeasonItem>;
|
||||||
|
episodes: Record<string, ProgressEpisodeItem>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProgressUpdateItem {
|
||||||
|
title?: string;
|
||||||
|
year?: number;
|
||||||
|
poster?: string;
|
||||||
|
type?: "show" | "movie";
|
||||||
|
progress?: ProgressItem;
|
||||||
|
tmdbId: string;
|
||||||
|
id: string;
|
||||||
|
episodeId?: string;
|
||||||
|
seasonId?: string;
|
||||||
|
episodeNumber?: number;
|
||||||
|
seasonNumber?: number;
|
||||||
|
action: "upsert" | "delete";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProgressResponse {
|
||||||
|
tmdbId: string;
|
||||||
|
season: {
|
||||||
|
id?: string;
|
||||||
|
number?: number;
|
||||||
|
};
|
||||||
|
episode: {
|
||||||
|
id?: string;
|
||||||
|
number?: number;
|
||||||
|
};
|
||||||
|
meta: {
|
||||||
|
title: string;
|
||||||
|
year: number;
|
||||||
|
poster?: string;
|
||||||
|
type: "show" | "movie";
|
||||||
|
};
|
||||||
|
duration: string;
|
||||||
|
watched: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
133
packages/api/src/user.ts
Normal file
133
packages/api/src/user.ts
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import { ofetch } from "ofetch";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
AccountWithToken,
|
||||||
|
BookmarkMediaItem,
|
||||||
|
BookmarkResponse,
|
||||||
|
ProgressMediaItem,
|
||||||
|
ProgressResponse,
|
||||||
|
SessionResponse,
|
||||||
|
UserEdit,
|
||||||
|
UserResponse,
|
||||||
|
} from "./types";
|
||||||
|
import { getAuthHeaders } from "./auth";
|
||||||
|
|
||||||
|
export function bookmarkResponsesToEntries(responses: BookmarkResponse[]) {
|
||||||
|
const entries = responses.map((bookmark) => {
|
||||||
|
const item: BookmarkMediaItem = {
|
||||||
|
...bookmark.meta,
|
||||||
|
updatedAt: new Date(bookmark.updatedAt).getTime(),
|
||||||
|
};
|
||||||
|
return [bookmark.tmdbId, item] as const;
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.fromEntries(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function progressResponsesToEntries(responses: ProgressResponse[]) {
|
||||||
|
const items: Record<string, ProgressMediaItem> = {};
|
||||||
|
|
||||||
|
responses.forEach((v) => {
|
||||||
|
if (!items[v.tmdbId]) {
|
||||||
|
items[v.tmdbId] = {
|
||||||
|
title: v.meta.title,
|
||||||
|
poster: v.meta.poster,
|
||||||
|
type: v.meta.type,
|
||||||
|
updatedAt: new Date(v.updatedAt).getTime(),
|
||||||
|
episodes: {},
|
||||||
|
seasons: {},
|
||||||
|
year: v.meta.year,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = items[v.tmdbId];
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
// Since each watched episode is a single array entry but with the same tmdbId, the root item updatedAt will only have the first episode's timestamp (which is not the newest).
|
||||||
|
// Here, we are setting it explicitly so the updatedAt always has the highest updatedAt from the episodes.
|
||||||
|
if (new Date(v.updatedAt).getTime() > item.updatedAt) {
|
||||||
|
item.updatedAt = new Date(v.updatedAt).getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.type === "movie") {
|
||||||
|
item.progress = {
|
||||||
|
duration: Number(v.duration),
|
||||||
|
watched: Number(v.watched),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.type === "show" && v.season.id && v.episode.id) {
|
||||||
|
item.seasons[v.season.id] = {
|
||||||
|
id: v.season.id,
|
||||||
|
number: v.season.number ?? 0,
|
||||||
|
title: "",
|
||||||
|
};
|
||||||
|
item.episodes[v.episode.id] = {
|
||||||
|
id: v.episode.id,
|
||||||
|
number: v.episode.number ?? 0,
|
||||||
|
title: "",
|
||||||
|
progress: {
|
||||||
|
duration: Number(v.duration),
|
||||||
|
watched: Number(v.watched),
|
||||||
|
},
|
||||||
|
seasonId: v.season.id,
|
||||||
|
updatedAt: new Date(v.updatedAt).getTime(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUser(
|
||||||
|
url: string,
|
||||||
|
token: string,
|
||||||
|
): Promise<{ user: UserResponse; session: SessionResponse }> {
|
||||||
|
return ofetch<{ user: UserResponse; session: SessionResponse }>(
|
||||||
|
"/users/@me",
|
||||||
|
{
|
||||||
|
headers: getAuthHeaders(token),
|
||||||
|
baseURL: url,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function editUser(
|
||||||
|
url: string,
|
||||||
|
account: AccountWithToken,
|
||||||
|
object: UserEdit,
|
||||||
|
): Promise<{ user: UserResponse; session: SessionResponse }> {
|
||||||
|
return ofetch<{ user: UserResponse; session: SessionResponse }>(
|
||||||
|
`/users/${account.userId}`,
|
||||||
|
{
|
||||||
|
method: "PATCH",
|
||||||
|
headers: getAuthHeaders(account.token),
|
||||||
|
body: object,
|
||||||
|
baseURL: url,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteUser(
|
||||||
|
url: string,
|
||||||
|
account: AccountWithToken,
|
||||||
|
): Promise<UserResponse> {
|
||||||
|
return ofetch<UserResponse>(`/users/${account.userId}`, {
|
||||||
|
headers: getAuthHeaders(account.token),
|
||||||
|
baseURL: url,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getBookmarks(url: string, account: AccountWithToken) {
|
||||||
|
return ofetch<BookmarkResponse[]>(`/users/${account.userId}/bookmarks`, {
|
||||||
|
headers: getAuthHeaders(account.token),
|
||||||
|
baseURL: url,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getProgress(url: string, account: AccountWithToken) {
|
||||||
|
return ofetch<ProgressResponse[]>(`/users/${account.userId}/progress`, {
|
||||||
|
headers: getAuthHeaders(account.token),
|
||||||
|
baseURL: url,
|
||||||
|
});
|
||||||
|
}
|
Reference in New Issue
Block a user