From 044b3616f97ec0c4d28a338528c51fdc8abd9ccd Mon Sep 17 00:00:00 2001 From: Eclypsed Date: Thu, 1 Feb 2024 18:10:15 -0500 Subject: [PATCH] Changed the DB schema AGAIN --- src/app.d.ts | 15 ++++ src/lib/server/users.db | Bin 32768 -> 24576 bytes src/lib/server/users.ts | 78 ++++-------------- .../api/users/[userId]/connections/+server.ts | 16 ++-- src/routes/login/+page.svelte | 4 +- src/routes/settings/+layout.svelte | 62 ++++++++++---- src/routes/settings/+page.svelte | 58 +------------ .../settings/connections/+page.server.ts | 23 ++++-- src/routes/settings/connections/+page.svelte | 59 ++++++------- 9 files changed, 133 insertions(+), 182 deletions(-) diff --git a/src/app.d.ts b/src/app.d.ts index 8b7e535..18c6af4 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -29,6 +29,21 @@ declare global { type ServiceType = 'jellyfin' | 'youtube-music' + interface Service { + type: ServiceType + userId: string + urlOrigin: string + username?: string + serverName?: string + } + + interface Connection { + id: string + user: User + service: Service + accessToken: string + } + interface MediaItem { connectionId: string serviceType: string diff --git a/src/lib/server/users.db b/src/lib/server/users.db index f24c00c3b51bfc7f3be290ee61e7c5d645ec390e..898f96372420f8ec29294a85d0c8284bbe306e84 100644 GIT binary patch delta 264 zcmZo@U}`wPI6+#FnSp_U4TxcYX`+s?I5UG@SqCrw4+d5~ZU%l){5CuPfAP)8MnOx5+J9$5^2D7Fq>trcD zlg%RhEdm*-=7wp8sU~K+Muvuox+dn9X}U>iMoGGsNh!w3=80)$Nr|a`naLUXIf=z8 zMu{qh1}do)Mg?a19-c`S0ZE3IrJnX!epBrz!`)j2;eFEzO&Ge55wiOD|MkS~(YGesfHG054&F-XJM zOmp%KK3T=0)U=}1;*5~|?9@Dk5Z8zh9fj12g3O{y1y8>aS9jN-$(#AKjCB;;{DWLQ z-Tf52T_ZJ$Q;W(nlT$rYG!=qe-CToQ{hVEc6@nq6#TuC@nwqBUlOOPzm~u1VgB=G`1EX1;gIpa$TopnboqSwjCQNqVm#Z(SEJ%er*33jx!OuTL!7tRuM@OL) zXg|;=Ajcw$7UiG{Y3j4Gi95?OGGykZq*fFc=46(n#wV7R { - const { type, serviceUserId, url } = db.prepare('SELECT * FROM Users WHERE id = ?').get(id) as DBServiceRow - const service: DBServiceData = { id, type: type as ServiceType, serviceUserId, url } - return service - } - - static addService = (type: ServiceType, serviceUserId: string, url: URL): DBServiceData => { - const serviceId = generateUUID() - db.prepare('INSERT INTO Services(id, type, serviceUserId, url) VALUES(?, ?, ?, ?)').run(serviceId, type, serviceUserId, url.origin) - return this.getService(serviceId) - } - - static deleteService = (id: string): void => { - const commandInfo = db.prepare('DELETE FROM Services WHERE id = ?').run(id) - if (commandInfo.changes === 0) throw new Error(`Serivce with id ${id} does not exist`) - } -} - export class Connections { - static getConnection = (id: string): DBConnectionData => { - const { userId, serviceId, accessToken, refreshToken, expiry } = db.prepare('SELECT * FROM Connections WHERE id = ?').get(id) as DBConnectionRow - const connection: DBConnectionData = { id, user: Users.getUser(userId)!, service: Services.getService(serviceId), accessToken, refreshToken, expiry } + static getConnection = (id: string): Connection => { + const { userId, service, accessToken } = db.prepare('SELECT * FROM Connections WHERE id = ?').get(id) as ConnectionsTableSchema + const connection: Connection = { id, user: Users.getUser(userId)!, service: JSON.parse(service), accessToken } return connection } - static getUserConnections = (userId: string): DBConnectionData[] => { - const connectionRows = db.prepare('SELECT * FROM Connections WHERE userId = ?').all(userId) as DBConnectionRow[] - const connections: DBConnectionData[] = [] + static getUserConnections = (userId: string): Connection[] => { + const connectionRows = db.prepare('SELECT * FROM Connections WHERE userId = ?').all(userId) as ConnectionsTableSchema[] + const connections: Connection[] = [] const user = Users.getUser(userId)! connectionRows.forEach((row) => { - const { id, serviceId, accessToken, refreshToken, expiry } = row - connections.push({ id, user, service: Services.getService(serviceId), accessToken, refreshToken, expiry }) + const { id, service, accessToken } = row + connections.push({ id, user, service: JSON.parse(service), accessToken }) }) return connections } - static addConnection = (userId: string, serviceId: string, accessToken: string, refreshToken: string | null, expiry: number | null): DBConnectionData => { + static addConnection = (userId: string, service: Service, accessToken: string): Connection => { const connectionId = generateUUID() - db.prepare('INSERT INTO Connections(id, userId, serviceId, accessToken, refreshToken, expiry) VALUES(?, ?, ?, ?, ?, ?)').run(connectionId, userId, serviceId, accessToken, refreshToken, expiry) + if (!isValidURL(service.urlOrigin)) throw new Error('Service does not have valid url') + db.prepare('INSERT INTO Connections(id, userId, service, accessToken) VALUES(?, ?, ?, ?)').run(connectionId, userId, JSON.stringify(service), accessToken) return this.getConnection(connectionId) } diff --git a/src/routes/api/users/[userId]/connections/+server.ts b/src/routes/api/users/[userId]/connections/+server.ts index 4e9c3bd..c478bdd 100644 --- a/src/routes/api/users/[userId]/connections/+server.ts +++ b/src/routes/api/users/[userId]/connections/+server.ts @@ -1,4 +1,4 @@ -import { Services, Connections } from '$lib/server/users' +import { Connections } from '$lib/server/users' import { isValidURL } from '$lib/utils' import type { RequestHandler } from '@sveltejs/kit' import { z } from 'zod' @@ -13,10 +13,8 @@ export const GET: RequestHandler = async ({ params }) => { const connectionSchema = z.object({ serviceType: z.enum(['jellyfin', 'youtube-music']), serviceUserId: z.string(), - url: z.string().refine((val) => isValidURL(val)), + urlOrigin: z.string().refine((val) => isValidURL(val)), accessToken: z.string(), - refreshToken: z.string().nullable().optional(), - expiry: z.number().nullable().optional(), }) export type NewConnection = z.infer @@ -28,9 +26,13 @@ export const POST: RequestHandler = async ({ params, request }) => { const connectionValidation = connectionSchema.safeParse(connection) if (!connectionValidation.success) return new Response(connectionValidation.error.message, { status: 400 }) - const { serviceType, serviceUserId, url, accessToken, refreshToken, expiry } = connectionValidation.data - const newService = Services.addService(serviceType as ServiceType, serviceUserId, new URL(url)) - const newConnection = Connections.addConnection(userId, newService.id, accessToken, refreshToken as string | null, expiry as number | null) + const { serviceType, serviceUserId, urlOrigin, accessToken } = connectionValidation.data + const service: Service = { + type: serviceType, + userId: serviceUserId, + urlOrigin: new URL(urlOrigin).origin, + } + const newConnection = Connections.addConnection(userId, service, accessToken) return new Response(JSON.stringify(newConnection)) } diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte index a031c36..0f4c4b2 100644 --- a/src/routes/login/+page.svelte +++ b/src/routes/login/+page.svelte @@ -64,10 +64,10 @@
-
+
-
+
import IconButton from '$lib/components/util/iconButton.svelte' + import type { LayoutServerData } from '../$types' + + export let data: LayoutServerData + + interface SettingRoute { + pathname: string + displayName: string + icon: string + } + + const accountRoutes: SettingRoute[] = [ + { + pathname: '/settings/connections', + displayName: 'Connections', + icon: 'fa-solid fa-circle-nodes', + }, + { + pathname: '/settings/devices', + displayName: 'Devices', + icon: 'fa-solid fa-mobile-screen', + }, + ] -
-

- history.back()}> - - - Account +
+

+ + history.back()}> + + + + Settings

-
+
+
- - diff --git a/src/routes/settings/+page.svelte b/src/routes/settings/+page.svelte index 21c225a..4c288a3 100644 --- a/src/routes/settings/+page.svelte +++ b/src/routes/settings/+page.svelte @@ -1,57 +1 @@ - - - +

Main Settings Page

diff --git a/src/routes/settings/connections/+page.server.ts b/src/routes/settings/connections/+page.server.ts index 249f342..e15f47b 100644 --- a/src/routes/settings/connections/+page.server.ts +++ b/src/routes/settings/connections/+page.server.ts @@ -1,16 +1,15 @@ import { fail } from '@sveltejs/kit' import { SECRET_INTERNAL_API_KEY } from '$env/static/private' -import type { DBConnectionData } from '$lib/server/users' import type { NewConnection } from '../../api/users/[userId]/connections/+server' import type { PageServerLoad, Actions } from './$types' export const load: PageServerLoad = async ({ fetch, locals }) => { - const connectionsResponse = await fetch(`/api/user/${locals.user.id}/connections`, { + const connectionsResponse = await fetch(`/api/users/${locals.user.id}/connections`, { method: 'GET', headers: { apikey: SECRET_INTERNAL_API_KEY }, }) - const userConnections: DBConnectionData[] = await connectionsResponse.json() + const userConnections: Connection[] = await connectionsResponse.json() return { userConnections } } @@ -32,7 +31,7 @@ export const actions: Actions = { const authData: Jellyfin.AuthData = await jellyfinAuthResponse.json() const newConnectionPayload: NewConnection = { - url: serverUrl.toString(), + urlOrigin: serverUrl.toString(), serviceType: 'jellyfin', serviceUserId: authData.User.Id, accessToken: authData.AccessToken, @@ -45,7 +44,21 @@ export const actions: Actions = { if (!newConnectionResponse.ok) return fail(500, { message: 'Internal Server Error' }) - const newConnection: DBConnectionData = await newConnectionResponse.json() + const newConnection: Connection = await newConnectionResponse.json() return { newConnection } }, + deleteConnection: async ({ request, fetch, locals }) => { + const formData = await request.formData() + const connectionId = formData.get('connectionId') + + const deleteConnectionResponse = await fetch(`/api/users/${locals.user.id}/connections`, { + method: 'DELETE', + headers: { apikey: SECRET_INTERNAL_API_KEY }, + body: JSON.stringify({ connectionId }), + }) + + if (!deleteConnectionResponse.ok) return fail(500, { message: 'Internal Server Error' }) + + return { deletedConnectionId: connectionId } + }, } diff --git a/src/routes/settings/connections/+page.svelte b/src/routes/settings/connections/+page.svelte index b3a22e9..a311d00 100644 --- a/src/routes/settings/connections/+page.svelte +++ b/src/routes/settings/connections/+page.svelte @@ -3,13 +3,14 @@ import { fly } from 'svelte/transition' import Services from '$lib/services.json' import JellyfinAuthBox from './jellyfinAuthBox.svelte' - import { newestAlert } from '$lib/stores' + import { newestAlert } from '$lib/stores.js' import IconButton from '$lib/components/util/iconButton.svelte' import Toggle from '$lib/components/util/toggle.svelte' + import type { PageServerData } from './$types.js' import type { SubmitFunction } from '@sveltejs/kit' - export let data - let connectionProfiles = data.connectionProfiles + export let data: PageServerData + let connections = data.userConnections const submitCredentials: SubmitFunction = ({ formData, action, cancel }) => { switch (action.search) { @@ -39,32 +40,26 @@ return async ({ result }) => { switch (result.type) { case 'failure': - $newestAlert = ['warning', result.data.message] - return + return ($newestAlert = ['warning', result.data?.message]) case 'success': - modal = null if (result.data?.newConnection) { - const newConnection = result.data.newConnection - connectionProfiles = [newConnection, ...connectionProfiles] + const newConnection: Connection = result.data.newConnection + connections = [newConnection, ...connections] - $newestAlert = ['success', `Added ${Services[newConnection.serviceType].displayName}`] - return + return ($newestAlert = ['success', `Added ${Services[newConnection.service.type].displayName}`]) } else if (result.data?.deletedConnectionId) { const id = result.data.deletedConnectionId - const indexToDelete = connectionProfiles.findIndex((profile) => profile.connectionId === id) - const serviceType = connectionProfiles[indexToDelete].serviceType + const indexToDelete = connections.findIndex((connection) => connection.id === id) + const serviceType = connections[indexToDelete].service.type - connectionProfiles.splice(indexToDelete, 1) - connectionProfiles = connectionProfiles + connections.splice(indexToDelete, 1) + connections = connections - $newestAlert = ['success', `Deleted ${Services[serviceType].displayName}`] - return + return ($newestAlert = ['success', `Deleted ${Services[serviceType].displayName}`]) } } } } - - let modal
@@ -72,35 +67,29 @@

Add Connection

{#each Object.entries(Services) as [serviceType, serviceData]} - {/each}
- {#each connectionProfiles as connectionProfile} - {@const serviceData = Services[connectionProfile.serviceType]} + {#each connections as connection} + {@const serviceData = Services[connection.service.type]}
{serviceData.displayName} icon
-
{connectionProfile?.username ? connectionProfile.username : 'Placeholder Account Name'}
+
{connection.service?.username ? connection.service.username : 'Placeholder Account Name'}
{serviceData.displayName} - {#if connectionProfile.serviceType === 'jellyfin' && connectionProfile?.serverName} - - {connectionProfile.serverName} + {#if connection.service.type === 'jellyfin' && connection.service?.serverName} + - {connection.service.serverName} {/if}
- (modal = `delete-${connectionProfile.connectionId}`)}> +
@@ -115,12 +104,12 @@
{/each}
- {#if modal} +