diff options
| author | Fuwn <[email protected]> | 2023-08-26 22:29:03 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2023-08-26 22:29:03 -0700 |
| commit | b89d0e7dada186e31be37e62a7a75efc2dbe9c99 (patch) | |
| tree | 8c9f6b5d7aa0f709c06d5eb45fbf763883b21c89 /src/routes | |
| download | due.moe-b89d0e7dada186e31be37e62a7a75efc2dbe9c99.tar.xz due.moe-b89d0e7dada186e31be37e62a7a75efc2dbe9c99.zip | |
feat: initial build
Diffstat (limited to 'src/routes')
| -rw-r--r-- | src/routes/+layout.server.ts | 5 | ||||
| -rw-r--r-- | src/routes/+layout.svelte | 45 | ||||
| -rw-r--r-- | src/routes/+page.svelte | 60 | ||||
| -rw-r--r-- | src/routes/anilist/increment/+server.ts | 27 | ||||
| -rw-r--r-- | src/routes/authentication/log-out/+server.ts | 7 | ||||
| -rw-r--r-- | src/routes/oauth/callback/+server.ts | 34 | ||||
| -rw-r--r-- | src/routes/settings/+page.svelte | 67 |
7 files changed, 245 insertions, 0 deletions
diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts new file mode 100644 index 00000000..9f05cf68 --- /dev/null +++ b/src/routes/+layout.server.ts @@ -0,0 +1,5 @@ +export const load = ({ locals }) => { + const { user } = locals; + + return { user }; +}; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 00000000..2445967e --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,45 @@ +<script> + import { PUBLIC_ANILIST_CLIENT_ID, PUBLIC_ANILIST_REDIRECT_URI } from '$env/static/public'; + import { userIdentity } from '$lib/AniList/identity'; + import { onMount } from 'svelte'; + import { lastActivityDate } from '$lib/AniList/activity'; + + export let data; + + let currentUserIdentity = { name: '', id: -1 }; + let lastActivityWasToday = true; + + onMount(async () => { + if (data.user !== undefined) { + currentUserIdentity = await userIdentity(data.user); + currentUserIdentity.name = `(${currentUserIdentity.name})`; + lastActivityWasToday = + (await lastActivityDate(currentUserIdentity)).toDateString() === new Date().toDateString(); + } + }); +</script> + +<p /> + +<h1><a href="/">期限</a></h1> + +{#if data.user === undefined} + <a + href={`https://anilist.co/api/v2/oauth/authorize?client_id=${PUBLIC_ANILIST_CLIENT_ID}&redirect_uri=${PUBLIC_ANILIST_REDIRECT_URI}&response_type=code`} + >Log in with AniList</a + > +{:else} + <a href="/authentication/log-out">Log out from AniList {currentUserIdentity.name}</a> +{/if} + +{#if !lastActivityWasToday} + <p /> + + <p>You don't have any new activity statuses from the past day! Create one to keep your streak!</p> +{/if} + +<p /> + +<hr /> + +<slot /> diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte new file mode 100644 index 00000000..ccd4b119 --- /dev/null +++ b/src/routes/+page.svelte @@ -0,0 +1,60 @@ +<script lang="ts"> + import { onMount } from 'svelte'; + import { userIdentity } from '$lib/AniList/identity'; + import AnimeList from '$lib/List/Due/AnimeList.svelte'; + import MangaList from '$lib/List/Due/MangaList.svelte'; + import displayUnresolved from '../stores/displayUnresolved'; + import closeAnimeByDefault from '../stores/closeAnimeByDefault'; + import closeMangaByDefault from '../stores/closeMangaByDefault'; + + export let data; + + $: displayingUnresolved = $displayUnresolved === 'true'; + $: mangaClosed = $closeMangaByDefault === 'true'; + $: animeClosed = $closeAnimeByDefault === 'true'; + + let currentUserIdentity = { name: '', id: -1 }; + + onMount(async () => { + if (data.user !== undefined) { + currentUserIdentity = await userIdentity(data.user); + currentUserIdentity.name = `(${currentUserIdentity.name})`; + } + }); +</script> + +<a href="/settings">Settings</a> + +{#if data.user === undefined} + Please log in to view due media. +{:else} + <p /> + + <details open={animeClosed}> + {#if currentUserIdentity.id != -1} + <AnimeList + user={data.user} + identity={currentUserIdentity} + displayUnresolved={displayingUnresolved} + /> + {:else} + <summary>Anime</summary> + <ul><li>Loading ...</li></ul> + {/if} + </details> + + <p /> + + <details open={mangaClosed}> + {#if currentUserIdentity.id != -1} + <MangaList + user={data.user} + identity={currentUserIdentity} + displayUnresolved={displayingUnresolved} + /> + {:else} + <summary>Manga</summary> + <ul><li>Loading ...</li></ul> + {/if} + </details> +{/if} diff --git a/src/routes/anilist/increment/+server.ts b/src/routes/anilist/increment/+server.ts new file mode 100644 index 00000000..4680236b --- /dev/null +++ b/src/routes/anilist/increment/+server.ts @@ -0,0 +1,27 @@ +export const GET = async ({ url, cookies }) => { + const userCookie = cookies.get('user'); + + if (!userCookie) { + return new Response('Unauthenticated', { status: 401 }); + } + + const user = JSON.parse(userCookie); + + return Response.json( + await ( + await fetch('https://graphql.anilist.co', { + method: 'POST', + headers: { + Authorization: `${user['token_type']} ${user['access_token']}`, + 'Content-Type': 'application/json', + Accept: 'application/json' + }, + body: JSON.stringify({ + query: `mutation { SaveMediaListEntry(mediaId: ${ + url.searchParams.get('id') || 'null' + }, progress: ${url.searchParams.get('progress') || 'null'}) { id } }` + }) + }) + ).json() + ); +}; diff --git a/src/routes/authentication/log-out/+server.ts b/src/routes/authentication/log-out/+server.ts new file mode 100644 index 00000000..22ef49d8 --- /dev/null +++ b/src/routes/authentication/log-out/+server.ts @@ -0,0 +1,7 @@ +import { redirect } from '@sveltejs/kit'; + +export const GET = ({ cookies }) => { + cookies.delete('user', { path: '/' }); + + throw redirect(303, '/'); +}; diff --git a/src/routes/oauth/callback/+server.ts b/src/routes/oauth/callback/+server.ts new file mode 100644 index 00000000..4b0b6739 --- /dev/null +++ b/src/routes/oauth/callback/+server.ts @@ -0,0 +1,34 @@ +import { dev } from '$app/environment'; +import { ANILIST_CLIENT_SECRET } from '$env/static/private'; +import { PUBLIC_ANILIST_CLIENT_ID, PUBLIC_ANILIST_REDIRECT_URI } from '$env/static/public'; +import { redirect } from '@sveltejs/kit'; + +export const GET = async ({ url, cookies }) => { + const formData = new FormData(); + + formData.append('grant_type', 'authorization_code'); + formData.append('client_id', PUBLIC_ANILIST_CLIENT_ID); + formData.append('client_secret', ANILIST_CLIENT_SECRET); + formData.append('redirect_uri', PUBLIC_ANILIST_REDIRECT_URI); + formData.append('code', url.searchParams.get('code') || "null"); + cookies.set( + 'user', + JSON.stringify( + await ( + await fetch('https://anilist.co/api/v2/oauth/token', { + method: 'POST', + body: formData + }) + ).json() + ), + { + path: '/', + maxAge: 60 * 60 * 24 * 7, + httpOnly: true, + sameSite: 'strict', + secure: !dev + } + ); + + throw redirect(303, '/'); +}; diff --git a/src/routes/settings/+page.svelte b/src/routes/settings/+page.svelte new file mode 100644 index 00000000..cdd268f9 --- /dev/null +++ b/src/routes/settings/+page.svelte @@ -0,0 +1,67 @@ +<script lang="ts"> + import displayUnresolved from '../../stores/displayUnresolved'; + import closeAnimeByDefault from '../../stores/closeAnimeByDefault'; + import closeMangaByDefault from '../../stores/closeMangaByDefault'; + import { chapterDatabase } from '$lib/chapterDatabase'; + import cacheMangaMinutes from '../../stores/cacheMangaMinutes'; + + export let data; + + $: displayingUnresolved = $displayUnresolved === 'true'; + $: mangaClosed = $closeMangaByDefault === 'true'; + $: animeClosed = $closeAnimeByDefault === 'true'; + + const pruneUnresolved = async () => { + const unresolved = await chapterDatabase.chapters.where('chapters').equals(-1).toArray(); + + const ids = unresolved.map((m) => m.id); + + await chapterDatabase.chapters.bulkDelete(ids); + }; + + const pruneAll = async () => { + const all = await chapterDatabase.chapters.toArray(); + + const ids = all.map((m) => m.id); + + await chapterDatabase.chapters.bulkDelete(ids); + }; +</script> + +<a href="/">Home</a> + +<p /> + +{#if data.user === undefined} + not +{:else} + <ul> + <li> + <a + href="#" + on:click={() => + displayingUnresolved ? displayUnresolved.set('false') : displayUnresolved.set('true')} + >{displayingUnresolved ? 'Hide' : 'Show'} unresolved</a + > + </li> + <li><a href="#" on:click={pruneUnresolved}>Prune cached, unresolved manga</a></li> + <li><a href="#" on:click={pruneAll}>Prune <b>ALL</b> cached manga</a></li> + <li> + <a + href="#" + on:click={() => + animeClosed ? closeAnimeByDefault.set('false') : closeAnimeByDefault.set('true')} + >{animeClosed ? 'Close' : 'Expand'} anime by default</a + > + </li> + <li> + <a + href="#" + on:click={() => + mangaClosed ? closeMangaByDefault.set('false') : closeMangaByDefault.set('true')} + >{mangaClosed ? 'Close' : 'Expand'} manga by default</a + > + </li> + <li>Re-cache manga every <input type="number" bind:value={$cacheMangaMinutes} /> minutes</li> + </ul> +{/if} |