FPGA_WebLab/server/database.ts

303 lines
7.8 KiB
TypeScript

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<typeof boardSchema>
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<typeof userSchema>
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<string> {
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<string> {
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<Changes, "Wrong type" | "ID conflict" | "Name conflict in one room"> {
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<Board>): Result<Array<Changes>, "Wrong type"> {
let arrayChanges: Array<Changes> = []
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<Board, "Wrong Type" | "No such Board">
export function remove(id: number): Result<Board, "Wrong Type" | "No such Board">
export function remove(board: Board): Result<Board, "Wrong Type" | "No such Board">
export function remove(arg1: any, arg2?: any): Result<Board, "Wrong Type" | "No such Board"> {
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<Option<Board>, "Wrong type">
export function find(id: number): Result<Option<Board>, "Wrong type">
export function find(arg1: any, arg2?: any): Result<Option<Board>, "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<boolean, "Wrong type">
export function includes(id: number): Result<boolean, "Wrong type">
export function includes(arg1: any, arg2?: any): Result<boolean, "Wrong type"> {
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<Option<User>, "Wrong Type">
export function find(name: string): Result<Option<User>, "Wrong Type">
export function find(arg: any): Result<Option<User>, "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)
}
}
}