aboutsummaryrefslogtreecommitdiff
path: root/src/routes
diff options
context:
space:
mode:
authorFuwn <[email protected]>2023-08-26 22:29:03 -0700
committerFuwn <[email protected]>2023-08-26 22:29:03 -0700
commitb89d0e7dada186e31be37e62a7a75efc2dbe9c99 (patch)
tree8c9f6b5d7aa0f709c06d5eb45fbf763883b21c89 /src/routes
downloaddue.moe-b89d0e7dada186e31be37e62a7a75efc2dbe9c99.tar.xz
due.moe-b89d0e7dada186e31be37e62a7a75efc2dbe9c99.zip
feat: initial build
Diffstat (limited to 'src/routes')
-rw-r--r--src/routes/+layout.server.ts5
-rw-r--r--src/routes/+layout.svelte45
-rw-r--r--src/routes/+page.svelte60
-rw-r--r--src/routes/anilist/increment/+server.ts27
-rw-r--r--src/routes/authentication/log-out/+server.ts7
-rw-r--r--src/routes/oauth/callback/+server.ts34
-rw-r--r--src/routes/settings/+page.svelte67
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}