2024-02-23 00:53:54 -05:00
import Database from 'better-sqlite3'
2024-01-23 01:21:41 -05:00
import { generateUUID } from '$lib/utils'
const db = new Database ( './src/lib/server/users.db' , { verbose : console.info } )
db . pragma ( 'foreign_keys = ON' )
2024-02-19 15:03:39 -05:00
const initUsersTable = ` CREATE TABLE IF NOT EXISTS Users(
id VARCHAR ( 36 ) PRIMARY KEY ,
username VARCHAR ( 30 ) UNIQUE NOT NULL ,
2024-02-23 00:53:54 -05:00
passwordHash VARCHAR ( 72 ) NOT NULL
2024-02-19 15:03:39 -05:00
) `
2024-02-18 00:01:54 -05:00
const initConnectionsTable = ` CREATE TABLE IF NOT EXISTS Connections(
id VARCHAR ( 36 ) PRIMARY KEY ,
userId VARCHAR ( 36 ) NOT NULL ,
2024-02-22 00:36:44 -05:00
type VARCHAR ( 36 ) NOT NULL ,
2024-02-23 00:53:54 -05:00
service TEXT ,
tokens TEXT
2024-02-18 00:01:54 -05:00
FOREIGN KEY ( userId ) REFERENCES Users ( id )
) `
2024-02-01 18:10:15 -05:00
db . exec ( initUsersTable ) , db . exec ( initConnectionsTable )
2024-01-23 01:21:41 -05:00
2024-02-01 18:10:15 -05:00
interface ConnectionsTableSchema {
2024-01-24 14:16:24 -05:00
id : string
userId : string
2024-02-23 00:53:54 -05:00
type : serviceType
service? : string
tokens? : string
2024-01-24 14:16:24 -05:00
}
2024-01-23 01:21:41 -05:00
export class Users {
2024-02-19 15:03:39 -05:00
static getUser = ( id : string ) : User | null = > {
2024-02-23 00:53:54 -05:00
const user = db . prepare ( ` SELECT * FROM Users WHERE id = ? ` ) . get ( id ) as User | null
2024-01-25 03:05:13 -05:00
return user
}
2024-02-19 15:03:39 -05:00
static getUsername = ( username : string ) : User | null = > {
2024-02-23 00:53:54 -05:00
const user = db . prepare ( ` SELECT * FROM Users WHERE lower(username) = ? ` ) . get ( username . toLowerCase ( ) ) as User | null
2024-01-25 03:05:13 -05:00
return user
}
2024-02-23 00:53:54 -05:00
static addUser = ( username : string , passwordHash : string ) : User | null = > {
2024-02-19 15:03:39 -05:00
if ( this . getUsername ( username ) ) return null
2024-01-24 14:16:24 -05:00
2024-01-23 01:21:41 -05:00
const userId = generateUUID ( )
2024-02-23 00:53:54 -05:00
db . prepare ( ` INSERT INTO Users(id, username, passwordHash) VALUES(?, ?, ?) ` ) . run ( userId , username , passwordHash )
2024-01-25 03:05:13 -05:00
return this . getUser ( userId ) !
}
static deleteUser = ( id : string ) : void = > {
2024-02-23 00:53:54 -05:00
const commandInfo = db . prepare ( ` DELETE FROM Users WHERE id = ? ` ) . run ( id )
2024-01-25 03:05:13 -05:00
if ( commandInfo . changes === 0 ) throw new Error ( ` User with id ${ id } does not exist ` )
2024-01-23 01:21:41 -05:00
}
2024-01-24 14:16:24 -05:00
}
2024-01-23 01:21:41 -05:00
2024-02-23 00:53:54 -05:00
type DBConnectionData < T extends serviceType > = T extends 'jellyfin'
? Omit < Jellyfin.Connection , 'service' > & { service : Pick < Jellyfin.Connection [ 'service' ] , 'userId' | 'urlOrigin' > }
: T extends 'youtube-music'
? Omit < YouTubeMusic.Connection , 'service' > & { service : Pick < YouTubeMusic.Connection [ 'service' ] , 'userId' > }
: never
2024-01-23 12:05:33 -05:00
export class Connections {
2024-02-23 00:53:54 -05:00
static getConnection = ( id : string ) : DBConnectionData < serviceType > = > {
const { userId , type , service , tokens } = db . prepare ( ` SELECT * FROM Connections WHERE id = ? ` ) . get ( id ) as ConnectionsTableSchema
const parsedService = service ? JSON . parse ( service ) : undefined
const parsedTokens = tokens ? JSON . parse ( tokens ) : undefined
const connection : DBConnectionData < typeof type > = { id , userId , type , service : parsedService , tokens : parsedTokens }
2024-01-24 14:16:24 -05:00
return connection
}
2024-02-23 00:53:54 -05:00
static getUserConnections = ( userId : string ) : DBConnectionData < serviceType > [ ] = > {
const connectionRows = db . prepare ( ` SELECT * FROM Connections WHERE userId = ? ` ) . all ( userId ) as ConnectionsTableSchema [ ]
const connections : DBConnectionData < serviceType > [ ] = [ ]
for ( const { id , type , service , tokens } of connectionRows ) {
const parsedService = service ? JSON . parse ( service ) : undefined
const parsedTokens = tokens ? JSON . parse ( tokens ) : undefined
connections . push ( { id , userId , type , service : parsedService , tokens : parsedTokens } )
2024-02-17 00:31:11 -05:00
}
2024-01-25 03:05:13 -05:00
return connections
}
2024-02-23 00:53:54 -05:00
static addConnection < T extends serviceType > ( type : T , connectionData : Omit < DBConnectionData < T > , 'id' | 'type' > ) : DBConnectionData < T > {
2024-01-24 14:16:24 -05:00
const connectionId = generateUUID ( )
2024-02-23 00:53:54 -05:00
const { userId , service , tokens } = connectionData
db . prepare ( ` INSERT INTO Connections(id, userId, type, service, tokens) VALUES(?, ?, ?, ?, ?) ` ) . run ( connectionId , userId , type , service , tokens )
return this . getConnection ( connectionId ) as DBConnectionData < T >
2024-01-24 14:16:24 -05:00
}
2024-01-25 03:05:13 -05:00
static deleteConnection = ( id : string ) : void = > {
2024-02-23 00:53:54 -05:00
const commandInfo = db . prepare ( ` DELETE FROM Connections WHERE id = ? ` ) . run ( id )
2024-01-25 03:05:13 -05:00
if ( commandInfo . changes === 0 ) throw new Error ( ` Connection with id: ${ id } does not exist ` )
}
2024-02-17 00:31:11 -05:00
2024-02-23 00:53:54 -05:00
static updateTokens = ( id : string , accessToken? : string , refreshToken? : string , expiry? : number ) : void = > {
const newTokens = { accessToken , refreshToken , expiry }
const commandInfo = db . prepare ( ` UPDATE Connections SET tokens = ? WHERE id = ? ` ) . run ( newTokens , id )
2024-02-17 00:31:11 -05:00
if ( commandInfo . changes === 0 ) throw new Error ( 'Failed to update tokens' )
}
2024-02-18 00:01:54 -05:00
2024-02-23 00:53:54 -05:00
static getExpiredConnections = ( userId : string ) : DBConnectionData < serviceType > [ ] = > {
const expiredRows = db . prepare ( ` SELECT * FROM Connections WHERE userId = ? AND json_extract(tokens, ' $ .expiry') < ? ` ) . all ( userId , Date . now ( ) ) as ConnectionsTableSchema [ ]
const connections : DBConnectionData < serviceType > [ ] = [ ]
for ( const { id , userId , type , service , tokens } of expiredRows ) {
const parsedService = service ? JSON . parse ( service ) : undefined
const parsedTokens = tokens ? JSON . parse ( tokens ) : undefined
connections . push ( { id , userId , type , service : parsedService , tokens : parsedTokens } )
2024-02-18 00:01:54 -05:00
}
return connections
}
2024-01-23 12:05:33 -05:00
}