move to biomejs, this is before unsafe lint run
Some checks failed
Code quality checks / biome (push) Failing after 16s

This commit is contained in:
creations 2025-04-13 09:14:23 -04:00
parent f4237afc59
commit 25fcd99acf
Signed by: creations
GPG key ID: 8F553AA4320FC711
43 changed files with 353 additions and 565 deletions

View 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

View file

@ -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
View 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"
}
}
}

View file

@ -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,
};

View file

@ -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();

View file

@ -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();

View file

@ -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();

View file

@ -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();

View file

@ -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();

View file

@ -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",
};
}

View file

@ -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",
},
},
];

View file

@ -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"
}
}

View file

@ -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;
}
}

View file

@ -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 {

View file

@ -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%);

View file

@ -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.";
}
}
});

View file

@ -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");

View file

@ -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++;
}

View file

@ -1,5 +1,5 @@
import { renderFile } from "ejs";
import { resolve } from "path";
import { renderFile } from "ejs";
export async function renderEjsTemplate(
viewName: string | string[],

View file

@ -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) => {

View file

@ -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;
}
}

View file

@ -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":

View file

@ -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;
}

View file

@ -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");

View file

@ -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 => {

View file

@ -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,

View file

@ -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) {

View file

@ -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,
});

View file

@ -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";

View file

@ -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 },
);

View file

@ -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}`);
}

View file

@ -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(

View file

@ -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`}`;
}

View file

@ -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

View file

@ -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";

View file

@ -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);

View file

@ -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}"`

View file

@ -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 },
);

View file

@ -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(
{

View file

@ -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>

View file

@ -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}`);
}
}

View file

@ -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",
},
};

View file

@ -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"]
}