mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-29 15:26:08 +00:00
Data importing on login and registering
Co-authored-by: William Oldham <github@binaryoverload.co.uk>
This commit is contained in:
parent
7a591c82b9
commit
fa29da1757
|
@ -3,13 +3,33 @@ import { ofetch } from "ofetch";
|
|||
import { getAuthHeaders } from "@/backend/accounts/auth";
|
||||
import { BookmarkResponse } from "@/backend/accounts/user";
|
||||
import { AccountWithToken } from "@/stores/auth";
|
||||
import { BookmarkMediaItem } from "@/stores/bookmarks";
|
||||
|
||||
export interface BookmarkInput {
|
||||
export interface BookmarkMetaInput {
|
||||
title: string;
|
||||
year: number;
|
||||
poster?: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface BookmarkInput {
|
||||
tmdbId: string;
|
||||
meta: BookmarkMetaInput;
|
||||
}
|
||||
|
||||
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(
|
||||
|
@ -23,10 +43,7 @@ export async function addBookmark(
|
|||
method: "POST",
|
||||
headers: getAuthHeaders(account.token),
|
||||
baseURL: url,
|
||||
body: {
|
||||
meta: input,
|
||||
tmdbId: input.tmdbId,
|
||||
},
|
||||
body: input,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
33
src/backend/accounts/import.ts
Normal file
33
src/backend/accounts/import.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { ofetch } from "ofetch";
|
||||
|
||||
import { getAuthHeaders } from "@/backend/accounts/auth";
|
||||
import { AccountWithToken } from "@/stores/auth";
|
||||
|
||||
import { BookmarkInput } from "./bookmarks";
|
||||
import { ProgressInput } from "./progress";
|
||||
|
||||
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),
|
||||
});
|
||||
}
|
|
@ -3,6 +3,7 @@ import { ofetch } from "ofetch";
|
|||
import { getAuthHeaders } from "@/backend/accounts/auth";
|
||||
import { ProgressResponse } from "@/backend/accounts/user";
|
||||
import { AccountWithToken } from "@/stores/auth";
|
||||
import { ProgressMediaItem, ProgressUpdateItem } from "@/stores/progress";
|
||||
|
||||
export interface ProgressInput {
|
||||
meta?: {
|
||||
|
@ -12,14 +13,70 @@ export interface ProgressInput {
|
|||
type: string;
|
||||
};
|
||||
tmdbId: string;
|
||||
watched?: number;
|
||||
duration?: number;
|
||||
watched: number;
|
||||
duration: number;
|
||||
seasonId?: string;
|
||||
episodeId?: string;
|
||||
seasonNumber?: number;
|
||||
episodeNumber?: number;
|
||||
}
|
||||
|
||||
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,
|
||||
}));
|
||||
}
|
||||
return [
|
||||
{
|
||||
duration: item.progress?.duration ?? 0,
|
||||
watched: item.progress?.watched ?? 0,
|
||||
tmdbId,
|
||||
meta: {
|
||||
title: item.title ?? "",
|
||||
type: item.type ?? "",
|
||||
year: item.year ?? NaN,
|
||||
poster: item.poster,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export async function setProgress(
|
||||
url: string,
|
||||
account: AccountWithToken,
|
||||
|
|
37
src/backend/accounts/settings.ts
Normal file
37
src/backend/accounts/settings.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { ofetch } from "ofetch";
|
||||
|
||||
import { getAuthHeaders } from "@/backend/accounts/auth";
|
||||
import { AccountWithToken } from "@/stores/auth";
|
||||
|
||||
export interface SettingsInput {
|
||||
applicationLanguage?: string;
|
||||
applicationTheme?: string;
|
||||
defaultSubtitleLanguage?: string;
|
||||
}
|
||||
|
||||
export interface SettingsResponse {
|
||||
applicationTheme?: string | null;
|
||||
applicationLanguage?: string | null;
|
||||
defaultSubtitleLanguage?: string | null;
|
||||
}
|
||||
|
||||
export function updateSettings(
|
||||
url: string,
|
||||
account: AccountWithToken,
|
||||
settings: SettingsInput
|
||||
) {
|
||||
return ofetch<SettingsResponse>(`/users/${account.userId}/settings`, {
|
||||
method: "PUT",
|
||||
body: settings,
|
||||
baseURL: url,
|
||||
headers: getAuthHeaders(account.token),
|
||||
});
|
||||
}
|
||||
|
||||
export function getSettings(url: string, account: AccountWithToken) {
|
||||
return ofetch<SettingsResponse>(`/users/${account.userId}/settings`, {
|
||||
method: "GET",
|
||||
baseURL: url,
|
||||
headers: getAuthHeaders(account.token),
|
||||
});
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import { useCallback } from "react";
|
||||
|
||||
import { bookmarkMediaToInput } from "@/backend/accounts/bookmarks";
|
||||
import {
|
||||
bytesToBase64,
|
||||
bytesToBase64Url,
|
||||
|
@ -7,7 +8,9 @@ import {
|
|||
keysFromMnemonic,
|
||||
signChallenge,
|
||||
} from "@/backend/accounts/crypto";
|
||||
import { importBookmarks, importProgress } from "@/backend/accounts/import";
|
||||
import { getLoginChallengeToken, loginAccount } from "@/backend/accounts/login";
|
||||
import { progressMediaItemToInputs } from "@/backend/accounts/progress";
|
||||
import {
|
||||
getRegisterChallengeToken,
|
||||
registerAccount,
|
||||
|
@ -16,7 +19,9 @@ import { removeSession } from "@/backend/accounts/sessions";
|
|||
import { getBookmarks, getProgress, getUser } from "@/backend/accounts/user";
|
||||
import { useAuthData } from "@/hooks/auth/useAuthData";
|
||||
import { useBackendUrl } from "@/hooks/auth/useBackendUrl";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { AccountWithToken, useAuthStore } from "@/stores/auth";
|
||||
import { BookmarkMediaItem } from "@/stores/bookmarks";
|
||||
import { ProgressMediaItem } from "@/stores/progress";
|
||||
|
||||
export interface RegistrationData {
|
||||
recaptchaToken?: string;
|
||||
|
@ -69,7 +74,7 @@ export function useAuth() {
|
|||
|
||||
const user = await getUser(backendUrl, loginResult.token);
|
||||
const seedBase64 = bytesToBase64(keys.seed);
|
||||
await userDataLogin(loginResult, user.user, user.session, seedBase64);
|
||||
return userDataLogin(loginResult, user.user, user.session, seedBase64);
|
||||
},
|
||||
[userDataLogin, backendUrl]
|
||||
);
|
||||
|
@ -106,7 +111,7 @@ export function useAuth() {
|
|||
profile: registerData.userData.profile,
|
||||
});
|
||||
|
||||
await userDataLogin(
|
||||
return userDataLogin(
|
||||
registerResult,
|
||||
registerResult.user,
|
||||
registerResult.session,
|
||||
|
@ -116,6 +121,33 @@ export function useAuth() {
|
|||
[backendUrl, userDataLogin]
|
||||
);
|
||||
|
||||
const importData = useCallback(
|
||||
async (
|
||||
account: AccountWithToken,
|
||||
progressItems: Record<string, ProgressMediaItem>,
|
||||
bookmarks: Record<string, BookmarkMediaItem>
|
||||
) => {
|
||||
if (
|
||||
Object.keys(progressItems).length === 0 &&
|
||||
Object.keys(bookmarks).length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const progressInputs = Object.entries(progressItems).flatMap(
|
||||
([tmdbId, item]) => progressMediaItemToInputs(tmdbId, item)
|
||||
);
|
||||
|
||||
const bookmarkInputs = Object.entries(bookmarks).map(([tmdbId, item]) =>
|
||||
bookmarkMediaToInput(tmdbId, item)
|
||||
);
|
||||
|
||||
await importProgress(backendUrl, account, progressInputs);
|
||||
await importBookmarks(backendUrl, account, bookmarkInputs);
|
||||
},
|
||||
[backendUrl]
|
||||
);
|
||||
|
||||
const restore = useCallback(async () => {
|
||||
if (!currentAccount) {
|
||||
return;
|
||||
|
@ -136,5 +168,6 @@ export function useAuth() {
|
|||
logout,
|
||||
register,
|
||||
restore,
|
||||
importData,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -24,19 +24,21 @@ export function useAuthData() {
|
|||
|
||||
const login = useCallback(
|
||||
async (
|
||||
account: LoginResponse,
|
||||
loginResponse: LoginResponse,
|
||||
user: UserResponse,
|
||||
session: SessionResponse,
|
||||
seed: string
|
||||
) => {
|
||||
setAccount({
|
||||
token: account.token,
|
||||
const account = {
|
||||
token: loginResponse.token,
|
||||
userId: user.id,
|
||||
sessionId: account.session.id,
|
||||
sessionId: loginResponse.session.id,
|
||||
deviceName: session.device,
|
||||
profile: user.profile,
|
||||
seed,
|
||||
});
|
||||
};
|
||||
setAccount(account);
|
||||
return account;
|
||||
},
|
||||
[setAccount]
|
||||
);
|
||||
|
|
|
@ -11,6 +11,8 @@ import {
|
|||
} from "@/components/layout/LargeCard";
|
||||
import { AuthInputBox } from "@/components/text-inputs/AuthInputBox";
|
||||
import { useAuth } from "@/hooks/auth/useAuth";
|
||||
import { useBookmarkStore } from "@/stores/bookmarks";
|
||||
import { useProgressStore } from "@/stores/progress";
|
||||
|
||||
interface LoginFormPartProps {
|
||||
onLogin?: () => void;
|
||||
|
@ -19,7 +21,9 @@ interface LoginFormPartProps {
|
|||
export function LoginFormPart(props: LoginFormPartProps) {
|
||||
const [mnemonic, setMnemonic] = useState("");
|
||||
const [device, setDevice] = useState("");
|
||||
const { login, restore } = useAuth();
|
||||
const { login, restore, importData } = useAuth();
|
||||
const progressItems = useProgressStore((store) => store.items);
|
||||
const bookmarkItems = useBookmarkStore((store) => store.bookmarks);
|
||||
|
||||
const [result, execute] = useAsyncFn(
|
||||
async (inputMnemonic: string, inputdevice: string) => {
|
||||
|
@ -27,14 +31,14 @@ export function LoginFormPart(props: LoginFormPartProps) {
|
|||
if (!verifyValidMnemonic(inputMnemonic))
|
||||
throw new Error("Invalid or incomplete passphrase");
|
||||
|
||||
await login({
|
||||
const account = await login({
|
||||
mnemonic: inputMnemonic,
|
||||
userData: {
|
||||
device: inputdevice,
|
||||
},
|
||||
});
|
||||
|
||||
// TODO import (and sort out conflicts)
|
||||
await importData(account, progressItems, bookmarkItems);
|
||||
|
||||
await restore();
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import { useState } from "react";
|
|||
import { useGoogleReCaptcha } from "react-google-recaptcha-v3";
|
||||
import { useAsyncFn } from "react-use";
|
||||
|
||||
import { updateSettings } from "@/backend/accounts/settings";
|
||||
import { Button } from "@/components/Button";
|
||||
import { Icon, Icons } from "@/components/Icon";
|
||||
import {
|
||||
|
@ -11,7 +12,13 @@ import {
|
|||
} from "@/components/layout/LargeCard";
|
||||
import { AuthInputBox } from "@/components/text-inputs/AuthInputBox";
|
||||
import { useAuth } from "@/hooks/auth/useAuth";
|
||||
import { useBackendUrl } from "@/hooks/auth/useBackendUrl";
|
||||
import { AccountProfile } from "@/pages/parts/auth/AccountCreatePart";
|
||||
import { useBookmarkStore } from "@/stores/bookmarks";
|
||||
import { useLanguageStore } from "@/stores/language";
|
||||
import { useProgressStore } from "@/stores/progress";
|
||||
import { useSubtitleStore } from "@/stores/subtitles";
|
||||
import { useThemeStore } from "@/stores/theme";
|
||||
|
||||
interface VerifyPassphraseProps {
|
||||
mnemonic: string | null;
|
||||
|
@ -22,7 +29,17 @@ interface VerifyPassphraseProps {
|
|||
|
||||
export function VerifyPassphrase(props: VerifyPassphraseProps) {
|
||||
const [mnemonic, setMnemonic] = useState("");
|
||||
const { register, restore } = useAuth();
|
||||
const { register, restore, importData } = useAuth();
|
||||
const progressItems = useProgressStore((store) => store.items);
|
||||
const bookmarkItems = useBookmarkStore((store) => store.bookmarks);
|
||||
|
||||
const applicationLanguage = useLanguageStore((store) => store.language);
|
||||
const defaultSubtitleLanguage = useSubtitleStore(
|
||||
(store) => store.lastSelectedLanguage
|
||||
);
|
||||
const applicationTheme = useThemeStore((store) => store.theme);
|
||||
|
||||
const backendUrl = useBackendUrl();
|
||||
|
||||
const { executeRecaptcha } = useGoogleReCaptcha();
|
||||
|
||||
|
@ -42,13 +59,19 @@ export function VerifyPassphrase(props: VerifyPassphraseProps) {
|
|||
if (inputMnemonic !== props.mnemonic)
|
||||
throw new Error("Passphrase doesn't match");
|
||||
|
||||
await register({
|
||||
const account = await register({
|
||||
mnemonic: inputMnemonic,
|
||||
userData: props.userData,
|
||||
recaptchaToken,
|
||||
});
|
||||
|
||||
// TODO import (and sort out conflicts)
|
||||
await importData(account, progressItems, bookmarkItems);
|
||||
|
||||
await updateSettings(backendUrl, account, {
|
||||
applicationLanguage,
|
||||
defaultSubtitleLanguage: defaultSubtitleLanguage ?? undefined,
|
||||
applicationTheme: applicationTheme ?? undefined,
|
||||
});
|
||||
|
||||
await restore();
|
||||
|
||||
|
|
|
@ -27,11 +27,13 @@ async function syncBookmarks(
|
|||
|
||||
if (item.action === "add") {
|
||||
await addBookmark(url, account, {
|
||||
poster: item.poster,
|
||||
title: item.title ?? "",
|
||||
meta: {
|
||||
poster: item.poster,
|
||||
title: item.title ?? "",
|
||||
type: item.type ?? "",
|
||||
year: item.year ?? NaN,
|
||||
},
|
||||
tmdbId: item.tmdbId,
|
||||
type: item.type ?? "",
|
||||
year: item.year ?? NaN,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import { useEffect } from "react";
|
||||
|
||||
import { removeProgress, setProgress } from "@/backend/accounts/progress";
|
||||
import {
|
||||
progressUpdateItemToInput,
|
||||
removeProgress,
|
||||
setProgress,
|
||||
} from "@/backend/accounts/progress";
|
||||
import { useBackendUrl } from "@/hooks/auth/useBackendUrl";
|
||||
import { AccountWithToken, useAuthStore } from "@/stores/auth";
|
||||
import { ProgressUpdateItem, useProgressStore } from "@/stores/progress";
|
||||
|
@ -32,21 +36,7 @@ async function syncProgress(
|
|||
}
|
||||
|
||||
if (item.action === "upsert") {
|
||||
await setProgress(url, account, {
|
||||
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,
|
||||
});
|
||||
await setProgress(url, account, progressUpdateItemToInput(item));
|
||||
continue;
|
||||
}
|
||||
} catch (err) {
|
||||
|
|
Loading…
Reference in a new issue