import { Database, type Changes } from "bun:sqlite"; import _ from "lodash"; import { Ok, Err, Result, None, Some, Option } from "ts-results-es"; import { z } from "zod"; const db = new Database("lab.sqlite", { strict: true }) initDB(db); const boardSchema = z.object({ id: z.number().nonnegative(), name: z.string(), room: z.string(), ipv4: z.string().ip({ version: "v4" }), ipv6: z.string().ip({ version: "v6" }), port: z.number().nonnegative().lte(65535), cmdID: z.number().nonnegative() }).partial({ ipv6: true, cmdID: true }) export type Board = z.infer export function isBoard(obj: any): obj is Board { return boardSchema.safeParse(obj).success } const userSchema = z.object({ id: z.number().nonnegative(), name: z.string(), password: z.string(), boardID: z.number(), }).partial({ boardID: true, }) export type User = z.infer export function isUser(obj: any): obj is User { return userSchema.safeParse(obj).success } function initDB(db: Database) { const tables = allTables() if (!tables.includes("Users")) { db.query(` CREATE TABLE Users( id INT PRIMARY KEY NOT NULL, name TEXT NOT NULL, password TEXT NOT NULL ); `).run(); } if (!tables.includes("Boards")) db.query(` CREATE TABLE Boards( id INT PRIMARY KEY NOT NULL, name TEXT NOT NULL, room TEXT NOT NULL, ipv4 CHAR(16) NOT NULL, ipv6 CHAR(46) , port INT NOT NULL ) `).run(); } export function allTables(): Array { const query = db.query(`SELECT name FROM sqlite_master WHERE type='table'`) var tables = new Array() // Flaten array for (const item of query.values()) { tables.push(item[0]) } query.finalize() return tables } export function tableColumnName(table: string): Array { const query = db.query(`PRAGMA table_info(${table})`) var columnName = new Array() for (const column of query.values()) { columnName.push(column[1]) } return columnName } export namespace BoardTable { export function add(board: Board): Result { if (!isBoard(board)) { return new Err("Wrong type") } // Ensure no id conflict if (board.id == 0) { const cnt = countAll() board.id = cnt + 1 } else { const retID = includes(board.id) if (retID.isOk()) { if (retID.value) return new Err("ID conflict") } else { return new Err("Wrong type") } } // Ensure no name conflict in the same room { const retName = includes(board.name, board.room) if (retName.isOk()) { if (retName.value) { return new Err("Name conflict in one room") } } else { return new Err("Wrong type") } } const query = db.query(` INSERT INTO Boards VALUES (${board.id}, '${board.name}, '${board.room}', '${board.ipv4}', '${_.isUndefined(board.ipv6) ? "NULL" : board.ipv6}', ${board.port}); `) return Ok(query.run()) } export function addFromArray(array: Array): Result, "Wrong type"> { let arrayChanges: Array = [] for (const item of array) { const ret = add(item) if (ret.isErr()) { return new Err("Wrong type") } else { arrayChanges.push(ret.value) } } return new Ok(arrayChanges) } export function all() { const query = db.query(`SELECT * FROM Boards`) const boards = query.all() query.finalize() return boards } export function countAll(): number { const query = db.query(`SELECT COUNT(*) FROM Boards`) return query.values()[0][0] as number } export function countByName(name: string): number { const query = db.query(`SELECT * FROM Boards WHERE name=${name}`) return query.values()[0][0] as number } export function countByRoom(room: string): number { const query = db.query(`SELECT * FROM Boards WHERE room=${room}`) return query.values()[0][0] as number } export function remove(name: string, room: string): Result export function remove(id: number): Result export function remove(board: Board): Result export function remove(arg1: any, arg2?: any): Result { let retBoard let condition: string if (isBoard(arg1)) { retBoard = _.cloneDeep(arg1) condition = `id=${arg1.id}` } else if (_.isNumber(arg1)) { retBoard = find(arg1) if (retBoard.isOk() && retBoard.value.isSome()) { retBoard = _.cloneDeep(retBoard.value.value) condition = `id=${arg1}` } else { return new Err("No such Board") } } else if (_.isString(arg1) && _.isString(arg2)) { retBoard = find(arg1, arg2) if (retBoard.isOk() && retBoard.value.isSome()) { retBoard = _.cloneDeep(retBoard.value.value) condition = `name=${arg1}` } else { return new Err("No such Board") } } else { return new Err("Wrong Type") } const query = db.query(`DELETE FROM Boards WHERE ${condition}`) query.run() return new Ok(retBoard) } export function find(name: string, room: string): Result, "Wrong type"> export function find(id: number): Result, "Wrong type"> export function find(arg1: any, arg2?: any): Result, "Wrong type"> { let condition: string if (_.isNumber(arg1)) { condition = `id=${arg1}` } else if (_.isString(arg1) && _.isString(arg2)) { condition = `name='${arg1}' AND room='${arg2}'` } else { return new Err("Wrong type") } const query = db.query(`SELECT * FROM Boards WHERE ${condition}`) const spRet = boardSchema.safeParse(query.get()) if (spRet.success) { return new Ok(Some(spRet.data)) } else { return new Ok(None) } } export function includes(name: string, room?: string): Result export function includes(id: number): Result export function includes(arg1: any, arg2?: any): Result { let condition: string if (_.isUndefined(arg2)) { if (_.isNumber(arg1)) { condition = `id=${arg1}` } else if (_.isString(arg1)) { condition = `name='${arg1}'` } else { return new Err("Wrong type") } } else { if (_.isString(arg1) && _.isString(arg2)) { condition = `name='${arg1} AND room=${arg2}'` } else { return new Err("Wrong type") } } const query = db.query(`SELECT COUNT(*) FROM Boards WHERE ${condition}`) const num = query.values()[0][0] as number return new Ok(num > 0 ? true : false) } } export namespace UserTable { export function countAll(): number { const query = db.query(`SELECT COUNT(*) FROM Users`) return query.values()[0][0] as number } export function find(id: number): Result, "Wrong Type"> export function find(name: string): Result, "Wrong Type"> export function find(arg: any): Result, "Wrong Type"> { let condition: string if (_.isNumber(arg)) { condition = `id=${arg}` } else if (_.isString(arg)) { condition = `name=${arg}` } else { return new Err("Wrong Type") } const query = db.query(`SELECT * FROM Users WHERE name='${arg}'`) const spRet = userSchema.safeParse(query.get()) if (spRet.success) { return new Ok(Some(spRet.data)) } else { return new Ok(None) } } }