import { join } from 'path'; import sqlite3 from 'sqlite3'; import { open, Database } from 'sqlite'; import { IcyNetUser } from '../../common/types/user'; import { DatabasePony, DatabaseUser } from '../types/database'; export class Persistence { private db!: Database; constructor(private _store = join(process.cwd(), 'ponies.db')) {} async initialize(): Promise { this.db = await open({ filename: this._store, driver: sqlite3.cached.Database, }); await this.db.migrate(); } async upsertUser(user: IcyNetUser): Promise { const existing = await this.db.get( `SELECT * FROM User WHERE id = ?`, user.id, ); if (existing) { if (existing.display_name !== user.display_name) { existing.display_name = user.display_name; await this.db.run(`UPDATE User SET display_name = ? WHERE id = ?`, [ user.display_name, user.id, ]); } return existing; } const time = new Date().toISOString(); await this.db.run( `INSERT INTO User (id, username, display_name, created_at) VALUES (?,?,?,?)`, [user.id, user.username, user.display_name, time], ); return { ...user, created_at: time }; } async upsertPony( user: IcyNetUser, data: DatabasePony, ): Promise { const pony = await this.db.get( 'SELECT * FROM Pony WHERE user_id = ?', [user.id], ); const updateData = this.serializePony({ ...pony, ...data }); if (pony) { await this.db.run( `UPDATE Pony SET display_name = ?, position = ?, rotation = ?, character = ? WHERE user_id = ?`, [ user.display_name, updateData.position, updateData.rotation, updateData.character, user.id, ], ); return this.deserializePony(updateData); } updateData.created_at = new Date().toISOString(); updateData.user_id = user.id; const keys = this.filterPonyKeys(updateData); const insert = Array.from({ length: keys.length }, () => '?').join(','); await this.db.run( `INSERT INTO Pony (${keys.join(',')}) VALUES (${insert})`, keys.map((key) => (updateData as any)[key]), ); return this.deserializePony(updateData); } async getUserPony(user: IcyNetUser): Promise { const result = await this.db.get( 'SELECT * FROM Pony WHERE user_id = ?', user.id, ); return result ? this.deserializePony(result) : null; } private serializePony(data: DatabasePony): DatabasePony { return { ...data, position: Array.isArray(data.position) ? JSON.stringify(data.position) : data.position, rotation: Array.isArray(data.rotation) ? JSON.stringify(data.rotation) : data.rotation, character: typeof data.character !== 'string' ? JSON.stringify(data.character) : data.character, }; } private deserializePony(data: DatabasePony): DatabasePony { return { ...data, position: Array.isArray(data.position) ? data.position : JSON.parse(data.position as string), rotation: Array.isArray(data.rotation) ? data.rotation : JSON.parse(data.rotation as string), character: typeof data.character !== 'string' ? data.character : JSON.parse(data.character as string), }; } private filterPonyKeys(data: DatabasePony): string[] { const keys = [ 'display_name', 'position', 'rotation', 'character', 'created_at', 'user_id', ]; return Object.keys(data).reduce( (list, current) => (keys.includes(current) ? [...list, current] : list), [], ); } }