479 lines
12 KiB
TypeScript
479 lines
12 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";
|
|
import { fun, type } from "./common";
|
|
|
|
const db = new Database("lab.sqlite", { strict: true })
|
|
initDB(db);
|
|
|
|
// Error Type
|
|
const BoardErrorTypeSchema = z.union([
|
|
z.literal("Wrong Type"),
|
|
z.literal("No Such Board(s)"),
|
|
z.literal("ID Conflict"),
|
|
z.literal("Name Conflict")
|
|
])
|
|
|
|
export type BoardErrorType = z.infer<typeof BoardErrorTypeSchema>
|
|
|
|
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 type BoardColumn = keyof Board
|
|
|
|
export function isBoard(obj: any): obj is Board {
|
|
return boardSchema.safeParse(obj).success
|
|
}
|
|
|
|
export function isBoardArray(obj: any): obj is Array<Board> {
|
|
return boardSchema.array().safeParse(obj).success
|
|
}
|
|
|
|
export function isBoardColumn(obj: any): obj is BoardColumn {
|
|
return boardSchema.keyof().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 type UserColumn = keyof User
|
|
|
|
export function isUser(obj: any): obj is User {
|
|
return userSchema.safeParse(obj).success
|
|
}
|
|
|
|
export function isUserArray(obj: any): obj is Array<User> {
|
|
return userSchema.array().safeParse(obj).success
|
|
}
|
|
|
|
export function isUserColumn(obj: any): obj is UserColumn {
|
|
return userSchema.keyof().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, BoardErrorType> {
|
|
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")
|
|
}
|
|
} 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>, BoardErrorType> {
|
|
|
|
let arrayChanges: Array<Changes> = []
|
|
for (const item of array) {
|
|
const ret = add(item)
|
|
if (ret.isErr()) {
|
|
return ret
|
|
} else {
|
|
arrayChanges.push(ret.value)
|
|
}
|
|
}
|
|
|
|
return new Ok(arrayChanges)
|
|
}
|
|
|
|
export function all(): Option<Array<Board>> {
|
|
const query = db.query(`SELECT * FROM Boards`)
|
|
const ret = query.all()
|
|
query.finalize()
|
|
|
|
if (isBoardArray(ret)) {
|
|
return Some(ret)
|
|
} else {
|
|
return None
|
|
}
|
|
}
|
|
|
|
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, BoardErrorType>
|
|
export function remove(id: number): Result<Board, BoardErrorType>
|
|
export function remove(board: Board): Result<Board, BoardErrorType>
|
|
export function remove(arg1: any, arg2?: any): Result<Board, BoardErrorType> {
|
|
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("Wrong Type")
|
|
}
|
|
|
|
} 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(s)")
|
|
}
|
|
|
|
} else {
|
|
return new Err("Wrong Type")
|
|
}
|
|
|
|
const query = db.query(`DELETE FROM Boards WHERE ${condition}`)
|
|
query.run()
|
|
|
|
return new Ok(retBoard)
|
|
}
|
|
|
|
export function removeAll(): Option<Array<Board>> {
|
|
const array = all()
|
|
const query = db.query(`DELETE FROM Boards`)
|
|
query.run()
|
|
|
|
|
|
if (array.isSome()) {
|
|
return new Some(array.value)
|
|
} else {
|
|
return None
|
|
}
|
|
}
|
|
|
|
export function removeByCondition(condition: string): Result<Array<Board>, BoardErrorType> {
|
|
const rsArr = findByCondition(condition)
|
|
if (rsArr.isNone()) {
|
|
return new Err("No Such Board(s)")
|
|
}
|
|
|
|
const query = db.query(`DELETE FROM Boards WHERE ${condition}`)
|
|
query.run()
|
|
|
|
return new Ok(rsArr.value)
|
|
}
|
|
|
|
export function removeByValue(columnName: string, val: string | Array<string>)
|
|
: Result<Array<Board>, BoardErrorType> {
|
|
|
|
if (!isBoardColumn(columnName)) {
|
|
return new Err("Wrong Type")
|
|
}
|
|
|
|
let condition: string
|
|
if (_.isString(val)) {
|
|
const retCond = fun.sqlConditionFromString(columnName, val, "OR")
|
|
if (retCond.isSome()) {
|
|
condition = retCond.value
|
|
} else {
|
|
return new Err("Wrong Type")
|
|
}
|
|
} else if (type.isStringArray(val)) {
|
|
const retCond = fun.sqlConditionFromArray(columnName, val, "OR")
|
|
if (retCond.isSome()) {
|
|
condition = retCond.value
|
|
} else {
|
|
return new Err("Wrong Type")
|
|
}
|
|
} else {
|
|
return new Err("Wrong Type")
|
|
}
|
|
|
|
return removeByCondition(condition)
|
|
}
|
|
|
|
export function removeByName(name: string | Array<string>)
|
|
: Result<Array<Board>, BoardErrorType> {
|
|
return removeByValue("name", name)
|
|
}
|
|
|
|
export function removeByRoom(room: string | Array<string>)
|
|
: Result<Array<Board>, BoardErrorType> {
|
|
|
|
return removeByValue("room", room)
|
|
}
|
|
|
|
export function rooms(): Option<Array<string>> {
|
|
const query = db.query(`SELECT DISTINCT room FROM Boards`)
|
|
let rooms: Array<string> = []
|
|
const retVal = query.values()
|
|
|
|
if (retVal.length > 0) {
|
|
for (const item of retVal) {
|
|
rooms.push(item[0] as string)
|
|
}
|
|
return new Some(rooms)
|
|
} else {
|
|
return None
|
|
}
|
|
}
|
|
|
|
|
|
export function find(name: string, room: string): Result<Option<Board>, BoardErrorType>
|
|
export function find(id: number): Result<Option<Board>, BoardErrorType>
|
|
export function find(arg1: any, arg2?: any): Result<Option<Board>, BoardErrorType> {
|
|
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 findByName(name: string | Array<string>): Result<Option<Array<Board>>, BoardErrorType> {
|
|
let condition: string
|
|
if (_.isString(name)) {
|
|
const retCond = fun.sqlConditionFromString("name", name, "OR")
|
|
if (retCond.isSome()) {
|
|
condition = retCond.value
|
|
} else {
|
|
return new Err("Wrong Type")
|
|
}
|
|
} else if (type.isStringArray(name)) {
|
|
const retCond = fun.sqlConditionFromArray("name", name, "OR")
|
|
if (retCond.isSome()) {
|
|
condition = retCond.value
|
|
} else {
|
|
return new Err("Wrong Type")
|
|
}
|
|
} else {
|
|
return new Err("Wrong Type")
|
|
}
|
|
|
|
return new Ok(findByCondition(condition))
|
|
}
|
|
|
|
export function findByCondition(condition: string): Option<Array<Board>> {
|
|
const query = db.query(`SELECT * FROM Boards WHERE ${condition}`)
|
|
const ret = query.all()
|
|
if (isBoardArray(ret)) {
|
|
return new Some(ret)
|
|
} else {
|
|
return None
|
|
}
|
|
}
|
|
|
|
export function findByValue(columnName: string, val: string | Array<string>)
|
|
: Result<Option<Array<Board>>, BoardErrorType> {
|
|
|
|
if (!isBoardColumn(columnName)) {
|
|
return new Err("Wrong Type")
|
|
}
|
|
|
|
let condition: string
|
|
if (_.isString(val)) {
|
|
const retCond = fun.sqlConditionFromString(columnName, val, "OR")
|
|
if (retCond.isSome()) {
|
|
condition = retCond.value
|
|
} else {
|
|
return new Err("Wrong Type")
|
|
}
|
|
} else if (type.isStringArray(val)) {
|
|
const retCond = fun.sqlConditionFromArray(columnName, val, "OR")
|
|
if (retCond.isSome()) {
|
|
condition = retCond.value
|
|
} else {
|
|
return new Err("Wrong Type")
|
|
}
|
|
} else {
|
|
return new Err("Wrong Type")
|
|
}
|
|
|
|
return new Ok(findByCondition(condition))
|
|
}
|
|
|
|
export function includes(name: string, room?: string): Result<boolean, BoardErrorType>
|
|
export function includes(id: number): Result<boolean, BoardErrorType>
|
|
export function includes(arg1: any, arg2?: any): Result<boolean, BoardErrorType> {
|
|
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)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|