refactor: moved all data access/storage into repositories
also various refactorings in resource editing (*/[id]/+page.server.ts) pages
This commit is contained in:
parent
5e3fb972e8
commit
d2e064d40f
@ -1,4 +1,4 @@
|
|||||||
import { db } from '$lib/server/db';
|
import { initRepos, users } from '$lib/server/db';
|
||||||
import { Guard } from '$lib/server/guard';
|
import { Guard } from '$lib/server/guard';
|
||||||
import { getUserFromSession } from '$lib/server/sessions';
|
import { getUserFromSession } from '$lib/server/sessions';
|
||||||
import type { Handle, ServerInit } from '@sveltejs/kit';
|
import type { Handle, ServerInit } from '@sveltejs/kit';
|
||||||
@ -7,21 +7,18 @@ import { writeFileSync } from 'fs';
|
|||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
|
|
||||||
export const init: ServerInit = async () => {
|
export const init: ServerInit = async () => {
|
||||||
const anyUser = db.data.users[0];
|
await initRepos();
|
||||||
|
|
||||||
if (!anyUser) {
|
if (!(await users.any())) {
|
||||||
const pass = nanoid();
|
const pass = nanoid();
|
||||||
|
|
||||||
await db.update(({ users }) => {
|
await users.create({
|
||||||
users.push({
|
|
||||||
id: nanoid(),
|
|
||||||
name: 'admin',
|
name: 'admin',
|
||||||
password: bcrypt.hashSync(pass, 10),
|
password: bcrypt.hashSync(pass, 10),
|
||||||
admin: true,
|
admin: true,
|
||||||
groups: [],
|
groups: [],
|
||||||
devices: [],
|
devices: [],
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`default admin password: ${pass}`);
|
console.log(`default admin password: ${pass}`);
|
||||||
console.log("saved to ./default_admin_pass.txt, don't share it and change it asap");
|
console.log("saved to ./default_admin_pass.txt, don't share it and change it asap");
|
||||||
@ -33,7 +30,7 @@ export const init: ServerInit = async () => {
|
|||||||
export const handle: Handle = async ({ event, resolve }) => {
|
export const handle: Handle = async ({ event, resolve }) => {
|
||||||
const { cookies, locals } = event;
|
const { cookies, locals } = event;
|
||||||
|
|
||||||
locals.guard = new Guard(getUserFromSession(cookies.get('session')));
|
locals.guard = new Guard(await getUserFromSession(cookies.get('session')));
|
||||||
|
|
||||||
return await resolve(event);
|
return await resolve(event);
|
||||||
};
|
};
|
||||||
|
|||||||
6
src/lib/server/commonResponses.ts
Normal file
6
src/lib/server/commonResponses.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { fail } from '@sveltejs/kit';
|
||||||
|
import type { ZodError } from 'zod';
|
||||||
|
|
||||||
|
export const FORBIDDEN = fail(403, { error: 'You do not have permission to do that.' });
|
||||||
|
export const SUCCESS = { success: true };
|
||||||
|
export const PARSE_ERROR = (err: ZodError) => fail(400, { error: err.errors[0].message });
|
||||||
@ -1,42 +1,16 @@
|
|||||||
import { JSONFilePreset } from 'lowdb/node';
|
import type { IDeviceRepo } from './repos/deviceRepo';
|
||||||
import type { Device } from './types/device';
|
import type { IGroupRepo } from './repos/groupRepo';
|
||||||
import type { Group } from './types/group';
|
import { LowStorage } from './repos/lowdb/_init';
|
||||||
import type { User } from './types/user';
|
import type { IUserRepo } from './repos/userRepo';
|
||||||
|
|
||||||
type AppData = {
|
export let users: IUserRepo;
|
||||||
users: User[];
|
export let groups: IGroupRepo;
|
||||||
groups: Group[];
|
export let devices: IDeviceRepo;
|
||||||
devices: Device[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const db = await JSONFilePreset<AppData>('./data/db.json', {
|
export async function initRepos() {
|
||||||
users: [],
|
const repos = await LowStorage.init();
|
||||||
groups: [],
|
|
||||||
devices: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
export function getUsersDevices(userId: string): Device[] {
|
users = repos.users;
|
||||||
const user = db.data.users.find((u) => u.id === userId)!;
|
groups = repos.groups;
|
||||||
|
devices = repos.devices;
|
||||||
if (user.admin) return db.data.devices;
|
|
||||||
|
|
||||||
const userDevices = db.data.devices.filter((d) => user.devices.includes(d.id));
|
|
||||||
const userGroups = db.data.groups.filter((g) => user.groups.includes(g.id));
|
|
||||||
const groupDevices = userGroups.flatMap((g) =>
|
|
||||||
db.data.devices.filter((d) => g.devices.includes(d.id)),
|
|
||||||
);
|
|
||||||
|
|
||||||
// concat and dedupe
|
|
||||||
return [...new Set([...userDevices, ...groupDevices])];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @deprecated */
|
|
||||||
export function assignDefaults<T>(defaults: Partial<T>, obj: any, options = { mutate: false }): T {
|
|
||||||
for (let k of Object.keys(obj)) {
|
|
||||||
if (obj[k] === undefined) {
|
|
||||||
delete obj[k];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return options.mutate ? Object.assign(defaults, obj) : Object.assign({}, defaults, obj);
|
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/lib/server/db/repos/deviceRepo.ts
Normal file
10
src/lib/server/db/repos/deviceRepo.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import type { New, Updated } from '../types';
|
||||||
|
import type { Device, DeviceError } from '../types/device';
|
||||||
|
|
||||||
|
export interface IDeviceRepo {
|
||||||
|
getAll(): Promise<Device[]>;
|
||||||
|
getById(id: string): Promise<Device | undefined>;
|
||||||
|
create(group: New<Device>): Promise<DeviceError | undefined>;
|
||||||
|
update(group: Updated<Device>): Promise<DeviceError | undefined>;
|
||||||
|
delete(groupId: string): Promise<DeviceError | undefined>;
|
||||||
|
}
|
||||||
11
src/lib/server/db/repos/groupRepo.ts
Normal file
11
src/lib/server/db/repos/groupRepo.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import type { New, Updated } from '../types';
|
||||||
|
import type { Group, GroupError } from '../types/group';
|
||||||
|
|
||||||
|
export interface IGroupRepo {
|
||||||
|
getAll(): Promise<Group[]>;
|
||||||
|
getById(id: string): Promise<Group | undefined>;
|
||||||
|
create(group: New<Group>): Promise<GroupError | undefined>;
|
||||||
|
update(group: Updated<Group>): Promise<GroupError | undefined>;
|
||||||
|
delete(groupId: string): Promise<GroupError | undefined>;
|
||||||
|
countUsers(groupId: string): Promise<number>;
|
||||||
|
}
|
||||||
29
src/lib/server/db/repos/lowdb/_init.ts
Normal file
29
src/lib/server/db/repos/lowdb/_init.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { JSONFilePreset } from 'lowdb/node';
|
||||||
|
import type { Device } from '../../types/device';
|
||||||
|
import type { Group } from '../../types/group';
|
||||||
|
import type { User } from '../../types/user';
|
||||||
|
import { LowUserRepo } from './userRepo';
|
||||||
|
import { LowGroupRepo } from './groupRepo';
|
||||||
|
import { LowDeviceRepo } from './deviceRepo';
|
||||||
|
|
||||||
|
const db = await JSONFilePreset<LowStorage.LowData>('./data/db.json', {
|
||||||
|
users: [],
|
||||||
|
groups: [],
|
||||||
|
devices: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
export namespace LowStorage {
|
||||||
|
export type LowData = {
|
||||||
|
users: User[];
|
||||||
|
groups: Group[];
|
||||||
|
devices: Device[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function init() {
|
||||||
|
return {
|
||||||
|
users: new LowUserRepo(db),
|
||||||
|
groups: new LowGroupRepo(db),
|
||||||
|
devices: new LowDeviceRepo(db),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
76
src/lib/server/db/repos/lowdb/deviceRepo.ts
Normal file
76
src/lib/server/db/repos/lowdb/deviceRepo.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import type { IDeviceRepo } from '../deviceRepo';
|
||||||
|
import type { Low } from 'lowdb';
|
||||||
|
import { type New, type Updated } from '../../types';
|
||||||
|
import { type Device, type DeviceError, DeviceErrors } from '../../types/device';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
import type { LowStorage } from './_init';
|
||||||
|
|
||||||
|
export class LowDeviceRepo implements IDeviceRepo {
|
||||||
|
private readonly db;
|
||||||
|
|
||||||
|
constructor(db: Low<LowStorage.LowData>) {
|
||||||
|
this.db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll(): Promise<Device[]> {
|
||||||
|
return this.db.data.devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getById(id: string): Promise<Device | undefined> {
|
||||||
|
return this.db.data.devices.find((d) => d.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(device: New<Device>): Promise<DeviceError | undefined> {
|
||||||
|
if (this.db.data.devices.find((d) => d.name === device.name)) {
|
||||||
|
return DeviceErrors.DUPLICATE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.db.update(({ devices }) => {
|
||||||
|
devices.push({ id: nanoid(), ...device });
|
||||||
|
});
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(device: Updated<Device>): Promise<DeviceError | undefined> {
|
||||||
|
if (this.db.data.devices.find((d) => d.name === device.name && d.id !== device.id)) {
|
||||||
|
return DeviceErrors.DUPLICATE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
let found = false;
|
||||||
|
await this.db.update(({ devices }) => {
|
||||||
|
const existingDevice = devices.find((d) => d.id === device.id);
|
||||||
|
if (!existingDevice) return;
|
||||||
|
|
||||||
|
Object.assign(existingDevice, device);
|
||||||
|
found = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return found ? undefined : DeviceErrors.NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(deviceId: string): Promise<DeviceError | undefined> {
|
||||||
|
let found = false;
|
||||||
|
|
||||||
|
this.db.data.devices = this.db.data.devices.filter((d) => {
|
||||||
|
if (d.id === deviceId) {
|
||||||
|
found = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
this.db.data.users.forEach((u) => {
|
||||||
|
u.devices = u.devices.filter((d) => d !== deviceId);
|
||||||
|
});
|
||||||
|
this.db.data.groups.forEach((g) => {
|
||||||
|
g.devices = g.devices.filter((d) => d !== deviceId);
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.db.write();
|
||||||
|
}
|
||||||
|
|
||||||
|
return found ? undefined : DeviceErrors.NOT_FOUND;
|
||||||
|
}
|
||||||
|
}
|
||||||
86
src/lib/server/db/repos/lowdb/groupRepo.ts
Normal file
86
src/lib/server/db/repos/lowdb/groupRepo.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import type { IGroupRepo } from '../groupRepo';
|
||||||
|
import type { Low } from 'lowdb';
|
||||||
|
import { type New, type Updated } from '../../types';
|
||||||
|
import { type Group, type GroupError, GroupErrors } from '../../types/group';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
import type { LowStorage } from './_init';
|
||||||
|
|
||||||
|
export class LowGroupRepo implements IGroupRepo {
|
||||||
|
private readonly db;
|
||||||
|
|
||||||
|
constructor(db: Low<LowStorage.LowData>) {
|
||||||
|
this.db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll(): Promise<Group[]> {
|
||||||
|
return this.db.data.groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getById(id: string): Promise<Group | undefined> {
|
||||||
|
return this.db.data.groups.find((g) => g.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(group: New<Group>): Promise<GroupError | undefined> {
|
||||||
|
if (this.db.data.groups.find((g) => g.name === group.name)) {
|
||||||
|
return GroupErrors.DUPLICATE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const deviceId of group.devices) {
|
||||||
|
if (!this.db.data.devices.some((d) => d.id === deviceId)) {
|
||||||
|
return GroupErrors.UNKNOWN_DEVICE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.db.update(({ groups }) => {
|
||||||
|
groups.push({ id: nanoid(), ...group });
|
||||||
|
});
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(group: Updated<Group>): Promise<GroupError | undefined> {
|
||||||
|
if (this.db.data.groups.find((g) => g.name === group.name && g.id !== group.id)) {
|
||||||
|
return GroupErrors.DUPLICATE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const deviceId of group.devices ?? []) {
|
||||||
|
if (!this.db.data.devices.some((d) => d.id === deviceId)) {
|
||||||
|
return GroupErrors.UNKNOWN_DEVICE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let found = false;
|
||||||
|
await this.db.update(({ groups }) => {
|
||||||
|
const existingGroup = groups.find((g) => g.id === group.id);
|
||||||
|
if (!existingGroup) return;
|
||||||
|
|
||||||
|
Object.assign(existingGroup, group);
|
||||||
|
found = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return found ? undefined : GroupErrors.NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(groupId: string): Promise<GroupError | undefined> {
|
||||||
|
let found = false;
|
||||||
|
|
||||||
|
this.db.data.groups = this.db.data.groups.filter((g) => {
|
||||||
|
if (g.id === groupId) {
|
||||||
|
found = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
this.db.data.users.forEach((u) => (u.groups = u.groups.filter((gid) => gid != groupId)));
|
||||||
|
await this.db.write();
|
||||||
|
}
|
||||||
|
|
||||||
|
return found ? undefined : GroupErrors.NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
async countUsers(groupId: string): Promise<number> {
|
||||||
|
return this.db.data.users.filter((u) => u.groups.includes(groupId)).length;
|
||||||
|
}
|
||||||
|
}
|
||||||
113
src/lib/server/db/repos/lowdb/userRepo.ts
Normal file
113
src/lib/server/db/repos/lowdb/userRepo.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import type { Low } from 'lowdb';
|
||||||
|
import { type New, type Updated } from '../../types';
|
||||||
|
import { UserErrors, type User } from '../../types/user';
|
||||||
|
import type { IUserRepo } from '../userRepo';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
import type { LowStorage } from './_init';
|
||||||
|
|
||||||
|
export class LowUserRepo implements IUserRepo {
|
||||||
|
private readonly db;
|
||||||
|
|
||||||
|
constructor(db: Low<LowStorage.LowData>) {
|
||||||
|
this.db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
async any() {
|
||||||
|
return this.db.data.users[0] != undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll() {
|
||||||
|
return this.db.data.users;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getById(id: string) {
|
||||||
|
return this.db.data.users.find((u) => u.id == id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getByName(name: string) {
|
||||||
|
return this.db.data.users.find((u) => u.name == name);
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(user: New<User>) {
|
||||||
|
if (!this.db.data.users.find((u) => u.name == user.name)) {
|
||||||
|
return UserErrors.DUPLICATE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let id of user.devices) {
|
||||||
|
if (!this.db.data.devices.some((d) => d.id == id)) {
|
||||||
|
return UserErrors.UNKNOWN_DEVICE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let id of user.groups) {
|
||||||
|
if (!this.db.data.groups.some((g) => g.id == id)) {
|
||||||
|
return UserErrors.UNKNOWN_GROUP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.db.update(({ users }) => {
|
||||||
|
users.push({ id: nanoid(), ...user });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(user: Updated<User>) {
|
||||||
|
if (this.db.data.users.find((u) => u.name == user.name && u.id != user.id)) {
|
||||||
|
return UserErrors.DUPLICATE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let id of user.devices ?? []) {
|
||||||
|
if (!this.db.data.devices.some((d) => d.id == id)) {
|
||||||
|
return UserErrors.UNKNOWN_DEVICE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let id of user.groups ?? []) {
|
||||||
|
if (!this.db.data.groups.some((g) => g.id == id)) {
|
||||||
|
return UserErrors.UNKNOWN_GROUP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let found = false;
|
||||||
|
await this.db.update(({ users }) => {
|
||||||
|
let existing = users.find((u) => u.id == user.id);
|
||||||
|
if (!existing) return;
|
||||||
|
|
||||||
|
Object.assign(existing, user);
|
||||||
|
found = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return found ? undefined : UserErrors.NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchDevices(userId: string) {
|
||||||
|
const user = this.db.data.users.find((u) => u.id === userId)!;
|
||||||
|
|
||||||
|
if (user.admin) return this.db.data.devices;
|
||||||
|
|
||||||
|
const userDevices = this.db.data.devices.filter((d) => user.devices.includes(d.id));
|
||||||
|
const userGroups = this.db.data.groups.filter((g) => user.groups.includes(g.id));
|
||||||
|
const groupDevices = userGroups.flatMap((g) =>
|
||||||
|
this.db.data.devices.filter((d) => g.devices.includes(d.id)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// concat and dedupe
|
||||||
|
return [...new Set([...userDevices, ...groupDevices])];
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(userId: string) {
|
||||||
|
let found = false;
|
||||||
|
|
||||||
|
this.db.data.users = this.db.data.users.filter((u) => {
|
||||||
|
if (u.id == userId) {
|
||||||
|
found = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.db.write();
|
||||||
|
|
||||||
|
return found ? undefined : UserErrors.NOT_FOUND;
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/lib/server/db/repos/userRepo.ts
Normal file
14
src/lib/server/db/repos/userRepo.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import type { New, Updated } from '../types';
|
||||||
|
import type { Device } from '../types/device';
|
||||||
|
import type { User, UserError } from '../types/user';
|
||||||
|
|
||||||
|
export interface IUserRepo {
|
||||||
|
any(): Promise<boolean>;
|
||||||
|
getAll(): Promise<User[]>;
|
||||||
|
getById(id: string): Promise<User | undefined>;
|
||||||
|
getByName(name: string): Promise<User | undefined>;
|
||||||
|
fetchDevices(userId: string): Promise<Device[]>;
|
||||||
|
create(user: New<User>): Promise<UserError | undefined>;
|
||||||
|
update(user: Updated<User>): Promise<UserError | undefined>;
|
||||||
|
delete(userId: string): Promise<UserError | undefined>;
|
||||||
|
}
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { AppError } from '.';
|
||||||
|
|
||||||
export type Device = {
|
export type Device = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -6,3 +8,16 @@ export type Device = {
|
|||||||
port: number;
|
port: number;
|
||||||
packets: number;
|
packets: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export class DeviceError extends AppError {
|
||||||
|
constructor(message: string, status: number) {
|
||||||
|
super(message, status);
|
||||||
|
this.name = 'DeviceError';
|
||||||
|
Object.setPrototypeOf(this, DeviceError.prototype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace DeviceErrors {
|
||||||
|
export const NOT_FOUND = new DeviceError('Group not found.', 404);
|
||||||
|
export const DUPLICATE_NAME = new DeviceError('Device with this name already exists.', 409);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,24 @@
|
|||||||
|
import { AppError } from '.';
|
||||||
|
|
||||||
export type Group = {
|
export type Group = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
devices: string[];
|
devices: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export class GroupError extends AppError {
|
||||||
|
constructor(message: string, status: number) {
|
||||||
|
super(message, status);
|
||||||
|
this.name = 'GroupError';
|
||||||
|
Object.setPrototypeOf(this, GroupError.prototype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace GroupErrors {
|
||||||
|
export const NOT_FOUND = new GroupError('Group not found.', 404);
|
||||||
|
export const DUPLICATE_NAME = new GroupError('Group with this name already exists.', 409);
|
||||||
|
export const UNKNOWN_DEVICE = new GroupError(
|
||||||
|
'One or more of the selected devices do not exist.',
|
||||||
|
400,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
38
src/lib/server/db/types/index.ts
Normal file
38
src/lib/server/db/types/index.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { fail } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
/** @deprecated unused for now - keeping around just in case but should be removed someday */
|
||||||
|
export class Result<TOk, TErr extends Error> {
|
||||||
|
public readonly err;
|
||||||
|
private readonly res;
|
||||||
|
|
||||||
|
constructor(res?: TOk, err?: TErr) {
|
||||||
|
this.res = res;
|
||||||
|
this.err = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unwrap(): TOk {
|
||||||
|
if (this.err) {
|
||||||
|
console.error('Tried to unwrap a Result that contained an error');
|
||||||
|
throw this.err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.res!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AppError extends Error {
|
||||||
|
public readonly status: number;
|
||||||
|
constructor(message: string, status: number) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'AppError';
|
||||||
|
this.status = status;
|
||||||
|
Object.setPrototypeOf(this, AppError.prototype);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toFail() {
|
||||||
|
return fail(this.status, { error: this.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type New<T> = Omit<T, 'id'>;
|
||||||
|
export type Updated<T> = Partial<T> & { id: string };
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { AppError } from '.';
|
||||||
|
|
||||||
export type User = {
|
export type User = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -14,3 +16,24 @@ export function toPublicUser(user: User): PublicUser {
|
|||||||
delete clonedUser.password;
|
delete clonedUser.password;
|
||||||
return clonedUser as PublicUser;
|
return clonedUser as PublicUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class UserError extends AppError {
|
||||||
|
constructor(message: string, status: number) {
|
||||||
|
super(message, status);
|
||||||
|
this.name = 'UserError';
|
||||||
|
Object.setPrototypeOf(this, UserError.prototype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace UserErrors {
|
||||||
|
export const NOT_FOUND = new UserError('User not found.', 404);
|
||||||
|
export const DUPLICATE_NAME = new UserError('User with this username already exists.', 409);
|
||||||
|
export const UNKNOWN_GROUP = new UserError(
|
||||||
|
'One or more of the selected groups do not exist.',
|
||||||
|
400,
|
||||||
|
);
|
||||||
|
export const UNKNOWN_DEVICE = new UserError(
|
||||||
|
'One or more of the selected devices do not exist.',
|
||||||
|
400,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { db } from './db';
|
import { users } from './db';
|
||||||
|
|
||||||
type SessionData = {
|
type SessionData = {
|
||||||
userId: string;
|
userId: string;
|
||||||
@ -15,13 +15,13 @@ export function createSession(data: SessionData) {
|
|||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUserFromSession(sessionId?: string) {
|
export async function getUserFromSession(sessionId?: string) {
|
||||||
if (!sessionId) return undefined;
|
if (!sessionId) return undefined;
|
||||||
|
|
||||||
const data = sessions.get(sessionId);
|
const data = sessions.get(sessionId);
|
||||||
if (!data) return undefined;
|
if (!data) return undefined;
|
||||||
|
|
||||||
return db.data.users.find((u) => u.id === data.userId);
|
return await users.getById(data.userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteSession(sessionId?: string) {
|
export function deleteSession(sessionId?: string) {
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { getUsersDevices } from '$lib/server/db';
|
import { users } from '$lib/server/db';
|
||||||
import type { ServerLoad } from '@sveltejs/kit';
|
import type { ServerLoad } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const load: ServerLoad = async ({ locals: { guard } }) => {
|
export const load: ServerLoad = async ({ locals: { guard } }) => {
|
||||||
const user = guard.requiresAuth().orRedirects().getUser();
|
const user = guard.requiresAuth().orRedirects().getUser();
|
||||||
return {
|
return {
|
||||||
devices: getUsersDevices(user.id),
|
devices: await users.fetchDevices(user.id),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { db, getUsersDevices } from '$lib/server/db/index.js';
|
import { FORBIDDEN, PARSE_ERROR, SUCCESS } from '$lib/server/commonResponses';
|
||||||
|
import { devices, users } from '$lib/server/db/index.js';
|
||||||
import { fail, redirect, type Actions, type ServerLoad } from '@sveltejs/kit';
|
import { fail, redirect, type Actions, type ServerLoad } from '@sveltejs/kit';
|
||||||
import { nanoid } from 'nanoid';
|
|
||||||
import validator from 'validator';
|
import validator from 'validator';
|
||||||
import { wake } from 'wake_on_lan';
|
import { wake } from 'wake_on_lan';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
@ -8,7 +8,7 @@ import { z } from 'zod';
|
|||||||
export const load: ServerLoad = async ({ locals: { guard }, params }) => {
|
export const load: ServerLoad = async ({ locals: { guard }, params }) => {
|
||||||
guard.requiresAdmin().orRedirects();
|
guard.requiresAdmin().orRedirects();
|
||||||
|
|
||||||
const device = db.data.devices.find((d) => d.id === params.id);
|
const device = await devices.getById(params.id ?? '');
|
||||||
|
|
||||||
if (!device && params.id !== 'new') {
|
if (!device && params.id !== 'new') {
|
||||||
redirect(302, '/dash/devices');
|
redirect(302, '/dash/devices');
|
||||||
@ -21,7 +21,7 @@ export const load: ServerLoad = async ({ locals: { guard }, params }) => {
|
|||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
update: async ({ request, params, locals: { guard } }) => {
|
update: async ({ request, params, locals: { guard } }) => {
|
||||||
if (guard.requiresAdmin().isFailed()) return fail(403);
|
if (guard.requiresAdmin().isFailed()) return FORBIDDEN;
|
||||||
|
|
||||||
const form = await request.formData();
|
const form = await request.formData();
|
||||||
|
|
||||||
@ -55,54 +55,29 @@ export const actions = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!parsed.success) {
|
if (!parsed.success) {
|
||||||
return fail(400, { error: parsed.error.errors[0].message });
|
return PARSE_ERROR(parsed.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.id === 'new') {
|
const err =
|
||||||
await db.update(({ devices }) => {
|
params.id === 'new'
|
||||||
devices.push({ id: nanoid(), ...parsed.data });
|
? await devices.create(parsed.data)
|
||||||
});
|
: await devices.update({ id: params.id ?? '', ...parsed.data });
|
||||||
|
|
||||||
return { success: true };
|
return err ? err.toFail() : SUCCESS;
|
||||||
}
|
|
||||||
|
|
||||||
let found = false;
|
|
||||||
|
|
||||||
await db.update(({ devices }) => {
|
|
||||||
let dev = devices.find((d) => d.id === params.id);
|
|
||||||
if (!dev) return;
|
|
||||||
|
|
||||||
Object.assign(dev, parsed.data);
|
|
||||||
found = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
return found ? { success: true } : fail(404, { error: 'Device not found.' });
|
|
||||||
},
|
},
|
||||||
|
|
||||||
delete: async ({ locals: { guard }, params }) => {
|
delete: async ({ locals: { guard }, params }) => {
|
||||||
if (guard.requiresAdmin().isFailed()) {
|
if (guard.requiresAdmin().isFailed()) return FORBIDDEN;
|
||||||
return fail(403);
|
|
||||||
}
|
|
||||||
|
|
||||||
db.data.devices = db.data.devices.filter((d) => d.id !== params.id);
|
const err = await devices.delete(params.id ?? '');
|
||||||
db.data.users.forEach((u) => {
|
return err ? err.toFail() : SUCCESS;
|
||||||
u.devices = u.devices.filter((d) => d !== params.id);
|
|
||||||
});
|
|
||||||
db.data.groups.forEach((g) => {
|
|
||||||
g.devices = g.devices.filter((d) => d !== params.id);
|
|
||||||
});
|
|
||||||
db.write();
|
|
||||||
|
|
||||||
return { success: true };
|
|
||||||
},
|
},
|
||||||
|
|
||||||
wake: async ({ params, locals: { guard } }) => {
|
wake: async ({ params, locals: { guard } }) => {
|
||||||
guard = guard.requiresAuth();
|
guard = guard.requiresAuth();
|
||||||
|
if (guard.isFailed()) return FORBIDDEN;
|
||||||
|
|
||||||
if (guard.isFailed()) {
|
const device = (await users.fetchDevices(guard.getUser().id)).find((d) => d.id === params.id);
|
||||||
console.log('Failed guard');
|
|
||||||
return fail(403);
|
|
||||||
}
|
|
||||||
|
|
||||||
const device = getUsersDevices(guard.getUser().id).find((d) => d.id === params.id);
|
|
||||||
|
|
||||||
if (!device) {
|
if (!device) {
|
||||||
return fail(404);
|
return fail(404);
|
||||||
@ -115,5 +90,7 @@ export const actions = {
|
|||||||
port: device.port,
|
port: device.port,
|
||||||
num_packets: device.packets,
|
num_packets: device.packets,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return SUCCESS;
|
||||||
},
|
},
|
||||||
} satisfies Actions;
|
} satisfies Actions;
|
||||||
|
|||||||
@ -1,16 +1,18 @@
|
|||||||
import { db } from '$lib/server/db';
|
import { groups } from '$lib/server/db';
|
||||||
import { type ServerLoad } from '@sveltejs/kit';
|
import { type ServerLoad } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const load: ServerLoad = async ({ locals: { guard } }) => {
|
export const load: ServerLoad = async ({ locals: { guard } }) => {
|
||||||
guard.requiresAdmin().orRedirects();
|
guard.requiresAdmin().orRedirects();
|
||||||
|
|
||||||
|
const allGroups = await groups.getAll();
|
||||||
|
let userCounts: { [key: string]: number } = {};
|
||||||
|
|
||||||
|
for (let g of allGroups) {
|
||||||
|
userCounts[g.id] = await groups.countUsers(g.id);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
groups: db.data.groups,
|
groups: allGroups,
|
||||||
userCounts: db.data.groups.reduce(
|
userCounts,
|
||||||
(acc, group) => {
|
|
||||||
acc[group.id] = db.data.users.filter((u) => u.groups.includes(group.id)).length;
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{} as Record<string, number>,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,26 +1,26 @@
|
|||||||
import { db } from '$lib/server/db';
|
import { FORBIDDEN, PARSE_ERROR, SUCCESS } from '$lib/server/commonResponses';
|
||||||
import { fail, redirect, type Actions, type ServerLoad } from '@sveltejs/kit';
|
import { devices, groups } from '$lib/server/db';
|
||||||
import { nanoid } from 'nanoid';
|
import { redirect, type Actions, type ServerLoad } from '@sveltejs/kit';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export const load: ServerLoad = async ({ locals: { guard }, params }) => {
|
export const load: ServerLoad = async ({ locals: { guard }, params }) => {
|
||||||
guard.requiresAdmin().orRedirects();
|
guard.requiresAdmin().orRedirects();
|
||||||
|
|
||||||
const group = db.data.groups.find((g) => g.id === params.id);
|
const group = await groups.getById(params.id ?? '');
|
||||||
|
|
||||||
if (!group && params.id !== 'new') {
|
if (!group && params.id !== 'new') {
|
||||||
redirect(302, '/dash/groups');
|
redirect(302, '/dash/groups');
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
devices: db.data.devices,
|
devices: await devices.getAll(),
|
||||||
group,
|
group,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
update: async ({ request, locals: { guard }, params }) => {
|
update: async ({ request, locals: { guard }, params }) => {
|
||||||
if (guard.requiresAdmin().isFailed()) return fail(403);
|
if (guard.requiresAdmin().isFailed()) return FORBIDDEN;
|
||||||
|
|
||||||
const form = await request.formData();
|
const form = await request.formData();
|
||||||
|
|
||||||
@ -29,12 +29,7 @@ export const actions = {
|
|||||||
.string({ message: 'Name is required.' })
|
.string({ message: 'Name is required.' })
|
||||||
.min(3, { message: 'Name must be at least 3 characters.' })
|
.min(3, { message: 'Name must be at least 3 characters.' })
|
||||||
.max(24, { message: 'Name must be at most 24 characters.' }),
|
.max(24, { message: 'Name must be at most 24 characters.' }),
|
||||||
|
devices: z.array(z.string()),
|
||||||
devices: z.array(
|
|
||||||
z.string().refine((v) => db.data.devices.find((d) => d.id === v), {
|
|
||||||
message: 'Invalid device ID.',
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const parsed = schema.safeParse({
|
const parsed = schema.safeParse({
|
||||||
@ -43,41 +38,21 @@ export const actions = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!parsed.success) {
|
if (!parsed.success) {
|
||||||
return fail(400, { error: parsed.error.errors[0].message });
|
return PARSE_ERROR(parsed.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.id === 'new') {
|
const err =
|
||||||
await db.update(({ groups }) => {
|
params.id === 'new'
|
||||||
groups.push({ id: nanoid(), ...parsed.data });
|
? await groups.create({ ...parsed.data })
|
||||||
});
|
: await groups.update({ id: params.id ?? '', ...parsed.data });
|
||||||
|
|
||||||
return { success: true };
|
return err ? err.toFail() : SUCCESS;
|
||||||
}
|
|
||||||
|
|
||||||
let found = false;
|
|
||||||
|
|
||||||
await db.update(({ groups }) => {
|
|
||||||
let group = groups.find((g) => g.id === params.id);
|
|
||||||
if (!group) return;
|
|
||||||
|
|
||||||
Object.assign(group, parsed.data);
|
|
||||||
found = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
return found ? { success: true } : fail(400, { error: 'Group not found.' });
|
|
||||||
},
|
},
|
||||||
|
|
||||||
delete: async ({ locals: { guard }, params }) => {
|
delete: async ({ locals: { guard }, params }) => {
|
||||||
if (guard.requiresAdmin().isFailed()) {
|
if (guard.requiresAdmin().isFailed()) return FORBIDDEN;
|
||||||
return fail(403);
|
|
||||||
}
|
|
||||||
|
|
||||||
db.data.groups = db.data.groups.filter((g) => g.id !== params.id);
|
const err = await groups.delete(params.id ?? '');
|
||||||
db.data.users.forEach((u) => {
|
return err ? err.toFail() : SUCCESS;
|
||||||
u.groups = u.groups.filter((g) => g !== params.id);
|
|
||||||
});
|
|
||||||
db.write();
|
|
||||||
|
|
||||||
redirect(302, '/dashboard/groups');
|
|
||||||
},
|
},
|
||||||
} satisfies Actions;
|
} satisfies Actions;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { db } from '$lib/server/db';
|
import { users } from '$lib/server/db';
|
||||||
import { toPublicUser, type User } from '$lib/server/db/types/user';
|
import { toPublicUser, type User } from '$lib/server/db/types/user';
|
||||||
import type { ServerLoad } from '@sveltejs/kit';
|
import type { ServerLoad } from '@sveltejs/kit';
|
||||||
|
|
||||||
@ -6,6 +6,6 @@ export const load: ServerLoad = async ({ locals: { guard } }) => {
|
|||||||
guard.requiresAdmin().orRedirects();
|
guard.requiresAdmin().orRedirects();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
users: db.data.users.map((u) => toPublicUser(u)),
|
users: (await users.getAll()).map((u) => toPublicUser(u)),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,29 +1,30 @@
|
|||||||
import { db } from '$lib/server/db';
|
import { FORBIDDEN, PARSE_ERROR, SUCCESS } from '$lib/server/commonResponses';
|
||||||
|
import { devices, groups, users } from '$lib/server/db';
|
||||||
|
import type { Updated } from '$lib/server/db/types';
|
||||||
import { toPublicUser, type User } from '$lib/server/db/types/user.js';
|
import { toPublicUser, type User } from '$lib/server/db/types/user.js';
|
||||||
import { fail, redirect, type Actions, type ServerLoad } from '@sveltejs/kit';
|
import { redirect, type Actions, type ServerLoad } from '@sveltejs/kit';
|
||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
import { nanoid } from 'nanoid';
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export const load: ServerLoad = async ({ locals: { guard }, params }) => {
|
export const load: ServerLoad = async ({ locals: { guard }, params }) => {
|
||||||
guard.requiresAdmin().orRedirects();
|
guard.requiresAdmin().orRedirects();
|
||||||
|
|
||||||
let user = db.data.users.find((u) => u.id === params.id);
|
let user = await users.getById(params.id ?? '');
|
||||||
|
|
||||||
if (!user && params.id !== 'new') {
|
if (!user && params.id !== 'new') {
|
||||||
redirect(302, '/dashboard/users');
|
redirect(302, '/dash/users');
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user: toPublicUser(user!),
|
user: toPublicUser(user!),
|
||||||
groups: db.data.groups,
|
groups: await groups.getAll(),
|
||||||
devices: db.data.devices,
|
devices: await devices.getAll(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
update: async ({ request, locals: { guard }, params }) => {
|
update: async ({ request, locals: { guard }, params }) => {
|
||||||
if (guard.requiresAdmin().isFailed()) return fail(403);
|
if (guard.requiresAdmin().isFailed()) return FORBIDDEN;
|
||||||
|
|
||||||
const form = await request.formData();
|
const form = await request.formData();
|
||||||
|
|
||||||
@ -35,23 +36,16 @@ export const actions = {
|
|||||||
admin: z.boolean(),
|
admin: z.boolean(),
|
||||||
password: z
|
password: z
|
||||||
.string()
|
.string()
|
||||||
.optional()
|
.refine((v) => (params.id === 'new' ? v.length > 0 : true), {
|
||||||
.refine((v) => (params.id === 'new' ? v && v.length > 0 : true), {
|
// if /new, password must be present
|
||||||
message: 'Password is required at user creation.',
|
message: 'Password is required at user creation.',
|
||||||
})
|
})
|
||||||
.refine((v) => !v || v.length >= 8, {
|
.refine((v) => (v.length > 0 ? v.length >= 8 : true), {
|
||||||
|
// if password present, must be at least 8 chars
|
||||||
message: 'Password must be at least 8 characters.',
|
message: 'Password must be at least 8 characters.',
|
||||||
}),
|
}),
|
||||||
groups: z.array(
|
groups: z.array(z.string()),
|
||||||
z.string().refine((v) => db.data.groups.find((g) => g.id === v), {
|
devices: z.array(z.string()),
|
||||||
message: 'Invalid group ID.',
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
devices: z.array(
|
|
||||||
z.string().refine((v) => db.data.devices.find((d) => d.id === v), {
|
|
||||||
message: 'Invalid device ID.',
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const parsed = schema.safeParse({
|
const parsed = schema.safeParse({
|
||||||
@ -63,49 +57,35 @@ export const actions = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!parsed.success) {
|
if (!parsed.success) {
|
||||||
return fail(400, { error: parsed.error.errors[0].message });
|
return PARSE_ERROR(parsed.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.id === 'new') {
|
if (params.id === 'new') {
|
||||||
await db.update(({ users }) => {
|
const err = await users.create({
|
||||||
users.push({
|
...parsed.data,
|
||||||
id: nanoid(),
|
password: bcrypt.hashSync(parsed.data.password, 10),
|
||||||
name: parsed.data.name,
|
|
||||||
admin: parsed.data.admin,
|
|
||||||
groups: parsed.data.groups,
|
|
||||||
devices: parsed.data.devices,
|
|
||||||
password: bcrypt.hashSync(parsed.data.password!, 10),
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return { success: true };
|
return err ? err.toFail() : SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
let found = false;
|
let updatedUser: Updated<User> = { id: params.id ?? '', ...parsed.data };
|
||||||
await db.update(({ users }) => {
|
|
||||||
let user = users.find((u) => u.id === params.id);
|
|
||||||
if (!user) return;
|
|
||||||
|
|
||||||
user.name = parsed.data.name;
|
if (parsed.data.password.length > 0) {
|
||||||
user.admin = parsed.data.admin;
|
updatedUser.password = bcrypt.hashSync(parsed.data.password, 10);
|
||||||
user.groups = parsed.data.groups;
|
} else {
|
||||||
user.devices = parsed.data.devices;
|
// avoid saving empty passwpord
|
||||||
|
delete updatedUser.password;
|
||||||
if (parsed.data.password && parsed.data.password.length > 0) {
|
|
||||||
user.password = bcrypt.hashSync(parsed.data.password, 10);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
found = true;
|
const err = await users.update(updatedUser);
|
||||||
});
|
return err ? err.toFail() : SUCCESS;
|
||||||
|
|
||||||
return found ? { success: true } : fail(404, { error: 'User not found.' });
|
|
||||||
},
|
},
|
||||||
delete: async ({ locals: { guard }, params }) => {
|
|
||||||
if (guard.requiresAdmin().isFailed()) {
|
|
||||||
return fail(403);
|
|
||||||
}
|
|
||||||
|
|
||||||
db.data.users = db.data.users.filter((u) => u.id !== params.id);
|
delete: async ({ locals: { guard }, params }) => {
|
||||||
db.write();
|
if (guard.requiresAdmin().isFailed()) return FORBIDDEN;
|
||||||
|
|
||||||
|
const err = await users.delete(params.id ?? '');
|
||||||
|
return err ? err.toFail() : SUCCESS;
|
||||||
},
|
},
|
||||||
} satisfies Actions;
|
} satisfies Actions;
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<p class="text-white">{form?.error}</p>
|
||||||
<EditPage createOnly={!data.user}>
|
<EditPage createOnly={!data.user}>
|
||||||
<InputText
|
<InputText
|
||||||
name="name"
|
name="name"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { db } from '$lib/server/db';
|
import { users } from '$lib/server/db';
|
||||||
import { createSession, getUserFromSession } from '$lib/server/sessions';
|
import { createSession } from '$lib/server/sessions';
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { fail, redirect } from '@sveltejs/kit';
|
||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
import type { Actions } from './$types';
|
import type { Actions } from './$types';
|
||||||
import { dev } from '$app/environment';
|
import { dev } from '$app/environment';
|
||||||
@ -16,17 +16,17 @@ export const actions = {
|
|||||||
const password = data.get('password')?.toString();
|
const password = data.get('password')?.toString();
|
||||||
|
|
||||||
if (!username || !password) {
|
if (!username || !password) {
|
||||||
return {
|
return fail(400, {
|
||||||
error: 'MISSING_CREDENTIALS',
|
error: 'MISSING_CREDENTIALS',
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = db.data.users.find((u) => u.name === username);
|
const user = await users.getByName(username);
|
||||||
|
|
||||||
if (!user || !bcrypt.compareSync(password, user.password)) {
|
if (!user || !bcrypt.compareSync(password, user.password)) {
|
||||||
return {
|
return fail(403, {
|
||||||
error: 'INVALID_CREDENTIALS',
|
error: 'INVALID_CREDENTIALS',
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
cookies.set(
|
cookies.set(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user