2024-01-23 01:21:41 -05:00
import Database from 'better-sqlite3'
import { generateUUID } from '$lib/utils'
2024-02-01 18:10:15 -05:00
import { isValidURL } from '$lib/utils'
2024-01-23 01:21:41 -05:00
const db = new Database ( './src/lib/server/users.db' , { verbose : console.info } )
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)'
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 ,
service TEXT NOT NULL ,
accessToken TEXT NOT NULL ,
refreshToken TEXT ,
expiry NUMBER ,
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-01-25 03:05:13 -05:00
type UserQueryParams = {
includePassword? : boolean
}
2024-02-01 18:10:15 -05:00
interface ConnectionsTableSchema {
2024-01-24 14:16:24 -05:00
id : string
userId : string
2024-02-01 18:10:15 -05:00
service : string
2024-02-18 00:01:54 -05:00
accessToken : string
refreshToken? : string
expiry? : number
2024-01-24 14:16:24 -05:00
}
2024-01-23 01:21:41 -05:00
export class Users {
2024-01-25 03:05:13 -05:00
static getUser = ( id : string , params : UserQueryParams | null = null ) : User | undefined = > {
const user = db . prepare ( 'SELECT * FROM Users WHERE id = ?' ) . get ( id ) as User | undefined
if ( user && ! params ? . includePassword ) delete user . password
return user
}
static getUsername = ( username : string , params : UserQueryParams | null = null ) : User | undefined = > {
const user = db . prepare ( 'SELECT * FROM Users WHERE lower(username) = ?' ) . get ( username . toLowerCase ( ) ) as User | undefined
if ( user && ! params ? . includePassword ) delete user . password
return user
}
static allUsers = ( includePassword : boolean = false ) : User [ ] = > {
const users = db . prepare ( 'SELECT * FROM Users' ) . all ( ) as User [ ]
if ( ! includePassword ) users . forEach ( ( user ) = > delete user . password )
return users
2024-01-24 14:16:24 -05:00
}
2024-01-23 01:21:41 -05:00
static addUser = ( username : string , hashedPassword : string ) : User = > {
const userId = generateUUID ( )
db . prepare ( 'INSERT INTO Users(id, username, password) VALUES(?, ?, ?)' ) . run ( userId , username , hashedPassword )
2024-01-25 03:05:13 -05:00
return this . getUser ( userId ) !
}
static deleteUser = ( id : string ) : void = > {
const commandInfo = db . prepare ( 'DELETE FROM Users WHERE id = ?' ) . run ( id )
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-01-23 12:05:33 -05:00
export class Connections {
2024-02-01 18:10:15 -05:00
static getConnection = ( id : string ) : Connection = > {
2024-02-18 00:01:54 -05:00
const { userId , service , accessToken , refreshToken , expiry } = db . prepare ( 'SELECT * FROM Connections WHERE id = ?' ) . get ( id ) as ConnectionsTableSchema
const connection : Connection = { id , userId , service : JSON.parse ( service ) , accessToken , refreshToken , expiry }
2024-01-24 14:16:24 -05:00
return connection
}
2024-02-01 18:10:15 -05:00
static getUserConnections = ( userId : string ) : Connection [ ] = > {
const connectionRows = db . prepare ( 'SELECT * FROM Connections WHERE userId = ?' ) . all ( userId ) as ConnectionsTableSchema [ ]
const connections : Connection [ ] = [ ]
2024-02-17 00:31:11 -05:00
for ( const row of connectionRows ) {
2024-02-18 00:01:54 -05:00
const { id , service , accessToken , refreshToken , expiry } = row
connections . push ( { id , userId , service : JSON.parse ( service ) , accessToken , refreshToken , expiry } )
2024-02-17 00:31:11 -05:00
}
2024-01-25 03:05:13 -05:00
return connections
}
2024-02-18 00:01:54 -05:00
static addConnection = ( userId : string , service : Service , accessToken : string , refreshToken? : string , expiry? : number ) : Connection = > {
2024-01-24 14:16:24 -05:00
const connectionId = generateUUID ( )
2024-02-01 18:10:15 -05:00
if ( ! isValidURL ( service . urlOrigin ) ) throw new Error ( 'Service does not have valid url' )
2024-02-18 00:01:54 -05:00
db . prepare ( 'INSERT INTO Connections(id, userId, service, accessToken, refreshToken, expiry) VALUES(?, ?, ?, ?, ?, ?)' ) . run ( connectionId , userId , JSON . stringify ( service ) , accessToken , refreshToken , expiry )
2024-01-24 14:16:24 -05:00
return this . getConnection ( connectionId )
}
2024-01-25 03:05:13 -05:00
static deleteConnection = ( id : string ) : void = > {
const commandInfo = db . prepare ( 'DELETE FROM Connections WHERE id = ?' ) . run ( id )
if ( commandInfo . changes === 0 ) throw new Error ( ` Connection with id: ${ id } does not exist ` )
}
2024-02-17 00:31:11 -05:00
2024-02-18 00:01:54 -05:00
static updateTokens = ( id : string , accessToken : string , refreshToken? : string , expiry? : number ) : void = > {
const commandInfo = db . prepare ( 'UPDATE Connections SET accessToken = ?, refreshToken = ?, expiry = ? WHERE id = ?' ) . run ( accessToken , refreshToken , expiry , 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
static getExpiredConnections = ( userId : string ) : Connection [ ] = > {
const expiredRows = db . prepare ( 'SELECT * FROM Connections WHERE userId = ? AND expiry < ?' ) . all ( userId , Date . now ( ) ) as ConnectionsTableSchema [ ]
const connections : Connection [ ] = [ ]
for ( const row of expiredRows ) {
const { id , userId , service , accessToken , refreshToken , expiry } = row
connections . push ( { id , userId , service : JSON.parse ( service ) , accessToken , refreshToken , expiry } )
}
return connections
}
2024-01-23 12:05:33 -05:00
}