Compare commits
No commits in common. "349415da08a34089ac9eb0d0bb2ed54f17a0afd0" and "945b49c467feaa6888e259b834a3c3259409f755" have entirely different histories.
349415da08
...
945b49c467
|
|
@ -0,0 +1,3 @@
|
|||
services:
|
||||
application:
|
||||
build: .
|
||||
|
|
@ -5,14 +5,11 @@ volumes:
|
|||
|
||||
services:
|
||||
application:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
build: https://git.maschek.info/patrick/stundenaufzeichnung.git#main
|
||||
ports:
|
||||
- 3000:3001
|
||||
- 3000:3000
|
||||
volumes:
|
||||
- type: volume
|
||||
source: user-data
|
||||
target: /app-data
|
||||
restart: always
|
||||
|
||||
|
|
|
|||
|
|
@ -4,50 +4,44 @@ import { error, redirect } from "@sveltejs/kit";
|
|||
import SessionStore from "$lib/server/session_store"
|
||||
import { init_db, close_db, get_user } from "$lib/server/database";
|
||||
|
||||
import Logs from "$lib/server/log";
|
||||
|
||||
async function init() {
|
||||
|
||||
Logs.process.info("Initializing server");
|
||||
|
||||
if (process.env.APP_USER_DATA_PATH == null) {
|
||||
throw new Error("APP_USER_DATA_PATH is not defined. Exiting.");
|
||||
console.log("APP_USER_DATA_PATH is not defined. Exiting.");
|
||||
process.exit(-1);
|
||||
}
|
||||
|
||||
if (process.env.APP_TMP_USER_DATA_PATH == null) {
|
||||
throw new Error("APP_TMP_USER_DATA_PATH is not defined. Exiting.");
|
||||
console.log("APP_TMP_USER_DATA_PATH is not defined. Exiting.");
|
||||
process.exit(-1);
|
||||
}
|
||||
|
||||
await init_db();
|
||||
|
||||
Logs.process.info("Initializing of server complete")
|
||||
console.log("started");
|
||||
|
||||
}
|
||||
|
||||
function deinit() {
|
||||
close_db();
|
||||
console.log('exit');
|
||||
}
|
||||
|
||||
await init();
|
||||
|
||||
process.on('exit', (_) => {
|
||||
deinit();
|
||||
Logs.process.info("Exiting server")
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGINT', (_) => {
|
||||
Logs.process.info("Received SIGINT, shutting down")
|
||||
console.log("SIGINT")
|
||||
process.exit(0);
|
||||
})
|
||||
|
||||
process.on("uncaughtExceptionMonitor", (error, origin) => {
|
||||
Logs.process.fatal(`Encountered uncaught exception (origin: ${origin}): ${error.name}${error.cause ? (" caused by "+error.cause) : ""}: ${error.message} `)
|
||||
})
|
||||
|
||||
await init();
|
||||
|
||||
export let handle: Handle = async function ({ event, resolve }) {
|
||||
|
||||
Logs.route.debug(`incoming ${event.request.method} request to: ${event.url.href} (route id: ${event.route.id})`);
|
||||
console.log("incoming ", event.request.method, " request to: ", event.url.href, " (route id: ", event.route.id, ")");
|
||||
|
||||
event.setHeaders({
|
||||
//"Strict-Transport-Security": "max-age=63072000; includeSubdomains; preload",
|
||||
|
|
@ -56,15 +50,15 @@ export let handle: Handle = async function ({ event, resolve }) {
|
|||
"Referrer-Policy": "strict-origin-when-cross-origin"
|
||||
})
|
||||
|
||||
console.log(event.url.href)
|
||||
if (event.route.id == null) {
|
||||
Logs.route.info(`Tried to access a route which does not exist: ${event.url.href}`)
|
||||
return error(404, "This page does not exist.");
|
||||
}
|
||||
|
||||
const token = event.cookies.get("session_id")
|
||||
const user = SessionStore.get_user_by_access_token(token ?? "")
|
||||
|
||||
console.log("tried access with token: ", token)
|
||||
|
||||
if (!token || !user) {
|
||||
if (event.request.method == "POST" && event.route.id != "/login") {
|
||||
return error(401, "Invalid Session");
|
||||
|
|
|
|||
|
|
@ -7,7 +7,13 @@ export async function authorize_password(username: string, password: string): Pr
|
|||
const user = get_user_by_name(username);
|
||||
const password_hash = user?.password ?? "";
|
||||
|
||||
console.log("Username: ", username);
|
||||
console.log("Password: ", password);
|
||||
|
||||
console.log(user);
|
||||
|
||||
const res = await Bun.password.verify(password, password_hash, "bcrypt");
|
||||
console.log("hash res:", res);
|
||||
|
||||
return res ? user : null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import b from "bun";
|
||||
import fs from "node:fs/promises"
|
||||
|
||||
import Logs from "$lib/server/log"
|
||||
import { User } from "$lib/server/database";
|
||||
import { MONTHS, toInt, padInt, parseDate, calculateDuration, isoToLocalDate, month_of, weekday_of } from "$lib/util"
|
||||
|
||||
|
|
@ -86,8 +85,6 @@ const GENDER_END: Map<string, string> = new Map(Object.entries({
|
|||
let locked_users: Set<Number> = new Set();
|
||||
|
||||
export async function getAllFiles(user: User) {
|
||||
Logs.documents.debug(`Requested all files for user id ${user.id}`)
|
||||
|
||||
const path = `${DOCUMENTS_PATH}/user-${user.id}`;
|
||||
|
||||
let file_names = (await fs.readdir(path, { recursive: true }).catch((_) => {
|
||||
|
|
@ -168,8 +165,7 @@ async function _runProtocted(f: Function, user: User, ...args: any[]) {
|
|||
if (locked_users.has(user.id)) {
|
||||
throw new UserBusyException(`Cannot generate pdf for user: ` ,user.id);
|
||||
}
|
||||
|
||||
Logs.documents.debug(`Locking user ${user.id}`)
|
||||
|
||||
locked_users.add(user.id);
|
||||
|
||||
try {
|
||||
|
|
@ -178,7 +174,6 @@ async function _runProtocted(f: Function, user: User, ...args: any[]) {
|
|||
throw e;
|
||||
} finally {
|
||||
locked_users.delete(user.id);
|
||||
Logs.documents.debug(`Unlocking user ${user.id}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -191,8 +186,6 @@ async function _generateRecordPDF(user: User, year: number, month: number, overr
|
|||
throw new FileExistsException(`${path}/${file_pref}.pdf`);
|
||||
}
|
||||
|
||||
Logs.documents.info(`Generating Record PDF ${year}-${month} for user id ${user.id}`)
|
||||
|
||||
fs.mkdir(path, { recursive: true });
|
||||
|
||||
let gen_path = await _genLatexRec(user, file_pref, year, month);
|
||||
|
|
@ -212,8 +205,6 @@ async function _generateEstimatePDF(user: User, year: number, quarter: number, o
|
|||
throw new FileExistsException(`${path}/${file_pref}.pdf`);
|
||||
}
|
||||
|
||||
Logs.documents.info(`Generating Estimate PDF ${year}/Quarter ${quarter} for user id ${user.id}`)
|
||||
|
||||
fs.mkdir(path, { recursive: true });
|
||||
|
||||
let gen_path = await _genLatexEst(user, file_pref, year, quarter);
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"process": {
|
||||
"tag": "process",
|
||||
"console": "stdout"
|
||||
},
|
||||
"db": {
|
||||
"tag": "database",
|
||||
"console": "stdout"
|
||||
},
|
||||
"user": {
|
||||
"tag": "user",
|
||||
"console": "stdout"
|
||||
},
|
||||
"documents": {
|
||||
"tag": "documents"
|
||||
},
|
||||
"route": {
|
||||
"tag": "routing",
|
||||
"severity": "debug",
|
||||
"console": "stdout"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
import Bun from "bun";
|
||||
import fs from "node:fs";
|
||||
|
||||
enum LogSeverity {
|
||||
FATAL = 0,
|
||||
ERROR = 1,
|
||||
WARN = 2,
|
||||
INFO = 3,
|
||||
DEBUG = 4,
|
||||
}
|
||||
|
||||
const ConsoleTypeValues = ["none", "stdout", "stderr"] as const;
|
||||
type ConsoleTypes = typeof ConsoleTypeValues[number]
|
||||
|
||||
interface LogConfig {
|
||||
tag: string
|
||||
severity?: keyof typeof LogSeverity | number
|
||||
logfile?: string
|
||||
console?: ConsoleTypes
|
||||
}
|
||||
|
||||
const DEFAULT_DIRECTORY = (process.env.APP_LOG_PATH ?? "./logs")
|
||||
const _startTime = new Date()
|
||||
|
||||
class Logger {
|
||||
|
||||
readonly tag: string
|
||||
logSeverity: LogSeverity = LogSeverity.INFO;
|
||||
console: ConsoleTypes
|
||||
|
||||
private _file: Bun.BunFile;
|
||||
private _fileWriter: Bun.FileSink;
|
||||
|
||||
//constructor(tag: string, dir: string = DEFAULT_DIRECTORY, filename: string = "log-" + _startTime.toISOString(), logSeverity = LogSeverity.INFO) {
|
||||
constructor(config: LogConfig) {
|
||||
this.tag = config.tag
|
||||
this.logSeverity = __severityFromStrNum(config.severity)
|
||||
this.console = config.console ?? "none"
|
||||
|
||||
fs.mkdirSync(DEFAULT_DIRECTORY, { recursive: true });
|
||||
|
||||
this._file = Bun.file(DEFAULT_DIRECTORY + "/" + (config.logfile ?? ("logs-"+__dateToISO8601Str(_startTime)+".txt")))
|
||||
this._fileWriter = this._file.writer();
|
||||
}
|
||||
|
||||
async log(severity: LogSeverity, message: string) {
|
||||
if (severity <= this.logSeverity) {
|
||||
const line = `[${new Date().toISOString()}] [${this.tag}] ${LogSeverity[severity]}: ${message}\n`
|
||||
|
||||
if (this.console !== "none") {
|
||||
Bun[this.console].write(line)
|
||||
}
|
||||
this._fileWriter.write(line)
|
||||
this._fileWriter.flush()
|
||||
}
|
||||
}
|
||||
|
||||
async fatal(message: string) { this.log(LogSeverity.FATAL, message) }
|
||||
async error(message: string) { this.log(LogSeverity.ERROR, message) }
|
||||
async warn(message: string) { this.log(LogSeverity.WARN, message) }
|
||||
async info(message: string) { this.log(LogSeverity.INFO, message) }
|
||||
async debug(message: string) { this.log(LogSeverity.DEBUG, message) }
|
||||
}
|
||||
|
||||
function __severityFromStrNum(severity?: string|number): LogSeverity {
|
||||
if (severity == null) {
|
||||
return LogSeverity.INFO
|
||||
}
|
||||
|
||||
switch (typeof severity) {
|
||||
case "string": {
|
||||
for (const key in LogSeverity) {
|
||||
if (severity.toUpperCase() == key) {
|
||||
return LogSeverity[key as keyof typeof LogSeverity];
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case "number": {
|
||||
for (const [key, value] of Object.entries(LogSeverity)) {
|
||||
if (severity === value) {
|
||||
return LogSeverity[key as keyof typeof LogSeverity];
|
||||
}
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
throw new Error("Failed to configure Logger: Encountered an invalid value for severity")
|
||||
return LogSeverity.INFO;
|
||||
}
|
||||
|
||||
function __dateToISO8601Str(date: Date) {
|
||||
const pad = (n: number, width = 2) => String(n).padStart(width, '0');
|
||||
|
||||
const year = date.getFullYear();
|
||||
const month = pad(date.getMonth() + 1);
|
||||
const day = pad(date.getDate());
|
||||
const hours = pad(date.getHours());
|
||||
const minutes = pad(date.getMinutes());
|
||||
const seconds = pad(date.getSeconds());
|
||||
const milliseconds = pad(date.getMilliseconds(), 3); // Ensure 3 digits
|
||||
|
||||
return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}.${milliseconds}`;
|
||||
}
|
||||
|
||||
import raw from "./log.config.json";
|
||||
const configs = raw as const;
|
||||
|
||||
type ConfigMap = Record<string, LogConfig>
|
||||
|
||||
const __validate_config = (config: unknown): config is ConfigMap => {
|
||||
if (typeof config !== "object" || config === null) throw new Error("Failed to configure Logger: Root of JSON is not an object");
|
||||
|
||||
for (const [key, value] of Object.entries(config)) {
|
||||
if (typeof value !== "object")
|
||||
throw new Error("Failed to configure Logger: Encountered a value which is not an object. Value of '" + key + "' is of type " + typeof value);
|
||||
if (value == null)
|
||||
throw new Error("Failed to configure Logger: Encountered a null value for key '" + key + "'");
|
||||
|
||||
if (typeof (value as any).tag !== "string")
|
||||
throw new Error("Failed to configure Logger: value of tag for '" + key + "' is not a string");
|
||||
if (value.severity != null && !(typeof (value as any).severity === "string" || typeof (value as any).severity === "number"))
|
||||
throw new Error("Failed to configure Logger: severity of key '" + key + "' is not a string or number (is " + typeof value.severity + ")");
|
||||
if (value.logfile != null && typeof (value as any).logfile !== "string")
|
||||
throw new Error("Failed to configure Logger: logfile of key '" + key + "' is not a string (is " + typeof value.logfile + ")");
|
||||
if (value.console != null && (typeof (value as any).console !== "string" || !ConsoleTypeValues.includes((value as any).console)))
|
||||
throw new Error("Failed to configure Logger: value for console for '" + key + "' is not any of " + ConsoleTypeValues.toString())
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export type LoggerMapKeys = keyof typeof configs;
|
||||
const Logs = (__validate_config(configs) ? Object.fromEntries(
|
||||
Object.entries(configs).map(([key, config]) => [key, new Logger(config)])
|
||||
) : {}) as Record<LoggerMapKeys, Logger>
|
||||
|
||||
export default Logs;
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import Crypto from "node:crypto";
|
||||
|
||||
import type { User } from "$lib/server/database";
|
||||
import Logs from "$lib/server/log"
|
||||
|
||||
import Crypto from "node:crypto";
|
||||
|
||||
interface UserSession {
|
||||
user: User;
|
||||
|
|
@ -38,8 +37,6 @@ const active_session_tokens = new Map<string, TokenInfo>();
|
|||
|
||||
function issue_access_token_for_user(user: User, expiry_date: Date): string {
|
||||
|
||||
Logs.user.info(`Issuing access token for user id ${user.id}. Expiry date: ${expiry_date.toISOString()}`)
|
||||
|
||||
let user_session = active_users.get(user.id);
|
||||
if (user_session === undefined) {
|
||||
user_session = {
|
||||
|
|
@ -93,7 +90,7 @@ function logout_user_session(token: string): boolean {
|
|||
const token_info = active_session_tokens.get(token);
|
||||
|
||||
if (!token_info) {
|
||||
Logs.user.warn(`Failed to logout user by token, because token does not exist`);
|
||||
console.log("this shouldn't happen.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -103,15 +100,12 @@ function logout_user_session(token: string): boolean {
|
|||
}
|
||||
|
||||
async function __clean_session_store() {
|
||||
|
||||
let cleaned_user_sessions = 0
|
||||
let cleaned_active_users = 0
|
||||
console.log("cleaning sessions");
|
||||
|
||||
const active_users_session = new Set<number>();
|
||||
active_session_tokens.forEach((token_info, token, token_map) => {
|
||||
if (token_info.expiry_time.getTime() < Date.now()) {
|
||||
token_map.delete(token);
|
||||
cleaned_user_sessions += 1
|
||||
} else {
|
||||
active_users_session.add(token_info.user_id);
|
||||
}
|
||||
|
|
@ -120,11 +114,8 @@ async function __clean_session_store() {
|
|||
active_users.forEach((user_info, user_id, user_map) => {
|
||||
if (user_info.last_active.getTime() + 15*60*1000 < Date.now() && !active_users.has(user_id)) {
|
||||
user_map.delete(user_id)
|
||||
cleaned_active_users += 1
|
||||
}
|
||||
});
|
||||
|
||||
Logs.user.info(`Cleaned ${cleaned_user_sessions} inactive session tokens and ${cleaned_active_users} inactive users`)
|
||||
}
|
||||
|
||||
export default class SessionStore {
|
||||
|
|
|
|||
|
|
@ -6,13 +6,11 @@ import type { RecordEntry, EstimatesEntry } from "$lib/db_types";
|
|||
|
||||
import { fail, redirect } from '@sveltejs/kit';
|
||||
|
||||
import Logs from "$lib/server/log"
|
||||
import { toInt, parseDate, isTimeValidHHMM } from "$lib/util"
|
||||
import { getRecordFiles } from "$lib/server/docstore";
|
||||
|
||||
export const load: PageServerLoad = async ({ locals }) => {
|
||||
if (!locals.user) {
|
||||
Logs.route.warn("An unauthorized user tried to access the record page")
|
||||
return fail(403, { message: "Unauthorized user" });
|
||||
}
|
||||
|
||||
|
|
@ -64,7 +62,6 @@ export const load: PageServerLoad = async ({ locals }) => {
|
|||
export const actions = {
|
||||
new_entry: async ({locals, request}) => {
|
||||
if (!locals.user) {
|
||||
Logs.route.warn("An unauthorized user tried to add a new record")
|
||||
return fail(403, { message: "Unauthorized user" })
|
||||
}
|
||||
|
||||
|
|
@ -116,25 +113,20 @@ export const actions = {
|
|||
return_obj.get("end")!.status = invalid;
|
||||
}
|
||||
|
||||
let invalid_values = [...return_obj.entries()].filter(([_, v]) => { return v.status != ok; }).map(([key, value]) => `${key}: ${value.status}`)
|
||||
if (invalid_values.length > 0) {
|
||||
Logs.user.info(`User id ${locals.user.id} failed to add a new record. Failed: ${invalid_values.join(", ")}`)
|
||||
if ([...return_obj.values()].some((v) => { return v.status != ok; })) {
|
||||
return fail(400, { new_entry: return_obj });
|
||||
}
|
||||
|
||||
let res = locals.user.insert_entry(date, start, end, comment);
|
||||
|
||||
if (!res) {
|
||||
Logs.user.error(`Failed to add new entry for user id ${locals.user.id} to database.`)
|
||||
return fail(500, { })
|
||||
}
|
||||
|
||||
Logs.user.info(`User id ${locals.user.id} created a new record`)
|
||||
|
||||
return { success: true, new_entry: return_obj };
|
||||
},
|
||||
edit_entry: async ({locals, request}) => {
|
||||
if (!locals.user) {
|
||||
Logs.route.warn("An unauthorized user tried to edit a record")
|
||||
return fail(403, { message: "Unauthorized user" })
|
||||
}
|
||||
|
||||
|
|
@ -197,9 +189,7 @@ export const actions = {
|
|||
return_obj.get("end")!.status = invalid;
|
||||
}
|
||||
|
||||
let invalid_values = [...return_obj.entries()].filter(([_, v]) => { return v.status != ok; }).map(([key, value]) => `${key}: ${value.status}`)
|
||||
if (invalid_values.length > 0) {
|
||||
Logs.user.info(`User id ${locals.user.id} failed to edit an entry. Failed: ${invalid_values.join(", ")}`)
|
||||
if ([...return_obj.values()].some((v) => { return v.status != ok; })) {
|
||||
return fail(400, { edit_entry: return_obj });
|
||||
}
|
||||
|
||||
|
|
@ -226,7 +216,6 @@ export const actions = {
|
|||
}
|
||||
|
||||
if (!res) {
|
||||
Logs.user.error(`Failed to edit an entry for user id ${locals.user.id} in database.`)
|
||||
return fail(500, { })
|
||||
}
|
||||
|
||||
|
|
@ -235,7 +224,6 @@ export const actions = {
|
|||
},
|
||||
remove_entry: async ({ locals, request })=> {
|
||||
if (!locals.user) {
|
||||
Logs.route.warn("An unauthorized user tried to delete a record")
|
||||
return fail(403, { message: "Unauthorized user" })
|
||||
}
|
||||
|
||||
|
|
@ -244,13 +232,11 @@ export const actions = {
|
|||
let id = data.get("id") as string;
|
||||
|
||||
if (id == null) {
|
||||
Logs.user.info(`User id ${locals.user.id} failed to remove an entry. Failed: id is missing`)
|
||||
return fail(400, { id: id });
|
||||
}
|
||||
|
||||
let user_id = toInt(id);
|
||||
if (isNaN(user_id)) {
|
||||
Logs.user.info(`User id ${locals.user.id} failed to remove an entry. Failed: id is not a Number`)
|
||||
return fail(400, { id: id });
|
||||
}
|
||||
|
||||
|
|
@ -265,7 +251,6 @@ export const actions = {
|
|||
}
|
||||
|
||||
if (!res) {
|
||||
Logs.user.error(`Failed to remove an entry for user id ${locals.user.id} from database.`)
|
||||
return fail(500, { });
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue