Added user administration
This commit is contained in:
commit
8c2039c8a4
|
|
@ -6,7 +6,7 @@
|
|||
"position": 0,
|
||||
"name": "Anzeigen"
|
||||
},
|
||||
"ADD": {
|
||||
"CREATE": {
|
||||
"position": 1,
|
||||
"name": "Anlegen"
|
||||
},
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
},
|
||||
"meta_permissions": {
|
||||
"MANAGE": {
|
||||
"permissions": ["VIEW", "ADD", "DELETE", "EDIT"],
|
||||
"permissions": ["VIEW", "CREATE", "DELETE", "EDIT"],
|
||||
"name": "Verwalten"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,17 +15,40 @@ const CHECK_QUERY: string =
|
|||
const USER_DATABASE_SETUP: string[] = [
|
||||
"PRAGMA foreign_keys = ON;",
|
||||
|
||||
"CREATE TABLE meta (key TEXT PRIMARY KEY NOT NULL, value NUMBER);",
|
||||
"INSERT INTO meta(key, value) VALUES ('triggerActive', 1);",
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
name TEXT,
|
||||
gender TEXT,
|
||||
address TEXT,
|
||||
username TEXT,
|
||||
username TEXT UNIQUE,
|
||||
password TEXT,
|
||||
permissions INTEGER DEFAULT 0,
|
||||
created DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);`,
|
||||
|
||||
`CREATE TABLE IF NOT EXISTS users_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
user_id INTEGER,
|
||||
name TEXT,
|
||||
gender TEXT,
|
||||
address TEXT,
|
||||
username TEXT,
|
||||
permissions INTEGER DEFAULT 0,
|
||||
created DATETIME NOT NULL,
|
||||
deleted DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
);`,
|
||||
|
||||
`CREATE TRIGGER user_delete_history
|
||||
BEFORE DELETE ON users
|
||||
WHEN (SELECT value FROM meta WHERE key = 'triggerActive') = 1
|
||||
BEGIN
|
||||
INSERT INTO users_history(user_id, name, gender, address, username, permissions, created) VALUES (OLD.id, OLD.name, OLD.gender, OLD.address, OLD.username, OLD.permissions, OLD.created);
|
||||
END;`,
|
||||
|
||||
`CREATE TABLE refresh_tokens (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
|
|
@ -65,6 +88,9 @@ const USER_DATABASE_EMPTY: string =
|
|||
const USER_DATABASE_UPDATE_PASSWORD: string =
|
||||
"UPDATE users SET password=$password WHERE id=$id;"
|
||||
|
||||
const USER_DATABASE_REMOVE_USER: string =
|
||||
"DELETE FROM users WHERE id = $id;"
|
||||
|
||||
/*const USER_DATABASE_ADD_ACCESS_TOKEN: string =
|
||||
"INSERT INTO access_tokens (user_id, token, expiry_date) VALUES ($user_id, $token, $expiry_date);"
|
||||
|
||||
|
|
@ -75,7 +101,7 @@ const ENTRY_DATABASE_SETUP: string[] = [
|
|||
"PRAGMA foreign_keys = ON;",
|
||||
|
||||
"CREATE TABLE meta (key TEXT PRIMARY KEY NOT NULL, value NUMBER);",
|
||||
"INSERT INTO meta(key, value) VALUES ('triggerActive', 1)",
|
||||
"INSERT INTO meta(key, value) VALUES ('triggerActive', 1);",
|
||||
|
||||
`CREATE TABLE records (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
|
|
@ -83,7 +109,8 @@ const ENTRY_DATABASE_SETUP: string[] = [
|
|||
start VARCHAR(5),
|
||||
end VARCHAR(5),
|
||||
comment TEXT,
|
||||
created DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
created DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
UNIQUE (date, start, end)
|
||||
);`,
|
||||
|
||||
`CREATE TABLE records_history (
|
||||
|
|
@ -170,6 +197,12 @@ const ENTRY_DATABASE_EDIT_ENTRY: string =
|
|||
const ENTRY_DATABASE_REMOVE_ENTRY: string =
|
||||
"DELETE FROM records WHERE id = $id;";
|
||||
|
||||
const ENTRY_DATABASE_EXPORT: string =
|
||||
"SELECT date, start, end, comment FROM records;"
|
||||
|
||||
const ENTRY_DATABASE_IMPORT: string =
|
||||
"INSERT OR IGNORE INTO records(date, start, end, comment) VALUES ($date, $start, $end, $comment);"
|
||||
|
||||
const ESTIMATES_DATABASE_GET_ALL: string =
|
||||
"SELECT * FROM estimates ORDER BY year DESC, quarter DESC;"
|
||||
|
||||
|
|
@ -182,6 +215,12 @@ const ESTIMATES_DATABASE_GET_QUART: string =
|
|||
const ESTIMATES_DATABASE_INSERT: string =
|
||||
"INSERT INTO estimates(year, quarter, estimate_0, estimate_1, estimate_2) VALUES ($year, $quarter, $estimate_0, $estimate_1, $estimate_2);"
|
||||
|
||||
const ESTIMATES_DATABASE_EXPORT: string =
|
||||
"SELECT year, quarter, estimate_0, estimate_1, estimate_2 FROM estimates;"
|
||||
|
||||
const ESTIMATES_DATABASE_IMPORT: string =
|
||||
"INSERT OR IGNORE INTO estimates(year, quarter, estimate_0, estimate_1, estimate_2) VALUES ($year, $quarter, $estimate_0, $estimate_1, $estimate_2);"
|
||||
|
||||
export class User {
|
||||
id: number;
|
||||
gender: string;
|
||||
|
|
@ -219,9 +258,9 @@ export class User {
|
|||
}
|
||||
}
|
||||
|
||||
get_months(): { year: string, month: string }[] {
|
||||
get_months(): { year: number, month: number }[] {
|
||||
const query = this._database.query(ENTRY_DATABASE_GET_MONTHS);
|
||||
const res = query.all();
|
||||
const res = query.all() as { year: string, month: string }[];
|
||||
|
||||
const ret = res.map((v) => { return { year: toInt(v.year), month: toInt(v.month) }})
|
||||
|
||||
|
|
@ -230,7 +269,7 @@ export class User {
|
|||
|
||||
get_quarters(): { year: number, quarter: number }[] {
|
||||
const query = this._database.query(ESTIMATES_DATABASE_GET_QUARTERS)
|
||||
const res = query.all();
|
||||
const res = query.all() as { year: number, quarter: number }[];
|
||||
|
||||
return res;
|
||||
}
|
||||
|
|
@ -256,14 +295,14 @@ export class User {
|
|||
}
|
||||
|
||||
const query = this._database.query(ENTRY_DATABASE_GET_ENTRIES_IN_MONTH);
|
||||
const res = query.all({ year: year.toString(), month: month.toString().padStart(2, '0') });
|
||||
const res = query.all({ year: year.toString(), month: month.toString().padStart(2, '0') }) as RecordEntry[];
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
get_entry(id: number): RecordEntry {
|
||||
const query = this._database.query(ENTRY_DATABASE_GET_ENTRY_BY_ID);
|
||||
const res = query.get({ id: id });
|
||||
const res = query.get({ id: id }) as RecordEntry;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
|
@ -305,23 +344,23 @@ export class User {
|
|||
|
||||
get_estimates(): Array<EstimatesEntry> {
|
||||
const query = this._database.query(ESTIMATES_DATABASE_GET_ALL);
|
||||
const res = query.all();
|
||||
const res = query.all() as EstimatesEntry[];
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
get_estimate(year: number, quarter: number): EstimatesEntry {
|
||||
const query = this._database.query(ESTIMATES_DATABASE_GET_QUART);
|
||||
const res = query.get({ year: year, quarter: quarter });
|
||||
const res = query.get({ year: year, quarter: quarter }) as EstimatesEntry;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
get_estimate_by_month(year: number, month: number): number {
|
||||
const query = this._database.query(ESTIMATES_DATABASE_GET_QUART);
|
||||
const res = query.all({ year: year, quarter: Math.floor(month / 4 + 1) });
|
||||
const res = query.get({ year: year, quarter: Math.floor(month / 4 + 1) }) as EstimatesEntry;
|
||||
|
||||
return res[0]?.[`estimate_${month % 3}`] ?? NaN;
|
||||
return res?.[`estimate_${month % 3}`] ?? NaN;
|
||||
}
|
||||
|
||||
insert_estimate(year: number, quarter: number, estimate_0: number, estimate_1: number, estimate_2: number) {
|
||||
|
|
@ -331,7 +370,6 @@ export class User {
|
|||
|
||||
const query = this._database.query(ESTIMATES_DATABASE_INSERT);
|
||||
const res = query.run({ year: year, quarter: quarter, estimate_0: estimate_0, estimate_1: estimate_1, estimate_2: estimate_2 });
|
||||
console.log(res)
|
||||
|
||||
return res.changes > 1;
|
||||
}
|
||||
|
|
@ -352,18 +390,18 @@ function is_db_initialized(db: Database): boolean {
|
|||
throw exception;
|
||||
}
|
||||
|
||||
console.log(exception);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function get_user_db_name(user: UserEntry) {
|
||||
return DATABASES_PATH + "user-" + user.id + ".sqlite"
|
||||
function get_user_db_name(user_id: number) {
|
||||
return DATABASES_PATH + "user-" + user_id + ".sqlite"
|
||||
}
|
||||
|
||||
function setup_db(db: Database, setup_queries: string[]) {
|
||||
setup_queries.forEach((q) => { /*console.log(q);*/ db.query(q).run(); });
|
||||
db.transaction(() => {
|
||||
setup_queries.forEach((q) => { db.query(q).run(); });
|
||||
})()
|
||||
}
|
||||
|
||||
export async function init_db() {
|
||||
|
|
@ -384,23 +422,15 @@ export function close_db() {
|
|||
}
|
||||
}
|
||||
|
||||
export async function create_user(user: { name: string, gender: string, address: string, username: string, password: string }): Promise<boolean> {
|
||||
export async function create_user(user: { name: string, gender: string, address: string, username: string, password: string }): Promise<number | bigint> {
|
||||
|
||||
user.password = await Bun.password.hash(user.password, { algorithm: "bcrypt", cost: 11});
|
||||
|
||||
try {
|
||||
const statement = user_database.query(USER_DATABASE_ADD_USER);
|
||||
const result = statement.run(user);
|
||||
const statement = user_database.query(USER_DATABASE_ADD_USER);
|
||||
const result = statement.run(user);
|
||||
|
||||
return result.changes == 1;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
if (e instanceof SQLiteError) {
|
||||
return false;
|
||||
}
|
||||
return result.lastInsertRowid;
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
export function get_all_user(): { id: number, username: string, name: string }[] {
|
||||
|
||||
|
|
@ -440,8 +470,8 @@ export function get_user_by_name(username: string): User | null {
|
|||
|
||||
fs.mkdir(DATABASES_PATH, { recursive: true });
|
||||
|
||||
let userdb = new Database(get_user_db_name(user), { create: true, strict: true });
|
||||
|
||||
let userdb = new Database(get_user_db_name(user.id), { create: true, strict: true });
|
||||
|
||||
if (!is_db_initialized(userdb)) {
|
||||
setup_db(userdb, ENTRY_DATABASE_SETUP);
|
||||
}
|
||||
|
|
@ -485,3 +515,109 @@ export function update_user_password(user_id: number, password: string) {
|
|||
|
||||
return result.changes > 0
|
||||
}
|
||||
|
||||
export function remove_user(user_id: number) {
|
||||
const query = user_database.prepare(USER_DATABASE_REMOVE_USER)
|
||||
const result = query.run({ id: user_id })
|
||||
|
||||
return result.changes > 0
|
||||
}
|
||||
|
||||
|
||||
type ExportEstimatesEntry = Pick<EstimatesEntry, Exclude<keyof EstimatesEntry, "id" | "created">>
|
||||
type ExportRecordsEntry = Pick<RecordEntry, Exclude<keyof RecordEntry, "id" | "created">>
|
||||
|
||||
export async function export_user_data(user_id: number): Promise<string | null> {
|
||||
const db_name = get_user_db_name(user_id)
|
||||
|
||||
if (!(await fs.exists(db_name))) {
|
||||
return null
|
||||
}
|
||||
|
||||
const userdb = new Database(db_name, { create: false, strict: true });
|
||||
|
||||
let user_data: { id: number } & Partial<{ estimates: Array<ExportEstimatesEntry>, records: Array<ExportRecordsEntry> }> = { id: user_id }
|
||||
|
||||
try {
|
||||
user_data.estimates = userdb.query(ESTIMATES_DATABASE_EXPORT).all() as Array<ExportEstimatesEntry>
|
||||
} catch (e) {
|
||||
if (!(e instanceof SQLiteError)) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
user_data.records = userdb.query(ENTRY_DATABASE_EXPORT).all() as Array<ExportRecordsEntry>
|
||||
} catch (e) {
|
||||
if (!(e instanceof SQLiteError)) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(user_data)
|
||||
}
|
||||
|
||||
export async function import_user_data(data: Partial<{ id: number, estimates: Array<unknown>, records: Array<unknown> }>) {
|
||||
|
||||
// null or unknown ar of type object
|
||||
if (!data.id || !(typeof data.id === "number")) {
|
||||
throw new Error("Invalid JSON: id")
|
||||
}
|
||||
|
||||
if (data.estimates != null) {
|
||||
|
||||
if (!(Array.isArray(data.estimates))) {
|
||||
throw new Error("Invalid JSON: estimates")
|
||||
}
|
||||
if (!data.estimates.every((v: unknown): v is ExportEstimatesEntry =>
|
||||
typeof v === "object"
|
||||
&& v != null
|
||||
&& "year" in v
|
||||
&& "quarter" in v
|
||||
&& "estimate_0" in v
|
||||
&& "estimate_1" in v
|
||||
&& "estimate_2" in v
|
||||
&& typeof v.year === "number"
|
||||
&& typeof v.quarter === "number"
|
||||
&& (typeof v.estimate_0 === "number" || v.estimate_0 == null)
|
||||
&& (typeof v.estimate_1 === "number" || v.estimate_1 == null)
|
||||
&& (typeof v.estimate_2 === "number" || v.estimate_2 == null))) {
|
||||
throw new Error("Invalid JSON: estimates entry")
|
||||
}
|
||||
}
|
||||
|
||||
if (data.records != null) {
|
||||
|
||||
if (!(Array.isArray(data.records))) {
|
||||
throw new Error("Invalid JSON: records")
|
||||
}
|
||||
|
||||
if (!data.records.every((v: unknown): v is ExportRecordsEntry =>
|
||||
typeof v === "object"
|
||||
&& v != null
|
||||
&& "date" in v
|
||||
&& "start" in v
|
||||
&& "end" in v
|
||||
&& "comment" in v
|
||||
&& typeof v.date === "string"
|
||||
&& typeof v.start === "string"
|
||||
&& typeof v.end === "string"
|
||||
&& typeof v.comment === "string")) {
|
||||
throw new Error("Invalid JSON: records entry")
|
||||
}
|
||||
}
|
||||
|
||||
const user_database_name = get_user_db_name(data.id)
|
||||
const user_database = new Database(user_database_name, { strict: true })
|
||||
|
||||
user_database.transaction((records: Array<ExportRecordsEntry> | null, estimates: Array<ExportEstimatesEntry> | null) => {
|
||||
if (records != null) {
|
||||
const query = user_database.prepare(ENTRY_DATABASE_IMPORT)
|
||||
for (const record of records) { query.run(record) }
|
||||
}
|
||||
if (estimates != null) {
|
||||
const query = user_database.prepare(ESTIMATES_DATABASE_IMPORT)
|
||||
for (const estimate of estimates) { query.run(estimate) }
|
||||
}
|
||||
})(data.records, data.estimates)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import type { PageServerLoad, Actions } from "./$types"
|
|||
|
||||
import type { UserEntry } from "$lib/db_types"
|
||||
|
||||
import { SQLiteError } from "bun:sqlite"
|
||||
import { fail, redirect } from "@sveltejs/kit"
|
||||
|
||||
import Permissions from "$lib/permissions"
|
||||
|
|
@ -9,7 +10,7 @@ import { toInt } from "$lib/util"
|
|||
|
||||
import Logs from "$lib/server/log"
|
||||
import SessionStore from "$lib/server/session_store"
|
||||
import { get_user_entry_by_id, updateUser } from "$lib/server/database"
|
||||
import { get_user_entry_by_id, updateUser, import_user_data, create_user, remove_user } from "$lib/server/database"
|
||||
import { change_password } from "$lib/server/auth"
|
||||
|
||||
export const load: PageServerLoad = ({ locals, url }) => {
|
||||
|
|
@ -20,7 +21,15 @@ export const load: PageServerLoad = ({ locals, url }) => {
|
|||
|
||||
let user: UserEntry|null = locals.user.toUserEntry()
|
||||
|
||||
if (locals.user.id != (toInt(url.searchParams.get("user") ?? locals.user.id.toFixed(0)))) {
|
||||
const target_user = url.searchParams.get("user") as string|null
|
||||
|
||||
if (target_user == "new") {
|
||||
return {
|
||||
user: null
|
||||
}
|
||||
}
|
||||
|
||||
if (locals.user.id != (toInt(target_user ?? locals.user.id.toFixed(0)))) {
|
||||
if (!Permissions.has(locals.user.permissions, Permissions.USERADMIN.VIEW)) {
|
||||
return fail(403, { message: "Insufficient Permissions" })
|
||||
}
|
||||
|
|
@ -58,7 +67,7 @@ export const actions = {
|
|||
|
||||
const data = await request.formData();
|
||||
|
||||
const id = toInt((data.get("id") as string|null) ?? "NaN")
|
||||
const id = data.get("id") == "new" ? -1 : toInt((data.get("id") as string|null) ?? "NaN")
|
||||
const name = data.get("name") as string|null
|
||||
const gender = data.get("gender") as string|null
|
||||
const address = data.get("address") as string|null
|
||||
|
|
@ -73,6 +82,39 @@ export const actions = {
|
|||
return fail(400, { message: "invalid request" })
|
||||
}
|
||||
|
||||
if (id == -1) {
|
||||
if (!Permissions.has(locals.user.permissions, Permissions.USERADMIN.CREATE)) {
|
||||
return fail(403, { message: "Keine Berechtigung" })
|
||||
}
|
||||
|
||||
if (username.length == 0) {
|
||||
return fail(400, { message: "Benutzername muss angegeben werden" })
|
||||
}
|
||||
|
||||
if (password1 == null || password2 == null || password1.length == 0 || password2.length == 0) {
|
||||
return fail(400, { message: "Passwort muss für einen neuen Benutzer angegeben werden" })
|
||||
}
|
||||
|
||||
if (password1 != password2) {
|
||||
return fail(400, { message: "Passwörter müssen übereinstimmen" })
|
||||
}
|
||||
|
||||
let new_user: number | bigint = -1
|
||||
try {
|
||||
new_user = await create_user({ name, gender, address, username, password: password1 })
|
||||
} catch (e) {
|
||||
if (e instanceof SQLiteError && e.code == "SQLITE_CONSTRAINT_UNIQUE") {
|
||||
return fail(400, { message: "Benutzername ist bereits vergeben" })
|
||||
}
|
||||
throw e
|
||||
}
|
||||
|
||||
if (new_user < 0) {
|
||||
return fail(500, { message: "Interner Fehler" })
|
||||
}
|
||||
return redirect(303, `/user?user=${new_user}`)
|
||||
}
|
||||
|
||||
if (locals.user.id != id
|
||||
&& (!Permissions.has(locals.user.permissions, Permissions.USERADMIN.EDIT)
|
||||
|| ((password1 != null || password2 != null) && !Permissions.has(locals.user.permissions, Permissions.USERADMIN.ADMIN)))) {
|
||||
|
|
@ -83,6 +125,7 @@ export const actions = {
|
|||
if (password1 != password2) {
|
||||
return fail(400, { message: "Passwörter müssen übereinstimmen" })
|
||||
}
|
||||
|
||||
const result = change_password(id, password1)
|
||||
if (!result) {
|
||||
return fail(500, { message: "Database failure"})
|
||||
|
|
@ -103,5 +146,86 @@ export const actions = {
|
|||
|
||||
return { message: "Erfolgreich gespeichert" }
|
||||
|
||||
},
|
||||
import: async ({ locals, request }) => {
|
||||
|
||||
if (!locals.user) {
|
||||
return fail(401, { message: "Unauthorized User" })
|
||||
}
|
||||
|
||||
const data = await request.formData()
|
||||
|
||||
const id = toInt(data.get("id") as string|null ?? "")
|
||||
const file = data.get("file")
|
||||
|
||||
if (isNaN(id)) {
|
||||
return fail(400, { message: "Invalid id" })
|
||||
}
|
||||
if (!file || typeof file === "string") {
|
||||
return fail(400, { message: "Invalid file" })
|
||||
}
|
||||
|
||||
|
||||
let import_data
|
||||
|
||||
try {
|
||||
import_data = await file.json()
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
console.log(e)
|
||||
return fail(400, { message: "Uploaded file is erronous" })
|
||||
}
|
||||
throw e
|
||||
}
|
||||
|
||||
if (!import_data.id || import_data.id != id) {
|
||||
return fail(400, { message: "Datei für falschen Benutzer hochgeladen" })
|
||||
}
|
||||
|
||||
if (locals.user.id != id) {
|
||||
if (!Permissions.has(locals.user.permissions, Permissions.USERADMIN.ADMIN)) {
|
||||
Logs.user.warn(`User ${locals.user.id} tried to import data for user ${id} without sufficient permissions`)
|
||||
return fail(403, { message: "Unzureichend Berechtigung" })
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
import_user_data(import_data)
|
||||
} catch (e) {
|
||||
Logs.user.error(`Failed to import data for user ${id} by user ${locals.user.id}: ${(e as Error).message}`)
|
||||
return fail(500, { message: "Import fehlgeschlagen" })
|
||||
}
|
||||
|
||||
return { message: "Import abgeschlossen"}
|
||||
},
|
||||
delete: async ({ locals, request }) => {
|
||||
|
||||
if (!locals.user) {
|
||||
return fail(401, { message: "Unauthorized User" })
|
||||
}
|
||||
|
||||
const data = await request.formData()
|
||||
|
||||
const user_id = toInt((data.get("user") as string|null) ?? "NaN")
|
||||
|
||||
if (isNaN(user_id)) {
|
||||
return fail(400, { message: "Bad request" })
|
||||
}
|
||||
|
||||
if (!Permissions.has(locals.user.permissions, Permissions.USERADMIN.DELETE)) {
|
||||
Logs.user.warn(`User ${locals.user.id} tried to delete user ${id} without sufficient permissions`)
|
||||
return fail(403, { message: "Unzureichend Berechtigung" })
|
||||
}
|
||||
|
||||
const result = remove_user(user_id)
|
||||
if (!result) {
|
||||
return fail(500, { message: "Fehler beim Löschen" })
|
||||
}
|
||||
|
||||
if (Permissions.has(locals.user.permissions, Permissions.USERADMIN.VIEW)) {
|
||||
return redirect(303, "/useradmin")
|
||||
}
|
||||
|
||||
return { message: "Erfolgreich gelöscht" }
|
||||
}
|
||||
} satisfies Actions
|
||||
|
|
|
|||
|
|
@ -7,13 +7,26 @@
|
|||
import Permissions from "$lib/permissions"
|
||||
|
||||
const { data, form }: PageProps = $props()
|
||||
|
||||
let file_upload: HTMLButtonElement
|
||||
$effect(() => {
|
||||
file_upload.disabled = true
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<form method="GET" id="form_export" action={`/user/export?${page.url.searchParams.toString()}`} ></form>
|
||||
<form method="POST" id="form_import" action="?/import" enctype="multipart/form-data"><input type="hidden" name="id" value={data.user?.id ?? "new"} /></form>
|
||||
<form method="POST" id="form_delete" action="?/delete" onsubmit={(event) => {
|
||||
if (!confirm(`Sicher ${data.user?.name} zu löschen?`)) {
|
||||
event.preventDefault()
|
||||
}}}></form>
|
||||
|
||||
<form method="POST" id="form_edit" action={`?/edit&${page.url.searchParams.toString()}`} use:enhance={() => {
|
||||
return async ({update}) => { update({ reset: false }) }
|
||||
}}>
|
||||
<input type="hidden" name="id" value={data.user.id} />
|
||||
<input type="hidden" name="id" value={data.user?.id ?? "new"} />
|
||||
|
||||
<div class="root">
|
||||
|
||||
|
|
@ -32,7 +45,7 @@
|
|||
<tbody>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td><input type="text" name="name" value={data.user.name} /></td>
|
||||
<td><input type="text" name="name" value={data.user?.name} /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Geschlecht</td>
|
||||
|
|
@ -43,7 +56,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>Addresse</td>
|
||||
<td><input type="text" name="address" value={data.user.address} /></td>
|
||||
<td><input type="text" name="address" value={data.user?.address} /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -60,7 +73,7 @@
|
|||
<tbody>
|
||||
<tr>
|
||||
<td>Benutzername</td>
|
||||
<td colspan="2"><input type="text" name="username" value={data.user.username} /></td>
|
||||
<td colspan="2"><input type="text" name="username" value={data.user?.username} /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Passwort ändern</td>
|
||||
|
|
@ -94,7 +107,7 @@
|
|||
type="checkbox"
|
||||
name="USERADMIN"
|
||||
value={permission.value}
|
||||
checked={Permissions.has(data.user.permissions, permission.value)}
|
||||
checked={Permissions.has(data.user?.permissions ?? 0, permission.value)}
|
||||
disabled={disabled}
|
||||
data-bits={Permissions.deconstruct(permission.value).join(" ")}
|
||||
onclick={(event) => {
|
||||
|
|
@ -131,8 +144,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}} />
|
||||
{permission.name}
|
||||
</label>
|
||||
|
|
@ -145,8 +156,37 @@
|
|||
{/if}
|
||||
|
||||
|
||||
<button type="submit">Speichern</button>
|
||||
<button class="save-button" type="submit">{data.user ? "Speichern" : "Anlegen"}</button>
|
||||
|
||||
<table>
|
||||
<colgroup>
|
||||
<col class="leader2" />
|
||||
<col class="form2 center-button" />
|
||||
</colgroup>
|
||||
|
||||
<thead>
|
||||
<tr><th colspan="2">Daten</th></tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Exportieren:</td>
|
||||
<td><button type="submit" form="form_export">Download</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">Importieren:</td>
|
||||
<td><input type="file" form="form_import" name="file" accept="application/json" onchange={
|
||||
(event) => {file_upload.disabled = ((event.target as HTMLInputElement).files?.length == 0)}} required/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><button type="submit" form="form_import" bind:this={file_upload} >Hochladen</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{#if data.user}
|
||||
<button class="delete_button" form="form_delete" name="user" value={data.user?.id}>Löschen</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
|
@ -198,9 +238,28 @@ label:has(input[type="checkbox"][disabled]) {
|
|||
color: gray;
|
||||
}
|
||||
|
||||
button {
|
||||
.save-button {
|
||||
width: 15%;
|
||||
margin-left: 85%;
|
||||
}
|
||||
|
||||
table td:has(button) {
|
||||
text-align: center;
|
||||
}
|
||||
table button {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
padding-left: 10px;
|
||||
border: 1px dotted lightblue;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.delete_button {
|
||||
margin-top: 50px;
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
import type { RequestHandler } from "./$types.d"
|
||||
|
||||
import { json, error } from "@sveltejs/kit"
|
||||
|
||||
import Logs from "$lib/server/log"
|
||||
import { get_user_entry_by_id, export_user_data } from "$lib/server/database"
|
||||
|
||||
import Permissions from "$lib/permissions"
|
||||
import { toInt } from "$lib/util"
|
||||
|
||||
export const GET: RequestHandler = async ({ locals, url }) => {
|
||||
if (locals.user == null) {
|
||||
return error(401, { message: "Unauthorized user" })
|
||||
}
|
||||
|
||||
let user_id = locals.user.id
|
||||
const user_id_param = toInt(url.searchParams.get("user") ?? "")
|
||||
|
||||
if (!isNaN(user_id_param) && user_id != user_id_param) {
|
||||
if (!Permissions.has(locals.user.permissions, Permissions.USERADMIN.ADMIN)) {
|
||||
Logs.user.warn(`User ${locals.user.id} tried to export records for user ${url.searchParams.get("user")} without sufficient permissions`)
|
||||
return error(403, { message: "Insufficient permissions" })
|
||||
}
|
||||
|
||||
const new_user = get_user_entry_by_id(user_id_param)
|
||||
if (new_user == null) {
|
||||
return error(400, { message: "invalid user" })
|
||||
}
|
||||
|
||||
user_id = new_user.id
|
||||
}
|
||||
|
||||
Logs.user.info(`User ${locals.user.id} exported userdata for user ${user_id}`)
|
||||
|
||||
const data = await export_user_data(user_id)
|
||||
return new Response(data, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Disposition': `attachment; filename=Export-${locals.user.username}-${(new Date()).toJSON()}.json`
|
||||
}})
|
||||
}
|
||||
|
|
@ -3,13 +3,14 @@
|
|||
import type { PageProps } from "./$types"
|
||||
import { enhance } from "$app/forms";
|
||||
|
||||
const { data }: PageProps = $props();
|
||||
import Permissions from "$lib/permissions"
|
||||
|
||||
console.log(data)
|
||||
const { data }: PageProps = $props();
|
||||
|
||||
</script>
|
||||
|
||||
<form method="GET" id="form_manage_user" action="user"></form>
|
||||
<form method="GET" id="form_add_user" action="user"></form>
|
||||
|
||||
<div>
|
||||
<h1>Benutzerverwaltung</h1>
|
||||
|
|
@ -20,6 +21,9 @@
|
|||
{/each}
|
||||
</select>
|
||||
<button type="submit" form="form_manage_user">Edit</button>
|
||||
{#if Permissions.has(data.loggedInAs.permissions, Permissions.USERADMIN.CREATE)}
|
||||
<button type="submit" form="form_add_user" name="user" value="new">Add</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue