after biome unsafe, restart frontend, add superadmin
This commit is contained in:
parent
25fcd99acf
commit
c02b519eee
41 changed files with 189 additions and 910 deletions
|
@ -1,4 +1,4 @@
|
|||
import { resolve } from "path";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
export const environment: Environment = {
|
||||
port: Number.parseInt(process.env.PORT || "8080", 10),
|
||||
|
|
|
@ -5,14 +5,14 @@ export const order: number = 6;
|
|||
|
||||
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||
let selfReservation = false;
|
||||
const activeReservation: ReservedSQL = reservation ?? (await sql.reserve());
|
||||
|
||||
if (!reservation) {
|
||||
reservation = await sql.reserve();
|
||||
selfReservation = true;
|
||||
}
|
||||
|
||||
try {
|
||||
await reservation`
|
||||
await activeReservation`
|
||||
CREATE TABLE IF NOT EXISTS avatars (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
owner UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
|
@ -28,7 +28,7 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
|||
throw error;
|
||||
} finally {
|
||||
if (selfReservation) {
|
||||
reservation.release();
|
||||
activeReservation.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,14 +5,14 @@ export const order: number = 5;
|
|||
|
||||
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||
let selfReservation = false;
|
||||
const activeReservation: ReservedSQL = reservation ?? (await sql.reserve());
|
||||
|
||||
if (!reservation) {
|
||||
reservation = await sql.reserve();
|
||||
selfReservation = true;
|
||||
}
|
||||
|
||||
try {
|
||||
await reservation`
|
||||
await activeReservation`
|
||||
CREATE TABLE IF NOT EXISTS files (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
owner UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
|
@ -37,7 +37,7 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
|||
);
|
||||
`;
|
||||
|
||||
const functionExists: { exists: boolean }[] = await reservation`
|
||||
const functionExists: { exists: boolean }[] = await activeReservation`
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM pg_proc
|
||||
JOIN pg_namespace ON pg_proc.pronamespace = pg_namespace.oid
|
||||
|
@ -46,7 +46,7 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
|||
`;
|
||||
|
||||
if (!functionExists[0].exists) {
|
||||
await reservation`
|
||||
await activeReservation`
|
||||
CREATE FUNCTION update_files_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
|
@ -57,7 +57,7 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
|||
`;
|
||||
}
|
||||
|
||||
const triggerExists: { exists: boolean }[] = await reservation`
|
||||
const triggerExists: { exists: boolean }[] = await activeReservation`
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM pg_trigger
|
||||
WHERE tgname = 'trigger_update_files_updated_at'
|
||||
|
@ -65,7 +65,7 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
|||
`;
|
||||
|
||||
if (!triggerExists[0].exists) {
|
||||
await reservation`
|
||||
await activeReservation`
|
||||
CREATE TRIGGER trigger_update_files_updated_at
|
||||
BEFORE UPDATE ON files
|
||||
FOR EACH ROW
|
||||
|
@ -80,7 +80,7 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
|||
throw error;
|
||||
} finally {
|
||||
if (selfReservation) {
|
||||
reservation.release();
|
||||
activeReservation.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,14 +5,14 @@ export const order: number = 4;
|
|||
|
||||
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||
let selfReservation = false;
|
||||
const activeReservation: ReservedSQL = reservation ?? (await sql.reserve());
|
||||
|
||||
if (!reservation) {
|
||||
reservation = await sql.reserve();
|
||||
selfReservation = true;
|
||||
}
|
||||
|
||||
try {
|
||||
await reservation`
|
||||
await activeReservation`
|
||||
CREATE TABLE IF NOT EXISTS folders (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
owner UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
|
@ -26,7 +26,7 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
|||
);
|
||||
`;
|
||||
|
||||
const functionExists: { exists: boolean }[] = await reservation`
|
||||
const functionExists: { exists: boolean }[] = await activeReservation`
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM pg_proc
|
||||
JOIN pg_namespace ON pg_proc.pronamespace = pg_namespace.oid
|
||||
|
@ -35,7 +35,7 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
|||
`;
|
||||
|
||||
if (!functionExists[0].exists) {
|
||||
await reservation`
|
||||
await activeReservation`
|
||||
CREATE FUNCTION update_folders_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
|
@ -46,7 +46,7 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
|||
`;
|
||||
}
|
||||
|
||||
const triggerExists: { exists: boolean }[] = await reservation`
|
||||
const triggerExists: { exists: boolean }[] = await activeReservation`
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM pg_trigger
|
||||
WHERE tgname = 'trigger_update_folders_updated_at'
|
||||
|
@ -54,7 +54,7 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
|||
`;
|
||||
|
||||
if (!triggerExists[0].exists) {
|
||||
await reservation`
|
||||
await activeReservation`
|
||||
CREATE TRIGGER trigger_update_folders_updated_at
|
||||
BEFORE UPDATE ON folders
|
||||
FOR EACH ROW
|
||||
|
@ -69,7 +69,7 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
|||
throw error;
|
||||
} finally {
|
||||
if (selfReservation) {
|
||||
reservation.release();
|
||||
activeReservation.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,14 +5,14 @@ export const order: number = 3;
|
|||
|
||||
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||
let selfReservation = false;
|
||||
const activeReservation: ReservedSQL = reservation ?? (await sql.reserve());
|
||||
|
||||
if (!reservation) {
|
||||
reservation = await sql.reserve();
|
||||
selfReservation = true;
|
||||
}
|
||||
|
||||
try {
|
||||
await reservation`
|
||||
await activeReservation`
|
||||
CREATE TABLE IF NOT EXISTS invites (
|
||||
id TEXT PRIMARY KEY NOT NULL UNIQUE,
|
||||
created_by UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
|
@ -27,7 +27,7 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
|||
throw error;
|
||||
} finally {
|
||||
if (selfReservation) {
|
||||
reservation.release();
|
||||
activeReservation.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,14 +21,14 @@ const defaultSettings: Setting[] = [
|
|||
|
||||
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||
let selfReservation = false;
|
||||
const activeReservation: ReservedSQL = reservation ?? (await sql.reserve());
|
||||
|
||||
if (!reservation) {
|
||||
reservation = await sql.reserve();
|
||||
selfReservation = true;
|
||||
}
|
||||
|
||||
try {
|
||||
await reservation`
|
||||
await activeReservation`
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
"key" VARCHAR(64) PRIMARY KEY NOT NULL UNIQUE,
|
||||
"value" TEXT NOT NULL,
|
||||
|
@ -37,7 +37,7 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
|||
);
|
||||
`;
|
||||
|
||||
const functionExists: { exists: boolean }[] = await reservation`
|
||||
const functionExists: { exists: boolean }[] = await activeReservation`
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM pg_proc
|
||||
JOIN pg_namespace ON pg_proc.pronamespace = pg_namespace.oid
|
||||
|
@ -46,7 +46,7 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
|||
`;
|
||||
|
||||
if (!functionExists[0].exists) {
|
||||
await reservation`
|
||||
await activeReservation`
|
||||
CREATE FUNCTION update_settings_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
|
@ -57,7 +57,7 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
|||
`;
|
||||
}
|
||||
|
||||
const triggerExists: { exists: boolean }[] = await reservation`
|
||||
const triggerExists: { exists: boolean }[] = await activeReservation`
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM pg_trigger
|
||||
WHERE tgname = 'trigger_update_settings_updated_at'
|
||||
|
@ -65,7 +65,7 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
|||
`;
|
||||
|
||||
if (!triggerExists[0].exists) {
|
||||
await reservation`
|
||||
await activeReservation`
|
||||
CREATE TRIGGER trigger_update_settings_updated_at
|
||||
BEFORE UPDATE ON settings
|
||||
FOR EACH ROW
|
||||
|
@ -74,7 +74,7 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
|||
}
|
||||
|
||||
for (const setting of defaultSettings) {
|
||||
await reservation`
|
||||
await activeReservation`
|
||||
INSERT INTO settings ("key", "value")
|
||||
VALUES (${setting.key}, ${setting.value})
|
||||
ON CONFLICT ("key") DO NOTHING;
|
||||
|
@ -88,7 +88,7 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
|||
throw error;
|
||||
} finally {
|
||||
if (selfReservation) {
|
||||
reservation.release();
|
||||
activeReservation.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,15 +100,15 @@ export async function getSetting(
|
|||
reservation?: ReservedSQL,
|
||||
): Promise<string | null> {
|
||||
let selfReservation = false;
|
||||
const activeReservation: ReservedSQL = reservation ?? (await sql.reserve());
|
||||
|
||||
if (!reservation) {
|
||||
reservation = await sql.reserve();
|
||||
selfReservation = true;
|
||||
}
|
||||
|
||||
try {
|
||||
const result: { value: string }[] =
|
||||
await reservation`SELECT value FROM settings WHERE "key" = ${key};`;
|
||||
await activeReservation`SELECT value FROM settings WHERE "key" = ${key};`;
|
||||
|
||||
if (result.length === 0) {
|
||||
return null;
|
||||
|
@ -120,7 +120,7 @@ export async function getSetting(
|
|||
throw error;
|
||||
} finally {
|
||||
if (selfReservation) {
|
||||
reservation.release();
|
||||
activeReservation.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -131,14 +131,14 @@ export async function setSetting(
|
|||
reservation?: ReservedSQL,
|
||||
): Promise<void> {
|
||||
let selfReservation = false;
|
||||
const activeReservation: ReservedSQL = reservation ?? (await sql.reserve());
|
||||
|
||||
if (!reservation) {
|
||||
reservation = await sql.reserve();
|
||||
selfReservation = true;
|
||||
}
|
||||
|
||||
try {
|
||||
await reservation`
|
||||
await activeReservation`
|
||||
INSERT INTO settings ("key", "value", updated_at)
|
||||
VALUES (${key}, ${value}, NOW())
|
||||
ON CONFLICT ("key")
|
||||
|
@ -148,7 +148,7 @@ export async function setSetting(
|
|||
throw error;
|
||||
} finally {
|
||||
if (selfReservation) {
|
||||
reservation.release();
|
||||
activeReservation.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -158,20 +158,20 @@ export async function deleteSetting(
|
|||
reservation?: ReservedSQL,
|
||||
): Promise<void> {
|
||||
let selfReservation = false;
|
||||
const activeReservation: ReservedSQL = reservation ?? (await sql.reserve());
|
||||
|
||||
if (!reservation) {
|
||||
reservation = await sql.reserve();
|
||||
selfReservation = true;
|
||||
}
|
||||
|
||||
try {
|
||||
await reservation`DELETE FROM settings WHERE "key" = ${key};`;
|
||||
await activeReservation`DELETE FROM settings WHERE "key" = ${key};`;
|
||||
} catch (error) {
|
||||
logger.error(["Could not delete the setting:", error as Error]);
|
||||
throw error;
|
||||
} finally {
|
||||
if (selfReservation) {
|
||||
reservation.release();
|
||||
activeReservation.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -180,15 +180,15 @@ export async function getAllSettings(
|
|||
reservation?: ReservedSQL,
|
||||
): Promise<{ key: string; value: string }[]> {
|
||||
let selfReservation = false;
|
||||
const activeReservation: ReservedSQL = reservation ?? (await sql.reserve());
|
||||
|
||||
if (!reservation) {
|
||||
reservation = await sql.reserve();
|
||||
selfReservation = true;
|
||||
}
|
||||
|
||||
try {
|
||||
const result: { key: string; value: string }[] =
|
||||
await reservation`SELECT "key", "value" FROM settings;`;
|
||||
await activeReservation`SELECT "key", "value" FROM settings;`;
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
|
@ -196,7 +196,7 @@ export async function getAllSettings(
|
|||
throw error;
|
||||
} finally {
|
||||
if (selfReservation) {
|
||||
reservation.release();
|
||||
activeReservation.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,14 +5,14 @@ export const order: number = 1;
|
|||
|
||||
export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
||||
let selfReservation = false;
|
||||
const activeReservation: ReservedSQL = reservation ?? (await sql.reserve());
|
||||
|
||||
if (!reservation) {
|
||||
reservation = await sql.reserve();
|
||||
selfReservation = true;
|
||||
}
|
||||
|
||||
try {
|
||||
await reservation`
|
||||
await activeReservation`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
authorization_token UUID NOT NULL UNIQUE DEFAULT gen_random_uuid(),
|
||||
|
@ -32,7 +32,7 @@ export async function createTable(reservation?: ReservedSQL): Promise<void> {
|
|||
throw error;
|
||||
} finally {
|
||||
if (selfReservation) {
|
||||
reservation.release();
|
||||
activeReservation.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"ejs": "^3.1.10",
|
||||
"eta": "^3.5.0",
|
||||
"exiftool-vendored": "^29.3.0",
|
||||
"fast-jwt": "6.0.1",
|
||||
"fluent-ffmpeg": "^2.1.3",
|
||||
|
|
|
@ -1,198 +0,0 @@
|
|||
.container {
|
||||
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);
|
||||
}
|
||||
|
||||
.content form {
|
||||
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%
|
||||
);
|
||||
}
|
||||
|
||||
.auth-logo {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.auth-logo h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.auth-logo p {
|
||||
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;
|
||||
}
|
||||
|
||||
.auth-header {
|
||||
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);
|
||||
}
|
||||
|
||||
.auth-form {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.auth-form form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.auth-toggle {
|
||||
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;
|
||||
}
|
||||
|
||||
.form-footer a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.form-footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.form-footer label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.auth-form button {
|
||||
margin-top: 0.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.password-group {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.password-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.password-wrapper input {
|
||||
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;
|
||||
}
|
||||
|
||||
.toggle-password:hover {
|
||||
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;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.auth-link {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.auth-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (width <= 480px) {
|
||||
.auth-card {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.auth-logo h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
/* sidebar */
|
||||
|
||||
.sidebar {
|
||||
background-color: var(--background-secondary);
|
||||
width: 220px;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-right: 1px solid var(--border);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.sidebar .actions {
|
||||
display: flex;
|
||||
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: 3rem;
|
||||
width: 100%;
|
||||
transition: background-color 0.2s ease;
|
||||
text-decoration: none;
|
||||
color: var(--text);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.sidebar .actions .action svg {
|
||||
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%);
|
||||
}
|
||||
|
||||
.sidebar .user-area img {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.sidebar .user-area .username {
|
||||
margin-top: 1rem;
|
||||
font-weight: bold;
|
||||
color: var(--text);
|
||||
}
|
|
@ -46,82 +46,3 @@ body {
|
|||
background-color: var(--background);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
textarea,
|
||||
select {
|
||||
font-family: inherit;
|
||||
font-size: 1rem;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
button,
|
||||
.button {
|
||||
cursor: pointer;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
background-color: var(--accent);
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
button:hover,
|
||||
.button:hover {
|
||||
background-color: var(--accent-hover);
|
||||
}
|
||||
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--border);
|
||||
background-color: var(--input-background);
|
||||
color: var(--text);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
textarea:focus,
|
||||
select:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 2px rgb(88 101 242 / 30%);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: bold;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: var(--svg-fill);
|
||||
transition: fill 0.2s ease;
|
||||
}
|
||||
|
||||
svg.stroke-only {
|
||||
fill: none;
|
||||
stroke: var(--svg-fill);
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
svg:hover {
|
||||
fill: var(--accent);
|
||||
}
|
||||
|
|
|
@ -1,130 +0,0 @@
|
|||
const loginForm = document.getElementById("login-form");
|
||||
const registerForm = document.getElementById("register-form");
|
||||
const errorMessage = document.getElementById("error-message");
|
||||
const rememberMe = document.getElementById("remember-me");
|
||||
const emailInput = document.getElementById("email");
|
||||
|
||||
if (emailInput && localStorage.getItem("email")) {
|
||||
emailInput.value = localStorage.getItem("email");
|
||||
}
|
||||
|
||||
if (loginForm) {
|
||||
loginForm.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const email = emailInput?.value.trim();
|
||||
const password = document.getElementById("password")?.value.trim();
|
||||
|
||||
if (!email || !password) {
|
||||
if (errorMessage) {
|
||||
errorMessage.style.display = "block";
|
||||
errorMessage.textContent = "Please enter both email and password.";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (rememberMe?.checked) {
|
||||
localStorage.setItem("email", email);
|
||||
} else {
|
||||
sessionStorage.setItem("email", email);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/auth/login", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "same-origin",
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
window.location.href = "/dashboard";
|
||||
} else {
|
||||
if (errorMessage) {
|
||||
errorMessage.style.display = "block";
|
||||
errorMessage.textContent =
|
||||
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.";
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (registerForm) {
|
||||
registerForm.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const email = emailInput?.value.trim();
|
||||
const username = document.getElementById("username")?.value.trim();
|
||||
const password = document.getElementById("password")?.value.trim();
|
||||
const inviteCode = document.getElementById("invite-code")?.value.trim();
|
||||
|
||||
if (!email || !password) {
|
||||
if (errorMessage) {
|
||||
errorMessage.style.display = "block";
|
||||
errorMessage.textContent = "Please enter email, password.";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/auth/register", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "same-origin",
|
||||
body: JSON.stringify({
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
invite: inviteCode,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
window.location.href = "/dashboard";
|
||||
} else {
|
||||
if (errorMessage) {
|
||||
errorMessage.style.display = "block";
|
||||
|
||||
if (Array.isArray(data.errors)) {
|
||||
errorMessage.innerHTML = data.errors
|
||||
.map((err) => `<p>${err}</p>`)
|
||||
.join("");
|
||||
} else {
|
||||
errorMessage.textContent =
|
||||
data.error || "An error occurred. Please try again.";
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Register error:", error);
|
||||
if (errorMessage) {
|
||||
errorMessage.style.display = "block";
|
||||
errorMessage.textContent = "An error occurred. Please try again.";
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const passwordInput = document.getElementById("password");
|
||||
const togglePassword = document.getElementById("toggle-password");
|
||||
|
||||
togglePassword.addEventListener("click", () => {
|
||||
if (passwordInput.type === "password") {
|
||||
passwordInput.type = "text";
|
||||
togglePassword.innerHTML =
|
||||
'<path d="M12 4.5c-5 0-9.27 3.11-11 7.5 1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zm0 13c-3.03 0-5.5-2.47-5.5-5.5s2.47-5.5 5.5-5.5 5.5 2.47 5.5 5.5-2.47 5.5-5.5 5.5z"/>';
|
||||
} else {
|
||||
passwordInput.type = "password";
|
||||
togglePassword.innerHTML =
|
||||
'<path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zm0 13c-3.03 0-5.5-2.47-5.5-5.5s2.47-5.5 5.5-5.5 5.5 2.47 5.5 5.5-2.47 5.5-5.5 5.5zm0-9a3.5 3.5 0 100 7 3.5 3.5 0 000-7z"/>';
|
||||
}
|
||||
});
|
|
@ -7,6 +7,7 @@ export async function authByToken(
|
|||
reservation?: ReservedSQL,
|
||||
): Promise<ApiUserSession | null> {
|
||||
let selfReservation = false;
|
||||
let activeReservation: ReservedSQL | undefined = reservation;
|
||||
|
||||
const authorizationHeader: string | null =
|
||||
request.headers.get("Authorization");
|
||||
|
@ -17,14 +18,14 @@ export async function authByToken(
|
|||
const authorizationToken: string = authorizationHeader.slice(7).trim();
|
||||
if (!authorizationToken || !isUUID(authorizationToken)) return null;
|
||||
|
||||
if (!reservation) {
|
||||
reservation = await sql.reserve();
|
||||
if (!activeReservation) {
|
||||
activeReservation = await sql.reserve();
|
||||
selfReservation = true;
|
||||
}
|
||||
|
||||
try {
|
||||
const result: User[] =
|
||||
await reservation`SELECT * FROM users WHERE authorization_token = ${authorizationToken};`;
|
||||
await activeReservation`SELECT * FROM users WHERE authorization_token = ${authorizationToken};`;
|
||||
|
||||
if (result.length === 0) return null;
|
||||
|
||||
|
@ -44,7 +45,7 @@ export async function authByToken(
|
|||
return null;
|
||||
} finally {
|
||||
if (selfReservation) {
|
||||
reservation.release();
|
||||
activeReservation.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ import { DateTime } from "luxon";
|
|||
|
||||
export function timestampToReadable(timestamp?: number): string {
|
||||
const date: Date =
|
||||
timestamp && !isNaN(timestamp) ? new Date(timestamp) : new Date();
|
||||
if (isNaN(date.getTime())) return "Invalid Date";
|
||||
timestamp && !Number.isNaN(timestamp) ? new Date(timestamp) : new Date();
|
||||
if (Number.isNaN(date.getTime())) return "Invalid Date";
|
||||
return date.toISOString().replace("T", " ").replace("Z", "");
|
||||
}
|
||||
|
||||
|
@ -84,15 +84,13 @@ export function isValidTimezone(timezone: string): boolean {
|
|||
}
|
||||
|
||||
export function generateRandomString(length?: number): string {
|
||||
if (!length) {
|
||||
length = length || Math.floor(Math.random() * 10) + 5;
|
||||
}
|
||||
const finalLength: number = length ?? Math.floor(Math.random() * 10) + 5;
|
||||
|
||||
const characters: string =
|
||||
const characters =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let result = "";
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
for (let i = 0; i < finalLength; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
}
|
||||
|
||||
|
|
|
@ -24,8 +24,7 @@ import { type ReservedSQL, sql } from "bun";
|
|||
error.message.includes("foreign key constraint")
|
||||
) {
|
||||
console.error(
|
||||
`Could not clear table "${table}" due to foreign key constraints.\n` +
|
||||
"Try using --cascade if you want to remove dependent records.",
|
||||
`Could not clear table "${table}" due to foreign key constraints.\nTry using --cascade if you want to remove dependent records.`,
|
||||
);
|
||||
} else {
|
||||
console.error("Could not clear table:", error);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { resolve } from "path";
|
||||
import { resolve } from "node:path";
|
||||
import { renderFile } from "ejs";
|
||||
|
||||
export async function renderEjsTemplate(
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import type { Stats } from "fs";
|
||||
import type { Stats } from "node:fs";
|
||||
import {
|
||||
type WriteStream,
|
||||
createWriteStream,
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
statSync,
|
||||
} from "fs";
|
||||
import { EOL } from "os";
|
||||
import { basename, join } from "path";
|
||||
} from "node:fs";
|
||||
import { EOL } from "node:os";
|
||||
import { basename, join } from "node:path";
|
||||
import { environment } from "@config/environment";
|
||||
import { timestampToReadable } from "@helpers/char";
|
||||
|
||||
|
|
|
@ -86,12 +86,12 @@ class RedisJson {
|
|||
}
|
||||
|
||||
return value;
|
||||
} else if (type === "STRING") {
|
||||
}
|
||||
if (type === "STRING") {
|
||||
const value: string | null = await this.client.get(key);
|
||||
return value;
|
||||
} else {
|
||||
throw new Error(`Invalid type: ${type}`);
|
||||
}
|
||||
throw new Error(`Invalid type: ${type}`);
|
||||
} catch (error) {
|
||||
logger.error(`Error getting value from Redis for key: ${key}`);
|
||||
logger.error(error as Error);
|
||||
|
|
|
@ -50,7 +50,7 @@ class SessionManager {
|
|||
|
||||
const userSessions: string[] = await redis
|
||||
.getInstance()
|
||||
.keys("session:*:" + token);
|
||||
.keys(`session:*:${token}`);
|
||||
if (!userSessions.length) return null;
|
||||
|
||||
const sessionData: unknown = await redis
|
||||
|
@ -76,7 +76,7 @@ class SessionManager {
|
|||
|
||||
const userSessions: string[] = await redis
|
||||
.getInstance()
|
||||
.keys("session:*:" + token);
|
||||
.keys(`session:*:${token}`);
|
||||
if (!userSessions.length) throw new Error("Session not found or expired");
|
||||
|
||||
const sessionKey: string = userSessions[0];
|
||||
|
@ -96,7 +96,7 @@ class SessionManager {
|
|||
public async verifySession(token: string): Promise<UserSession> {
|
||||
const userSessions: string[] = await redis
|
||||
.getInstance()
|
||||
.keys("session:*:" + token);
|
||||
.keys(`session:*:${token}`);
|
||||
if (!userSessions.length) throw new Error("Session not found or expired");
|
||||
|
||||
const sessionData: unknown = await redis
|
||||
|
@ -122,7 +122,7 @@ class SessionManager {
|
|||
|
||||
const userSessions: string[] = await redis
|
||||
.getInstance()
|
||||
.keys("session:*:" + token);
|
||||
.keys(`session:*:${token}`);
|
||||
if (!userSessions.length) return;
|
||||
|
||||
await redis.getInstance().delete("JSON", userSessions[0]);
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { join, resolve } from "path";
|
||||
import { join, resolve } from "node: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";
|
||||
|
||||
declare var self: Worker;
|
||||
declare let self: Worker;
|
||||
|
||||
async function generateVideoThumbnail(
|
||||
filePath: string,
|
||||
|
@ -47,37 +47,34 @@ async function generateImageThumbnail(
|
|||
thumbnailPath: string,
|
||||
): Promise<ArrayBuffer> {
|
||||
return new Promise(
|
||||
async (
|
||||
(
|
||||
resolve: (value: ArrayBuffer) => void,
|
||||
reject: (reason: Error) => void,
|
||||
) => {
|
||||
try {
|
||||
const options: {
|
||||
responseType: "buffer";
|
||||
height: number;
|
||||
jpegOptions: {
|
||||
force: boolean;
|
||||
quality: number;
|
||||
};
|
||||
} = {
|
||||
height: 320,
|
||||
responseType: "buffer",
|
||||
jpegOptions: {
|
||||
force: true,
|
||||
quality: 60,
|
||||
},
|
||||
};
|
||||
): void => {
|
||||
const options = {
|
||||
height: 320,
|
||||
responseType: "buffer" as const,
|
||||
jpegOptions: {
|
||||
force: true,
|
||||
quality: 60,
|
||||
},
|
||||
};
|
||||
|
||||
const thumbnailBuffer: Buffer = await imageThumbnail(filePath, options);
|
||||
|
||||
await Bun.write(thumbnailPath, thumbnailBuffer.buffer);
|
||||
resolve(await Bun.file(thumbnailPath).arrayBuffer());
|
||||
|
||||
await Bun.file(filePath).unlink();
|
||||
await Bun.file(thumbnailPath).unlink();
|
||||
} catch (error) {
|
||||
reject(error as Error);
|
||||
}
|
||||
imageThumbnail(filePath, options)
|
||||
.then(
|
||||
(thumbnailBuffer: Buffer): Promise<ArrayBuffer> =>
|
||||
Bun.write(thumbnailPath, thumbnailBuffer.buffer).then(
|
||||
(): Promise<ArrayBuffer> => Bun.file(thumbnailPath).arrayBuffer(),
|
||||
),
|
||||
)
|
||||
.then((arrayBuffer: ArrayBuffer) => {
|
||||
resolve(arrayBuffer);
|
||||
return Promise.all([
|
||||
Bun.file(filePath).unlink(),
|
||||
Bun.file(thumbnailPath).unlink(),
|
||||
]);
|
||||
})
|
||||
.catch(reject);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
81
src/index.ts
81
src/index.ts
|
@ -1,9 +1,9 @@
|
|||
import { existsSync, mkdirSync } from "fs";
|
||||
import { resolve } from "path";
|
||||
import { existsSync, mkdirSync } from "node:fs";
|
||||
import { readdir } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import { dataType } from "@config/environment";
|
||||
import { logger } from "@helpers/logger";
|
||||
import { type ReservedSQL, s3, sql } from "bun";
|
||||
import { readdir } from "fs/promises";
|
||||
|
||||
import { serverHandler } from "@/server";
|
||||
|
||||
|
@ -39,59 +39,52 @@ async function initializeDatabase(): Promise<void> {
|
|||
|
||||
async function main(): Promise<void> {
|
||||
try {
|
||||
try {
|
||||
await sql`SELECT 1;`;
|
||||
await sql`SELECT 1;`;
|
||||
|
||||
logger.info([
|
||||
"Connected to PostgreSQL on",
|
||||
`${process.env.PGHOST}:${process.env.PGPORT}`,
|
||||
]);
|
||||
} catch (error) {
|
||||
logger.error([
|
||||
"Could not establish a connection to PostgreSQL:",
|
||||
error as Error,
|
||||
]);
|
||||
process.exit(1);
|
||||
}
|
||||
logger.info([
|
||||
"Connected to PostgreSQL on",
|
||||
`${process.env.PGHOST}:${process.env.PGPORT}`,
|
||||
]);
|
||||
} catch (error) {
|
||||
logger.error([
|
||||
"Could not establish a connection to PostgreSQL:",
|
||||
error as Error,
|
||||
]);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (dataType.type === "local" && dataType.path) {
|
||||
if (!existsSync(dataType.path)) {
|
||||
try {
|
||||
mkdirSync(dataType.path);
|
||||
} catch (error) {
|
||||
logger.error([
|
||||
"Could not create datasource local directory",
|
||||
error as Error,
|
||||
]);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(["Using local datasource directory", `${dataType.path}`]);
|
||||
} else {
|
||||
if (dataType.type === "local" && dataType.path) {
|
||||
if (!existsSync(dataType.path)) {
|
||||
try {
|
||||
await s3.write("test", "test");
|
||||
await s3.delete("test");
|
||||
|
||||
logger.info([
|
||||
"Connected to S3 with bucket",
|
||||
`${process.env.S3_BUCKET}`,
|
||||
]);
|
||||
mkdirSync(dataType.path);
|
||||
} catch (error) {
|
||||
logger.error([
|
||||
"Could not establish a connection to S3 bucket:",
|
||||
"Could not create datasource local directory",
|
||||
error as Error,
|
||||
]);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
await redis.initialize();
|
||||
serverHandler.initialize();
|
||||
await initializeDatabase();
|
||||
} catch (error) {
|
||||
throw error;
|
||||
logger.info(["Using local datasource directory", `${dataType.path}`]);
|
||||
} else {
|
||||
try {
|
||||
await s3.write("test", "test");
|
||||
await s3.delete("test");
|
||||
|
||||
logger.info(["Connected to S3 with bucket", `${process.env.S3_BUCKET}`]);
|
||||
} catch (error) {
|
||||
logger.error([
|
||||
"Could not establish a connection to S3 bucket:",
|
||||
error as Error,
|
||||
]);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
await redis.initialize();
|
||||
serverHandler.initialize();
|
||||
await initializeDatabase();
|
||||
}
|
||||
|
||||
main().catch((error: Error) => {
|
||||
|
|
|
@ -66,11 +66,11 @@ async function handler(
|
|||
password ? { check: isValidPassword(password), field: "Password" } : null,
|
||||
].filter(Boolean) as UserValidation[];
|
||||
|
||||
validations.forEach(({ check }: UserValidation): void => {
|
||||
for (const { check } of validations) {
|
||||
if (!check.valid && check.error) {
|
||||
errors.push(check.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!username && !email) {
|
||||
errors.push("Either a username or an email is required.");
|
||||
|
|
|
@ -49,11 +49,11 @@ async function handler(
|
|||
{ check: isValidPassword(password), field: "Password" },
|
||||
];
|
||||
|
||||
validations.forEach(({ check }: UserValidation): void => {
|
||||
for (const { check } of validations) {
|
||||
if (!check.valid && check.error) {
|
||||
errors.push(check.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const normalizedUsername: string = username.normalize("NFC");
|
||||
const reservation: ReservedSQL = await sql.reserve();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { resolve } from "path";
|
||||
import { resolve } from "node:path";
|
||||
import { dataType } from "@config/environment";
|
||||
import { type SQLQuery, s3, sql } from "bun";
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { resolve } from "path";
|
||||
import { resolve } from "node:path";
|
||||
import { dataType } from "@config/environment";
|
||||
import { getSetting } from "@config/sql/settings";
|
||||
import {
|
||||
|
@ -174,13 +174,9 @@ async function processFile(
|
|||
let hashedPassword: string | null = null;
|
||||
|
||||
if (user_provided_password) {
|
||||
try {
|
||||
hashedPassword = await bunPassword.hash(user_provided_password, {
|
||||
algorithm: "argon2id",
|
||||
});
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
hashedPassword = await bunPassword.hash(user_provided_password, {
|
||||
algorithm: "argon2id",
|
||||
});
|
||||
}
|
||||
|
||||
const randomUUID: string = randomUUIDv7();
|
||||
|
@ -217,7 +213,7 @@ async function processFile(
|
|||
// ? Should work not sure about non-english characters
|
||||
const sanitizedFileName: string = rawName
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
.replace(/\p{Mn}/gu, "")
|
||||
.replace(/[^a-zA-Z0-9._-]/g, "_")
|
||||
.toLowerCase();
|
||||
|
||||
|
@ -276,7 +272,7 @@ async function processFile(
|
|||
return;
|
||||
}
|
||||
} else {
|
||||
path = "/uploads/" + uuidWithExtension;
|
||||
path = `/uploads/${uuidWithExtension}`;
|
||||
|
||||
try {
|
||||
await s3.write(path, fileBuffer);
|
||||
|
@ -321,7 +317,7 @@ async function processFile(
|
|||
return;
|
||||
}
|
||||
|
||||
if (uploadEntry.password) delete uploadEntry.password;
|
||||
if (uploadEntry.password) uploadEntry.password = undefined;
|
||||
|
||||
uploadEntry.url = `${userHeaderOptions.domain}/raw/${uploadEntry.name}`;
|
||||
successfulFiles.push(uploadEntry);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { resolve } from "path";
|
||||
import { resolve } from "node:path";
|
||||
import { dataType } from "@config/environment";
|
||||
import { s3, sql } from "bun";
|
||||
|
||||
|
@ -113,16 +113,15 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
|||
},
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return Response.json(
|
||||
{
|
||||
success: true,
|
||||
code: 200,
|
||||
message: "Avatar deleted",
|
||||
},
|
||||
{ status: 200 },
|
||||
);
|
||||
}
|
||||
return Response.json(
|
||||
{
|
||||
success: true,
|
||||
code: 200,
|
||||
message: "Avatar deleted",
|
||||
},
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(["Error processing delete request:", error as Error]);
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { resolve } from "path";
|
||||
import { resolve } from "node:path";
|
||||
import { dataType } from "@config/environment";
|
||||
import { isValidTypeOrExtension } from "@config/sql/avatars";
|
||||
import { getSetting } from "@config/sql/settings";
|
||||
|
@ -201,17 +201,16 @@ async function handler(
|
|||
},
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return Response.json(
|
||||
{
|
||||
success: true,
|
||||
code: 200,
|
||||
message: "Avatar uploaded",
|
||||
url: message,
|
||||
},
|
||||
{ status: 200 },
|
||||
);
|
||||
}
|
||||
return Response.json(
|
||||
{
|
||||
success: true,
|
||||
code: 200,
|
||||
message: "Avatar uploaded",
|
||||
url: message,
|
||||
},
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(["Error processing file:", error as Error]);
|
||||
|
||||
|
|
|
@ -110,9 +110,9 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
|||
);
|
||||
}
|
||||
|
||||
delete user.password;
|
||||
delete user.authorization_token;
|
||||
if (!isSelf) delete user.email;
|
||||
user.password = undefined;
|
||||
user.authorization_token = undefined;
|
||||
if (!isSelf) user.email = undefined;
|
||||
|
||||
user.roles = user.roles ? user.roles[0].split(",") : [];
|
||||
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
import { getSetting } from "@config/sql/settings";
|
||||
import { renderEjsTemplate } from "@helpers/ejs";
|
||||
|
||||
const routeDef: RouteDef = {
|
||||
method: "GET",
|
||||
accepts: "*/*",
|
||||
returns: "text/html",
|
||||
};
|
||||
|
||||
async function handler(request: ExtendedRequest): Promise<Response> {
|
||||
if (request.session) return Response.redirect("/");
|
||||
|
||||
const instanceName: string =
|
||||
(await getSetting("instance_name")) || "Unnamed Instance";
|
||||
|
||||
const ejsTemplateData: EjsTemplateData = {
|
||||
title: `Login - ${instanceName}`,
|
||||
instance_name: instanceName,
|
||||
};
|
||||
|
||||
return await renderEjsTemplate("auth/login", ejsTemplateData);
|
||||
}
|
||||
|
||||
export { handler, routeDef };
|
|
@ -1,41 +0,0 @@
|
|||
import { getSetting } from "@config/sql/settings";
|
||||
import { renderEjsTemplate } from "@helpers/ejs";
|
||||
import { type ReservedSQL, sql } from "bun";
|
||||
|
||||
import { logger } from "@/helpers/logger";
|
||||
|
||||
const routeDef: RouteDef = {
|
||||
method: "GET",
|
||||
accepts: "*/*",
|
||||
returns: "text/html",
|
||||
};
|
||||
|
||||
async function handler(request: ExtendedRequest): Promise<Response> {
|
||||
if (request.session) return Response.redirect("/");
|
||||
|
||||
const reservation: ReservedSQL = await sql.reserve();
|
||||
try {
|
||||
const [firstUser] = await sql`SELECT COUNT(*) FROM users`;
|
||||
|
||||
const instanceName: string =
|
||||
(await getSetting("instance_name", reservation)) || "Unnamed Instance";
|
||||
const requiresInvite: boolean =
|
||||
(await getSetting("enable_invitations", reservation)) === "true" &&
|
||||
firstUser.count !== "0";
|
||||
|
||||
const ejsTemplateData: EjsTemplateData = {
|
||||
title: `Register - ${instanceName}`,
|
||||
instance_name: instanceName,
|
||||
requires_invite: requiresInvite,
|
||||
};
|
||||
|
||||
return await renderEjsTemplate("auth/register", ejsTemplateData);
|
||||
} catch (error) {
|
||||
logger.error(["Error rendering register page", error as Error]);
|
||||
return Response.redirect("/");
|
||||
} finally {
|
||||
reservation.release();
|
||||
}
|
||||
}
|
||||
|
||||
export { handler, routeDef };
|
|
@ -1,22 +0,0 @@
|
|||
import { renderEjsTemplate } from "@helpers/ejs";
|
||||
|
||||
const routeDef: RouteDef = {
|
||||
method: "GET",
|
||||
accepts: "*/*",
|
||||
returns: "text/html",
|
||||
};
|
||||
|
||||
async function handler(request: ExtendedRequest): Promise<Response> {
|
||||
// if (!request.session) {
|
||||
// return Response.redirect("/auth/login");
|
||||
// }
|
||||
|
||||
const ejsTemplateData: EjsTemplateData = {
|
||||
title: "Hello, World!",
|
||||
active: "dashboard",
|
||||
};
|
||||
|
||||
return await renderEjsTemplate("dashboard/index.ejs", ejsTemplateData);
|
||||
}
|
||||
|
||||
export { handler, routeDef };
|
|
@ -1,4 +1,4 @@
|
|||
import { resolve } from "path";
|
||||
import { resolve } from "node:path";
|
||||
import { dataType } from "@config/environment";
|
||||
import { type BunFile, type ReservedSQL, sql } from "bun";
|
||||
|
||||
|
@ -115,7 +115,7 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
|||
}
|
||||
|
||||
if (json === "true" || json === "1") {
|
||||
delete fileData.password;
|
||||
fileData.password = undefined;
|
||||
fileData.tags = fileData.tags = fileData.tags[0]?.trim()
|
||||
? fileData.tags[0].split(",").filter((tag: string) => tag.trim())
|
||||
: [];
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { resolve } from "path";
|
||||
import { resolve } from "node:path";
|
||||
import { dataType } from "@config/environment";
|
||||
import { isValidUsername } from "@config/sql/users";
|
||||
import { type BunFile, type ReservedSQL, sql } from "bun";
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { resolve } from "path";
|
||||
import { resolve } from "node:path";
|
||||
import { environment } from "@config/environment";
|
||||
import { logger } from "@helpers/logger";
|
||||
import {
|
||||
|
@ -41,10 +41,14 @@ class ServerHandler {
|
|||
maxRequestBodySize: 10 * 1024 * 1024 * 1024, // 10GB ? will be changed to env var soon
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`Server running at http://${server.hostname}:${server.port}`,
|
||||
true,
|
||||
);
|
||||
const accessUrls: string[] = [
|
||||
`http://${server.hostname}:${server.port}`,
|
||||
`http://localhost:${server.port}`,
|
||||
`http://127.0.0.1:${server.port}`,
|
||||
];
|
||||
|
||||
logger.info(`Server running at ${accessUrls[0]}`);
|
||||
logger.info(`Access via: ${accessUrls[1]} or ${accessUrls[2]}`, true);
|
||||
|
||||
this.logRoutes();
|
||||
}
|
||||
|
@ -82,10 +86,9 @@ class ServerHandler {
|
|||
return new Response(fileContent, {
|
||||
headers: { "Content-Type": contentType },
|
||||
});
|
||||
} else {
|
||||
logger.warn(`File not found: ${filePath}`);
|
||||
return new Response("Not Found", { status: 404 });
|
||||
}
|
||||
logger.warn(`File not found: ${filePath}`);
|
||||
return new Response("Not Found", { status: 404 });
|
||||
} catch (error) {
|
||||
logger.error([`Error serving static file: ${pathname}`, error as Error]);
|
||||
return new Response("Internal Server Error", { status: 500 });
|
||||
|
@ -93,10 +96,11 @@ class ServerHandler {
|
|||
}
|
||||
|
||||
private async handleRequest(
|
||||
request: ExtendedRequest,
|
||||
request: Request,
|
||||
server: BunServer,
|
||||
): Promise<Response> {
|
||||
request.startPerf = performance.now();
|
||||
const extendedRequest: ExtendedRequest = request as ExtendedRequest;
|
||||
extendedRequest.startPerf = performance.now();
|
||||
|
||||
const pathname: string = new URL(request.url).pathname;
|
||||
if (pathname.startsWith("/public") || pathname === "/favicon.ico") {
|
||||
|
@ -185,12 +189,12 @@ class ServerHandler {
|
|||
{ status: 406 },
|
||||
);
|
||||
} else {
|
||||
request.params = params;
|
||||
request.query = query;
|
||||
request.actualContentType = actualContentType;
|
||||
extendedRequest.params = params;
|
||||
extendedRequest.query = query;
|
||||
extendedRequest.actualContentType = actualContentType;
|
||||
|
||||
request.session =
|
||||
(await authByToken(request)) ||
|
||||
extendedRequest.session =
|
||||
(await authByToken(extendedRequest)) ||
|
||||
(await sessionManager.getSession(request));
|
||||
|
||||
response = await routeModule.handler(request, requestBody, server);
|
||||
|
@ -242,7 +246,7 @@ class ServerHandler {
|
|||
`(${response.status})`,
|
||||
[
|
||||
request.url,
|
||||
`${(performance.now() - request.startPerf).toFixed(2)}ms`,
|
||||
`${(performance.now() - extendedRequest.startPerf).toFixed(2)}ms`,
|
||||
ip || "unknown",
|
||||
],
|
||||
"90",
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<%- include("../global", { styles: ["auth"], scripts: ["auth"] }) %>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="auth-container">
|
||||
<div class="auth-logo">
|
||||
<h1><%= instance_name %></h1>
|
||||
<p>Sign in to your account</p>
|
||||
</div>
|
||||
|
||||
<div class="auth-card">
|
||||
<div class="auth-header">
|
||||
<h2>Welcome Back</h2>
|
||||
</div>
|
||||
|
||||
<%- include("../partials/authForm", { pageType: "login" }) %>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,25 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<%- include("../global", { styles: ["auth"], scripts: ["auth"] }) %>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="auth-container">
|
||||
<div class="auth-logo">
|
||||
<h1><%= instance_name %></h1>
|
||||
<p>Create your account</p>
|
||||
</div>
|
||||
|
||||
<div class="auth-card">
|
||||
<div class="auth-header">
|
||||
<h2>Join Us</h2>
|
||||
</div>
|
||||
|
||||
<%- include("../partials/authForm", { pageType: "register" }) %>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,13 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<%- include("../global", { styles: ["dashboard/index"], scripts: [] }) %>
|
||||
</head>
|
||||
<body>
|
||||
<%- include("../partials/sidebar") %>
|
||||
<div class="content">
|
||||
<h1>Dashboard</h1>
|
||||
<p>Welcome to the dashboard!</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,60 +0,0 @@
|
|||
<div class="auth-form">
|
||||
<div class="error-message" id="error-message">
|
||||
<%= pageType==="register" ? "Registration failed. Please try again."
|
||||
: "Invalid email or password. Please try again." %>
|
||||
</div>
|
||||
|
||||
<form id="<%= pageType === "register" ? "register-form" : "login-form" %>" class="form">
|
||||
<% if (pageType==="register") { %>
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" name="username" id="username" required placeholder="Enter your username">
|
||||
</div>
|
||||
|
||||
<% if (requires_invite === true) { %>
|
||||
<div class="form-group">
|
||||
<label for="invite">Invite Code</label>
|
||||
<input type="text" name="invite" id="invite-code" required placeholder="Enter your invite code">
|
||||
</div>
|
||||
<% } %>
|
||||
<% } %>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<input type="email" name="email" id="email" required placeholder="Enter your email">
|
||||
</div>
|
||||
|
||||
<div class="form-group password-group">
|
||||
<label for="password">Password</label>
|
||||
<div class="password-wrapper">
|
||||
<input type="password" name="password" id="password" required placeholder="Enter your password">
|
||||
<svg id="toggle-password" class="toggle-password" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zm0 13c-3.03 0-5.5-2.47-5.5-5.5s2.47-5.5 5.5-5.5 5.5 2.47 5.5 5.5-2.47 5.5-5.5 5.5zm0-9a3.5 3.5 0 100 7 3.5 3.5 0 000-7z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if (pageType !=="register" ) { %>
|
||||
<div class="form-footer">
|
||||
<label>
|
||||
<input type="checkbox" name="remember" id="remember-me">Remember me
|
||||
</label>
|
||||
<a href="/auth/forgot-password">Forgot password?</a>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<button type="submit">
|
||||
<%= pageType==="register" ? "Register" : "Login" %>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="auth-toggle">
|
||||
<p>
|
||||
<%= pageType==="register" ? "Already have an account?" : "Don't have an account?" %>
|
||||
<a href="<%= pageType === 'register' ? '/auth/login' : '/auth/register' %>" class="auth-link">
|
||||
<%= pageType==="register" ? "Login" : "Register" %>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
|
@ -1,12 +0,0 @@
|
|||
<div class="sidebar">
|
||||
<div class="actions">
|
||||
<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>
|
||||
<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>
|
Loading…
Add table
Reference in a new issue