From 861c03b85160972431ca9b262345d15edecf9acb Mon Sep 17 00:00:00 2001 From: Fuwn Date: Tue, 24 Oct 2023 17:12:54 -0700 Subject: feat: badge wall --- .dockerignore | 1 + .eslintignore | 1 + .gitignore | 1 + .prettierignore | 1 + src/lib/userBadgesDatabase.ts | 57 +++++ src/routes/api/badges/add/+server.ts | 23 ++ src/routes/api/badges/remove/+server.ts | 22 ++ src/routes/user/[user]/+page.server.ts | 5 + src/routes/user/[user]/+page.svelte | 40 ++++ src/routes/user/[user]/badges/+page.server.ts | 11 + src/routes/user/[user]/badges/+page.svelte | 301 ++++++++++++++++++++++++++ 11 files changed, 463 insertions(+) create mode 100644 src/lib/userBadgesDatabase.ts create mode 100644 src/routes/api/badges/add/+server.ts create mode 100644 src/routes/api/badges/remove/+server.ts create mode 100644 src/routes/user/[user]/+page.server.ts create mode 100644 src/routes/user/[user]/+page.svelte create mode 100644 src/routes/user/[user]/badges/+page.server.ts create mode 100644 src/routes/user/[user]/badges/+page.svelte diff --git a/.dockerignore b/.dockerignore index ced480f7..f1e46035 100644 --- a/.dockerignore +++ b/.dockerignore @@ -13,6 +13,7 @@ node_modules .env .env.* !.env.example +*.sqlite3 # PNPM pnpm-lock.yaml diff --git a/.eslintignore b/.eslintignore index ced480f7..f1e46035 100644 --- a/.eslintignore +++ b/.eslintignore @@ -13,6 +13,7 @@ node_modules .env .env.* !.env.example +*.sqlite3 # PNPM pnpm-lock.yaml diff --git a/.gitignore b/.gitignore index ced480f7..f1e46035 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ node_modules .env .env.* !.env.example +*.sqlite3 # PNPM pnpm-lock.yaml diff --git a/.prettierignore b/.prettierignore index ced480f7..f1e46035 100644 --- a/.prettierignore +++ b/.prettierignore @@ -13,6 +13,7 @@ node_modules .env .env.* !.env.example +*.sqlite3 # PNPM pnpm-lock.yaml diff --git a/src/lib/userBadgesDatabase.ts b/src/lib/userBadgesDatabase.ts new file mode 100644 index 00000000..c129770e --- /dev/null +++ b/src/lib/userBadgesDatabase.ts @@ -0,0 +1,57 @@ +import { dev } from '$app/environment'; +import Database from 'better-sqlite3'; + +export interface Badge { + post: string; + image: string; + description?: string; + id?: number; +} + +const database = new Database('./due_moe.sqlite3', { + verbose: dev ? console.log : undefined +}); + +database.exec(`create table if not exists user_badges ( + id integer primary key, + user_id integer not null, + post text not null, + image text not null, + description text default null, + time timestamp default current_timestamp +)`); + +export const getUserBadges = (userId: number): Badge[] => { + return database + .prepare('select * from user_badges where user_id = ?') + .all(String(userId)) as Badge[]; +}; + +export const addUserBadge = (userId: number, badge: Badge) => { + const { post, image, description } = badge; + const statement = database.prepare(` + insert into user_badges (user_id, post, image, description) values (?, ?, ?, ?) + `); + + statement.run(userId, post, image, description); +}; + +export const addUserBadges = (userId: number, badges: Badge[]) => { + const statement = database.prepare(` + insert into user_badges (user_id, post, image, description) values (?, ?, ?, ?) + `); + + for (const badge of badges) { + const { post, image, description } = badge; + + statement.run(userId, post, image, description); + } +}; + +export const removeUserBadge = (userId: number, id: number) => { + if (!isNaN(id)) { + const statement = database.prepare('delete from user_badges where user_id = ? and id = ?'); + + statement.run(userId, id); + } +}; diff --git a/src/routes/api/badges/add/+server.ts b/src/routes/api/badges/add/+server.ts new file mode 100644 index 00000000..6ce9421e --- /dev/null +++ b/src/routes/api/badges/add/+server.ts @@ -0,0 +1,23 @@ +import { userIdentity } from '$lib/AniList/identity.js'; +import { addUserBadges } from '$lib/userBadgesDatabase.js'; + +export const POST = async ({ cookies, request }) => { + const userCookie = cookies.get('user'); + + if (!userCookie) { + return new Response('Unauthenticated', { status: 401 }); + } + + const user = JSON.parse(userCookie); + const identity = await userIdentity({ + tokenType: user['token_type'], + expiresIn: user['expires_in'], + accessToken: user['access_token'], + refreshToken: user['refresh_token'] + }); + const formData = await request.json(); + + addUserBadges(identity.id, formData); + + return Response.json({}); +}; diff --git a/src/routes/api/badges/remove/+server.ts b/src/routes/api/badges/remove/+server.ts new file mode 100644 index 00000000..8b05369a --- /dev/null +++ b/src/routes/api/badges/remove/+server.ts @@ -0,0 +1,22 @@ +import { userIdentity } from '$lib/AniList/identity.js'; +import { removeUserBadge } from '$lib/userBadgesDatabase.js'; + +export const POST = async ({ url, cookies }) => { + const userCookie = cookies.get('user'); + + if (!userCookie) { + return new Response('Unauthenticated', { status: 401 }); + } + + const user = JSON.parse(userCookie); + const identity = await userIdentity({ + tokenType: user['token_type'], + expiresIn: user['expires_in'], + accessToken: user['access_token'], + refreshToken: user['refresh_token'] + }); + + removeUserBadge(identity.id, Number(url.searchParams.get('id'))); + + return Response.json({}); +}; diff --git a/src/routes/user/[user]/+page.server.ts b/src/routes/user/[user]/+page.server.ts new file mode 100644 index 00000000..76d2d889 --- /dev/null +++ b/src/routes/user/[user]/+page.server.ts @@ -0,0 +1,5 @@ +export const load = ({ params }) => { + return { + username: params.user + }; +}; diff --git a/src/routes/user/[user]/+page.svelte b/src/routes/user/[user]/+page.svelte new file mode 100644 index 00000000..227ba252 --- /dev/null +++ b/src/routes/user/[user]/+page.svelte @@ -0,0 +1,40 @@ + + +{#if userData === null} + Could not load user profile for @{data.username}. + +

+ + Does this user exist? +{:else if userData === undefined} + Loading ... +{:else} +

+ @{userData.name} + • Badge Wall +

+ + This user has watched {(userData.statistics.anime.minutesWatched / 60 / 24).toFixed(1)} days of anime + and read + {((userData.statistics.manga.chaptersRead * 8.58) / 60 / 24).toFixed(1)} days of manga. +{/if} diff --git a/src/routes/user/[user]/badges/+page.server.ts b/src/routes/user/[user]/badges/+page.server.ts new file mode 100644 index 00000000..4be5bcd2 --- /dev/null +++ b/src/routes/user/[user]/badges/+page.server.ts @@ -0,0 +1,11 @@ +import { user } from '$lib/AniList/user.js'; +import { getUserBadges } from '$lib/userBadgesDatabase.js'; + +export const load = async ({ params }) => { + const badges = getUserBadges((await user(params.user)).id); + + return { + username: params.user, + badges + }; +}; diff --git a/src/routes/user/[user]/badges/+page.svelte b/src/routes/user/[user]/badges/+page.svelte new file mode 100644 index 00000000..276bf1ca --- /dev/null +++ b/src/routes/user/[user]/badges/+page.svelte @@ -0,0 +1,301 @@ + + +{#await currentUserIdentity} + Loading ... +{:then identity} + {@const isOwner = identity && identity.name === data.username} +

+ Back to Profile + {#if isOwner} + • + (editMode = !editMode)} + >{editMode ? 'Disable' : 'Enable'} Edit Mode + {/if} +

+ + {#if editMode && isOwner} +

+ Delete mode is enabled. Click on an image to delete it. There is no confirmation, so be + careful! +

+ +

+ + + + Add Badge +

+ {/if} + +
+ {#each data.badges as badge} + {#if editMode} + removeBadge(badge)} id={`badge-${badge.id}`}> + {badge.description} + + {:else} + + {badge.description} + + {/if} + {/each} +
+{/await} + + -- cgit v1.2.3