move to biomejs, this is before unsafe lint run
Some checks failed
Code quality checks / biome (push) Failing after 16s
Some checks failed
Code quality checks / biome (push) Failing after 16s
This commit is contained in:
parent
f4237afc59
commit
25fcd99acf
43 changed files with 353 additions and 565 deletions
24
.forgejo/workflows/biomejs.yml
Normal file
24
.forgejo/workflows/biomejs.yml
Normal file
|
@ -0,0 +1,24 @@
|
|||
name: Code quality checks
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
biome:
|
||||
runs-on: docker
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Bun
|
||||
run: |
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
export BUN_INSTALL="$HOME/.bun"
|
||||
echo "$BUN_INSTALL/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install Dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Run Biome with verbose output
|
||||
run: bunx biome ci . --verbose
|
7
.vscode/extensions.json
vendored
7
.vscode/extensions.json
vendored
|
@ -1,9 +1,8 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"recommendations": [
|
||||
"mikestead.dotenv",
|
||||
"EditorConfig.EditorConfig",
|
||||
"leonzalion.vscode-ejs",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"stylelint.vscode-stylelint"
|
||||
]
|
||||
"biomejs.biome"
|
||||
]
|
||||
}
|
||||
|
|
35
biome.json
Normal file
35
biome.json
Normal file
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": false
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": true,
|
||||
"ignore": []
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "tab",
|
||||
"lineEnding": "lf"
|
||||
},
|
||||
"organizeImports": {
|
||||
"enabled": true
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "double",
|
||||
"indentStyle": "tab",
|
||||
"lineEnding": "lf",
|
||||
"jsxQuoteStyle": "double",
|
||||
"semicolons": "always"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
import { resolve } from "path";
|
||||
|
||||
export const environment: Environment = {
|
||||
port: parseInt(process.env.PORT || "8080", 10),
|
||||
port: Number.parseInt(process.env.PORT || "8080", 10),
|
||||
host: process.env.HOST || "0.0.0.0",
|
||||
development:
|
||||
process.env.NODE_ENV === "development" ||
|
||||
process.argv.includes("--dev"),
|
||||
process.env.NODE_ENV === "development" || process.argv.includes("--dev"),
|
||||
};
|
||||
|
||||
export const redisConfig: {
|
||||
|
@ -15,7 +14,7 @@ export const redisConfig: {
|
|||
password?: string | undefined;
|
||||
} = {
|
||||
host: process.env.REDIS_HOST || "localhost",
|
||||
port: parseInt(process.env.REDIS_PORT || "6379", 10),
|
||||
port: Number.parseInt(process.env.REDIS_PORT || "6379", 10),
|
||||
username: process.env.REDIS_USERNAME || undefined,
|
||||
password: process.env.REDIS_PASSWORD || undefined,
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@ import { type ReservedSQL, sql } from "bun";
|
|||
export const order: number = 6;
|
||||
|
||||
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||
let selfReservation: boolean = false;
|
||||
let selfReservation = false;
|
||||
|
||||
if (!reservation) {
|
||||
reservation = await sql.reserve();
|
||||
|
|
|
@ -4,7 +4,7 @@ import { type ReservedSQL, sql } from "bun";
|
|||
export const order: number = 5;
|
||||
|
||||
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||
let selfReservation: boolean = false;
|
||||
let selfReservation = false;
|
||||
|
||||
if (!reservation) {
|
||||
reservation = await sql.reserve();
|
||||
|
|
|
@ -4,7 +4,7 @@ import { type ReservedSQL, sql } from "bun";
|
|||
export const order: number = 4;
|
||||
|
||||
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||
let selfReservation: boolean = false;
|
||||
let selfReservation = false;
|
||||
|
||||
if (!reservation) {
|
||||
reservation = await sql.reserve();
|
||||
|
|
|
@ -4,7 +4,7 @@ import { type ReservedSQL, sql } from "bun";
|
|||
export const order: number = 3;
|
||||
|
||||
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||
let selfReservation: boolean = false;
|
||||
let selfReservation = false;
|
||||
|
||||
if (!reservation) {
|
||||
reservation = await sql.reserve();
|
||||
|
|
|
@ -20,7 +20,7 @@ const defaultSettings: Setting[] = [
|
|||
];
|
||||
|
||||
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||
let selfReservation: boolean = false;
|
||||
let selfReservation = false;
|
||||
|
||||
if (!reservation) {
|
||||
reservation = await sql.reserve();
|
||||
|
@ -99,7 +99,7 @@ export async function getSetting(
|
|||
key: string,
|
||||
reservation?: ReservedSQL,
|
||||
): Promise<string | null> {
|
||||
let selfReservation: boolean = false;
|
||||
let selfReservation = false;
|
||||
|
||||
if (!reservation) {
|
||||
reservation = await sql.reserve();
|
||||
|
@ -130,7 +130,7 @@ export async function setSetting(
|
|||
value: string,
|
||||
reservation?: ReservedSQL,
|
||||
): Promise<void> {
|
||||
let selfReservation: boolean = false;
|
||||
let selfReservation = false;
|
||||
|
||||
if (!reservation) {
|
||||
reservation = await sql.reserve();
|
||||
|
@ -157,7 +157,7 @@ export async function deleteSetting(
|
|||
key: string,
|
||||
reservation?: ReservedSQL,
|
||||
): Promise<void> {
|
||||
let selfReservation: boolean = false;
|
||||
let selfReservation = false;
|
||||
|
||||
if (!reservation) {
|
||||
reservation = await sql.reserve();
|
||||
|
@ -179,7 +179,7 @@ export async function deleteSetting(
|
|||
export async function getAllSettings(
|
||||
reservation?: ReservedSQL,
|
||||
): Promise<{ key: string; value: string }[]> {
|
||||
let selfReservation: boolean = false;
|
||||
let selfReservation = false;
|
||||
|
||||
if (!reservation) {
|
||||
reservation = await sql.reserve();
|
||||
|
|
|
@ -4,7 +4,7 @@ import { type ReservedSQL, sql } from "bun";
|
|||
export const order: number = 1;
|
||||
|
||||
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||
let selfReservation: boolean = false;
|
||||
let selfReservation = false;
|
||||
|
||||
if (!reservation) {
|
||||
reservation = await sql.reserve();
|
||||
|
@ -114,7 +114,8 @@ export function isValidPassword(password: string): {
|
|||
if (!passwordRestrictions.regex.test(password)) {
|
||||
return {
|
||||
valid: false,
|
||||
error: "Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character",
|
||||
error:
|
||||
"Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
142
eslint.config.js
142
eslint.config.js
|
@ -1,142 +0,0 @@
|
|||
import pluginJs from "@eslint/js";
|
||||
import tseslintPlugin from "@typescript-eslint/eslint-plugin";
|
||||
import tsParser from "@typescript-eslint/parser";
|
||||
import prettier from "eslint-plugin-prettier";
|
||||
import promisePlugin from "eslint-plugin-promise";
|
||||
import simpleImportSort from "eslint-plugin-simple-import-sort";
|
||||
import unicorn from "eslint-plugin-unicorn";
|
||||
import unusedImports from "eslint-plugin-unused-imports";
|
||||
import globals from "globals";
|
||||
import stylelintPlugin from "stylelint";
|
||||
|
||||
/** @type {import('eslint').Linter.FlatConfig[]} */
|
||||
export default [
|
||||
{
|
||||
files: ["**/*.{js,mjs,cjs}"],
|
||||
languageOptions: {
|
||||
globals: globals.node,
|
||||
},
|
||||
...pluginJs.configs.recommended,
|
||||
plugins: {
|
||||
"simple-import-sort": simpleImportSort,
|
||||
"unused-imports": unusedImports,
|
||||
promise: promisePlugin,
|
||||
prettier: prettier,
|
||||
unicorn: unicorn,
|
||||
},
|
||||
rules: {
|
||||
"eol-last": ["error", "always"],
|
||||
"no-multiple-empty-lines": ["error", { max: 1, maxEOF: 1 }],
|
||||
"no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
|
||||
"simple-import-sort/imports": "error",
|
||||
"simple-import-sort/exports": "error",
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
"unused-imports/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
vars: "all",
|
||||
varsIgnorePattern: "^_",
|
||||
args: "after-used",
|
||||
argsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
"promise/always-return": "error",
|
||||
"promise/no-return-wrap": "error",
|
||||
"promise/param-names": "error",
|
||||
"promise/catch-or-return": "error",
|
||||
"promise/no-nesting": "warn",
|
||||
"promise/no-promise-in-callback": "warn",
|
||||
"promise/no-callback-in-promise": "warn",
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
{
|
||||
useTabs: true,
|
||||
tabWidth: 4,
|
||||
},
|
||||
],
|
||||
indent: ["error", "tab", { SwitchCase: 1 }],
|
||||
"unicorn/filename-case": [
|
||||
"error",
|
||||
{
|
||||
case: "camelCase",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
languageOptions: {
|
||||
parser: tsParser,
|
||||
globals: globals.node,
|
||||
},
|
||||
plugins: {
|
||||
"@typescript-eslint": tseslintPlugin,
|
||||
"simple-import-sort": simpleImportSort,
|
||||
"unused-imports": unusedImports,
|
||||
promise: promisePlugin,
|
||||
prettier: prettier,
|
||||
unicorn: unicorn,
|
||||
},
|
||||
rules: {
|
||||
...tseslintPlugin.configs.recommended.rules,
|
||||
quotes: ["error", "double"],
|
||||
"eol-last": ["error", "always"],
|
||||
"no-multiple-empty-lines": ["error", { max: 1, maxEOF: 1 }],
|
||||
"no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
|
||||
"simple-import-sort/imports": "error",
|
||||
"simple-import-sort/exports": "error",
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
"unused-imports/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
vars: "all",
|
||||
varsIgnorePattern: "^_",
|
||||
args: "after-used",
|
||||
argsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
"promise/always-return": "error",
|
||||
"promise/no-return-wrap": "error",
|
||||
"promise/param-names": "error",
|
||||
"promise/catch-or-return": "error",
|
||||
"promise/no-nesting": "warn",
|
||||
"promise/no-promise-in-callback": "warn",
|
||||
"promise/no-callback-in-promise": "warn",
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
{
|
||||
useTabs: true,
|
||||
tabWidth: 4,
|
||||
},
|
||||
],
|
||||
indent: ["error", "tab", { SwitchCase: 1 }],
|
||||
"unicorn/filename-case": [
|
||||
"error",
|
||||
{
|
||||
case: "camelCase",
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/explicit-function-return-type": ["error"],
|
||||
"@typescript-eslint/explicit-module-boundary-types": ["error"],
|
||||
"@typescript-eslint/typedef": [
|
||||
"error",
|
||||
{
|
||||
arrowParameter: true,
|
||||
variableDeclaration: true,
|
||||
propertyDeclaration: true,
|
||||
memberVariableDeclaration: true,
|
||||
parameter: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["**/*.{css,scss,sass,less}"],
|
||||
plugins: {
|
||||
stylelint: stylelintPlugin,
|
||||
},
|
||||
rules: {
|
||||
"stylelint/rule-name": "error",
|
||||
},
|
||||
},
|
||||
];
|
38
package.json
38
package.json
|
@ -1,46 +1,36 @@
|
|||
{
|
||||
"name": "bun_frontend_template",
|
||||
"name": "atums.world",
|
||||
"private": true,
|
||||
"module": "src/index.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "bun run src/index.ts",
|
||||
"dev": "bun run --watch src/index.ts --dev",
|
||||
"lint": "eslint",
|
||||
"lint:fix": "bun lint --fix",
|
||||
"dev": "bun run --hot src/index.ts --dev",
|
||||
"lint": "bunx biome check",
|
||||
"lint:fix": "bunx biome check --fix",
|
||||
"cleanup": "rm -rf logs node_modules bun.lockdb",
|
||||
"clearTable": "bun run src/helpers/commands/clearTable.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.22.0",
|
||||
"@types/bun": "^1.2.5",
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@types/bun": "^1.2.9",
|
||||
"@types/ejs": "^3.1.5",
|
||||
"@types/fluent-ffmpeg": "^2.1.27",
|
||||
"@types/image-thumbnail": "^1.0.4",
|
||||
"@types/luxon": "^3.4.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
||||
"@typescript-eslint/parser": "^8.26.1",
|
||||
"eslint": "^9.22.0",
|
||||
"eslint-plugin-prettier": "^5.2.3",
|
||||
"eslint-plugin-promise": "^7.2.1",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"eslint-plugin-stylelint": "^0.1.1",
|
||||
"eslint-plugin-unicorn": "^56.0.1",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"globals": "^15.15.0",
|
||||
"prettier": "^3.5.3",
|
||||
"stylelint": "^16.16.0",
|
||||
"stylelint-config-standard": "^37.0.0"
|
||||
"@types/luxon": "^3.6.2",
|
||||
"globals": "16.0.0",
|
||||
"prettier": "^3.5.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.7.3"
|
||||
"typescript": "^5.8.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ejs": "^3.1.10",
|
||||
"exiftool-vendored": "^29.2.0",
|
||||
"fast-jwt": "^5.0.5",
|
||||
"exiftool-vendored": "^29.3.0",
|
||||
"fast-jwt": "6.0.1",
|
||||
"fluent-ffmpeg": "^2.1.3",
|
||||
"image-thumbnail": "^1.0.17",
|
||||
"luxon": "^3.5.0",
|
||||
"luxon": "^3.6.1",
|
||||
"redis": "^4.7.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,194 +1,198 @@
|
|||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.content {
|
||||
border: 1px solid var(--border);
|
||||
background-color: var(--background-secondary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
width: clamp(200px, 50%, 300px);
|
||||
border: 1px solid var(--border);
|
||||
background-color: var(--background-secondary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
width: clamp(200px, 50%, 300px);
|
||||
}
|
||||
|
||||
.content form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.auth-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, rgba(31 30 30 / 90%) 0%, rgba(45 45 45 / 90%) 100%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(31 30 30 / 90%) 0%,
|
||||
rgba(45 45 45 / 90%) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.auth-logo {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.auth-logo h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--accent);
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.auth-logo p {
|
||||
color: var(--text-secondary);
|
||||
margin-top: 0.5rem;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
background-color: var(--background-secondary);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--card-shadow);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
overflow: hidden;
|
||||
animation: fade-in 0.5s ease;
|
||||
background-color: var(--background-secondary);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--card-shadow);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
overflow: hidden;
|
||||
animation: fade-in 0.5s ease;
|
||||
}
|
||||
|
||||
.auth-header {
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background-color: rgba(0 0 0 / 10%);
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background-color: rgba(0 0 0 / 10%);
|
||||
}
|
||||
|
||||
.auth-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
color: var(--text);
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.auth-form {
|
||||
padding: 1.5rem;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.auth-form form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.auth-toggle {
|
||||
text-align: center;
|
||||
margin-top: 1.5rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-secondary);
|
||||
text-align: center;
|
||||
margin-top: 1.5rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.form-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 0.9rem;
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 0.9rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.form-footer a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.form-footer a:hover {
|
||||
text-decoration: underline;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.form-footer label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.auth-form button {
|
||||
margin-top: 0.5rem;
|
||||
width: 100%;
|
||||
margin-top: 0.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.password-group {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.password-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.password-wrapper input {
|
||||
width: 100%;
|
||||
padding-right: 2rem;
|
||||
width: 100%;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
.toggle-password {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
cursor: pointer;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
fill: var(--text-secondary);
|
||||
transition: fill 0.2s ease;
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
cursor: pointer;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
fill: var(--text-secondary);
|
||||
transition: fill 0.2s ease;
|
||||
}
|
||||
|
||||
.toggle-password:hover {
|
||||
fill: var(--text);
|
||||
fill: var(--text);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: var(--error);
|
||||
background-color: rgb(237 66 69 / 10%);
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 1.5rem;
|
||||
border-radius: 4px;
|
||||
display: none;
|
||||
font-size: 0.9rem;
|
||||
color: var(--error);
|
||||
background-color: rgb(237 66 69 / 10%);
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 1.5rem;
|
||||
border-radius: 4px;
|
||||
display: none;
|
||||
font-size: 0.9rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.auth-link {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.auth-link:hover {
|
||||
text-decoration: underline;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (width <= 480px) {
|
||||
.auth-card {
|
||||
max-width: 100%;
|
||||
}
|
||||
.auth-card {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.auth-logo h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
.auth-logo h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,19 +5,6 @@ body {
|
|||
|
||||
/* sidebar */
|
||||
|
||||
/* <div class="sidebar">
|
||||
<div class="actions">
|
||||
<a href="/dashboard" class="action">
|
||||
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M1 6V15H6V11C6 9.89543 6.89543 9 8 9C9.10457 9 10 9.89543 10 11V15H15V6L8 0L1 6Z" fill=""></path> </g></svg>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="user-area">
|
||||
<img src="https://cdn5.vectorstock.com/i/1000x1000/08/19/gray-photo-placeholder-icon-design-ui-vector-35850819.jpg" alt="User avatar">
|
||||
<div class="username">John Doe</div>
|
||||
</div>
|
||||
</div> */
|
||||
|
||||
.sidebar {
|
||||
background-color: var(--background-secondary);
|
||||
width: 220px;
|
||||
|
@ -26,6 +13,8 @@ body {
|
|||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-right: 1px solid var(--border);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.sidebar .actions {
|
||||
|
@ -33,34 +22,47 @@ body {
|
|||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.sidebar .actions .action {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
gap: .5rem;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
height: 20px;
|
||||
height: 3rem;
|
||||
width: 100%;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s ease;
|
||||
text-decoration: none;
|
||||
color: var(--text);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.sidebar .actions .action svg {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
.sidebar .actions .action:hover {
|
||||
background-color: var(--background);
|
||||
}
|
||||
|
||||
.sidebar .actions .action.active {
|
||||
background-color: var(--background);
|
||||
}
|
||||
|
||||
.sidebar .actions .action.active:hover {
|
||||
background-color: var(--background-secondary);
|
||||
}
|
||||
|
||||
.sidebar .user-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
border-top: 1px solid var(--border);
|
||||
background-color: rgba(0 0 0 / 10%);
|
||||
background-color: rgba(0 0 0 / 10%);
|
||||
}
|
||||
|
||||
.sidebar .user-area img {
|
||||
|
|
|
@ -31,7 +31,8 @@
|
|||
|
||||
@font-face {
|
||||
font-family: Fira Code;
|
||||
src: url("/public/assets/fonts/Fira_code/FiraCode-Regular.ttf") format("truetype");
|
||||
src: url("/public/assets/fonts/Fira_code/FiraCode-Regular.ttf")
|
||||
format("truetype");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
@ -52,14 +53,18 @@ body {
|
|||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
input, button, textarea, select {
|
||||
input,
|
||||
button,
|
||||
textarea,
|
||||
select {
|
||||
font-family: inherit;
|
||||
font-size: 1rem;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
button, .button {
|
||||
button,
|
||||
.button {
|
||||
cursor: pointer;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
|
@ -70,11 +75,14 @@ button, .button {
|
|||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
button:hover, .button:hover {
|
||||
button:hover,
|
||||
.button:hover {
|
||||
background-color: var(--accent-hover);
|
||||
}
|
||||
|
||||
input, textarea, select {
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--border);
|
||||
background-color: var(--input-background);
|
||||
|
@ -83,7 +91,9 @@ input, textarea, select {
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input:focus, textarea:focus, select:focus {
|
||||
input:focus,
|
||||
textarea:focus,
|
||||
select:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 2px rgb(88 101 242 / 30%);
|
||||
|
|
|
@ -18,8 +18,7 @@ if (loginForm) {
|
|||
if (!email || !password) {
|
||||
if (errorMessage) {
|
||||
errorMessage.style.display = "block";
|
||||
errorMessage.textContent =
|
||||
"Please enter both email and password.";
|
||||
errorMessage.textContent = "Please enter both email and password.";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -46,16 +45,14 @@ if (loginForm) {
|
|||
if (errorMessage) {
|
||||
errorMessage.style.display = "block";
|
||||
errorMessage.textContent =
|
||||
data.error ||
|
||||
"Invalid email or password. Please try again.";
|
||||
data.error || "Invalid email or password. Please try again.";
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Login error:", error);
|
||||
if (errorMessage) {
|
||||
errorMessage.style.display = "block";
|
||||
errorMessage.textContent =
|
||||
"An error occurred. Please try again.";
|
||||
errorMessage.textContent = "An error occurred. Please try again.";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -103,8 +100,7 @@ if (loginForm) {
|
|||
.join("");
|
||||
} else {
|
||||
errorMessage.textContent =
|
||||
data.error ||
|
||||
"An error occurred. Please try again.";
|
||||
data.error || "An error occurred. Please try again.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,8 +108,7 @@ if (loginForm) {
|
|||
console.error("Register error:", error);
|
||||
if (errorMessage) {
|
||||
errorMessage.style.display = "block";
|
||||
errorMessage.textContent =
|
||||
"An error occurred. Please try again.";
|
||||
errorMessage.textContent = "An error occurred. Please try again.";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@ export async function authByToken(
|
|||
request: ExtendedRequest,
|
||||
reservation?: ReservedSQL,
|
||||
): Promise<ApiUserSession | null> {
|
||||
let selfReservation: boolean = false;
|
||||
let selfReservation = false;
|
||||
|
||||
const authorizationHeader: string | null =
|
||||
request.headers.get("Authorization");
|
||||
|
|
|
@ -48,7 +48,7 @@ export function parseDuration(input: string): DurationObject {
|
|||
};
|
||||
|
||||
for (const match of matches) {
|
||||
const value: number = parseInt(match[1], 10);
|
||||
const value: number = Number.parseInt(match[1], 10);
|
||||
const unit: string = match[2];
|
||||
|
||||
switch (unit) {
|
||||
|
@ -90,12 +90,10 @@ export function generateRandomString(length?: number): string {
|
|||
|
||||
const characters: string =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let result: string = "";
|
||||
let result = "";
|
||||
|
||||
for (let i: number = 0; i < length; i++) {
|
||||
result += characters.charAt(
|
||||
Math.floor(Math.random() * characters.length),
|
||||
);
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -172,9 +170,7 @@ export function nameWithoutExtension(fileName: string): string {
|
|||
if (lastDotIndex <= 0) return fileName;
|
||||
|
||||
const ext: string = fileName.slice(lastDotIndex + 1).toLowerCase();
|
||||
return knownExtensions.has(ext)
|
||||
? fileName.slice(0, lastDotIndex)
|
||||
: fileName;
|
||||
return knownExtensions.has(ext) ? fileName.slice(0, lastDotIndex) : fileName;
|
||||
}
|
||||
|
||||
export function supportsExif(mimeType: string, extension: string): boolean {
|
||||
|
@ -211,13 +207,11 @@ export function parseArgs(): Record<string, string | boolean> {
|
|||
const args: string[] = process.argv.slice(2);
|
||||
const parsedArgs: Record<string, string | boolean> = {};
|
||||
|
||||
for (let i: number = 0; i < args.length; i++) {
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i].startsWith("--")) {
|
||||
const key: string = args[i].slice(2);
|
||||
const value: string | boolean =
|
||||
args[i + 1] && !args[i + 1].startsWith("--")
|
||||
? args[i + 1]
|
||||
: true;
|
||||
args[i + 1] && !args[i + 1].startsWith("--") ? args[i + 1] : true;
|
||||
parsedArgs[key] = value;
|
||||
if (value !== true) i++;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { renderFile } from "ejs";
|
||||
import { resolve } from "path";
|
||||
import { renderFile } from "ejs";
|
||||
|
||||
export async function renderEjsTemplate(
|
||||
viewName: string | string[],
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import { environment } from "@config/environment";
|
||||
import { timestampToReadable } from "@helpers/char";
|
||||
import type { Stats } from "fs";
|
||||
import {
|
||||
type WriteStream,
|
||||
createWriteStream,
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
statSync,
|
||||
WriteStream,
|
||||
} from "fs";
|
||||
import { EOL } from "os";
|
||||
import { basename, join } from "path";
|
||||
import { environment } from "@config/environment";
|
||||
import { timestampToReadable } from "@helpers/char";
|
||||
|
||||
class Logger {
|
||||
private static instance: Logger;
|
||||
|
@ -37,7 +37,7 @@ class Logger {
|
|||
mkdirSync(logDir, { recursive: true });
|
||||
}
|
||||
|
||||
let addSeparator: boolean = false;
|
||||
let addSeparator = false;
|
||||
|
||||
if (existsSync(logFile)) {
|
||||
const fileStats: Stats = statSync(logFile);
|
||||
|
@ -66,9 +66,9 @@ class Logger {
|
|||
|
||||
private extractFileName(stack: string): string {
|
||||
const stackLines: string[] = stack.split("\n");
|
||||
let callerFile: string = "";
|
||||
let callerFile = "";
|
||||
|
||||
for (let i: number = 2; i < stackLines.length; i++) {
|
||||
for (let i = 2; i < stackLines.length; i++) {
|
||||
const line: string = stackLines[i].trim();
|
||||
if (line && !line.includes("Logger.") && line.includes("(")) {
|
||||
callerFile = line.split("(")[1]?.split(")")[0] || "";
|
||||
|
@ -91,7 +91,7 @@ class Logger {
|
|||
return { filename, timestamp: readableTimestamp };
|
||||
}
|
||||
|
||||
public info(message: string | string[], breakLine: boolean = false): void {
|
||||
public info(message: string | string[], breakLine = false): void {
|
||||
const stack: string = new Error().stack || "";
|
||||
const { filename, timestamp } = this.getCallerInfo(stack);
|
||||
|
||||
|
@ -110,7 +110,7 @@ class Logger {
|
|||
this.writeConsoleMessageColored(logMessageParts, breakLine);
|
||||
}
|
||||
|
||||
public warn(message: string | string[], breakLine: boolean = false): void {
|
||||
public warn(message: string | string[], breakLine = false): void {
|
||||
const stack: string = new Error().stack || "";
|
||||
const { filename, timestamp } = this.getCallerInfo(stack);
|
||||
|
||||
|
@ -131,7 +131,7 @@ class Logger {
|
|||
|
||||
public error(
|
||||
message: string | Error | ErrorEvent | (string | Error)[],
|
||||
breakLine: boolean = false,
|
||||
breakLine = false,
|
||||
): void {
|
||||
const stack: string = new Error().stack || "";
|
||||
const { filename, timestamp } = this.getCallerInfo(stack);
|
||||
|
@ -161,7 +161,7 @@ class Logger {
|
|||
bracketMessage2: string,
|
||||
message: string | string[],
|
||||
color: string,
|
||||
breakLine: boolean = false,
|
||||
breakLine = false,
|
||||
): void {
|
||||
const stack: string = new Error().stack || "";
|
||||
const { timestamp } = this.getCallerInfo(stack);
|
||||
|
@ -189,7 +189,7 @@ class Logger {
|
|||
|
||||
private writeConsoleMessageColored(
|
||||
logMessageParts: ILogMessageParts,
|
||||
breakLine: boolean = false,
|
||||
breakLine = false,
|
||||
): void {
|
||||
const logMessage: string = Object.keys(logMessageParts)
|
||||
.map((key: string) => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { redisConfig } from "@config/environment";
|
||||
import { logger } from "@helpers/logger";
|
||||
import { createClient, type RedisClientType } from "redis";
|
||||
import { type RedisClientType, createClient } from "redis";
|
||||
|
||||
class RedisJson {
|
||||
private static instance: RedisJson | null = null;
|
||||
|
@ -21,11 +21,7 @@ class RedisJson {
|
|||
});
|
||||
|
||||
RedisJson.instance.client.on("error", (err: Error) => {
|
||||
logger.error([
|
||||
"Error connecting to Redis:",
|
||||
err,
|
||||
redisConfig.host,
|
||||
]);
|
||||
logger.error(["Error connecting to Redis:", err, redisConfig.host]);
|
||||
|
||||
process.exit(1);
|
||||
});
|
||||
|
@ -167,10 +163,7 @@ class RedisJson {
|
|||
try {
|
||||
await this.client.expire(key, seconds);
|
||||
} catch (error) {
|
||||
logger.error([
|
||||
`Error expiring key in Redis: ${key}`,
|
||||
error as Error,
|
||||
]);
|
||||
logger.error([`Error expiring key in Redis: ${key}`, error as Error]);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,8 +45,7 @@ class SessionManager {
|
|||
const cookie: string | null = request.headers.get("Cookie");
|
||||
if (!cookie) return null;
|
||||
|
||||
const token: string | null =
|
||||
cookie.match(/session=([^;]+)/)?.[1] || null;
|
||||
const token: string | null = cookie.match(/session=([^;]+)/)?.[1] || null;
|
||||
if (!token) return null;
|
||||
|
||||
const userSessions: string[] = await redis
|
||||
|
@ -72,15 +71,13 @@ class SessionManager {
|
|||
const cookie: string | null = request.headers.get("Cookie");
|
||||
if (!cookie) throw new Error("No session found in request");
|
||||
|
||||
const token: string | null =
|
||||
cookie.match(/session=([^;]+)/)?.[1] || null;
|
||||
const token: string | null = cookie.match(/session=([^;]+)/)?.[1] || null;
|
||||
if (!token) throw new Error("Session token not found");
|
||||
|
||||
const userSessions: string[] = await redis
|
||||
.getInstance()
|
||||
.keys("session:*:" + token);
|
||||
if (!userSessions.length)
|
||||
throw new Error("Session not found or expired");
|
||||
if (!userSessions.length) throw new Error("Session not found or expired");
|
||||
|
||||
const sessionKey: string = userSessions[0];
|
||||
|
||||
|
@ -100,8 +97,7 @@ class SessionManager {
|
|||
const userSessions: string[] = await redis
|
||||
.getInstance()
|
||||
.keys("session:*:" + token);
|
||||
if (!userSessions.length)
|
||||
throw new Error("Session not found or expired");
|
||||
if (!userSessions.length) throw new Error("Session not found or expired");
|
||||
|
||||
const sessionData: unknown = await redis
|
||||
.getInstance()
|
||||
|
@ -121,8 +117,7 @@ class SessionManager {
|
|||
const cookie: string | null = request.headers.get("Cookie");
|
||||
if (!cookie) return;
|
||||
|
||||
const token: string | null =
|
||||
cookie.match(/session=([^;]+)/)?.[1] || null;
|
||||
const token: string | null = cookie.match(/session=([^;]+)/)?.[1] || null;
|
||||
if (!token) return;
|
||||
|
||||
const userSessions: string[] = await redis
|
||||
|
@ -152,7 +147,7 @@ class SessionManager {
|
|||
domain,
|
||||
} = options || {};
|
||||
|
||||
let cookie: string = `session=${encodeURIComponent(token)}; Path=${path}; Max-Age=${maxAge}`;
|
||||
let cookie = `session=${encodeURIComponent(token)}; Path=${path}; Max-Age=${maxAge}`;
|
||||
|
||||
if (httpOnly) cookie += "; HttpOnly";
|
||||
|
||||
|
@ -173,7 +168,7 @@ class SessionManager {
|
|||
}
|
||||
|
||||
const [, value, unit] = match;
|
||||
const num: number = parseInt(value, 10);
|
||||
const num: number = Number.parseInt(value, 10);
|
||||
|
||||
switch (unit) {
|
||||
case "s":
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { join, resolve } from "path";
|
||||
import { dataType } from "@config/environment.ts";
|
||||
import { logger } from "@helpers/logger.ts";
|
||||
import { type BunFile, s3, sql } from "bun";
|
||||
import ffmpeg from "fluent-ffmpeg";
|
||||
import imageThumbnail from "image-thumbnail";
|
||||
import { join, resolve } from "path";
|
||||
|
||||
declare var self: Worker;
|
||||
|
||||
|
@ -22,10 +22,7 @@ async function generateVideoThumbnail(
|
|||
.format("mjpeg")
|
||||
.output(thumbnailPath)
|
||||
.on("error", (error: Error) => {
|
||||
logger.error([
|
||||
"failed to generate thumbnail",
|
||||
error as Error,
|
||||
]);
|
||||
logger.error(["failed to generate thumbnail", error as Error]);
|
||||
reject(error);
|
||||
})
|
||||
|
||||
|
@ -71,10 +68,7 @@ async function generateImageThumbnail(
|
|||
},
|
||||
};
|
||||
|
||||
const thumbnailBuffer: Buffer = await imageThumbnail(
|
||||
filePath,
|
||||
options,
|
||||
);
|
||||
const thumbnailBuffer: Buffer = await imageThumbnail(filePath, options);
|
||||
|
||||
await Bun.write(thumbnailPath, thumbnailBuffer.buffer);
|
||||
resolve(await Bun.file(thumbnailPath).arrayBuffer());
|
||||
|
@ -104,20 +98,14 @@ async function createThumbnails(files: FileEntry[]): Promise<void> {
|
|||
try {
|
||||
fileArrayBuffer = await Bun.file(filePath).arrayBuffer();
|
||||
} catch {
|
||||
logger.error([
|
||||
"Could not generate thumbnail for file:",
|
||||
fileName,
|
||||
]);
|
||||
logger.error(["Could not generate thumbnail for file:", fileName]);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
fileArrayBuffer = await s3.file(fileName).arrayBuffer();
|
||||
} catch {
|
||||
logger.error([
|
||||
"Could not generate thumbnail for file:",
|
||||
fileName,
|
||||
]);
|
||||
logger.error(["Could not generate thumbnail for file:", fileName]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -149,10 +137,7 @@ async function createThumbnails(files: FileEntry[]): Promise<void> {
|
|||
: await generateImageThumbnail(tempFilePath, tempThumbnailPath);
|
||||
|
||||
if (!thumbnailArrayBuffer) {
|
||||
logger.error([
|
||||
"Could not generate thumbnail for file:",
|
||||
fileName,
|
||||
]);
|
||||
logger.error(["Could not generate thumbnail for file:", fileName]);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
13
src/index.ts
13
src/index.ts
|
@ -1,9 +1,9 @@
|
|||
import { existsSync, mkdirSync } from "fs";
|
||||
import { resolve } from "path";
|
||||
import { dataType } from "@config/environment";
|
||||
import { logger } from "@helpers/logger";
|
||||
import { type ReservedSQL, s3, sql } from "bun";
|
||||
import { existsSync, mkdirSync } from "fs";
|
||||
import { readdir } from "fs/promises";
|
||||
import { resolve } from "path";
|
||||
|
||||
import { serverHandler } from "@/server";
|
||||
|
||||
|
@ -17,9 +17,7 @@ async function initializeDatabase(): Promise<void> {
|
|||
files
|
||||
.filter((file: string): boolean => file.endsWith(".ts"))
|
||||
.map(async (file: string): Promise<Module> => {
|
||||
const module: Module["module"] = await import(
|
||||
resolve(sqlDir, file)
|
||||
);
|
||||
const module: Module["module"] = await import(resolve(sqlDir, file));
|
||||
return { file, module };
|
||||
}),
|
||||
);
|
||||
|
@ -69,10 +67,7 @@ async function main(): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
logger.info([
|
||||
"Using local datasource directory",
|
||||
`${dataType.path}`,
|
||||
]);
|
||||
logger.info(["Using local datasource directory", `${dataType.path}`]);
|
||||
} else {
|
||||
try {
|
||||
await s3.write("test", "test");
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
isValidPassword,
|
||||
isValidUsername,
|
||||
} from "@config/sql/users";
|
||||
import { password as bunPassword, type ReservedSQL, sql } from "bun";
|
||||
import { type ReservedSQL, password as bunPassword, sql } from "bun";
|
||||
|
||||
import { logger } from "@/helpers/logger";
|
||||
import { sessionManager } from "@/helpers/sessions";
|
||||
|
@ -61,13 +61,9 @@ async function handler(
|
|||
const errors: string[] = [];
|
||||
|
||||
const validations: UserValidation[] = [
|
||||
username
|
||||
? { check: isValidUsername(username), field: "Username" }
|
||||
: null,
|
||||
username ? { check: isValidUsername(username), field: "Username" } : null,
|
||||
email ? { check: isValidEmail(email), field: "Email" } : null,
|
||||
password
|
||||
? { check: isValidPassword(password), field: "Password" }
|
||||
: null,
|
||||
password ? { check: isValidPassword(password), field: "Password" } : null,
|
||||
].filter(Boolean) as UserValidation[];
|
||||
|
||||
validations.forEach(({ check }: UserValidation): void => {
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
isValidPassword,
|
||||
isValidUsername,
|
||||
} from "@config/sql/users";
|
||||
import { password as bunPassword, type ReservedSQL, sql } from "bun";
|
||||
import { type ReservedSQL, password as bunPassword, sql } from "bun";
|
||||
|
||||
import { isValidTimezone } from "@/helpers/char";
|
||||
import { logger } from "@/helpers/logger";
|
||||
|
@ -57,9 +57,9 @@ async function handler(
|
|||
|
||||
const normalizedUsername: string = username.normalize("NFC");
|
||||
const reservation: ReservedSQL = await sql.reserve();
|
||||
let firstUser: boolean = false;
|
||||
let firstUser = false;
|
||||
let inviteData: Invite | null = null;
|
||||
let roles: string[] = [];
|
||||
const roles: string[] = [];
|
||||
|
||||
try {
|
||||
const registrationEnabled: boolean =
|
||||
|
@ -69,11 +69,10 @@ async function handler(
|
|||
|
||||
firstUser =
|
||||
Number(
|
||||
(await reservation`SELECT COUNT(*) AS count FROM users;`)[0]
|
||||
?.count,
|
||||
(await reservation`SELECT COUNT(*) AS count FROM users;`)[0]?.count,
|
||||
) === 0;
|
||||
|
||||
let inviteValid: boolean = true;
|
||||
let inviteValid = true;
|
||||
if (!firstUser && invite) {
|
||||
const inviteValidation: { valid: boolean; error?: string } =
|
||||
isValidInvite(invite);
|
||||
|
@ -184,10 +183,7 @@ async function handler(
|
|||
if (inviteData?.role) roles.push(inviteData.role);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error([
|
||||
"Error inserting user into the database:",
|
||||
error as Error,
|
||||
]);
|
||||
logger.error(["Error inserting user into the database:", error as Error]);
|
||||
return Response.json(
|
||||
{
|
||||
success: false,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { dataType } from "@config/environment";
|
||||
import { s3, sql, type SQLQuery } from "bun";
|
||||
import { resolve } from "path";
|
||||
import { dataType } from "@config/environment";
|
||||
import { type SQLQuery, s3, sql } from "bun";
|
||||
|
||||
import { isUUID } from "@/helpers/char";
|
||||
import { logger } from "@/helpers/logger";
|
||||
|
@ -134,26 +134,14 @@ async function handler(
|
|||
|
||||
try {
|
||||
if (file && !(typeof file === "string" && file.length === 0)) {
|
||||
await processFile(
|
||||
request,
|
||||
file,
|
||||
isAdmin,
|
||||
failedFiles,
|
||||
successfulFiles,
|
||||
);
|
||||
await processFile(request, file, isAdmin, failedFiles, successfulFiles);
|
||||
} else if (files) {
|
||||
files = Array.isArray(files)
|
||||
? files
|
||||
: files.split(/[, ]+/).filter(Boolean);
|
||||
|
||||
for (const file of files) {
|
||||
await processFile(
|
||||
request,
|
||||
file,
|
||||
isAdmin,
|
||||
failedFiles,
|
||||
successfulFiles,
|
||||
);
|
||||
await processFile(request, file, isAdmin, failedFiles, successfulFiles);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import { resolve } from "path";
|
||||
import { dataType } from "@config/environment";
|
||||
import { getSetting } from "@config/sql/settings";
|
||||
import {
|
||||
type SQLQuery,
|
||||
password as bunPassword,
|
||||
randomUUIDv7,
|
||||
s3,
|
||||
sql,
|
||||
type SQLQuery,
|
||||
} from "bun";
|
||||
import { exiftool } from "exiftool-vendored";
|
||||
import { DateTime } from "luxon";
|
||||
import { resolve } from "path";
|
||||
|
||||
import {
|
||||
generateRandomString,
|
||||
|
@ -97,9 +97,7 @@ async function removeExifData(
|
|||
LocationName: null,
|
||||
};
|
||||
|
||||
await exiftool.write(tempInputPath, tagsToRemove, [
|
||||
"-overwrite_original",
|
||||
]);
|
||||
await exiftool.write(tempInputPath, tagsToRemove, ["-overwrite_original"]);
|
||||
|
||||
return await Bun.file(tempInputPath).arrayBuffer();
|
||||
} catch (error) {
|
||||
|
@ -161,9 +159,9 @@ async function processFile(
|
|||
};
|
||||
|
||||
const extension: string | null = getExtension(file.name);
|
||||
let rawName: string | null = nameWithoutExtension(file.name);
|
||||
const rawName: string | null = nameWithoutExtension(file.name);
|
||||
const maxViews: number | null =
|
||||
parseInt(user_provided_max_views, 10) || null;
|
||||
Number.parseInt(user_provided_max_views, 10) || null;
|
||||
|
||||
if (!rawName) {
|
||||
failedFiles.push({
|
||||
|
@ -190,7 +188,7 @@ async function processFile(
|
|||
? user_provided_tags
|
||||
: (user_provided_tags?.split(/[, ]+/).filter(Boolean) ?? []);
|
||||
|
||||
let uploadEntry: FileUpload = {
|
||||
const uploadEntry: FileUpload = {
|
||||
id: randomUUID as UUID,
|
||||
owner: session.id as UUID,
|
||||
name: rawName,
|
||||
|
@ -201,9 +199,7 @@ async function processFile(
|
|||
password: hashedPassword,
|
||||
favorite: user_wants_favorite === "true" || user_wants_favorite === "1",
|
||||
tags: tags,
|
||||
expires_at: delete_short_string
|
||||
? getNewTimeUTC(delete_short_string)
|
||||
: null,
|
||||
expires_at: delete_short_string ? getNewTimeUTC(delete_short_string) : null,
|
||||
};
|
||||
|
||||
if (name_format === "date") {
|
||||
|
@ -366,9 +362,7 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
|||
requestBody.append(
|
||||
"file",
|
||||
new Blob([body], { type: request.actualContentType }),
|
||||
request.actualContentType === "text/plain"
|
||||
? "file.txt"
|
||||
: "file.json",
|
||||
request.actualContentType === "text/plain" ? "file.txt" : "file.json",
|
||||
);
|
||||
} else {
|
||||
return Response.json(
|
||||
|
@ -442,20 +436,16 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
|||
}
|
||||
|
||||
const filesThatSupportThumbnails: FileUpload[] = successfulFiles.filter(
|
||||
(file: FileUpload): boolean =>
|
||||
supportsThumbnail(file.mime_type as string),
|
||||
(file: FileUpload): boolean => supportsThumbnail(file.mime_type as string),
|
||||
);
|
||||
if (
|
||||
(await getSetting("enable_thumbnails")) === "true" &&
|
||||
filesThatSupportThumbnails.length > 0
|
||||
) {
|
||||
try {
|
||||
const worker: Worker = new Worker(
|
||||
"./src/helpers/workers/thumbnails.ts",
|
||||
{
|
||||
type: "module",
|
||||
},
|
||||
);
|
||||
const worker: Worker = new Worker("./src/helpers/workers/thumbnails.ts", {
|
||||
type: "module",
|
||||
});
|
||||
worker.postMessage({
|
||||
files: filesThatSupportThumbnails,
|
||||
});
|
||||
|
|
|
@ -67,9 +67,7 @@ async function handler(
|
|||
);
|
||||
}
|
||||
|
||||
const expirationDate: string | null = expires
|
||||
? getNewTimeUTC(expires)
|
||||
: null;
|
||||
const expirationDate: string | null = expires ? getNewTimeUTC(expires) : null;
|
||||
const maxUses: number = Number(max_uses) || 1;
|
||||
const inviteRole: string = role || "user";
|
||||
|
||||
|
|
|
@ -53,7 +53,8 @@ async function handler(
|
|||
{
|
||||
success: false,
|
||||
code: 400,
|
||||
error: "Expected key to be a string and value to be a string, boolean, or number",
|
||||
error:
|
||||
"Expected key to be a string and value to be a string, boolean, or number",
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { resolve } from "path";
|
||||
import { dataType } from "@config/environment";
|
||||
import { s3, sql } from "bun";
|
||||
import { resolve } from "path";
|
||||
|
||||
import { logger } from "@/helpers/logger";
|
||||
import { sessionManager } from "@/helpers/sessions";
|
||||
|
@ -21,9 +21,7 @@ async function deleteAvatar(
|
|||
|
||||
try {
|
||||
if (dataType.type === "local" && dataType.path) {
|
||||
await Bun.file(
|
||||
resolve(dataType.path, "avatars", fileName),
|
||||
).unlink();
|
||||
await Bun.file(resolve(dataType.path, "avatars", fileName)).unlink();
|
||||
} else {
|
||||
await s3.delete(`/avatars/${fileName}`);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { resolve } from "path";
|
||||
import { dataType } from "@config/environment";
|
||||
import { isValidTypeOrExtension } from "@config/sql/avatars";
|
||||
import { getSetting } from "@config/sql/settings";
|
||||
import { s3, sql } from "bun";
|
||||
import { resolve } from "path";
|
||||
|
||||
import { getBaseUrl, getExtension } from "@/helpers/char";
|
||||
import { logger } from "@/helpers/logger";
|
||||
|
@ -50,10 +50,7 @@ async function processFile(
|
|||
await s3.delete(`/avatars/${existingFileName}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error([
|
||||
"Error deleting existing avatar file:",
|
||||
error as Error,
|
||||
]);
|
||||
logger.error(["Error deleting existing avatar file:", error as Error]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,9 +135,7 @@ async function handler(
|
|||
}
|
||||
|
||||
const file: File | null =
|
||||
(formData.get("file") as File) ||
|
||||
(formData.get("avatar") as File) ||
|
||||
null;
|
||||
(formData.get("file") as File) || (formData.get("avatar") as File) || null;
|
||||
|
||||
if (!file.type || file.type === "") {
|
||||
return Response.json(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { type ReservedSQL, sql, type SQLQuery } from "bun";
|
||||
import { type ReservedSQL, type SQLQuery, sql } from "bun";
|
||||
|
||||
import { isUUID } from "@/helpers/char";
|
||||
import { logger } from "@/helpers/logger";
|
||||
|
@ -107,24 +107,21 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
|||
);
|
||||
}
|
||||
|
||||
const safeCount: number = Math.min(parseInt(count) || 25, 100);
|
||||
const safePage: number = Math.max(parseInt(page) || 0, 0);
|
||||
const safeCount: number = Math.min(Number.parseInt(count) || 25, 100);
|
||||
const safePage: number = Math.max(Number.parseInt(page) || 0, 0);
|
||||
const offset: number = safePage * safeCount;
|
||||
const sortColumn: string = sort_by || "created_at";
|
||||
const order: "ASC" | "DESC" = validSortOrder(sort_order) as "ASC" | "DESC";
|
||||
const safeSearchValue: string = escapeLike(search_value || "");
|
||||
|
||||
let files: FileEntry[];
|
||||
let totalPages: number = 0;
|
||||
let totalFiles: number = 0;
|
||||
let totalPages = 0;
|
||||
let totalFiles = 0;
|
||||
const reservation: ReservedSQL = await sql.reserve();
|
||||
|
||||
try {
|
||||
// ! i really dont understand why bun wont accept reservation(order)`
|
||||
function orderBy(
|
||||
field_name: string,
|
||||
orderBy: "ASC" | "DESC",
|
||||
): SQLQuery {
|
||||
function orderBy(field_name: string, orderBy: "ASC" | "DESC"): SQLQuery {
|
||||
return reservation`ORDER BY ${reservation(field_name)} ${orderBy === "ASC" ? reservation`ASC` : reservation`DESC`}`;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
|||
}
|
||||
|
||||
let user: GetUser | null = null;
|
||||
let isSelf: boolean = false;
|
||||
let isSelf = false;
|
||||
const isId: boolean = isUUID(query);
|
||||
const normalized: string = isId ? query : query.normalize("NFC");
|
||||
const isAdmin: boolean = request.session
|
||||
|
|
|
@ -18,8 +18,7 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
|||
const [firstUser] = await sql`SELECT COUNT(*) FROM users`;
|
||||
|
||||
const instanceName: string =
|
||||
(await getSetting("instance_name", reservation)) ||
|
||||
"Unnamed Instance";
|
||||
(await getSetting("instance_name", reservation)) || "Unnamed Instance";
|
||||
const requiresInvite: boolean =
|
||||
(await getSetting("enable_invitations", reservation)) === "true" &&
|
||||
firstUser.count !== "0";
|
||||
|
|
|
@ -13,6 +13,7 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
|||
|
||||
const ejsTemplateData: EjsTemplateData = {
|
||||
title: "Hello, World!",
|
||||
active: "dashboard",
|
||||
};
|
||||
|
||||
return await renderEjsTemplate("dashboard/index.ejs", ejsTemplateData);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { resolve } from "path";
|
||||
import { dataType } from "@config/environment";
|
||||
import { type BunFile, type ReservedSQL, sql } from "bun";
|
||||
import { resolve } from "path";
|
||||
|
||||
import { isUUID, nameWithoutExtension } from "@/helpers/char";
|
||||
import { logger } from "@/helpers/logger";
|
||||
|
@ -139,9 +139,7 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
|||
} else {
|
||||
path = resolve(
|
||||
dataType.path,
|
||||
`${fileData.id}${
|
||||
fileData.extension ? `.${fileData.extension}` : ""
|
||||
}`,
|
||||
`${fileData.id}${fileData.extension ? `.${fileData.extension}` : ""}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
@ -157,9 +155,7 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
|||
|
||||
return new Response(bunStream, {
|
||||
headers: {
|
||||
"Content-Type": shouldShowThumbnail
|
||||
? "image/jpeg"
|
||||
: fileData.mime_type,
|
||||
"Content-Type": shouldShowThumbnail ? "image/jpeg" : fileData.mime_type,
|
||||
"Content-Disposition":
|
||||
downloadFile === "true" || downloadFile === "1"
|
||||
? `attachment; filename="${fileData.original_name || fileData.name}"`
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { resolve } from "path";
|
||||
import { dataType } from "@config/environment";
|
||||
import { isValidUsername } from "@config/sql/users";
|
||||
import { type BunFile, type ReservedSQL, sql } from "bun";
|
||||
import { resolve } from "path";
|
||||
|
||||
import { getBaseUrl, isUUID, nameWithoutExtension } from "@/helpers/char";
|
||||
import { logger } from "@/helpers/logger";
|
||||
|
@ -71,11 +71,12 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
|||
if (json === "true" || json === "1") {
|
||||
return Response.json(
|
||||
{
|
||||
success: true, code: 200,
|
||||
success: true,
|
||||
code: 200,
|
||||
avatar: {
|
||||
...avatar,
|
||||
url: `${getBaseUrl(request)}/user/avatar/${user.id}`,
|
||||
}
|
||||
},
|
||||
},
|
||||
{ status: 200 },
|
||||
);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { resolve } from "path";
|
||||
import { environment } from "@config/environment";
|
||||
import { logger } from "@helpers/logger";
|
||||
import {
|
||||
|
@ -6,7 +7,6 @@ import {
|
|||
type MatchedRoute,
|
||||
type Serve,
|
||||
} from "bun";
|
||||
import { resolve } from "path";
|
||||
|
||||
import { webSocketHandler } from "@/websocket";
|
||||
|
||||
|
@ -77,8 +77,7 @@ class ServerHandler {
|
|||
|
||||
if (await file.exists()) {
|
||||
const fileContent: ArrayBuffer = await file.arrayBuffer();
|
||||
const contentType: string =
|
||||
file.type || "application/octet-stream";
|
||||
const contentType: string = file.type || "application/octet-stream";
|
||||
|
||||
return new Response(fileContent, {
|
||||
headers: { "Content-Type": contentType },
|
||||
|
@ -88,10 +87,7 @@ class ServerHandler {
|
|||
return new Response("Not Found", { status: 404 });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error([
|
||||
`Error serving static file: ${pathname}`,
|
||||
error as Error,
|
||||
]);
|
||||
logger.error([`Error serving static file: ${pathname}`, error as Error]);
|
||||
return new Response("Internal Server Error", { status: 500 });
|
||||
}
|
||||
}
|
||||
|
@ -116,8 +112,7 @@ class ServerHandler {
|
|||
|
||||
try {
|
||||
const routeModule: RouteModule = await import(filePath);
|
||||
const contentType: string | null =
|
||||
request.headers.get("Content-Type");
|
||||
const contentType: string | null = request.headers.get("Content-Type");
|
||||
const actualContentType: string | null = contentType
|
||||
? contentType.split(";")[0].trim()
|
||||
: null;
|
||||
|
@ -144,9 +139,7 @@ class ServerHandler {
|
|||
|
||||
if (
|
||||
(Array.isArray(routeModule.routeDef.method) &&
|
||||
!routeModule.routeDef.method.includes(
|
||||
request.method,
|
||||
)) ||
|
||||
!routeModule.routeDef.method.includes(request.method)) ||
|
||||
(!Array.isArray(routeModule.routeDef.method) &&
|
||||
routeModule.routeDef.method !== request.method)
|
||||
) {
|
||||
|
@ -171,9 +164,7 @@ class ServerHandler {
|
|||
if (Array.isArray(expectedContentType)) {
|
||||
matchesAccepts =
|
||||
expectedContentType.includes("*/*") ||
|
||||
expectedContentType.includes(
|
||||
actualContentType || "",
|
||||
);
|
||||
expectedContentType.includes(actualContentType || "");
|
||||
} else {
|
||||
matchesAccepts =
|
||||
expectedContentType === "*/*" ||
|
||||
|
@ -202,11 +193,7 @@ class ServerHandler {
|
|||
(await authByToken(request)) ||
|
||||
(await sessionManager.getSession(request));
|
||||
|
||||
response = await routeModule.handler(
|
||||
request,
|
||||
requestBody,
|
||||
server,
|
||||
);
|
||||
response = await routeModule.handler(request, requestBody, server);
|
||||
|
||||
if (routeModule.routeDef.returns !== "*/*") {
|
||||
response.headers.set(
|
||||
|
@ -217,10 +204,7 @@ class ServerHandler {
|
|||
}
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
logger.error([
|
||||
`Error handling route ${request.url}:`,
|
||||
error as Error,
|
||||
]);
|
||||
logger.error([`Error handling route ${request.url}:`, error as Error]);
|
||||
|
||||
response = Response.json(
|
||||
{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<div class="sidebar">
|
||||
<div class="actions">
|
||||
<a href="/dashboard" class="action">
|
||||
<svg class="stroke-only" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M5 9.77746V16.2C5 17.8802 5 18.7203 5.32698 19.362C5.6146 19.9265 6.07354 20.3854 6.63803 20.673C6.78894 20.7499 6.95082 20.8087 7.13202 20.8537M21 12L15.5668 5.96399C14.3311 4.59122 13.7133 3.90484 12.9856 3.65144C12.3466 3.42888 11.651 3.42893 11.0119 3.65159C10.2843 3.90509 9.66661 4.59157 8.43114 5.96452L3 12M19 5.00002V16.2C19 17.8802 19 18.7203 18.673 19.362C18.3854 19.9265 17.9265 20.3854 17.362 20.673C17.2111 20.7499 17.0492 20.8087 16.868 20.8537M7.13202 20.8537C7.65017 18.6448 9.63301 17 12 17C14.367 17 16.3498 18.6448 16.868 20.8537M7.13202 20.8537C7.72133 21 8.51495 21 9.8 21H14.2C15.485 21 16.2787 21 16.868 20.8537M14 12C14 13.1046 13.1046 14 12 14C10.8954 14 10 13.1046 10 12C10 10.8954 10.8954 10 12 10C13.1046 10 14 10.8954 14 12Z" stroke="" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>
|
||||
<a href="/dashboard" class="action <%- active === 'dashboard' ? 'active' : '' %>">
|
||||
<svg fill="" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" stroke=""><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M31.772 16.043l-15.012-15.724c-0.189-0.197-0.449-0.307-0.721-0.307s-0.533 0.111-0.722 0.307l-15.089 15.724c-0.383 0.398-0.369 1.031 0.029 1.414 0.399 0.382 1.031 0.371 1.414-0.029l1.344-1.401v14.963c0 0.552 0.448 1 1 1h6.986c0.551 0 0.998-0.445 1-0.997l0.031-9.989h7.969v9.986c0 0.552 0.448 1 1 1h6.983c0.552 0 1-0.448 1-1v-14.968l1.343 1.407c0.197 0.204 0.459 0.308 0.722 0.308 0.249 0 0.499-0.092 0.692-0.279 0.398-0.382 0.411-1.015 0.029-1.413zM26.985 14.213v15.776h-4.983v-9.986c0-0.552-0.448-1-1-1h-9.965c-0.551 0-0.998 0.445-1 0.997l-0.031 9.989h-4.989v-15.777c0-0.082-0.013-0.162-0.032-0.239l11.055-11.52 10.982 11.507c-0.021 0.081-0.036 0.165-0.036 0.252z"></path> </g></svg>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { logger } from "@helpers/logger";
|
||||
import { type ServerWebSocket } from "bun";
|
||||
import type { ServerWebSocket } from "bun";
|
||||
|
||||
class WebSocketHandler {
|
||||
public handleMessage(ws: ServerWebSocket, message: string): void {
|
||||
|
@ -20,11 +20,7 @@ class WebSocketHandler {
|
|||
}
|
||||
}
|
||||
|
||||
public handleClose(
|
||||
ws: ServerWebSocket,
|
||||
code: number,
|
||||
reason: string,
|
||||
): void {
|
||||
public handleClose(ws: ServerWebSocket, code: number, reason: string): void {
|
||||
logger.warn(`WebSocket closed with code ${code}, reason: ${reason}`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
/** @type {import('stylelint').Config} */
|
||||
export default {
|
||||
extends: ["stylelint-config-standard"],
|
||||
rules: {
|
||||
"color-function-notation": "modern",
|
||||
"font-family-name-quotes": "always-where-required",
|
||||
"declaration-empty-line-before": "never",
|
||||
},
|
||||
};
|
|
@ -2,28 +2,14 @@
|
|||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
],
|
||||
"@config/*": [
|
||||
"config/*"
|
||||
],
|
||||
"@types/*": [
|
||||
"types/*"
|
||||
],
|
||||
"@helpers/*": [
|
||||
"src/helpers/*"
|
||||
]
|
||||
"@/*": ["src/*"],
|
||||
"@config/*": ["config/*"],
|
||||
"@types/*": ["types/*"],
|
||||
"@helpers/*": ["src/helpers/*"]
|
||||
},
|
||||
"typeRoots": [
|
||||
"./src/types",
|
||||
"./node_modules/@types"
|
||||
],
|
||||
"typeRoots": ["./src/types", "./node_modules/@types"],
|
||||
// Enable latest features
|
||||
"lib": [
|
||||
"ESNext",
|
||||
"DOM"
|
||||
],
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
|
@ -41,11 +27,7 @@
|
|||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
"types",
|
||||
"config"
|
||||
],
|
||||
"include": ["src", "types", "config"]
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue