Experimenting with typescript API validation

This commit is contained in:
Eclypsed
2024-01-31 01:17:24 -05:00
parent fcbcf6780d
commit b7daf9c27c
2 changed files with 43 additions and 28 deletions

View File

@@ -4,7 +4,7 @@ import { generateUUID } from '$lib/utils'
const db = new Database('./src/lib/server/users.db', { verbose: console.info }) const db = new Database('./src/lib/server/users.db', { verbose: console.info })
db.pragma('foreign_keys = ON') db.pragma('foreign_keys = ON')
const initUsersTable = 'CREATE TABLE IF NOT EXISTS Users(id VARCHAR(36) PRIMARY KEY, username VARCHAR(30) UNIQUE NOT NULL, password VARCHAR(72) NOT NULL)' const initUsersTable = 'CREATE TABLE IF NOT EXISTS Users(id VARCHAR(36) PRIMARY KEY, username VARCHAR(30) UNIQUE NOT NULL, password VARCHAR(72) NOT NULL)'
const initServicesTable = 'CREATE TABLE IF NOT EXISTS Services(id VARCHAR(36) PRIMARY KEY, type VARCHAR(64) NOT NULL, userId TEXT NOT NULL, url TEXT NOT NULL)' const initServicesTable = 'CREATE TABLE IF NOT EXISTS Services(id VARCHAR(36) PRIMARY KEY, type VARCHAR(64) NOT NULL, serviceUserId TEXT NOT NULL, url TEXT NOT NULL)'
const initConnectionsTable = const initConnectionsTable =
'CREATE TABLE IF NOT EXISTS Connections(id VARCHAR(36) PRIMARY KEY, userId VARCHAR(36) NOT NULL, serviceId VARCHAR(36), accessToken TEXT NOT NULL, refreshToken TEXT, expiry INTEGER, FOREIGN KEY(userId) REFERENCES Users(id), FOREIGN KEY(serviceId) REFERENCES Services(id))' 'CREATE TABLE IF NOT EXISTS Connections(id VARCHAR(36) PRIMARY KEY, userId VARCHAR(36) NOT NULL, serviceId VARCHAR(36), accessToken TEXT NOT NULL, refreshToken TEXT, expiry INTEGER, FOREIGN KEY(userId) REFERENCES Users(id), FOREIGN KEY(serviceId) REFERENCES Services(id))'
db.exec(initUsersTable) db.exec(initUsersTable)
@@ -15,21 +15,21 @@ type UserQueryParams = {
includePassword?: boolean includePassword?: boolean
} }
export interface DBServiceData { interface DBServiceData {
id: string id: string
type: ServiceType type: ServiceType
userId: string serviceUserId: string
url: string url: string
} }
interface DBServiceRow { interface DBServiceRow {
id: string id: string
type: string type: string
userId: string serviceUserId: string
url: string url: string
} }
export interface DBConnectionData { interface DBConnectionData {
id: string id: string
user: User user: User
service: DBServiceData service: DBServiceData
@@ -80,14 +80,14 @@ export class Users {
export class Services { export class Services {
static getService = (id: string): DBServiceData => { static getService = (id: string): DBServiceData => {
const { type, userId, url } = db.prepare('SELECT * FROM Users WHERE id = ?').get(id) as DBServiceRow const { type, serviceUserId, url } = db.prepare('SELECT * FROM Users WHERE id = ?').get(id) as DBServiceRow
const service: DBServiceData = { id, type: type as ServiceType, userId, url } const service: DBServiceData = { id, type: type as ServiceType, serviceUserId, url }
return service return service
} }
static addService = (type: ServiceType, userId: string, url: URL): DBServiceData => { static addService = (type: ServiceType, serviceUserId: string, url: URL): DBServiceData => {
const serviceId = generateUUID() const serviceId = generateUUID()
db.prepare('INSERT INTO Services(id, type, userId, url) VALUES(?, ?, ?, ?)').run(serviceId, type, userId, url.origin) db.prepare('INSERT INTO Services(id, type, serviceUserId, url) VALUES(?, ?, ?, ?)').run(serviceId, type, serviceUserId, url.origin)
return this.getService(serviceId) return this.getService(serviceId)
} }

View File

@@ -1,37 +1,52 @@
import { Services, type DBServiceData, Connections, type DBConnectionData } from '$lib/server/users' import { Services, Connections } from '$lib/server/users'
import type { RequestHandler } from '@sveltejs/kit' import type { RequestHandler } from '@sveltejs/kit'
import { z } from 'zod' import { z } from 'zod'
const isValidURL = (url: string): boolean => {
try {
new URL(url)
return true
} catch {
return false
}
}
export const GET: RequestHandler = async ({ params }) => { export const GET: RequestHandler = async ({ params }) => {
const userId = params.userId as string const userId = params.userId as string
const repsonseHeaders = new Headers({
'Content-Type': 'application/json',
})
const connections = Connections.getUserConnections(userId) const connections = Connections.getUserConnections(userId)
return new Response(JSON.stringify(connections), { headers: repsonseHeaders }) return new Response(JSON.stringify(connections))
} }
export const PATCH: RequestHandler = async ({ params, request }) => { export const PATCH: RequestHandler = async ({ params, request }) => {
const userId = params.userId as string const userId = params.userId as string
const serviceSchema = z.object({
serviceType: z.enum(['jellyfin', 'youtube-music']),
userId: z.string(),
url: z.string(),
})
const connectionSchema = z.object({ const connectionSchema = z.object({
userId: z.string(), serviceType: z.enum(['jellyfin', 'youtube-music']),
serviceId: z.string(), serviceUserId: z.string(),
url: z.string().refine((val) => isValidURL(val)),
accessToken: z.string(), accessToken: z.string(),
refreshToken: z.string().nullable(), refreshToken: z.string().nullable().optional(),
expiry: z.number().nullable(), expiry: z.number().nullable().optional(),
}) })
const { service, connection } = await request.json() const connection = await request.json()
const serviceValidation = serviceSchema.safeParse(service) const connectionValidation = connectionSchema.safeParse(connection)
if (!serviceValidation.success) return new Response(serviceValidation.error.message, { status: 400 }) 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)
return new Response(JSON.stringify(newConnection))
}
export const DELETE: RequestHandler = async ({ request }) => {
const connectionId: string = await request.json()
try {
Connections.deleteConnection(connectionId)
return new Response('Connection Deleted')
} catch {
return new Response('Connection does not exist', { status: 400 })
}
} }