init branch
This commit is contained in:
		@@ -14,10 +14,15 @@
 | 
			
		||||
      devShells = forEachSupportedSystem ({ pkgs }: {
 | 
			
		||||
        default = pkgs.mkShell {
 | 
			
		||||
          packages = with pkgs; [ 
 | 
			
		||||
            # Frontend
 | 
			
		||||
            bun
 | 
			
		||||
            sqlite
 | 
			
		||||
            sqls
 | 
			
		||||
            sql-studio
 | 
			
		||||
            # Backend
 | 
			
		||||
            dotnetCorePackages.sdk_9_0
 | 
			
		||||
            dotnetCorePackages.aspnetcore_9_0
 | 
			
		||||
            nuget
 | 
			
		||||
 | 
			
		||||
            # LSP
 | 
			
		||||
            typescript-language-server
 | 
			
		||||
 
 | 
			
		||||
@@ -1,66 +0,0 @@
 | 
			
		||||
import { type } from "./common"
 | 
			
		||||
import _ from "lodash"
 | 
			
		||||
import { expect, test } from "bun:test"
 | 
			
		||||
 | 
			
		||||
const CYCLES = 10000
 | 
			
		||||
test("Test Integer and Unsigned Integer", () => {
 | 
			
		||||
  for (let i = 0; i < CYCLES; i++) {
 | 
			
		||||
 | 
			
		||||
    // Unsigned Integer
 | 
			
		||||
    expect(type.UInteger.safeParse(_.random(0, Math.pow(2, 53), false)).success).toBeTrue()
 | 
			
		||||
    expect(type.UInt8.safeParse(_.random(0, Math.pow(2, 8) - 1, false)).success).toBeTrue()
 | 
			
		||||
    expect(type.UInt8.safeParse(_.random(Math.pow(2, 8), Math.pow(2, 53), false)).success).toBeFalse()
 | 
			
		||||
    expect(type.UInt16.safeParse(_.random(0, Math.pow(2, 16) - 1, false)).success).toBeTrue()
 | 
			
		||||
    expect(type.UInt16.safeParse(_.random(Math.pow(2, 16), Math.pow(2, 53), false)).success).toBeFalse()
 | 
			
		||||
    expect(type.UInt32.safeParse(_.random(0, Math.pow(2, 32) - 1, false)).success).toBeTrue()
 | 
			
		||||
    expect(type.UInt32.safeParse(_.random(Math.pow(2, 32), Math.pow(2, 53), false)).success).toBeFalse()
 | 
			
		||||
 | 
			
		||||
    // Integer
 | 
			
		||||
    expect(type.Integer.safeParse(_.random(-Math.pow(2, 52), Math.pow(2, 52) - 1, false)).success).toBeTrue()
 | 
			
		||||
    expect(type.Int8.safeParse(_.random(-Math.pow(2, 7), Math.pow(2, 7) - 1, false)).success).toBeTrue()
 | 
			
		||||
    expect(type.Int8.safeParse(_.random(Math.pow(2, 7), Math.pow(2, 52), false)).success).toBeFalse()
 | 
			
		||||
    expect(type.Int8.safeParse(_.random(-Math.pow(2, 52), -Math.pow(2, 7) - 1, false)).success).toBeFalse()
 | 
			
		||||
    expect(type.Int16.safeParse(_.random(-Math.pow(2, 15), Math.pow(2, 15) - 1, false)).success).toBeTrue()
 | 
			
		||||
    expect(type.Int16.safeParse(_.random(Math.pow(2, 15), Math.pow(2, 52), false)).success).toBeFalse()
 | 
			
		||||
    expect(type.Int16.safeParse(_.random(-Math.pow(2, 52), -Math.pow(2, 15) - 1, false)).success).toBeFalse()
 | 
			
		||||
    expect(type.Int32.safeParse(_.random(-Math.pow(2, 31), Math.pow(2, 31) - 1, false)).success).toBeTrue()
 | 
			
		||||
    expect(type.Int32.safeParse(_.random(Math.pow(2, 31), Math.pow(2, 52), false)).success).toBeFalse()
 | 
			
		||||
    expect(type.Int32.safeParse(_.random(-Math.pow(2, 52), -Math.pow(2, 31) - 1, false)).success).toBeFalse()
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test("Test Number Processor Function", () => {
 | 
			
		||||
  // Convert Number to Uint8Array
 | 
			
		||||
  expect(type.numberToBytes(0xFF, 1).unwrap()[0]).toBe(255)
 | 
			
		||||
  expect(type.numberToBytes(0xAAAA, 2).unwrap()).toEqual(new Uint8Array([0xAA, 0xAA]))
 | 
			
		||||
  expect(type.numberToBytes(0x12345678, 4).unwrap()).toEqual(new Uint8Array([0x78, 0x56, 0x34, 0x12]))
 | 
			
		||||
  expect(type.numberToBytes(0x12345678, 4, true).unwrap()).toEqual(new Uint8Array([0x12, 0x34, 0x56, 0x78]))
 | 
			
		||||
 | 
			
		||||
  // Convert Uint8Array to Number
 | 
			
		||||
  expect(type.bytesToNumber(new Uint8Array([0xFF]))).toBe(255)
 | 
			
		||||
  expect(type.bytesToNumber(new Uint8Array([0xAA, 0xAA]))).toEqual(0xAAAA)
 | 
			
		||||
  expect(type.bytesToNumber(new Uint8Array([0x78, 0x56, 0x34, 0x12]))).toEqual(0x12345678)
 | 
			
		||||
  expect(type.bytesToNumber(new Uint8Array([0x12, 0x34, 0x56, 0x78]), true)).toEqual(0x12345678)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  // Number Match 
 | 
			
		||||
  for (let i = 0; i < CYCLES; i++) {
 | 
			
		||||
    const num1 = _.random(CYCLES / 2, false)
 | 
			
		||||
    const num2 = _.random(CYCLES / 2, false)
 | 
			
		||||
 | 
			
		||||
    expect(type.numberMatch(num1, num2)).toBe((num1 & num2) === num2 ? true : false)
 | 
			
		||||
    expect(type.numberMatch(num1, num2, "True", "False")).toBe((num1 & num2) === num2 ? "True" : "False")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Number Set, Unset, Toggle and Get Bit
 | 
			
		||||
  expect(type.numberSetBit(0, 5)).toBe(0b10000)
 | 
			
		||||
  expect(type.numberUnsetBit(0b1111, 3)).toBe(0b1011)
 | 
			
		||||
  expect(type.numberToggleBit(0b1010, 3)).toBe(0b1110)
 | 
			
		||||
  expect(type.numberBit(0b1100, 2)).toBe(0)
 | 
			
		||||
 | 
			
		||||
  // Get High / Low Bits Num 
 | 
			
		||||
  expect(type.numberHighBitsNum(0xFF).unwrap()).toBe(8)
 | 
			
		||||
  expect(type.numberHighBitsNum(0xAA).unwrap()).toBe(4)
 | 
			
		||||
  expect(type.numberLowBitsNum(0xFF, 8).unwrap()).toBe(0)
 | 
			
		||||
  expect(type.numberLowBitsNum(0xAA, 8).unwrap()).toBe(4)
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										191
									
								
								server/common.ts
									
									
									
									
									
								
							
							
						
						
									
										191
									
								
								server/common.ts
									
									
									
									
									
								
							@@ -1,191 +0,0 @@
 | 
			
		||||
import _ from "lodash"
 | 
			
		||||
import { Option, Some, None, Result, Ok, Err } from "ts-results-es";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
 | 
			
		||||
export function UNUSED(_: unknown): void { }
 | 
			
		||||
 | 
			
		||||
export namespace type {
 | 
			
		||||
 | 
			
		||||
  const NUMBER_MAX_LENGTH = 32
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  export const ErrorTypeSchema = z.union([
 | 
			
		||||
    z.literal("Not Integer"),
 | 
			
		||||
    z.literal("Not Unsigned Integer"),
 | 
			
		||||
    z.literal("Not 32Bits Integer")
 | 
			
		||||
  ])
 | 
			
		||||
 | 
			
		||||
  export type ErrorType = z.infer<typeof ErrorTypeSchema>
 | 
			
		||||
 | 
			
		||||
  export const Integer = z.number().int()
 | 
			
		||||
  export const UInteger = z.number().int().nonnegative()
 | 
			
		||||
 | 
			
		||||
  export const UInt8 = UInteger.lt(Math.pow(2, 8))
 | 
			
		||||
  export const UInt16 = UInteger.lt(Math.pow(2, 16))
 | 
			
		||||
  export const UInt32 = UInteger.lt(Math.pow(2, 32))
 | 
			
		||||
 | 
			
		||||
  export const Int8 = Integer.lt(Math.pow(2, 7)).gte(-Math.pow(2, 8))
 | 
			
		||||
  export const Int16 = Integer.lt(Math.pow(2, 15)).gte(-Math.pow(2, 16))
 | 
			
		||||
  export const Int32 = Integer.lt(Math.pow(2, 31)).gte(-Math.pow(2, 32))
 | 
			
		||||
 | 
			
		||||
  export function numberToBytes(num: number, bytesLength: number, isRightHigh: boolean = false)
 | 
			
		||||
    : Result<Uint8Array, ErrorType> {
 | 
			
		||||
    // Check Integer
 | 
			
		||||
    if (!Int32.safeParse(num).success && !UInt32.lte(32).safeParse(bytesLength).success) {
 | 
			
		||||
      console.error(`Number : ${num}, 2 ^ 31 = ${2 ^ 31}`)
 | 
			
		||||
      console.error(Int32.safeParse(num).error?.message)
 | 
			
		||||
      return new Err("Not 32Bits Integer")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var array = new Uint8Array(bytesLength)
 | 
			
		||||
 | 
			
		||||
    if (isRightHigh) {
 | 
			
		||||
      for (let i = 0; i < bytesLength; i++) {
 | 
			
		||||
        array[bytesLength - 1 - i] = ((num >> (i << 3)) & 0xFF)
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      for (let i = 0; i < bytesLength; i++) {
 | 
			
		||||
        array[i] = ((num >> (i << 3)) & 0xFF)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return new Ok(array)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export function bytesToNumber(bytes: Uint8Array, isRightHigh: boolean = false): number {
 | 
			
		||||
    let num = 0
 | 
			
		||||
    const len = bytes.length
 | 
			
		||||
    if (isRightHigh) {
 | 
			
		||||
      for (let i = 0; i < len; i++) {
 | 
			
		||||
        num += bytes[len - 1 - i] << (i << 3)
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      for (let i = 0; i < len; i++) {
 | 
			
		||||
        num += bytes[i] << (i << 3)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return num
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  export function numberMatch(
 | 
			
		||||
    srcNum: number,
 | 
			
		||||
    destNum: number
 | 
			
		||||
  ): boolean;
 | 
			
		||||
 | 
			
		||||
  export function numberMatch<T>(
 | 
			
		||||
    srcNum: number,
 | 
			
		||||
    destNum: number,
 | 
			
		||||
    True: T,
 | 
			
		||||
    False: T
 | 
			
		||||
  ): T;
 | 
			
		||||
 | 
			
		||||
  export function numberMatch<T>(
 | 
			
		||||
    srcNum: number,
 | 
			
		||||
    destNum: number,
 | 
			
		||||
    True: T = true as T,
 | 
			
		||||
    False: T = false as T
 | 
			
		||||
  ): T {
 | 
			
		||||
    const ret = (srcNum & destNum) === destNum;
 | 
			
		||||
    return ret ? True : False;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export function numberMatchEnum() {
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export function numberSetBit(num: number, loc: number): number {
 | 
			
		||||
    return num | (1 << (loc - 1))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export function numberUnsetBit(num: number, loc: number): number {
 | 
			
		||||
    return num & ~(1 << (loc - 1))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export function numberToggleBit(num: number, loc: number): number {
 | 
			
		||||
    return num ^ (1 << (loc - 1))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export function numberBit(num: number, loc: number): number {
 | 
			
		||||
    return (num >> (loc - 1)) & 1
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export function numberHighBitsNum(num: number, maxLen: number = NUMBER_MAX_LENGTH): Result<number, ErrorType> {
 | 
			
		||||
    if (!Int32.safeParse(num).success && maxLen > 32) {
 | 
			
		||||
      return new Err("Not 32Bits Integer")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let cnt = 0
 | 
			
		||||
    for (let i = 0; i < maxLen; i++) {
 | 
			
		||||
      if (num & (1 << i)) {
 | 
			
		||||
        cnt++
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return new Ok(cnt)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export function numberLowBitsNum(num: number, maxLen: number): Result<number, ErrorType> {
 | 
			
		||||
    if (!Int32.safeParse(num).success && maxLen > 32) {
 | 
			
		||||
      return new Err("Not 32Bits Integer")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let cnt = 0
 | 
			
		||||
    for (let i = 0; i < maxLen; i++) {
 | 
			
		||||
      if (!(num & (1 << i))) {
 | 
			
		||||
        cnt++
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return new Ok(cnt)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export function isStringArray(obj: any): obj is Array<string> {
 | 
			
		||||
    return z.string().array().safeParse(obj).success
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export function isNumberArray(obj: any): obj is Array<number> {
 | 
			
		||||
    return z.number().array().safeParse(obj).success
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export namespace fun {
 | 
			
		||||
 | 
			
		||||
  export function randomFromArray(array: Array<any>) {
 | 
			
		||||
    return array[_.random(0, array.length - 1, false)]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export function sqlConditionFromArray(
 | 
			
		||||
    columnName: string,
 | 
			
		||||
    array: Array<string>,
 | 
			
		||||
    type: "AND" | "OR"
 | 
			
		||||
  ): Option<string> {
 | 
			
		||||
    let condition: string = ""
 | 
			
		||||
    const len = array.length
 | 
			
		||||
    if (len == 0) {
 | 
			
		||||
      return None
 | 
			
		||||
    }
 | 
			
		||||
    for (let i = 0; i < len; i++) {
 | 
			
		||||
      condition.concat(`${columnName}=${array[i]}`)
 | 
			
		||||
      if (i != len - 1) {
 | 
			
		||||
        condition.concat(` ${type} `)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return new Some(condition)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export function sqlConditionFromString(
 | 
			
		||||
    columnName: string,
 | 
			
		||||
    str: string,
 | 
			
		||||
    type: "AND" | "OR",
 | 
			
		||||
    delimiter: string = " "
 | 
			
		||||
  ): Option<string> {
 | 
			
		||||
    if (str.length == 0) {
 | 
			
		||||
      return None
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const array = str.split(delimiter)
 | 
			
		||||
    return sqlConditionFromArray(columnName, array, type)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -1,65 +0,0 @@
 | 
			
		||||
import { test, expect } from "bun:test"
 | 
			
		||||
import * as db from "./database.ts"
 | 
			
		||||
import _, { rearg } from "lodash"
 | 
			
		||||
import { None, Ok, Option, Some } from "ts-results-es"
 | 
			
		||||
 | 
			
		||||
// Test basic function for database
 | 
			
		||||
test("DataBase", () => {
 | 
			
		||||
  const allTables = db.allTables()
 | 
			
		||||
  expect(allTables).toBeArray()
 | 
			
		||||
  expect(allTables).toEqual(["Users", "Boards"])
 | 
			
		||||
  expect(db.BoardTable.countAll()).toBe(0)
 | 
			
		||||
  expect(db.UserTable.countAll()).toBe(0)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// Test Boards table function
 | 
			
		||||
const boardsNumber = 10
 | 
			
		||||
const rooms = ["A1", "A1", "A1", "A1", "A1", "A2", "A2", "A2", "A2", "A2"]
 | 
			
		||||
test("Find something empty", () => {
 | 
			
		||||
  const findEmptyByID = db.BoardTable.find(_.random(0, boardsNumber))
 | 
			
		||||
  expect(findEmptyByID).toEqual(Ok(None))
 | 
			
		||||
  const findEmptyByName = db.BoardTable.find("Hello", "World")
 | 
			
		||||
  expect(findEmptyByName).toEqual(Ok(None))
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test("Add some boards", () => {
 | 
			
		||||
  const boardsArray: Array<db.Board> = []
 | 
			
		||||
  for (let i = 0; i < boardsNumber; i++) {
 | 
			
		||||
    boardsArray.push({
 | 
			
		||||
      id: i,
 | 
			
		||||
      name: `Board ${i}`,
 | 
			
		||||
      room: rooms[i],
 | 
			
		||||
      ipv4: `192.168.172.${i}`,
 | 
			
		||||
      port: i,
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
  const retAdd = db.BoardTable.addFromArray(boardsArray)
 | 
			
		||||
  const isAddOk = retAdd.isOk()
 | 
			
		||||
  expect(isAddOk).toBeTrue()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test("Get boards table column unique element", () => {
 | 
			
		||||
  expect(db.BoardTable.rooms().unwrap()).toEqual(["A1", "A2"])
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test("Find something from boards table", () => {
 | 
			
		||||
  expect(db.BoardTable.find(1)).toEqual(Ok(Some({
 | 
			
		||||
    id: 1,
 | 
			
		||||
    name: `Board ${1}`,
 | 
			
		||||
    room: rooms[1],
 | 
			
		||||
    ipv4: `192.168.172.${1}`,
 | 
			
		||||
    port: 1,
 | 
			
		||||
  } as db.Board)))
 | 
			
		||||
  expect(db.BoardTable.find("Board 3", "A1")).toEqual(Ok(Some({
 | 
			
		||||
    id: 3,
 | 
			
		||||
    name: `Board ${3}`,
 | 
			
		||||
    room: rooms[3],
 | 
			
		||||
    ipv4: `192.168.172.${3}`,
 | 
			
		||||
    port: 3,
 | 
			
		||||
  } as db.Board)))
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test("Get count of elements from boards table", () => {
 | 
			
		||||
  expect(db.BoardTable.countByName("Board 1")).toBe(1)
 | 
			
		||||
  expect(db.BoardTable.countByRoom("A1")).toBe(5)
 | 
			
		||||
})
 | 
			
		||||
@@ -1,478 +0,0 @@
 | 
			
		||||
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)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -1,21 +0,0 @@
 | 
			
		||||
import { createBunServeHandler } from "trpc-bun-adapter";
 | 
			
		||||
import { appRouter } from "./router.ts"
 | 
			
		||||
 | 
			
		||||
Bun.serve(createBunServeHandler({
 | 
			
		||||
  router: appRouter,
 | 
			
		||||
  responseMeta() {
 | 
			
		||||
    return {
 | 
			
		||||
      status: 200,
 | 
			
		||||
      headers: {
 | 
			
		||||
        "Access-Control-Allow-Origin": "*",
 | 
			
		||||
        "Access-Control-Allow-Methods": "GET, PUT, POST, OPTIONS",
 | 
			
		||||
        "Access-Control-Allow-Headers": "Content-Type, Authorization"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}, {
 | 
			
		||||
  port: 3002,
 | 
			
		||||
  fetch() {
 | 
			
		||||
    return Response.json({ message: "Not Found" }, { status: 404 });
 | 
			
		||||
  }
 | 
			
		||||
}))
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
import { MsgProtocol } from "./msgProtocol";
 | 
			
		||||
@@ -1,79 +0,0 @@
 | 
			
		||||
import { type TransferListItem } from "worker_threads";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { UNUSED } from "./common";
 | 
			
		||||
 | 
			
		||||
export namespace MsgProtocol {
 | 
			
		||||
  const QueryDataSchema = z.object({ type: z.literal("Query"), args: z.any() })
 | 
			
		||||
  const ResultDataSchema = z.object({ type: z.literal("Result"), result: z.any() })
 | 
			
		||||
  const ErrorDataSchema = z.object({ type: z.literal("Error"), error: z.string() })
 | 
			
		||||
 | 
			
		||||
  const MessageSchema = z.object({
 | 
			
		||||
    command: z.string(),
 | 
			
		||||
    data: z.discriminatedUnion("type", [QueryDataSchema, ResultDataSchema, ErrorDataSchema]),
 | 
			
		||||
    dest: z.string(),
 | 
			
		||||
    src: z.string(),
 | 
			
		||||
  })
 | 
			
		||||
  const MessageQuerySchema = z.object({
 | 
			
		||||
    command: z.string(),
 | 
			
		||||
    data: QueryDataSchema,
 | 
			
		||||
    dest: z.string(),
 | 
			
		||||
    src: z.string(),
 | 
			
		||||
  })
 | 
			
		||||
  const MessageResultSchema = z.object({
 | 
			
		||||
    command: z.string(),
 | 
			
		||||
    data: ResultDataSchema,
 | 
			
		||||
    dest: z.string(),
 | 
			
		||||
    src: z.string(),
 | 
			
		||||
  })
 | 
			
		||||
  const MessageErrorSchema = z.object({
 | 
			
		||||
    command: z.string(),
 | 
			
		||||
    data: ErrorDataSchema,
 | 
			
		||||
    dest: z.string(),
 | 
			
		||||
    src: z.string(),
 | 
			
		||||
  })
 | 
			
		||||
  const MessageHandlerSchema = z.function()
 | 
			
		||||
    .args(z.union([MessageResultSchema, MessageErrorSchema]))
 | 
			
		||||
    .returns(z.void())
 | 
			
		||||
 | 
			
		||||
  export type Message = z.infer<typeof MessageSchema>
 | 
			
		||||
  export type MessageQuery = z.infer<typeof MessageQuerySchema>
 | 
			
		||||
  export type MessageResult = z.infer<typeof MessageResultSchema>
 | 
			
		||||
  export type MessageError = z.infer<typeof MessageErrorSchema>
 | 
			
		||||
  export type MessageHandler = z.infer<typeof MessageHandlerSchema>
 | 
			
		||||
 | 
			
		||||
  export function isMessage(obj: any): obj is Message {
 | 
			
		||||
    return MessageSchema.safeParse(obj).success
 | 
			
		||||
  }
 | 
			
		||||
  export function isMessageQuery(obj: any): obj is MessageQuery {
 | 
			
		||||
    return MessageQuerySchema.safeParse(obj).success
 | 
			
		||||
  }
 | 
			
		||||
  export function isMessageResult(obj: any): obj is MessageResult {
 | 
			
		||||
    return MessageResultSchema.safeParse(obj).success
 | 
			
		||||
  }
 | 
			
		||||
  export function isMessageError(obj: any): obj is MessageError {
 | 
			
		||||
    return MessageErrorSchema.safeParse(obj).success
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export function genMessageResult(result: any, srcMsg: MessageQuery): MessageResult {
 | 
			
		||||
    return {
 | 
			
		||||
      command: srcMsg.command,
 | 
			
		||||
      dest: srcMsg.src,
 | 
			
		||||
      src: srcMsg.dest,
 | 
			
		||||
      data: {
 | 
			
		||||
        type: "Result",
 | 
			
		||||
        result: result
 | 
			
		||||
      }
 | 
			
		||||
    } as MessageResult
 | 
			
		||||
  }
 | 
			
		||||
  export function genMessageError(error: string, srcMsg: MessageQuery): MessageError {
 | 
			
		||||
    return {
 | 
			
		||||
      command: srcMsg.command,
 | 
			
		||||
      dest: srcMsg.src,
 | 
			
		||||
      src: srcMsg.dest,
 | 
			
		||||
      data: {
 | 
			
		||||
        type: "Error",
 | 
			
		||||
        error: error
 | 
			
		||||
      }
 | 
			
		||||
    } as MessageError
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +0,0 @@
 | 
			
		||||
import { router, publicProcedure } from "./trpc.ts"
 | 
			
		||||
 | 
			
		||||
export const appRouter = router({
 | 
			
		||||
  api: router({
 | 
			
		||||
    status: publicProcedure.query(() => "OK"),
 | 
			
		||||
    signUp: publicProcedure.query((opts) => {
 | 
			
		||||
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export type AppRouter = typeof appRouter;
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
import { initTRPC } from '@trpc/server';
 | 
			
		||||
 | 
			
		||||
const t = initTRPC.create();
 | 
			
		||||
 | 
			
		||||
export const router = t.router;
 | 
			
		||||
export const publicProcedure = t.procedure;
 | 
			
		||||
@@ -1,22 +0,0 @@
 | 
			
		||||
import { expect, test } from "bun:test"
 | 
			
		||||
import { udpServer, udpClient } from "./udp"
 | 
			
		||||
 | 
			
		||||
const udpServerAddr = "127.0.0.1"
 | 
			
		||||
const udpServerPort = await udpServer.port()
 | 
			
		||||
 | 
			
		||||
console.info("Ready to Test UDP")
 | 
			
		||||
 | 
			
		||||
test("Test Send String", async () => {
 | 
			
		||||
  const str = "Hello My Server"
 | 
			
		||||
  const retSend = await udpClient.sendString(str, udpServerPort, udpServerAddr)
 | 
			
		||||
  expect(retSend).toBe(true)
 | 
			
		||||
 | 
			
		||||
  const retReceive = await udpServer.lastestData(udpServerAddr)
 | 
			
		||||
  if (retReceive.isSome()) {
 | 
			
		||||
    const data = retReceive.value
 | 
			
		||||
    expect(data.body).toBe(str)
 | 
			
		||||
    expect(data.address).toBe(udpServerAddr)
 | 
			
		||||
  } else {
 | 
			
		||||
    expect().fail("Not Receive Anything")
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
@@ -1,80 +0,0 @@
 | 
			
		||||
import { resolve } from "path";
 | 
			
		||||
import Tinypool from "tinypool";
 | 
			
		||||
import type { UDPReceiveProcotol, UDPSendProtocol } from "./udpProtocol";
 | 
			
		||||
import type { udp } from "bun";
 | 
			
		||||
import { Option } from "ts-results-es";
 | 
			
		||||
 | 
			
		||||
const SERVER_PATH = resolve(__dirname, "./udpServer.ts")
 | 
			
		||||
const CLIENT_PATH = resolve(__dirname, "./udpClient.ts")
 | 
			
		||||
 | 
			
		||||
export type UDPBodyType = (
 | 
			
		||||
  udp.Data |
 | 
			
		||||
  UDPSendProtocol.CmdPackage |
 | 
			
		||||
  UDPSendProtocol.DataPackage |
 | 
			
		||||
  UDPReceiveProcotol.ReadPackage |
 | 
			
		||||
  UDPReceiveProcotol.WritePackage
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type UDPDataType<T extends UDPBodyType = UDPBodyType> = {
 | 
			
		||||
  address: string,
 | 
			
		||||
  body: T,
 | 
			
		||||
  port: number,
 | 
			
		||||
  date?: string,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const udpClientsPool = new Tinypool({
 | 
			
		||||
  filename: CLIENT_PATH,
 | 
			
		||||
  workerData: {}
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const udpServerPool = new Tinypool({
 | 
			
		||||
  filename: SERVER_PATH,
 | 
			
		||||
  workerData: {},
 | 
			
		||||
  maxThreads: 1,
 | 
			
		||||
  minThreads: 1,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export namespace udpServer {
 | 
			
		||||
 | 
			
		||||
  export async function port(): Promise<number> {
 | 
			
		||||
    return udpServerPool.run(null, { name: "port" })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export async function lastestData(address: string): Promise<Option<UDPDataType<udp.Data>>> {
 | 
			
		||||
    return udpServerPool.run(address, { name: "lastestData" })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export async function oldestData(address: string): Promise<Option<UDPDataType<udp.Data>>> {
 | 
			
		||||
    return udpServerPool.run(address, { name: "oldestData" })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export namespace udpClient {
 | 
			
		||||
 | 
			
		||||
  export async function sendString(data: string, port: number, address: string): Promise<boolean> {
 | 
			
		||||
    return udpClientsPool.run({
 | 
			
		||||
      body: data,
 | 
			
		||||
      port: port,
 | 
			
		||||
      address: address
 | 
			
		||||
    } as UDPDataType<string>, { name: "sendString" })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export async function sendUint8Array(data: Uint8Array, port: number, address: string): Promise<boolean> {
 | 
			
		||||
    return udpClientsPool.run({
 | 
			
		||||
      body: data,
 | 
			
		||||
      port: port,
 | 
			
		||||
      address: address
 | 
			
		||||
    } as UDPDataType<Uint8Array>, { name: "sendUint8Array" })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export async function sendBunData(data: udp.Data, port: number, address: string): Promise<boolean> {
 | 
			
		||||
    return udpClientsPool.run({
 | 
			
		||||
      body: data,
 | 
			
		||||
      port: port,
 | 
			
		||||
      address: address
 | 
			
		||||
    } as UDPDataType<udp.Data>, { name: "sendUint8Array" })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
export type { UDPDataType }
 | 
			
		||||
@@ -1,17 +0,0 @@
 | 
			
		||||
import type { udp } from "bun"
 | 
			
		||||
import type { UDPDataType } from "./udp"
 | 
			
		||||
 | 
			
		||||
const udpClient = await Bun.udpSocket({})
 | 
			
		||||
 | 
			
		||||
export function sendString(data: UDPDataType<string>): boolean {
 | 
			
		||||
  return udpClient.send(data.body, data.port, data.address)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function sendUint8Array(data: UDPDataType<Uint8Array>): boolean {
 | 
			
		||||
  return udpClient.send(data.body, data.port, data.address)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function sendBunData(data: UDPDataType<udp.Data>): boolean {
 | 
			
		||||
  return udpClient.send(data.body, data.port, data.address)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1,201 +0,0 @@
 | 
			
		||||
import { type } from "./common";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import _ from "lodash";
 | 
			
		||||
import { Err, Ok, type Result } from "ts-results-es";
 | 
			
		||||
import { isUint8Array } from "util/types";
 | 
			
		||||
 | 
			
		||||
const PKG_SIGN_ADDR = 0x00
 | 
			
		||||
const PKG_SIGN_DATA = 0xFF
 | 
			
		||||
const PKG_SIGN_READ = 0x0F
 | 
			
		||||
const PKG_SIGN_WRITE = 0xF0
 | 
			
		||||
 | 
			
		||||
export namespace UDPSendProtocol {
 | 
			
		||||
 | 
			
		||||
  const CMDLOC_BURST_TYPE = 5
 | 
			
		||||
  const CMDLOC_TASK_ID = 3
 | 
			
		||||
  const CMDLOC_READ_WRITE_TYPE = 0
 | 
			
		||||
 | 
			
		||||
  const CmdPackageOptionsSchema = z.object({
 | 
			
		||||
    burstType: z.enum(["Fixed", "Extend"]),
 | 
			
		||||
    taskID: z.number().nonnegative().lt(4),
 | 
			
		||||
    readWriteType: z.enum(["r", "w"])
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  export type CmdPackageOptions = z.infer<typeof CmdPackageOptionsSchema>
 | 
			
		||||
 | 
			
		||||
  export function isCmdPackageOptions(obj: any): obj is CmdPackageOptions {
 | 
			
		||||
    return CmdPackageOptionsSchema.safeParse(obj).success
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export class CmdPackage {
 | 
			
		||||
    private ID: number = PKG_SIGN_ADDR
 | 
			
		||||
    private commandType: number = 0
 | 
			
		||||
    private burstLength: number = 0
 | 
			
		||||
    private _reserved: number = 0
 | 
			
		||||
    private address: number = 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    constructor(array: Uint8Array)
 | 
			
		||||
    constructor(options: CmdPackageOptions)
 | 
			
		||||
    constructor(arg: any) {
 | 
			
		||||
      if (isCmdPackageOptions(arg)) {
 | 
			
		||||
        this.setCommandType(arg)
 | 
			
		||||
      } else if (isUint8Array(arg)) {
 | 
			
		||||
        this.ID = arg[0]
 | 
			
		||||
        this.commandType = arg[1]
 | 
			
		||||
        this.burstLength = arg[2]
 | 
			
		||||
        this._reserved = arg[3]
 | 
			
		||||
        this.address = type.bytesToNumber(arg.slice(4))
 | 
			
		||||
      } else {
 | 
			
		||||
        throw new Err("Wrong Type")
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setCommandType(options: CmdPackageOptions) {
 | 
			
		||||
      const validOptions = CmdPackageOptionsSchema.parse(options)
 | 
			
		||||
      this.commandType = (
 | 
			
		||||
        ((validOptions.burstType === "Fixed") ? 1 << CMDLOC_BURST_TYPE : 0) |
 | 
			
		||||
        (validOptions.taskID << CMDLOC_TASK_ID) |
 | 
			
		||||
        ((validOptions.readWriteType === "r") ? 1 << CMDLOC_READ_WRITE_TYPE : 0)
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setBurstLength(len: number): Result<null, "Not 8Bits Unsigned Integer"> {
 | 
			
		||||
      if (!type.UInt8.safeParse(len).success) {
 | 
			
		||||
        return new Err("Not 8Bits Unsigned Integer")
 | 
			
		||||
      }
 | 
			
		||||
      this.burstLength = len
 | 
			
		||||
      return new Ok(null)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setAddress(addr: number): Result<null, "Not 32Bits Unsigned Integer"> {
 | 
			
		||||
      if (!type.UInt32.safeParse(addr).success) {
 | 
			
		||||
        return new Err("Not 32Bits Unsigned Integer")
 | 
			
		||||
      }
 | 
			
		||||
      this.address = addr
 | 
			
		||||
      return new Ok(null)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    options(): CmdPackageOptions {
 | 
			
		||||
      return {
 | 
			
		||||
        burstType: type.numberMatch(this.commandType, CMDLOC_BURST_TYPE, "Extend", "Fixed"),
 | 
			
		||||
        taskID: (this.commandType >> CMDLOC_TASK_ID) & 0b0011,
 | 
			
		||||
        readWriteType: type.numberMatch(this.commandType, CMDLOC_READ_WRITE_TYPE, "w", "r")
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    toUint8Array(): Uint8Array {
 | 
			
		||||
      var array = new Uint8Array(8)
 | 
			
		||||
 | 
			
		||||
      array[0] = this.ID
 | 
			
		||||
      array[1] = this.commandType
 | 
			
		||||
      array[2] = this.burstLength
 | 
			
		||||
      array[3] = this._reserved
 | 
			
		||||
 | 
			
		||||
      // Already check address length at the begining of set address
 | 
			
		||||
      let addressBytes = type.numberToBytes(this.address, 4).unwrap()
 | 
			
		||||
      array.set(addressBytes, 4)
 | 
			
		||||
 | 
			
		||||
      return array
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export function isCmdPackage(obj: any): obj is CmdPackage {
 | 
			
		||||
    return obj instanceof CmdPackage
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export class DataPackage {
 | 
			
		||||
    private ID: number = PKG_SIGN_DATA
 | 
			
		||||
    private _reserved: number = 0
 | 
			
		||||
    private body: Uint8Array
 | 
			
		||||
 | 
			
		||||
    constructor(body: Uint8Array) {
 | 
			
		||||
      this.body = body
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    toUint8Array(): Uint8Array {
 | 
			
		||||
      var array = new Uint8Array(4 + this.body.length)
 | 
			
		||||
 | 
			
		||||
      array[0] = this.ID
 | 
			
		||||
      array.fill(this._reserved, 1, 4)
 | 
			
		||||
      array.set(this.body, 4)
 | 
			
		||||
 | 
			
		||||
      return array
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export function isDataPackage(obj: any): obj is DataPackage {
 | 
			
		||||
    return obj instanceof DataPackage
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export namespace UDPReceiveProcotol {
 | 
			
		||||
 | 
			
		||||
  export class ReadPackage {
 | 
			
		||||
    private ID: number = PKG_SIGN_READ
 | 
			
		||||
    private taskID: number = 0
 | 
			
		||||
    private resp: number = 0
 | 
			
		||||
    private _reserved: number = 0
 | 
			
		||||
    body: Uint8Array
 | 
			
		||||
 | 
			
		||||
    constructor(array: Uint8Array) {
 | 
			
		||||
      if (array.length < 5) {
 | 
			
		||||
        throw new Err("Not Long Enough")
 | 
			
		||||
      }
 | 
			
		||||
      this.ID = array[0]
 | 
			
		||||
      this.taskID = array[1]
 | 
			
		||||
      this.resp = array[2]
 | 
			
		||||
      this._reserved = array[3]
 | 
			
		||||
      this.body = new Uint8Array(array.slice(4))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    toUint8Array(): Uint8Array {
 | 
			
		||||
      let array = new Uint8Array(4 + this.body.length)
 | 
			
		||||
 | 
			
		||||
      array[0] = this.ID
 | 
			
		||||
      array[1] = this.taskID
 | 
			
		||||
      array[2] = this.resp
 | 
			
		||||
      array[3] = this._reserved
 | 
			
		||||
      array.set(this.body, 4)
 | 
			
		||||
 | 
			
		||||
      return array
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export function isReadPackage(obj: any): obj is ReadPackage {
 | 
			
		||||
    return obj instanceof ReadPackage
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export class WritePackage {
 | 
			
		||||
    private ID: number = PKG_SIGN_WRITE
 | 
			
		||||
    private taskID: number = 0
 | 
			
		||||
    private resp: number = 0
 | 
			
		||||
    private _reserved: number = 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    constructor(array: Uint8Array) {
 | 
			
		||||
      if (array.length < 4) {
 | 
			
		||||
        throw new Err("Not Long Enough")
 | 
			
		||||
      }
 | 
			
		||||
      this.ID = array[0]
 | 
			
		||||
      this.taskID = array[1]
 | 
			
		||||
      this.resp = array[2]
 | 
			
		||||
      this._reserved = array[3]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    toUint8Array(): Uint8Array {
 | 
			
		||||
      return new Uint8Array([
 | 
			
		||||
        this.ID,
 | 
			
		||||
        this.taskID,
 | 
			
		||||
        this.resp,
 | 
			
		||||
        this._reserved
 | 
			
		||||
      ])
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export function isWritePackage(obj: any): obj is WritePackage {
 | 
			
		||||
    return obj instanceof WritePackage
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,97 +0,0 @@
 | 
			
		||||
import _ from "lodash"
 | 
			
		||||
import { type udp } from "bun"
 | 
			
		||||
import type { UDPDataType } from "./udp";
 | 
			
		||||
import { None, Some, type Option } from "ts-results-es";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
 | 
			
		||||
// Bun Data Type
 | 
			
		||||
interface BinaryTypeList {
 | 
			
		||||
  arraybuffer: ArrayBuffer;
 | 
			
		||||
  buffer: Buffer;
 | 
			
		||||
  uint8array: Uint8Array;
 | 
			
		||||
}
 | 
			
		||||
type BinaryType = keyof BinaryTypeList;
 | 
			
		||||
 | 
			
		||||
const receivedData: Map<string, Array<{
 | 
			
		||||
  body: udp.Data,
 | 
			
		||||
  port: number,
 | 
			
		||||
  date: string
 | 
			
		||||
}>> = new Map()
 | 
			
		||||
 | 
			
		||||
const udpServer = await Bun.udpSocket({
 | 
			
		||||
  port: 33000,
 | 
			
		||||
  socket: {
 | 
			
		||||
    data(
 | 
			
		||||
      _socket: udp.Socket<BinaryType>,
 | 
			
		||||
      data: BinaryTypeList[BinaryType],
 | 
			
		||||
      port: number,
 | 
			
		||||
      address: string,
 | 
			
		||||
    ) {
 | 
			
		||||
      // Add Received Data
 | 
			
		||||
      let arrayData = receivedData.get(address)
 | 
			
		||||
      if (_.isUndefined(arrayData)) {
 | 
			
		||||
 | 
			
		||||
      } else {
 | 
			
		||||
        receivedData.set(address, [])
 | 
			
		||||
        arrayData.push({
 | 
			
		||||
          body: data,
 | 
			
		||||
          port: port,
 | 
			
		||||
          date: new Date().toUTCString()
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
function port(): number {
 | 
			
		||||
  return udpServer.port
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function lastestData(address: string): Option<UDPDataType<udp.Data>> {
 | 
			
		||||
  if (!z.string().ip().safeParse(address).success) {
 | 
			
		||||
    return None
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const arrayData = receivedData.get(address)
 | 
			
		||||
  if (_.isUndefined(arrayData)) {
 | 
			
		||||
    return None
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const data = arrayData.pop()
 | 
			
		||||
  if (_.isUndefined(data)) {
 | 
			
		||||
    return None
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return Some({
 | 
			
		||||
    address: address,
 | 
			
		||||
    body: data.body,
 | 
			
		||||
    port: data.port,
 | 
			
		||||
    date: data.date,
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function oldestData(address: string): Option<UDPDataType<udp.Data>> {
 | 
			
		||||
  if (!z.string().ip().safeParse(address).success) {
 | 
			
		||||
    return None
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const arrayData = receivedData.get(address)
 | 
			
		||||
  if (_.isUndefined(arrayData)) {
 | 
			
		||||
    return None
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const data = arrayData.shift()
 | 
			
		||||
  if (_.isUndefined(data)) {
 | 
			
		||||
    return None
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return Some({
 | 
			
		||||
    address: address,
 | 
			
		||||
    body: data.body,
 | 
			
		||||
    port: data.port,
 | 
			
		||||
    date: data.date,
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { port, lastestData, oldestData }
 | 
			
		||||
 | 
			
		||||
@@ -1,26 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    // Enable latest features
 | 
			
		||||
    "lib": [
 | 
			
		||||
      "ESNext"
 | 
			
		||||
    ],
 | 
			
		||||
    "target": "ESNext",
 | 
			
		||||
    "module": "ESNext",
 | 
			
		||||
    "moduleDetection": "force",
 | 
			
		||||
    "jsx": "react-jsx",
 | 
			
		||||
    "allowJs": true,
 | 
			
		||||
    // Bundler mode
 | 
			
		||||
    "moduleResolution": "bundler",
 | 
			
		||||
    "allowImportingTsExtensions": true,
 | 
			
		||||
    "verbatimModuleSyntax": true,
 | 
			
		||||
    "noEmit": true,
 | 
			
		||||
    // Best practices
 | 
			
		||||
    "strict": true,
 | 
			
		||||
    "skipLibCheck": true,
 | 
			
		||||
    "noFallthroughCasesInSwitch": true,
 | 
			
		||||
    // Some stricter flags
 | 
			
		||||
    "noUnusedLocals": true,
 | 
			
		||||
    "noUnusedParameters": true,
 | 
			
		||||
    "noPropertyAccessFromIndexSignature": true
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user