mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-26 20:31:49 +01:00
bookmark syncing
This commit is contained in:
parent
fa990d16b2
commit
ab4d72ed1a
46
src/backend/accounts/bookmarks.ts
Normal file
46
src/backend/accounts/bookmarks.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { ofetch } from "ofetch";
|
||||||
|
|
||||||
|
import { getAuthHeaders } from "@/backend/accounts/auth";
|
||||||
|
import { BookmarkResponse } from "@/backend/accounts/user";
|
||||||
|
import { AccountWithToken } from "@/stores/auth";
|
||||||
|
|
||||||
|
export interface BookmarkInput {
|
||||||
|
title: string;
|
||||||
|
year: number;
|
||||||
|
poster?: string;
|
||||||
|
type: string;
|
||||||
|
tmdbId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
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: {
|
||||||
|
meta: 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,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
@ -130,6 +130,7 @@ export function scrapePartsToProviderMetric(
|
|||||||
|
|
||||||
export function useReportProviders() {
|
export function useReportProviders() {
|
||||||
const report = useCallback((items: ProviderMetric[]) => {
|
const report = useCallback((items: ProviderMetric[]) => {
|
||||||
|
if (items.length === 0) return;
|
||||||
reportProviders(items);
|
reportProviders(items);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
import "core-js/stable";
|
import "core-js/stable";
|
||||||
|
import "./stores/__old/imports";
|
||||||
|
import "@/setup/ga";
|
||||||
|
import "@/setup/index.css";
|
||||||
|
|
||||||
import React, { Suspense } from "react";
|
import React, { Suspense } from "react";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
@ -13,13 +17,11 @@ import { MigrationPart } from "@/pages/parts/migrations/MigrationPart";
|
|||||||
import App from "@/setup/App";
|
import App from "@/setup/App";
|
||||||
import { conf } from "@/setup/config";
|
import { conf } from "@/setup/config";
|
||||||
import i18n from "@/setup/i18n";
|
import i18n from "@/setup/i18n";
|
||||||
import "@/setup/ga";
|
import { BookmarkSyncer } from "@/stores/bookmarks/BookmarkSyncer";
|
||||||
import "@/setup/index.css";
|
|
||||||
import { useLanguageStore } from "@/stores/language";
|
import { useLanguageStore } from "@/stores/language";
|
||||||
import { useThemeStore } from "@/stores/theme";
|
import { useThemeStore } from "@/stores/theme";
|
||||||
|
|
||||||
import { initializeChromecast } from "./setup/chromecast";
|
import { initializeChromecast } from "./setup/chromecast";
|
||||||
import "./stores/__old/imports";
|
|
||||||
import { initializeOldStores } from "./stores/__old/migrations";
|
import { initializeOldStores } from "./stores/__old/migrations";
|
||||||
|
|
||||||
// initialize
|
// initialize
|
||||||
@ -77,6 +79,7 @@ ReactDOM.render(
|
|||||||
<HelmetProvider>
|
<HelmetProvider>
|
||||||
<Suspense fallback={<LoadingScreen type="lazy" />}>
|
<Suspense fallback={<LoadingScreen type="lazy" />}>
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
|
<BookmarkSyncer />
|
||||||
<TheRouter>
|
<TheRouter>
|
||||||
<MigrationRunner />
|
<MigrationRunner />
|
||||||
</TheRouter>
|
</TheRouter>
|
||||||
|
78
src/stores/bookmarks/BookmarkSyncer.tsx
Normal file
78
src/stores/bookmarks/BookmarkSyncer.tsx
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
import { addBookmark, removeBookmark } from "@/backend/accounts/bookmarks";
|
||||||
|
import { useBackendUrl } from "@/hooks/auth/useBackendUrl";
|
||||||
|
import { AccountWithToken, useAuthStore } from "@/stores/auth";
|
||||||
|
import { BookmarkUpdateItem, useBookmarkStore } from "@/stores/bookmarks";
|
||||||
|
|
||||||
|
const syncIntervalMs = 5 * 1000;
|
||||||
|
|
||||||
|
async function syncBookmarks(
|
||||||
|
items: BookmarkUpdateItem[],
|
||||||
|
finish: (id: string) => void,
|
||||||
|
url: string,
|
||||||
|
account: AccountWithToken | null
|
||||||
|
) {
|
||||||
|
for (const item of items) {
|
||||||
|
// complete it beforehand so it doesn't get handled while in progress
|
||||||
|
finish(item.id);
|
||||||
|
|
||||||
|
if (!account) return; // not logged in, dont sync to server
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (item.action === "delete") {
|
||||||
|
await removeBookmark(url, account, item.tmdbId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.action === "add") {
|
||||||
|
await addBookmark(url, account, {
|
||||||
|
poster: item.poster,
|
||||||
|
title: item.title ?? "",
|
||||||
|
tmdbId: item.tmdbId,
|
||||||
|
type: item.type ?? "",
|
||||||
|
year: item.year ?? NaN,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(
|
||||||
|
`Failed to sync bookmark: ${item.tmdbId} - ${item.action}`,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BookmarkSyncer() {
|
||||||
|
const clearUpdateQueue = useBookmarkStore((s) => s.clearUpdateQueue);
|
||||||
|
const removeUpdateItem = useBookmarkStore((s) => s.removeUpdateItem);
|
||||||
|
const url = useBackendUrl();
|
||||||
|
|
||||||
|
// when booting for the first time, clear update queue.
|
||||||
|
// we dont want to process persisted update items
|
||||||
|
useEffect(() => {
|
||||||
|
clearUpdateQueue();
|
||||||
|
}, [clearUpdateQueue]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
(async () => {
|
||||||
|
const state = useBookmarkStore.getState();
|
||||||
|
const user = useAuthStore.getState();
|
||||||
|
await syncBookmarks(
|
||||||
|
state.updateQueue,
|
||||||
|
removeUpdateItem,
|
||||||
|
url,
|
||||||
|
user.account
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
}, syncIntervalMs);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(interval);
|
||||||
|
};
|
||||||
|
}, [removeUpdateItem, url]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
@ -12,25 +12,59 @@ export interface BookmarkMediaItem {
|
|||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BookmarkUpdateItem {
|
||||||
|
tmdbId: string;
|
||||||
|
title?: string;
|
||||||
|
year?: number;
|
||||||
|
id: string;
|
||||||
|
poster?: string;
|
||||||
|
type?: "show" | "movie";
|
||||||
|
action: "delete" | "add";
|
||||||
|
}
|
||||||
|
|
||||||
export interface BookmarkStore {
|
export interface BookmarkStore {
|
||||||
bookmarks: Record<string, BookmarkMediaItem>;
|
bookmarks: Record<string, BookmarkMediaItem>;
|
||||||
|
updateQueue: BookmarkUpdateItem[];
|
||||||
addBookmark(meta: PlayerMeta): void;
|
addBookmark(meta: PlayerMeta): void;
|
||||||
removeBookmark(id: string): void;
|
removeBookmark(id: string): void;
|
||||||
replaceBookmarks(items: Record<string, BookmarkMediaItem>): void;
|
replaceBookmarks(items: Record<string, BookmarkMediaItem>): void;
|
||||||
clear(): void;
|
clear(): void;
|
||||||
|
clearUpdateQueue(): void;
|
||||||
|
removeUpdateItem(id: string): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let updateId = 0;
|
||||||
|
|
||||||
export const useBookmarkStore = create(
|
export const useBookmarkStore = create(
|
||||||
persist(
|
persist(
|
||||||
immer<BookmarkStore>((set) => ({
|
immer<BookmarkStore>((set) => ({
|
||||||
bookmarks: {},
|
bookmarks: {},
|
||||||
|
updateQueue: [],
|
||||||
removeBookmark(id) {
|
removeBookmark(id) {
|
||||||
set((s) => {
|
set((s) => {
|
||||||
|
updateId += 1;
|
||||||
|
s.updateQueue.push({
|
||||||
|
id: updateId.toString(),
|
||||||
|
action: "delete",
|
||||||
|
tmdbId: id,
|
||||||
|
});
|
||||||
|
|
||||||
delete s.bookmarks[id];
|
delete s.bookmarks[id];
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
addBookmark(meta) {
|
addBookmark(meta) {
|
||||||
set((s) => {
|
set((s) => {
|
||||||
|
updateId += 1;
|
||||||
|
s.updateQueue.push({
|
||||||
|
id: updateId.toString(),
|
||||||
|
action: "add",
|
||||||
|
tmdbId: meta.tmdbId,
|
||||||
|
type: meta.type,
|
||||||
|
title: meta.title,
|
||||||
|
year: meta.releaseYear,
|
||||||
|
poster: meta.poster,
|
||||||
|
});
|
||||||
|
|
||||||
s.bookmarks[meta.tmdbId] = {
|
s.bookmarks[meta.tmdbId] = {
|
||||||
type: meta.type,
|
type: meta.type,
|
||||||
title: meta.title,
|
title: meta.title,
|
||||||
@ -48,6 +82,16 @@ export const useBookmarkStore = create(
|
|||||||
clear() {
|
clear() {
|
||||||
this.replaceBookmarks({});
|
this.replaceBookmarks({});
|
||||||
},
|
},
|
||||||
|
clearUpdateQueue() {
|
||||||
|
set((s) => {
|
||||||
|
s.updateQueue = [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
removeUpdateItem(id: string) {
|
||||||
|
set((s) => {
|
||||||
|
s.updateQueue = [...s.updateQueue.filter((v) => v.id !== id)];
|
||||||
|
});
|
||||||
|
},
|
||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
name: "__MW::bookmarks",
|
name: "__MW::bookmarks",
|
||||||
|
@ -35,6 +35,19 @@ export interface ProgressMediaItem {
|
|||||||
episodes: Record<string, ProgressEpisodeItem>;
|
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;
|
||||||
|
action: "upsert" | "delete";
|
||||||
|
}
|
||||||
|
|
||||||
export interface UpdateItemOptions {
|
export interface UpdateItemOptions {
|
||||||
meta: PlayerMeta;
|
meta: PlayerMeta;
|
||||||
progress: ProgressItem;
|
progress: ProgressItem;
|
||||||
@ -42,18 +55,29 @@ export interface UpdateItemOptions {
|
|||||||
|
|
||||||
export interface ProgressStore {
|
export interface ProgressStore {
|
||||||
items: Record<string, ProgressMediaItem>;
|
items: Record<string, ProgressMediaItem>;
|
||||||
|
updateQueue: ProgressUpdateItem[];
|
||||||
updateItem(ops: UpdateItemOptions): void;
|
updateItem(ops: UpdateItemOptions): void;
|
||||||
removeItem(id: string): void;
|
removeItem(id: string): void;
|
||||||
replaceItems(items: Record<string, ProgressMediaItem>): void;
|
replaceItems(items: Record<string, ProgressMediaItem>): void;
|
||||||
clear(): void;
|
clear(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let updateId = 0;
|
||||||
|
|
||||||
export const useProgressStore = create(
|
export const useProgressStore = create(
|
||||||
persist(
|
persist(
|
||||||
immer<ProgressStore>((set) => ({
|
immer<ProgressStore>((set) => ({
|
||||||
items: {},
|
items: {},
|
||||||
|
updateQueue: [],
|
||||||
removeItem(id) {
|
removeItem(id) {
|
||||||
set((s) => {
|
set((s) => {
|
||||||
|
updateId += 1;
|
||||||
|
s.updateQueue.push({
|
||||||
|
id: updateId.toString(),
|
||||||
|
action: "delete",
|
||||||
|
tmdbId: id,
|
||||||
|
});
|
||||||
|
|
||||||
delete s.items[id];
|
delete s.items[id];
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -64,6 +88,22 @@ export const useProgressStore = create(
|
|||||||
},
|
},
|
||||||
updateItem({ meta, progress }) {
|
updateItem({ meta, progress }) {
|
||||||
set((s) => {
|
set((s) => {
|
||||||
|
// add to updateQueue
|
||||||
|
updateId += 1;
|
||||||
|
s.updateQueue.push({
|
||||||
|
tmdbId: meta.tmdbId,
|
||||||
|
title: meta.title,
|
||||||
|
year: meta.releaseYear,
|
||||||
|
poster: meta.poster,
|
||||||
|
type: meta.type,
|
||||||
|
progress: { ...progress },
|
||||||
|
id: updateId.toString(),
|
||||||
|
episodeId: meta.episode?.tmdbId,
|
||||||
|
seasonId: meta.season?.tmdbId,
|
||||||
|
action: "upsert",
|
||||||
|
});
|
||||||
|
|
||||||
|
// add to progress store
|
||||||
if (!s.items[meta.tmdbId])
|
if (!s.items[meta.tmdbId])
|
||||||
s.items[meta.tmdbId] = {
|
s.items[meta.tmdbId] = {
|
||||||
type: meta.type,
|
type: meta.type,
|
||||||
|
Loading…
Reference in New Issue
Block a user