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
|
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
|
@ -3,7 +3,6 @@
|
||||||
"mikestead.dotenv",
|
"mikestead.dotenv",
|
||||||
"EditorConfig.EditorConfig",
|
"EditorConfig.EditorConfig",
|
||||||
"leonzalion.vscode-ejs",
|
"leonzalion.vscode-ejs",
|
||||||
"dbaeumer.vscode-eslint",
|
"biomejs.biome"
|
||||||
"stylelint.vscode-stylelint"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
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";
|
import { resolve } from "path";
|
||||||
|
|
||||||
export const environment: Environment = {
|
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",
|
host: process.env.HOST || "0.0.0.0",
|
||||||
development:
|
development:
|
||||||
process.env.NODE_ENV === "development" ||
|
process.env.NODE_ENV === "development" || process.argv.includes("--dev"),
|
||||||
process.argv.includes("--dev"),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const redisConfig: {
|
export const redisConfig: {
|
||||||
|
@ -15,7 +14,7 @@ export const redisConfig: {
|
||||||
password?: string | undefined;
|
password?: string | undefined;
|
||||||
} = {
|
} = {
|
||||||
host: process.env.REDIS_HOST || "localhost",
|
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,
|
username: process.env.REDIS_USERNAME || undefined,
|
||||||
password: process.env.REDIS_PASSWORD || undefined,
|
password: process.env.REDIS_PASSWORD || undefined,
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { type ReservedSQL, sql } from "bun";
|
||||||
export const order: number = 6;
|
export const order: number = 6;
|
||||||
|
|
||||||
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||||
let selfReservation: boolean = false;
|
let selfReservation = false;
|
||||||
|
|
||||||
if (!reservation) {
|
if (!reservation) {
|
||||||
reservation = await sql.reserve();
|
reservation = await sql.reserve();
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { type ReservedSQL, sql } from "bun";
|
||||||
export const order: number = 5;
|
export const order: number = 5;
|
||||||
|
|
||||||
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||||
let selfReservation: boolean = false;
|
let selfReservation = false;
|
||||||
|
|
||||||
if (!reservation) {
|
if (!reservation) {
|
||||||
reservation = await sql.reserve();
|
reservation = await sql.reserve();
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { type ReservedSQL, sql } from "bun";
|
||||||
export const order: number = 4;
|
export const order: number = 4;
|
||||||
|
|
||||||
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||||
let selfReservation: boolean = false;
|
let selfReservation = false;
|
||||||
|
|
||||||
if (!reservation) {
|
if (!reservation) {
|
||||||
reservation = await sql.reserve();
|
reservation = await sql.reserve();
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { type ReservedSQL, sql } from "bun";
|
||||||
export const order: number = 3;
|
export const order: number = 3;
|
||||||
|
|
||||||
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||||
let selfReservation: boolean = false;
|
let selfReservation = false;
|
||||||
|
|
||||||
if (!reservation) {
|
if (!reservation) {
|
||||||
reservation = await sql.reserve();
|
reservation = await sql.reserve();
|
||||||
|
|
|
@ -20,7 +20,7 @@ const defaultSettings: Setting[] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||||
let selfReservation: boolean = false;
|
let selfReservation = false;
|
||||||
|
|
||||||
if (!reservation) {
|
if (!reservation) {
|
||||||
reservation = await sql.reserve();
|
reservation = await sql.reserve();
|
||||||
|
@ -99,7 +99,7 @@ export async function getSetting(
|
||||||
key: string,
|
key: string,
|
||||||
reservation?: ReservedSQL,
|
reservation?: ReservedSQL,
|
||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
let selfReservation: boolean = false;
|
let selfReservation = false;
|
||||||
|
|
||||||
if (!reservation) {
|
if (!reservation) {
|
||||||
reservation = await sql.reserve();
|
reservation = await sql.reserve();
|
||||||
|
@ -130,7 +130,7 @@ export async function setSetting(
|
||||||
value: string,
|
value: string,
|
||||||
reservation?: ReservedSQL,
|
reservation?: ReservedSQL,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let selfReservation: boolean = false;
|
let selfReservation = false;
|
||||||
|
|
||||||
if (!reservation) {
|
if (!reservation) {
|
||||||
reservation = await sql.reserve();
|
reservation = await sql.reserve();
|
||||||
|
@ -157,7 +157,7 @@ export async function deleteSetting(
|
||||||
key: string,
|
key: string,
|
||||||
reservation?: ReservedSQL,
|
reservation?: ReservedSQL,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let selfReservation: boolean = false;
|
let selfReservation = false;
|
||||||
|
|
||||||
if (!reservation) {
|
if (!reservation) {
|
||||||
reservation = await sql.reserve();
|
reservation = await sql.reserve();
|
||||||
|
@ -179,7 +179,7 @@ export async function deleteSetting(
|
||||||
export async function getAllSettings(
|
export async function getAllSettings(
|
||||||
reservation?: ReservedSQL,
|
reservation?: ReservedSQL,
|
||||||
): Promise<{ key: string; value: string }[]> {
|
): Promise<{ key: string; value: string }[]> {
|
||||||
let selfReservation: boolean = false;
|
let selfReservation = false;
|
||||||
|
|
||||||
if (!reservation) {
|
if (!reservation) {
|
||||||
reservation = await sql.reserve();
|
reservation = await sql.reserve();
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { type ReservedSQL, sql } from "bun";
|
||||||
export const order: number = 1;
|
export const order: number = 1;
|
||||||
|
|
||||||
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||||
let selfReservation: boolean = false;
|
let selfReservation = false;
|
||||||
|
|
||||||
if (!reservation) {
|
if (!reservation) {
|
||||||
reservation = await sql.reserve();
|
reservation = await sql.reserve();
|
||||||
|
@ -114,7 +114,8 @@ export function isValidPassword(password: string): {
|
||||||
if (!passwordRestrictions.regex.test(password)) {
|
if (!passwordRestrictions.regex.test(password)) {
|
||||||
return {
|
return {
|
||||||
valid: false,
|
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",
|
"module": "src/index.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "bun run src/index.ts",
|
"start": "bun run src/index.ts",
|
||||||
"dev": "bun run --watch src/index.ts --dev",
|
"dev": "bun run --hot src/index.ts --dev",
|
||||||
"lint": "eslint",
|
"lint": "bunx biome check",
|
||||||
"lint:fix": "bun lint --fix",
|
"lint:fix": "bunx biome check --fix",
|
||||||
"cleanup": "rm -rf logs node_modules bun.lockdb",
|
"cleanup": "rm -rf logs node_modules bun.lockdb",
|
||||||
"clearTable": "bun run src/helpers/commands/clearTable.ts"
|
"clearTable": "bun run src/helpers/commands/clearTable.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.22.0",
|
"@biomejs/biome": "^1.9.4",
|
||||||
"@types/bun": "^1.2.5",
|
"@types/bun": "^1.2.9",
|
||||||
"@types/ejs": "^3.1.5",
|
"@types/ejs": "^3.1.5",
|
||||||
"@types/fluent-ffmpeg": "^2.1.27",
|
"@types/fluent-ffmpeg": "^2.1.27",
|
||||||
"@types/image-thumbnail": "^1.0.4",
|
"@types/image-thumbnail": "^1.0.4",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.6.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
"globals": "16.0.0",
|
||||||
"@typescript-eslint/parser": "^8.26.1",
|
"prettier": "^3.5.3"
|
||||||
"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"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5.7.3"
|
"typescript": "^5.8.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
"exiftool-vendored": "^29.2.0",
|
"exiftool-vendored": "^29.3.0",
|
||||||
"fast-jwt": "^5.0.5",
|
"fast-jwt": "6.0.1",
|
||||||
"fluent-ffmpeg": "^2.1.3",
|
"fluent-ffmpeg": "^2.1.3",
|
||||||
"image-thumbnail": "^1.0.17",
|
"image-thumbnail": "^1.0.17",
|
||||||
"luxon": "^3.5.0",
|
"luxon": "^3.6.1",
|
||||||
"redis": "^4.7.0"
|
"redis": "^4.7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,11 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: linear-gradient(135deg, rgba(31 30 30 / 90%) 0%, rgba(45 45 45 / 90%) 100%);
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(31 30 30 / 90%) 0%,
|
||||||
|
rgba(45 45 45 / 90%) 100%
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-logo {
|
.auth-logo {
|
||||||
|
|
|
@ -5,19 +5,6 @@ body {
|
||||||
|
|
||||||
/* sidebar */
|
/* 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 {
|
.sidebar {
|
||||||
background-color: var(--background-secondary);
|
background-color: var(--background-secondary);
|
||||||
width: 220px;
|
width: 220px;
|
||||||
|
@ -26,6 +13,8 @@ body {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
border-right: 1px solid var(--border);
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar .actions {
|
.sidebar .actions {
|
||||||
|
@ -33,27 +22,40 @@ body {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar .actions .action {
|
.sidebar .actions .action {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: .5rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
height: 20px;
|
height: 3rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 4px;
|
|
||||||
transition: background-color 0.2s ease;
|
transition: background-color 0.2s ease;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--text);
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar .actions .action svg {
|
.sidebar .actions .action svg {
|
||||||
width: 25px;
|
width: 15px;
|
||||||
height: 25px;
|
height: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar .actions .action:hover {
|
.sidebar .actions .action:hover {
|
||||||
background-color: var(--background);
|
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 {
|
.sidebar .user-area {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -31,7 +31,8 @@
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: Fira Code;
|
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-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
@ -52,14 +53,18 @@ body {
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
input, button, textarea, select {
|
input,
|
||||||
|
button,
|
||||||
|
textarea,
|
||||||
|
select {
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
button, .button {
|
button,
|
||||||
|
.button {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0.75rem 1.5rem;
|
padding: 0.75rem 1.5rem;
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -70,11 +75,14 @@ button, .button {
|
||||||
transition: background-color 0.2s ease;
|
transition: background-color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover, .button:hover {
|
button:hover,
|
||||||
|
.button:hover {
|
||||||
background-color: var(--accent-hover);
|
background-color: var(--accent-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
input, textarea, select {
|
input,
|
||||||
|
textarea,
|
||||||
|
select {
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
background-color: var(--input-background);
|
background-color: var(--input-background);
|
||||||
|
@ -83,7 +91,9 @@ input, textarea, select {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:focus, textarea:focus, select:focus {
|
input:focus,
|
||||||
|
textarea:focus,
|
||||||
|
select:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: var(--accent);
|
border-color: var(--accent);
|
||||||
box-shadow: 0 0 0 2px rgb(88 101 242 / 30%);
|
box-shadow: 0 0 0 2px rgb(88 101 242 / 30%);
|
||||||
|
|
|
@ -18,8 +18,7 @@ if (loginForm) {
|
||||||
if (!email || !password) {
|
if (!email || !password) {
|
||||||
if (errorMessage) {
|
if (errorMessage) {
|
||||||
errorMessage.style.display = "block";
|
errorMessage.style.display = "block";
|
||||||
errorMessage.textContent =
|
errorMessage.textContent = "Please enter both email and password.";
|
||||||
"Please enter both email and password.";
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -46,16 +45,14 @@ if (loginForm) {
|
||||||
if (errorMessage) {
|
if (errorMessage) {
|
||||||
errorMessage.style.display = "block";
|
errorMessage.style.display = "block";
|
||||||
errorMessage.textContent =
|
errorMessage.textContent =
|
||||||
data.error ||
|
data.error || "Invalid email or password. Please try again.";
|
||||||
"Invalid email or password. Please try again.";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Login error:", error);
|
console.error("Login error:", error);
|
||||||
if (errorMessage) {
|
if (errorMessage) {
|
||||||
errorMessage.style.display = "block";
|
errorMessage.style.display = "block";
|
||||||
errorMessage.textContent =
|
errorMessage.textContent = "An error occurred. Please try again.";
|
||||||
"An error occurred. Please try again.";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -103,8 +100,7 @@ if (loginForm) {
|
||||||
.join("");
|
.join("");
|
||||||
} else {
|
} else {
|
||||||
errorMessage.textContent =
|
errorMessage.textContent =
|
||||||
data.error ||
|
data.error || "An error occurred. Please try again.";
|
||||||
"An error occurred. Please try again.";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,8 +108,7 @@ if (loginForm) {
|
||||||
console.error("Register error:", error);
|
console.error("Register error:", error);
|
||||||
if (errorMessage) {
|
if (errorMessage) {
|
||||||
errorMessage.style.display = "block";
|
errorMessage.style.display = "block";
|
||||||
errorMessage.textContent =
|
errorMessage.textContent = "An error occurred. Please try again.";
|
||||||
"An error occurred. Please try again.";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,7 +6,7 @@ export async function authByToken(
|
||||||
request: ExtendedRequest,
|
request: ExtendedRequest,
|
||||||
reservation?: ReservedSQL,
|
reservation?: ReservedSQL,
|
||||||
): Promise<ApiUserSession | null> {
|
): Promise<ApiUserSession | null> {
|
||||||
let selfReservation: boolean = false;
|
let selfReservation = false;
|
||||||
|
|
||||||
const authorizationHeader: string | null =
|
const authorizationHeader: string | null =
|
||||||
request.headers.get("Authorization");
|
request.headers.get("Authorization");
|
||||||
|
|
|
@ -48,7 +48,7 @@ export function parseDuration(input: string): DurationObject {
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const match of matches) {
|
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];
|
const unit: string = match[2];
|
||||||
|
|
||||||
switch (unit) {
|
switch (unit) {
|
||||||
|
@ -90,12 +90,10 @@ export function generateRandomString(length?: number): string {
|
||||||
|
|
||||||
const characters: string =
|
const characters: string =
|
||||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
let result: string = "";
|
let result = "";
|
||||||
|
|
||||||
for (let i: number = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
result += characters.charAt(
|
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||||
Math.floor(Math.random() * characters.length),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -172,9 +170,7 @@ export function nameWithoutExtension(fileName: string): string {
|
||||||
if (lastDotIndex <= 0) return fileName;
|
if (lastDotIndex <= 0) return fileName;
|
||||||
|
|
||||||
const ext: string = fileName.slice(lastDotIndex + 1).toLowerCase();
|
const ext: string = fileName.slice(lastDotIndex + 1).toLowerCase();
|
||||||
return knownExtensions.has(ext)
|
return knownExtensions.has(ext) ? fileName.slice(0, lastDotIndex) : fileName;
|
||||||
? fileName.slice(0, lastDotIndex)
|
|
||||||
: fileName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function supportsExif(mimeType: string, extension: string): boolean {
|
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 args: string[] = process.argv.slice(2);
|
||||||
const parsedArgs: Record<string, string | boolean> = {};
|
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("--")) {
|
if (args[i].startsWith("--")) {
|
||||||
const key: string = args[i].slice(2);
|
const key: string = args[i].slice(2);
|
||||||
const value: string | boolean =
|
const value: string | boolean =
|
||||||
args[i + 1] && !args[i + 1].startsWith("--")
|
args[i + 1] && !args[i + 1].startsWith("--") ? args[i + 1] : true;
|
||||||
? args[i + 1]
|
|
||||||
: true;
|
|
||||||
parsedArgs[key] = value;
|
parsedArgs[key] = value;
|
||||||
if (value !== true) i++;
|
if (value !== true) i++;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { renderFile } from "ejs";
|
|
||||||
import { resolve } from "path";
|
import { resolve } from "path";
|
||||||
|
import { renderFile } from "ejs";
|
||||||
|
|
||||||
export async function renderEjsTemplate(
|
export async function renderEjsTemplate(
|
||||||
viewName: string | string[],
|
viewName: string | string[],
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import { environment } from "@config/environment";
|
|
||||||
import { timestampToReadable } from "@helpers/char";
|
|
||||||
import type { Stats } from "fs";
|
import type { Stats } from "fs";
|
||||||
import {
|
import {
|
||||||
|
type WriteStream,
|
||||||
createWriteStream,
|
createWriteStream,
|
||||||
existsSync,
|
existsSync,
|
||||||
mkdirSync,
|
mkdirSync,
|
||||||
statSync,
|
statSync,
|
||||||
WriteStream,
|
|
||||||
} from "fs";
|
} from "fs";
|
||||||
import { EOL } from "os";
|
import { EOL } from "os";
|
||||||
import { basename, join } from "path";
|
import { basename, join } from "path";
|
||||||
|
import { environment } from "@config/environment";
|
||||||
|
import { timestampToReadable } from "@helpers/char";
|
||||||
|
|
||||||
class Logger {
|
class Logger {
|
||||||
private static instance: Logger;
|
private static instance: Logger;
|
||||||
|
@ -37,7 +37,7 @@ class Logger {
|
||||||
mkdirSync(logDir, { recursive: true });
|
mkdirSync(logDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
let addSeparator: boolean = false;
|
let addSeparator = false;
|
||||||
|
|
||||||
if (existsSync(logFile)) {
|
if (existsSync(logFile)) {
|
||||||
const fileStats: Stats = statSync(logFile);
|
const fileStats: Stats = statSync(logFile);
|
||||||
|
@ -66,9 +66,9 @@ class Logger {
|
||||||
|
|
||||||
private extractFileName(stack: string): string {
|
private extractFileName(stack: string): string {
|
||||||
const stackLines: string[] = stack.split("\n");
|
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();
|
const line: string = stackLines[i].trim();
|
||||||
if (line && !line.includes("Logger.") && line.includes("(")) {
|
if (line && !line.includes("Logger.") && line.includes("(")) {
|
||||||
callerFile = line.split("(")[1]?.split(")")[0] || "";
|
callerFile = line.split("(")[1]?.split(")")[0] || "";
|
||||||
|
@ -91,7 +91,7 @@ class Logger {
|
||||||
return { filename, timestamp: readableTimestamp };
|
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 stack: string = new Error().stack || "";
|
||||||
const { filename, timestamp } = this.getCallerInfo(stack);
|
const { filename, timestamp } = this.getCallerInfo(stack);
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ class Logger {
|
||||||
this.writeConsoleMessageColored(logMessageParts, breakLine);
|
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 stack: string = new Error().stack || "";
|
||||||
const { filename, timestamp } = this.getCallerInfo(stack);
|
const { filename, timestamp } = this.getCallerInfo(stack);
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ class Logger {
|
||||||
|
|
||||||
public error(
|
public error(
|
||||||
message: string | Error | ErrorEvent | (string | Error)[],
|
message: string | Error | ErrorEvent | (string | Error)[],
|
||||||
breakLine: boolean = false,
|
breakLine = false,
|
||||||
): void {
|
): void {
|
||||||
const stack: string = new Error().stack || "";
|
const stack: string = new Error().stack || "";
|
||||||
const { filename, timestamp } = this.getCallerInfo(stack);
|
const { filename, timestamp } = this.getCallerInfo(stack);
|
||||||
|
@ -161,7 +161,7 @@ class Logger {
|
||||||
bracketMessage2: string,
|
bracketMessage2: string,
|
||||||
message: string | string[],
|
message: string | string[],
|
||||||
color: string,
|
color: string,
|
||||||
breakLine: boolean = false,
|
breakLine = false,
|
||||||
): void {
|
): void {
|
||||||
const stack: string = new Error().stack || "";
|
const stack: string = new Error().stack || "";
|
||||||
const { timestamp } = this.getCallerInfo(stack);
|
const { timestamp } = this.getCallerInfo(stack);
|
||||||
|
@ -189,7 +189,7 @@ class Logger {
|
||||||
|
|
||||||
private writeConsoleMessageColored(
|
private writeConsoleMessageColored(
|
||||||
logMessageParts: ILogMessageParts,
|
logMessageParts: ILogMessageParts,
|
||||||
breakLine: boolean = false,
|
breakLine = false,
|
||||||
): void {
|
): void {
|
||||||
const logMessage: string = Object.keys(logMessageParts)
|
const logMessage: string = Object.keys(logMessageParts)
|
||||||
.map((key: string) => {
|
.map((key: string) => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { redisConfig } from "@config/environment";
|
import { redisConfig } from "@config/environment";
|
||||||
import { logger } from "@helpers/logger";
|
import { logger } from "@helpers/logger";
|
||||||
import { createClient, type RedisClientType } from "redis";
|
import { type RedisClientType, createClient } from "redis";
|
||||||
|
|
||||||
class RedisJson {
|
class RedisJson {
|
||||||
private static instance: RedisJson | null = null;
|
private static instance: RedisJson | null = null;
|
||||||
|
@ -21,11 +21,7 @@ class RedisJson {
|
||||||
});
|
});
|
||||||
|
|
||||||
RedisJson.instance.client.on("error", (err: Error) => {
|
RedisJson.instance.client.on("error", (err: Error) => {
|
||||||
logger.error([
|
logger.error(["Error connecting to Redis:", err, redisConfig.host]);
|
||||||
"Error connecting to Redis:",
|
|
||||||
err,
|
|
||||||
redisConfig.host,
|
|
||||||
]);
|
|
||||||
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
@ -167,10 +163,7 @@ class RedisJson {
|
||||||
try {
|
try {
|
||||||
await this.client.expire(key, seconds);
|
await this.client.expire(key, seconds);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error([
|
logger.error([`Error expiring key in Redis: ${key}`, error as Error]);
|
||||||
`Error expiring key in Redis: ${key}`,
|
|
||||||
error as Error,
|
|
||||||
]);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,8 +45,7 @@ class SessionManager {
|
||||||
const cookie: string | null = request.headers.get("Cookie");
|
const cookie: string | null = request.headers.get("Cookie");
|
||||||
if (!cookie) return null;
|
if (!cookie) return null;
|
||||||
|
|
||||||
const token: string | null =
|
const token: string | null = cookie.match(/session=([^;]+)/)?.[1] || null;
|
||||||
cookie.match(/session=([^;]+)/)?.[1] || null;
|
|
||||||
if (!token) return null;
|
if (!token) return null;
|
||||||
|
|
||||||
const userSessions: string[] = await redis
|
const userSessions: string[] = await redis
|
||||||
|
@ -72,15 +71,13 @@ class SessionManager {
|
||||||
const cookie: string | null = request.headers.get("Cookie");
|
const cookie: string | null = request.headers.get("Cookie");
|
||||||
if (!cookie) throw new Error("No session found in request");
|
if (!cookie) throw new Error("No session found in request");
|
||||||
|
|
||||||
const token: string | null =
|
const token: string | null = cookie.match(/session=([^;]+)/)?.[1] || null;
|
||||||
cookie.match(/session=([^;]+)/)?.[1] || null;
|
|
||||||
if (!token) throw new Error("Session token not found");
|
if (!token) throw new Error("Session token not found");
|
||||||
|
|
||||||
const userSessions: string[] = await redis
|
const userSessions: string[] = await redis
|
||||||
.getInstance()
|
.getInstance()
|
||||||
.keys("session:*:" + token);
|
.keys("session:*:" + token);
|
||||||
if (!userSessions.length)
|
if (!userSessions.length) throw new Error("Session not found or expired");
|
||||||
throw new Error("Session not found or expired");
|
|
||||||
|
|
||||||
const sessionKey: string = userSessions[0];
|
const sessionKey: string = userSessions[0];
|
||||||
|
|
||||||
|
@ -100,8 +97,7 @@ class SessionManager {
|
||||||
const userSessions: string[] = await redis
|
const userSessions: string[] = await redis
|
||||||
.getInstance()
|
.getInstance()
|
||||||
.keys("session:*:" + token);
|
.keys("session:*:" + token);
|
||||||
if (!userSessions.length)
|
if (!userSessions.length) throw new Error("Session not found or expired");
|
||||||
throw new Error("Session not found or expired");
|
|
||||||
|
|
||||||
const sessionData: unknown = await redis
|
const sessionData: unknown = await redis
|
||||||
.getInstance()
|
.getInstance()
|
||||||
|
@ -121,8 +117,7 @@ class SessionManager {
|
||||||
const cookie: string | null = request.headers.get("Cookie");
|
const cookie: string | null = request.headers.get("Cookie");
|
||||||
if (!cookie) return;
|
if (!cookie) return;
|
||||||
|
|
||||||
const token: string | null =
|
const token: string | null = cookie.match(/session=([^;]+)/)?.[1] || null;
|
||||||
cookie.match(/session=([^;]+)/)?.[1] || null;
|
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
|
|
||||||
const userSessions: string[] = await redis
|
const userSessions: string[] = await redis
|
||||||
|
@ -152,7 +147,7 @@ class SessionManager {
|
||||||
domain,
|
domain,
|
||||||
} = options || {};
|
} = 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";
|
if (httpOnly) cookie += "; HttpOnly";
|
||||||
|
|
||||||
|
@ -173,7 +168,7 @@ class SessionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
const [, value, unit] = match;
|
const [, value, unit] = match;
|
||||||
const num: number = parseInt(value, 10);
|
const num: number = Number.parseInt(value, 10);
|
||||||
|
|
||||||
switch (unit) {
|
switch (unit) {
|
||||||
case "s":
|
case "s":
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
|
import { join, resolve } from "path";
|
||||||
import { dataType } from "@config/environment.ts";
|
import { dataType } from "@config/environment.ts";
|
||||||
import { logger } from "@helpers/logger.ts";
|
import { logger } from "@helpers/logger.ts";
|
||||||
import { type BunFile, s3, sql } from "bun";
|
import { type BunFile, s3, sql } from "bun";
|
||||||
import ffmpeg from "fluent-ffmpeg";
|
import ffmpeg from "fluent-ffmpeg";
|
||||||
import imageThumbnail from "image-thumbnail";
|
import imageThumbnail from "image-thumbnail";
|
||||||
import { join, resolve } from "path";
|
|
||||||
|
|
||||||
declare var self: Worker;
|
declare var self: Worker;
|
||||||
|
|
||||||
|
@ -22,10 +22,7 @@ async function generateVideoThumbnail(
|
||||||
.format("mjpeg")
|
.format("mjpeg")
|
||||||
.output(thumbnailPath)
|
.output(thumbnailPath)
|
||||||
.on("error", (error: Error) => {
|
.on("error", (error: Error) => {
|
||||||
logger.error([
|
logger.error(["failed to generate thumbnail", error as Error]);
|
||||||
"failed to generate thumbnail",
|
|
||||||
error as Error,
|
|
||||||
]);
|
|
||||||
reject(error);
|
reject(error);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -71,10 +68,7 @@ async function generateImageThumbnail(
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const thumbnailBuffer: Buffer = await imageThumbnail(
|
const thumbnailBuffer: Buffer = await imageThumbnail(filePath, options);
|
||||||
filePath,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
|
|
||||||
await Bun.write(thumbnailPath, thumbnailBuffer.buffer);
|
await Bun.write(thumbnailPath, thumbnailBuffer.buffer);
|
||||||
resolve(await Bun.file(thumbnailPath).arrayBuffer());
|
resolve(await Bun.file(thumbnailPath).arrayBuffer());
|
||||||
|
@ -104,20 +98,14 @@ async function createThumbnails(files: FileEntry[]): Promise<void> {
|
||||||
try {
|
try {
|
||||||
fileArrayBuffer = await Bun.file(filePath).arrayBuffer();
|
fileArrayBuffer = await Bun.file(filePath).arrayBuffer();
|
||||||
} catch {
|
} catch {
|
||||||
logger.error([
|
logger.error(["Could not generate thumbnail for file:", fileName]);
|
||||||
"Could not generate thumbnail for file:",
|
|
||||||
fileName,
|
|
||||||
]);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
fileArrayBuffer = await s3.file(fileName).arrayBuffer();
|
fileArrayBuffer = await s3.file(fileName).arrayBuffer();
|
||||||
} catch {
|
} catch {
|
||||||
logger.error([
|
logger.error(["Could not generate thumbnail for file:", fileName]);
|
||||||
"Could not generate thumbnail for file:",
|
|
||||||
fileName,
|
|
||||||
]);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,10 +137,7 @@ async function createThumbnails(files: FileEntry[]): Promise<void> {
|
||||||
: await generateImageThumbnail(tempFilePath, tempThumbnailPath);
|
: await generateImageThumbnail(tempFilePath, tempThumbnailPath);
|
||||||
|
|
||||||
if (!thumbnailArrayBuffer) {
|
if (!thumbnailArrayBuffer) {
|
||||||
logger.error([
|
logger.error(["Could not generate thumbnail for file:", fileName]);
|
||||||
"Could not generate thumbnail for file:",
|
|
||||||
fileName,
|
|
||||||
]);
|
|
||||||
continue;
|
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 { dataType } from "@config/environment";
|
||||||
import { logger } from "@helpers/logger";
|
import { logger } from "@helpers/logger";
|
||||||
import { type ReservedSQL, s3, sql } from "bun";
|
import { type ReservedSQL, s3, sql } from "bun";
|
||||||
import { existsSync, mkdirSync } from "fs";
|
|
||||||
import { readdir } from "fs/promises";
|
import { readdir } from "fs/promises";
|
||||||
import { resolve } from "path";
|
|
||||||
|
|
||||||
import { serverHandler } from "@/server";
|
import { serverHandler } from "@/server";
|
||||||
|
|
||||||
|
@ -17,9 +17,7 @@ async function initializeDatabase(): Promise<void> {
|
||||||
files
|
files
|
||||||
.filter((file: string): boolean => file.endsWith(".ts"))
|
.filter((file: string): boolean => file.endsWith(".ts"))
|
||||||
.map(async (file: string): Promise<Module> => {
|
.map(async (file: string): Promise<Module> => {
|
||||||
const module: Module["module"] = await import(
|
const module: Module["module"] = await import(resolve(sqlDir, file));
|
||||||
resolve(sqlDir, file)
|
|
||||||
);
|
|
||||||
return { file, module };
|
return { file, module };
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
@ -69,10 +67,7 @@ async function main(): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info([
|
logger.info(["Using local datasource directory", `${dataType.path}`]);
|
||||||
"Using local datasource directory",
|
|
||||||
`${dataType.path}`,
|
|
||||||
]);
|
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await s3.write("test", "test");
|
await s3.write("test", "test");
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {
|
||||||
isValidPassword,
|
isValidPassword,
|
||||||
isValidUsername,
|
isValidUsername,
|
||||||
} from "@config/sql/users";
|
} 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 { logger } from "@/helpers/logger";
|
||||||
import { sessionManager } from "@/helpers/sessions";
|
import { sessionManager } from "@/helpers/sessions";
|
||||||
|
@ -61,13 +61,9 @@ async function handler(
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
|
|
||||||
const validations: UserValidation[] = [
|
const validations: UserValidation[] = [
|
||||||
username
|
username ? { check: isValidUsername(username), field: "Username" } : null,
|
||||||
? { check: isValidUsername(username), field: "Username" }
|
|
||||||
: null,
|
|
||||||
email ? { check: isValidEmail(email), field: "Email" } : null,
|
email ? { check: isValidEmail(email), field: "Email" } : null,
|
||||||
password
|
password ? { check: isValidPassword(password), field: "Password" } : null,
|
||||||
? { check: isValidPassword(password), field: "Password" }
|
|
||||||
: null,
|
|
||||||
].filter(Boolean) as UserValidation[];
|
].filter(Boolean) as UserValidation[];
|
||||||
|
|
||||||
validations.forEach(({ check }: UserValidation): void => {
|
validations.forEach(({ check }: UserValidation): void => {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
isValidPassword,
|
isValidPassword,
|
||||||
isValidUsername,
|
isValidUsername,
|
||||||
} from "@config/sql/users";
|
} 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 { isValidTimezone } from "@/helpers/char";
|
||||||
import { logger } from "@/helpers/logger";
|
import { logger } from "@/helpers/logger";
|
||||||
|
@ -57,9 +57,9 @@ async function handler(
|
||||||
|
|
||||||
const normalizedUsername: string = username.normalize("NFC");
|
const normalizedUsername: string = username.normalize("NFC");
|
||||||
const reservation: ReservedSQL = await sql.reserve();
|
const reservation: ReservedSQL = await sql.reserve();
|
||||||
let firstUser: boolean = false;
|
let firstUser = false;
|
||||||
let inviteData: Invite | null = null;
|
let inviteData: Invite | null = null;
|
||||||
let roles: string[] = [];
|
const roles: string[] = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const registrationEnabled: boolean =
|
const registrationEnabled: boolean =
|
||||||
|
@ -69,11 +69,10 @@ async function handler(
|
||||||
|
|
||||||
firstUser =
|
firstUser =
|
||||||
Number(
|
Number(
|
||||||
(await reservation`SELECT COUNT(*) AS count FROM users;`)[0]
|
(await reservation`SELECT COUNT(*) AS count FROM users;`)[0]?.count,
|
||||||
?.count,
|
|
||||||
) === 0;
|
) === 0;
|
||||||
|
|
||||||
let inviteValid: boolean = true;
|
let inviteValid = true;
|
||||||
if (!firstUser && invite) {
|
if (!firstUser && invite) {
|
||||||
const inviteValidation: { valid: boolean; error?: string } =
|
const inviteValidation: { valid: boolean; error?: string } =
|
||||||
isValidInvite(invite);
|
isValidInvite(invite);
|
||||||
|
@ -184,10 +183,7 @@ async function handler(
|
||||||
if (inviteData?.role) roles.push(inviteData.role);
|
if (inviteData?.role) roles.push(inviteData.role);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error([
|
logger.error(["Error inserting user into the database:", error as Error]);
|
||||||
"Error inserting user into the database:",
|
|
||||||
error as Error,
|
|
||||||
]);
|
|
||||||
return Response.json(
|
return Response.json(
|
||||||
{
|
{
|
||||||
success: false,
|
success: false,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { dataType } from "@config/environment";
|
|
||||||
import { s3, sql, type SQLQuery } from "bun";
|
|
||||||
import { resolve } from "path";
|
import { resolve } from "path";
|
||||||
|
import { dataType } from "@config/environment";
|
||||||
|
import { type SQLQuery, s3, sql } from "bun";
|
||||||
|
|
||||||
import { isUUID } from "@/helpers/char";
|
import { isUUID } from "@/helpers/char";
|
||||||
import { logger } from "@/helpers/logger";
|
import { logger } from "@/helpers/logger";
|
||||||
|
@ -134,26 +134,14 @@ async function handler(
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (file && !(typeof file === "string" && file.length === 0)) {
|
if (file && !(typeof file === "string" && file.length === 0)) {
|
||||||
await processFile(
|
await processFile(request, file, isAdmin, failedFiles, successfulFiles);
|
||||||
request,
|
|
||||||
file,
|
|
||||||
isAdmin,
|
|
||||||
failedFiles,
|
|
||||||
successfulFiles,
|
|
||||||
);
|
|
||||||
} else if (files) {
|
} else if (files) {
|
||||||
files = Array.isArray(files)
|
files = Array.isArray(files)
|
||||||
? files
|
? files
|
||||||
: files.split(/[, ]+/).filter(Boolean);
|
: files.split(/[, ]+/).filter(Boolean);
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
await processFile(
|
await processFile(request, file, isAdmin, failedFiles, successfulFiles);
|
||||||
request,
|
|
||||||
file,
|
|
||||||
isAdmin,
|
|
||||||
failedFiles,
|
|
||||||
successfulFiles,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
|
import { resolve } from "path";
|
||||||
import { dataType } from "@config/environment";
|
import { dataType } from "@config/environment";
|
||||||
import { getSetting } from "@config/sql/settings";
|
import { getSetting } from "@config/sql/settings";
|
||||||
import {
|
import {
|
||||||
|
type SQLQuery,
|
||||||
password as bunPassword,
|
password as bunPassword,
|
||||||
randomUUIDv7,
|
randomUUIDv7,
|
||||||
s3,
|
s3,
|
||||||
sql,
|
sql,
|
||||||
type SQLQuery,
|
|
||||||
} from "bun";
|
} from "bun";
|
||||||
import { exiftool } from "exiftool-vendored";
|
import { exiftool } from "exiftool-vendored";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
import { resolve } from "path";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
generateRandomString,
|
generateRandomString,
|
||||||
|
@ -97,9 +97,7 @@ async function removeExifData(
|
||||||
LocationName: null,
|
LocationName: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
await exiftool.write(tempInputPath, tagsToRemove, [
|
await exiftool.write(tempInputPath, tagsToRemove, ["-overwrite_original"]);
|
||||||
"-overwrite_original",
|
|
||||||
]);
|
|
||||||
|
|
||||||
return await Bun.file(tempInputPath).arrayBuffer();
|
return await Bun.file(tempInputPath).arrayBuffer();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -161,9 +159,9 @@ async function processFile(
|
||||||
};
|
};
|
||||||
|
|
||||||
const extension: string | null = getExtension(file.name);
|
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 =
|
const maxViews: number | null =
|
||||||
parseInt(user_provided_max_views, 10) || null;
|
Number.parseInt(user_provided_max_views, 10) || null;
|
||||||
|
|
||||||
if (!rawName) {
|
if (!rawName) {
|
||||||
failedFiles.push({
|
failedFiles.push({
|
||||||
|
@ -190,7 +188,7 @@ async function processFile(
|
||||||
? user_provided_tags
|
? user_provided_tags
|
||||||
: (user_provided_tags?.split(/[, ]+/).filter(Boolean) ?? []);
|
: (user_provided_tags?.split(/[, ]+/).filter(Boolean) ?? []);
|
||||||
|
|
||||||
let uploadEntry: FileUpload = {
|
const uploadEntry: FileUpload = {
|
||||||
id: randomUUID as UUID,
|
id: randomUUID as UUID,
|
||||||
owner: session.id as UUID,
|
owner: session.id as UUID,
|
||||||
name: rawName,
|
name: rawName,
|
||||||
|
@ -201,9 +199,7 @@ async function processFile(
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
favorite: user_wants_favorite === "true" || user_wants_favorite === "1",
|
favorite: user_wants_favorite === "true" || user_wants_favorite === "1",
|
||||||
tags: tags,
|
tags: tags,
|
||||||
expires_at: delete_short_string
|
expires_at: delete_short_string ? getNewTimeUTC(delete_short_string) : null,
|
||||||
? getNewTimeUTC(delete_short_string)
|
|
||||||
: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (name_format === "date") {
|
if (name_format === "date") {
|
||||||
|
@ -366,9 +362,7 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
||||||
requestBody.append(
|
requestBody.append(
|
||||||
"file",
|
"file",
|
||||||
new Blob([body], { type: request.actualContentType }),
|
new Blob([body], { type: request.actualContentType }),
|
||||||
request.actualContentType === "text/plain"
|
request.actualContentType === "text/plain" ? "file.txt" : "file.json",
|
||||||
? "file.txt"
|
|
||||||
: "file.json",
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return Response.json(
|
return Response.json(
|
||||||
|
@ -442,20 +436,16 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const filesThatSupportThumbnails: FileUpload[] = successfulFiles.filter(
|
const filesThatSupportThumbnails: FileUpload[] = successfulFiles.filter(
|
||||||
(file: FileUpload): boolean =>
|
(file: FileUpload): boolean => supportsThumbnail(file.mime_type as string),
|
||||||
supportsThumbnail(file.mime_type as string),
|
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
(await getSetting("enable_thumbnails")) === "true" &&
|
(await getSetting("enable_thumbnails")) === "true" &&
|
||||||
filesThatSupportThumbnails.length > 0
|
filesThatSupportThumbnails.length > 0
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const worker: Worker = new Worker(
|
const worker: Worker = new Worker("./src/helpers/workers/thumbnails.ts", {
|
||||||
"./src/helpers/workers/thumbnails.ts",
|
|
||||||
{
|
|
||||||
type: "module",
|
type: "module",
|
||||||
},
|
});
|
||||||
);
|
|
||||||
worker.postMessage({
|
worker.postMessage({
|
||||||
files: filesThatSupportThumbnails,
|
files: filesThatSupportThumbnails,
|
||||||
});
|
});
|
||||||
|
|
|
@ -67,9 +67,7 @@ async function handler(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const expirationDate: string | null = expires
|
const expirationDate: string | null = expires ? getNewTimeUTC(expires) : null;
|
||||||
? getNewTimeUTC(expires)
|
|
||||||
: null;
|
|
||||||
const maxUses: number = Number(max_uses) || 1;
|
const maxUses: number = Number(max_uses) || 1;
|
||||||
const inviteRole: string = role || "user";
|
const inviteRole: string = role || "user";
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,8 @@ async function handler(
|
||||||
{
|
{
|
||||||
success: false,
|
success: false,
|
||||||
code: 400,
|
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 },
|
{ status: 400 },
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
import { resolve } from "path";
|
||||||
import { dataType } from "@config/environment";
|
import { dataType } from "@config/environment";
|
||||||
import { s3, sql } from "bun";
|
import { s3, sql } from "bun";
|
||||||
import { resolve } from "path";
|
|
||||||
|
|
||||||
import { logger } from "@/helpers/logger";
|
import { logger } from "@/helpers/logger";
|
||||||
import { sessionManager } from "@/helpers/sessions";
|
import { sessionManager } from "@/helpers/sessions";
|
||||||
|
@ -21,9 +21,7 @@ async function deleteAvatar(
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (dataType.type === "local" && dataType.path) {
|
if (dataType.type === "local" && dataType.path) {
|
||||||
await Bun.file(
|
await Bun.file(resolve(dataType.path, "avatars", fileName)).unlink();
|
||||||
resolve(dataType.path, "avatars", fileName),
|
|
||||||
).unlink();
|
|
||||||
} else {
|
} else {
|
||||||
await s3.delete(`/avatars/${fileName}`);
|
await s3.delete(`/avatars/${fileName}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
import { resolve } from "path";
|
||||||
import { dataType } from "@config/environment";
|
import { dataType } from "@config/environment";
|
||||||
import { isValidTypeOrExtension } from "@config/sql/avatars";
|
import { isValidTypeOrExtension } from "@config/sql/avatars";
|
||||||
import { getSetting } from "@config/sql/settings";
|
import { getSetting } from "@config/sql/settings";
|
||||||
import { s3, sql } from "bun";
|
import { s3, sql } from "bun";
|
||||||
import { resolve } from "path";
|
|
||||||
|
|
||||||
import { getBaseUrl, getExtension } from "@/helpers/char";
|
import { getBaseUrl, getExtension } from "@/helpers/char";
|
||||||
import { logger } from "@/helpers/logger";
|
import { logger } from "@/helpers/logger";
|
||||||
|
@ -50,10 +50,7 @@ async function processFile(
|
||||||
await s3.delete(`/avatars/${existingFileName}`);
|
await s3.delete(`/avatars/${existingFileName}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error([
|
logger.error(["Error deleting existing avatar file:", error as Error]);
|
||||||
"Error deleting existing avatar file:",
|
|
||||||
error as Error,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,9 +135,7 @@ async function handler(
|
||||||
}
|
}
|
||||||
|
|
||||||
const file: File | null =
|
const file: File | null =
|
||||||
(formData.get("file") as File) ||
|
(formData.get("file") as File) || (formData.get("avatar") as File) || null;
|
||||||
(formData.get("avatar") as File) ||
|
|
||||||
null;
|
|
||||||
|
|
||||||
if (!file.type || file.type === "") {
|
if (!file.type || file.type === "") {
|
||||||
return Response.json(
|
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 { isUUID } from "@/helpers/char";
|
||||||
import { logger } from "@/helpers/logger";
|
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 safeCount: number = Math.min(Number.parseInt(count) || 25, 100);
|
||||||
const safePage: number = Math.max(parseInt(page) || 0, 0);
|
const safePage: number = Math.max(Number.parseInt(page) || 0, 0);
|
||||||
const offset: number = safePage * safeCount;
|
const offset: number = safePage * safeCount;
|
||||||
const sortColumn: string = sort_by || "created_at";
|
const sortColumn: string = sort_by || "created_at";
|
||||||
const order: "ASC" | "DESC" = validSortOrder(sort_order) as "ASC" | "DESC";
|
const order: "ASC" | "DESC" = validSortOrder(sort_order) as "ASC" | "DESC";
|
||||||
const safeSearchValue: string = escapeLike(search_value || "");
|
const safeSearchValue: string = escapeLike(search_value || "");
|
||||||
|
|
||||||
let files: FileEntry[];
|
let files: FileEntry[];
|
||||||
let totalPages: number = 0;
|
let totalPages = 0;
|
||||||
let totalFiles: number = 0;
|
let totalFiles = 0;
|
||||||
const reservation: ReservedSQL = await sql.reserve();
|
const reservation: ReservedSQL = await sql.reserve();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// ! i really dont understand why bun wont accept reservation(order)`
|
// ! i really dont understand why bun wont accept reservation(order)`
|
||||||
function orderBy(
|
function orderBy(field_name: string, orderBy: "ASC" | "DESC"): SQLQuery {
|
||||||
field_name: string,
|
|
||||||
orderBy: "ASC" | "DESC",
|
|
||||||
): SQLQuery {
|
|
||||||
return reservation`ORDER BY ${reservation(field_name)} ${orderBy === "ASC" ? reservation`ASC` : reservation`DESC`}`;
|
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 user: GetUser | null = null;
|
||||||
let isSelf: boolean = false;
|
let isSelf = false;
|
||||||
const isId: boolean = isUUID(query);
|
const isId: boolean = isUUID(query);
|
||||||
const normalized: string = isId ? query : query.normalize("NFC");
|
const normalized: string = isId ? query : query.normalize("NFC");
|
||||||
const isAdmin: boolean = request.session
|
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 [firstUser] = await sql`SELECT COUNT(*) FROM users`;
|
||||||
|
|
||||||
const instanceName: string =
|
const instanceName: string =
|
||||||
(await getSetting("instance_name", reservation)) ||
|
(await getSetting("instance_name", reservation)) || "Unnamed Instance";
|
||||||
"Unnamed Instance";
|
|
||||||
const requiresInvite: boolean =
|
const requiresInvite: boolean =
|
||||||
(await getSetting("enable_invitations", reservation)) === "true" &&
|
(await getSetting("enable_invitations", reservation)) === "true" &&
|
||||||
firstUser.count !== "0";
|
firstUser.count !== "0";
|
||||||
|
|
|
@ -13,6 +13,7 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
||||||
|
|
||||||
const ejsTemplateData: EjsTemplateData = {
|
const ejsTemplateData: EjsTemplateData = {
|
||||||
title: "Hello, World!",
|
title: "Hello, World!",
|
||||||
|
active: "dashboard",
|
||||||
};
|
};
|
||||||
|
|
||||||
return await renderEjsTemplate("dashboard/index.ejs", ejsTemplateData);
|
return await renderEjsTemplate("dashboard/index.ejs", ejsTemplateData);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
import { resolve } from "path";
|
||||||
import { dataType } from "@config/environment";
|
import { dataType } from "@config/environment";
|
||||||
import { type BunFile, type ReservedSQL, sql } from "bun";
|
import { type BunFile, type ReservedSQL, sql } from "bun";
|
||||||
import { resolve } from "path";
|
|
||||||
|
|
||||||
import { isUUID, nameWithoutExtension } from "@/helpers/char";
|
import { isUUID, nameWithoutExtension } from "@/helpers/char";
|
||||||
import { logger } from "@/helpers/logger";
|
import { logger } from "@/helpers/logger";
|
||||||
|
@ -139,9 +139,7 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
||||||
} else {
|
} else {
|
||||||
path = resolve(
|
path = resolve(
|
||||||
dataType.path,
|
dataType.path,
|
||||||
`${fileData.id}${
|
`${fileData.id}${fileData.extension ? `.${fileData.extension}` : ""}`,
|
||||||
fileData.extension ? `.${fileData.extension}` : ""
|
|
||||||
}`,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -157,9 +155,7 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
||||||
|
|
||||||
return new Response(bunStream, {
|
return new Response(bunStream, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": shouldShowThumbnail
|
"Content-Type": shouldShowThumbnail ? "image/jpeg" : fileData.mime_type,
|
||||||
? "image/jpeg"
|
|
||||||
: fileData.mime_type,
|
|
||||||
"Content-Disposition":
|
"Content-Disposition":
|
||||||
downloadFile === "true" || downloadFile === "1"
|
downloadFile === "true" || downloadFile === "1"
|
||||||
? `attachment; filename="${fileData.original_name || fileData.name}"`
|
? `attachment; filename="${fileData.original_name || fileData.name}"`
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
import { resolve } from "path";
|
||||||
import { dataType } from "@config/environment";
|
import { dataType } from "@config/environment";
|
||||||
import { isValidUsername } from "@config/sql/users";
|
import { isValidUsername } from "@config/sql/users";
|
||||||
import { type BunFile, type ReservedSQL, sql } from "bun";
|
import { type BunFile, type ReservedSQL, sql } from "bun";
|
||||||
import { resolve } from "path";
|
|
||||||
|
|
||||||
import { getBaseUrl, isUUID, nameWithoutExtension } from "@/helpers/char";
|
import { getBaseUrl, isUUID, nameWithoutExtension } from "@/helpers/char";
|
||||||
import { logger } from "@/helpers/logger";
|
import { logger } from "@/helpers/logger";
|
||||||
|
@ -71,11 +71,12 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
||||||
if (json === "true" || json === "1") {
|
if (json === "true" || json === "1") {
|
||||||
return Response.json(
|
return Response.json(
|
||||||
{
|
{
|
||||||
success: true, code: 200,
|
success: true,
|
||||||
|
code: 200,
|
||||||
avatar: {
|
avatar: {
|
||||||
...avatar,
|
...avatar,
|
||||||
url: `${getBaseUrl(request)}/user/avatar/${user.id}`,
|
url: `${getBaseUrl(request)}/user/avatar/${user.id}`,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{ status: 200 },
|
{ status: 200 },
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { resolve } from "path";
|
||||||
import { environment } from "@config/environment";
|
import { environment } from "@config/environment";
|
||||||
import { logger } from "@helpers/logger";
|
import { logger } from "@helpers/logger";
|
||||||
import {
|
import {
|
||||||
|
@ -6,7 +7,6 @@ import {
|
||||||
type MatchedRoute,
|
type MatchedRoute,
|
||||||
type Serve,
|
type Serve,
|
||||||
} from "bun";
|
} from "bun";
|
||||||
import { resolve } from "path";
|
|
||||||
|
|
||||||
import { webSocketHandler } from "@/websocket";
|
import { webSocketHandler } from "@/websocket";
|
||||||
|
|
||||||
|
@ -77,8 +77,7 @@ class ServerHandler {
|
||||||
|
|
||||||
if (await file.exists()) {
|
if (await file.exists()) {
|
||||||
const fileContent: ArrayBuffer = await file.arrayBuffer();
|
const fileContent: ArrayBuffer = await file.arrayBuffer();
|
||||||
const contentType: string =
|
const contentType: string = file.type || "application/octet-stream";
|
||||||
file.type || "application/octet-stream";
|
|
||||||
|
|
||||||
return new Response(fileContent, {
|
return new Response(fileContent, {
|
||||||
headers: { "Content-Type": contentType },
|
headers: { "Content-Type": contentType },
|
||||||
|
@ -88,10 +87,7 @@ class ServerHandler {
|
||||||
return new Response("Not Found", { status: 404 });
|
return new Response("Not Found", { status: 404 });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error([
|
logger.error([`Error serving static file: ${pathname}`, error as Error]);
|
||||||
`Error serving static file: ${pathname}`,
|
|
||||||
error as Error,
|
|
||||||
]);
|
|
||||||
return new Response("Internal Server Error", { status: 500 });
|
return new Response("Internal Server Error", { status: 500 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,8 +112,7 @@ class ServerHandler {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const routeModule: RouteModule = await import(filePath);
|
const routeModule: RouteModule = await import(filePath);
|
||||||
const contentType: string | null =
|
const contentType: string | null = request.headers.get("Content-Type");
|
||||||
request.headers.get("Content-Type");
|
|
||||||
const actualContentType: string | null = contentType
|
const actualContentType: string | null = contentType
|
||||||
? contentType.split(";")[0].trim()
|
? contentType.split(";")[0].trim()
|
||||||
: null;
|
: null;
|
||||||
|
@ -144,9 +139,7 @@ class ServerHandler {
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(Array.isArray(routeModule.routeDef.method) &&
|
(Array.isArray(routeModule.routeDef.method) &&
|
||||||
!routeModule.routeDef.method.includes(
|
!routeModule.routeDef.method.includes(request.method)) ||
|
||||||
request.method,
|
|
||||||
)) ||
|
|
||||||
(!Array.isArray(routeModule.routeDef.method) &&
|
(!Array.isArray(routeModule.routeDef.method) &&
|
||||||
routeModule.routeDef.method !== request.method)
|
routeModule.routeDef.method !== request.method)
|
||||||
) {
|
) {
|
||||||
|
@ -171,9 +164,7 @@ class ServerHandler {
|
||||||
if (Array.isArray(expectedContentType)) {
|
if (Array.isArray(expectedContentType)) {
|
||||||
matchesAccepts =
|
matchesAccepts =
|
||||||
expectedContentType.includes("*/*") ||
|
expectedContentType.includes("*/*") ||
|
||||||
expectedContentType.includes(
|
expectedContentType.includes(actualContentType || "");
|
||||||
actualContentType || "",
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
matchesAccepts =
|
matchesAccepts =
|
||||||
expectedContentType === "*/*" ||
|
expectedContentType === "*/*" ||
|
||||||
|
@ -202,11 +193,7 @@ class ServerHandler {
|
||||||
(await authByToken(request)) ||
|
(await authByToken(request)) ||
|
||||||
(await sessionManager.getSession(request));
|
(await sessionManager.getSession(request));
|
||||||
|
|
||||||
response = await routeModule.handler(
|
response = await routeModule.handler(request, requestBody, server);
|
||||||
request,
|
|
||||||
requestBody,
|
|
||||||
server,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (routeModule.routeDef.returns !== "*/*") {
|
if (routeModule.routeDef.returns !== "*/*") {
|
||||||
response.headers.set(
|
response.headers.set(
|
||||||
|
@ -217,10 +204,7 @@ class ServerHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
logger.error([
|
logger.error([`Error handling route ${request.url}:`, error as Error]);
|
||||||
`Error handling route ${request.url}:`,
|
|
||||||
error as Error,
|
|
||||||
]);
|
|
||||||
|
|
||||||
response = Response.json(
|
response = Response.json(
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<a href="/dashboard" class="action">
|
<a href="/dashboard" class="action <%- active === 'dashboard' ? 'active' : '' %>">
|
||||||
<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>
|
<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>
|
<span>Dashboard</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { logger } from "@helpers/logger";
|
import { logger } from "@helpers/logger";
|
||||||
import { type ServerWebSocket } from "bun";
|
import type { ServerWebSocket } from "bun";
|
||||||
|
|
||||||
class WebSocketHandler {
|
class WebSocketHandler {
|
||||||
public handleMessage(ws: ServerWebSocket, message: string): void {
|
public handleMessage(ws: ServerWebSocket, message: string): void {
|
||||||
|
@ -20,11 +20,7 @@ class WebSocketHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleClose(
|
public handleClose(ws: ServerWebSocket, code: number, reason: string): void {
|
||||||
ws: ServerWebSocket,
|
|
||||||
code: number,
|
|
||||||
reason: string,
|
|
||||||
): void {
|
|
||||||
logger.warn(`WebSocket closed with code ${code}, reason: ${reason}`);
|
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": {
|
"compilerOptions": {
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": [
|
"@/*": ["src/*"],
|
||||||
"src/*"
|
"@config/*": ["config/*"],
|
||||||
],
|
"@types/*": ["types/*"],
|
||||||
"@config/*": [
|
"@helpers/*": ["src/helpers/*"]
|
||||||
"config/*"
|
|
||||||
],
|
|
||||||
"@types/*": [
|
|
||||||
"types/*"
|
|
||||||
],
|
|
||||||
"@helpers/*": [
|
|
||||||
"src/helpers/*"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"typeRoots": [
|
"typeRoots": ["./src/types", "./node_modules/@types"],
|
||||||
"./src/types",
|
|
||||||
"./node_modules/@types"
|
|
||||||
],
|
|
||||||
// Enable latest features
|
// Enable latest features
|
||||||
"lib": [
|
"lib": ["ESNext", "DOM"],
|
||||||
"ESNext",
|
|
||||||
"DOM"
|
|
||||||
],
|
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
|
@ -41,11 +27,7 @@
|
||||||
// Some stricter flags (disabled by default)
|
// Some stricter flags (disabled by default)
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": false,
|
"noUnusedParameters": false,
|
||||||
"noPropertyAccessFromIndexSignature": false,
|
"noPropertyAccessFromIndexSignature": false
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src", "types", "config"]
|
||||||
"src",
|
|
||||||
"types",
|
|
||||||
"config"
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue