This commit is contained in:
Omer Sabic 2024-04-01 23:17:17 +02:00
parent cc0cb11879
commit feaea3975e
24 changed files with 948 additions and 121 deletions

View File

@ -0,0 +1,13 @@
DO $$ BEGIN
CREATE TYPE "bookType" AS ENUM('shonen', 'seinen', 'manhua');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
CREATE TYPE "roleEnum" AS ENUM('Member', 'Admin');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
ALTER TABLE "users" ADD COLUMN "role" "roleEnum" DEFAULT 'Member' NOT NULL;

View File

@ -0,0 +1,2 @@
ALTER TABLE "users" ALTER COLUMN "id" SET DATA TYPE text;--> statement-breakpoint
ALTER TABLE "users" ALTER COLUMN "id" DROP DEFAULT;

View File

@ -0,0 +1 @@
ALTER TABLE "reading_progress" ALTER COLUMN "user_id" SET DATA TYPE text;

View File

@ -0,0 +1,165 @@
{
"id": "94be70b1-469e-45d7-b78e-ce1cbe863401",
"prevId": "432591f9-af02-4d29-89ec-2c89c53477ff",
"version": "5",
"dialect": "pg",
"tables": {
"books": {
"name": "books",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"type": {
"name": "type",
"type": "bookType",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"reading_progress": {
"name": "reading_progress",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"book_id": {
"name": "book_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"chapters_read": {
"name": "chapters_read",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"date_created": {
"name": "date_created",
"type": "date",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"reading_progress_user_id_users_id_fk": {
"name": "reading_progress_user_id_users_id_fk",
"tableFrom": "reading_progress",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"reading_progress_book_id_books_id_fk": {
"name": "reading_progress_book_id_books_id_fk",
"tableFrom": "reading_progress",
"tableTo": "books",
"columnsFrom": [
"book_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"discord_id": {
"name": "discord_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"role": {
"name": "role",
"type": "roleEnum",
"primaryKey": false,
"notNull": true,
"default": "'Member'"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"bookType": {
"name": "bookType",
"values": {
"shonen": "shonen",
"seinen": "seinen",
"manhua": "manhua"
}
},
"roleEnum": {
"name": "roleEnum",
"values": {
"Member": "Member",
"Admin": "Admin"
}
}
},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,164 @@
{
"id": "704381af-461b-4b62-b176-e887328a56d6",
"prevId": "94be70b1-469e-45d7-b78e-ce1cbe863401",
"version": "5",
"dialect": "pg",
"tables": {
"books": {
"name": "books",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"type": {
"name": "type",
"type": "bookType",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"reading_progress": {
"name": "reading_progress",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"book_id": {
"name": "book_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"chapters_read": {
"name": "chapters_read",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"date_created": {
"name": "date_created",
"type": "date",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"reading_progress_user_id_users_id_fk": {
"name": "reading_progress_user_id_users_id_fk",
"tableFrom": "reading_progress",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"reading_progress_book_id_books_id_fk": {
"name": "reading_progress_book_id_books_id_fk",
"tableFrom": "reading_progress",
"tableTo": "books",
"columnsFrom": [
"book_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"discord_id": {
"name": "discord_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"role": {
"name": "role",
"type": "roleEnum",
"primaryKey": false,
"notNull": true,
"default": "'Member'"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"bookType": {
"name": "bookType",
"values": {
"shonen": "shonen",
"seinen": "seinen",
"manhua": "manhua"
}
},
"roleEnum": {
"name": "roleEnum",
"values": {
"Member": "Member",
"Admin": "Admin"
}
}
},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -0,0 +1,164 @@
{
"id": "2bef9e91-c268-4187-9a46-731b75855e99",
"prevId": "704381af-461b-4b62-b176-e887328a56d6",
"version": "5",
"dialect": "pg",
"tables": {
"books": {
"name": "books",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"type": {
"name": "type",
"type": "bookType",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"reading_progress": {
"name": "reading_progress",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"book_id": {
"name": "book_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"chapters_read": {
"name": "chapters_read",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"date_created": {
"name": "date_created",
"type": "date",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"reading_progress_user_id_users_id_fk": {
"name": "reading_progress_user_id_users_id_fk",
"tableFrom": "reading_progress",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"reading_progress_book_id_books_id_fk": {
"name": "reading_progress_book_id_books_id_fk",
"tableFrom": "reading_progress",
"tableTo": "books",
"columnsFrom": [
"book_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"discord_id": {
"name": "discord_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"role": {
"name": "role",
"type": "roleEnum",
"primaryKey": false,
"notNull": true,
"default": "'Member'"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"bookType": {
"name": "bookType",
"values": {
"shonen": "shonen",
"seinen": "seinen",
"manhua": "manhua"
}
},
"roleEnum": {
"name": "roleEnum",
"values": {
"Member": "Member",
"Admin": "Admin"
}
}
},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@ -8,6 +8,27 @@
"when": 1711490394796,
"tag": "0000_fixed_the_captain",
"breakpoints": true
},
{
"idx": 1,
"version": "5",
"when": 1711814267500,
"tag": "0001_old_bromley",
"breakpoints": true
},
{
"idx": 2,
"version": "5",
"when": 1711828439946,
"tag": "0002_far_xavin",
"breakpoints": true
},
{
"idx": 3,
"version": "5",
"when": 1711828480011,
"tag": "0003_sloppy_ironclad",
"breakpoints": true
}
]
}

9
src/app.d.ts vendored
View File

@ -3,7 +3,14 @@
declare global {
namespace App {
// interface Error {}
// interface Locals {}
interface Locals {
user: {
id: string,
name: string,
discordid: string,
role: Member | Admin
}
}
// interface PageData {}
// interface PageState {}
// interface Platform {}

View File

@ -0,0 +1,55 @@
<script>
import { Button } from "$lib/components/ui/button";
import * as Popover from "$lib/components/ui/popover";
import * as Select from "$lib/components/ui/select";
import * as Command from "$lib/components/ui/command";
import { ChevronDown } from "lucide-svelte";
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
/**
* @type {"role" | "manga"}
*/
export let type = "role";
export let placeholder = "";
const types = {
"role": ["Member", "Admin"],
"manga": ['shonen', 'seinen', 'manhua']
}
const options = types[type];
/** @type {string} */
export let selected = options[0];
/** @param {string} value */
function select(value) {
selected = value;
dispatch("select", value);
}
/** @type {string} */
export let name = type;
$: selected;
</script>
<Select.Root
selected={{ value: selected, label: selected }}
onSelectedChange={(e) => {
select(e?.value || "");
}}
>
<Select.Trigger class="w-[180px]">
<Select.Value placeholder={placeholder} />
</Select.Trigger>
<Select.Content>
<Select.Group>
<Select.Label>Roles</Select.Label>
{#each options as option}
<Select.Item value={option} label={option}>{option}</Select.Item>
{/each}
</Select.Group>
</Select.Content>
<Select.Input {name} />
</Select.Root>

View File

@ -1,68 +0,0 @@
<script>
import { Button } from "$lib/components/ui/button";
import * as Popover from "$lib/components/ui/popover";
import * as Select from "$lib/components/ui/select";
import * as Command from "$lib/components/ui/command";
import { ChevronDown } from "lucide-svelte";
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
const roles = ["Member", "Admin"];
/** @type {"Member" | "Admin"} */
export let selected = "Member";
/** @param {"Member" | "Admin"} value */
function select(value) {
console.log(value);
selected = value;
dispatch("select", value);
}
$: selected;
</script>
<!-- <Popover.Root>
<Popover.Trigger asChild let:builder>
<Button builders={[builder]} variant="outline" class="ml-auto">
{selected}
<ChevronDown class="ml-2 h-4 w-4 text-muted-foreground" />
</Button>
</Popover.Trigger>
<Popover.Content class="p-0" align="end">
<Command.Root>
<Command.List>
<Command.Group>
<Command.Item class="flex flex-col items-start space-y-1 px-4 py-2 cursor-pointer" on:click={() => select("Member")}>
<p>Member</p>
<p class="text-sm text-muted-foreground">
Can read manga and submit updates.
</p>
</Command.Item>
<Command.Item class="flex flex-col items-start space-y-1 px-4 py-2 cursor-pointer" on:click={() => select("Admin")}>
<p>Admin</p>
<p class="text-sm text-muted-foreground">
Can read, edit, delete and add users, manga and activity.
</p>
</Command.Item>
</Command.Group>
</Command.List>
</Command.Root>
</Popover.Content>
</Popover.Root> -->
<Select.Root>
<Select.Trigger class="w-[180px]">
<Select.Value placeholder="Select a role" />
</Select.Trigger>
<Select.Content>
<Select.Group>
<Select.Label>Roles</Select.Label>
{#each roles as role}
<Select.Item value={role} label={role}>{role}</Select.Item>
{/each}
</Select.Group>
</Select.Content>
<Select.Input name="userRole" />
</Select.Root>

8
src/lib/auth/utils.js Normal file
View File

@ -0,0 +1,8 @@
/**
*
* @param {import("@sveltejs/kit").RequestEvent} event
*/
export async function getRole(event) {
if(!event.locals.user) return -1;
return ["Member", "Admin"].indexOf(event.locals.user.role);
}

View File

@ -17,14 +17,14 @@
<Button variant="ghost" builders={[builder]} class="relative h-8 w-8 rounded-full">
<Avatar.Root class="h-8 w-8">
<!-- <Avatar.Image src="/avatars/01.png" alt="@{user.username}" /> -->
<Avatar.Fallback>{user.username?.substring(0,2)}</Avatar.Fallback>
<Avatar.Fallback>{user.name?.substring(0,2)}</Avatar.Fallback>
</Avatar.Root>
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content class="w-56" align="end">
<DropdownMenu.Label class="font-normal">
<div class="flex flex-col space-y-1">
<p class="text-sm font-medium leading-none">@{user.username}</p>
<p class="text-sm font-medium leading-none">@{user.name}</p>
</div>
</DropdownMenu.Label>
<DropdownMenu.Separator />

View File

@ -1,10 +1,10 @@
import { pgTable, serial, text, varchar } from "drizzle-orm/pg-core";
import { drizzle } from "drizzle-orm/node-postgres";
import { Client } from "pg";
import pg from "pg";
import * as env from '$env/static/private';
// or
const client = new Client({
const client = new pg.Client({
host: env.DB_HOST,
user: env.DB_USER,
password: env.DB_PASSWORD,

View File

@ -2,11 +2,19 @@ import { date, integer, pgEnum, pgTable, serial, text, uuid, varchar } from 'dri
import { drizzle } from 'drizzle-orm/node-postgres';
export const bookType = pgEnum('bookType', ['shonen', 'seinen', 'manhua']);
export const roleEnum = pgEnum('roleEnum', ['Member', 'Admin']);
export const usersTable = pgTable('users', {
id: uuid('id').primaryKey().defaultRandom(),
discord_id: text('discord_id').notNull(),
name: text('name').notNull()
id: text('id').primaryKey().$defaultFn(() => {
var text = "";
var possible = "abcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < 4; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}),
discord_id: text('discord_id').notNull(),
name: text('name').notNull(),
role: roleEnum("role").default("Member").notNull()
});
export const booksTable = pgTable('books', {
@ -17,7 +25,7 @@ export const booksTable = pgTable('books', {
export const readingUpdatesTable = pgTable('reading_progress', {
id: uuid('id').primaryKey().defaultRandom(),
user_id: uuid('user_id').references(() => usersTable.id).notNull(),
user_id: text('user_id').references(() => usersTable.id).notNull(),
book_id: uuid('book_id').references(() => booksTable.id).notNull(),
chapters_read: integer('chapters_read').notNull(),
date_created: date('date_created').defaultNow().notNull()

View File

@ -3,17 +3,7 @@ import { writable } from 'svelte/store'
let stored = null;
if(browser && localStorage.auth) {
stored = JSON.parse(localStorage.auth);
}
/**
* @type {import('svelte/store').Writable<{id: string, username: string} | null>}
*/
export const userStore = writable(stored || null)
userStore.subscribe((value) => {
if(browser) {
localStorage.auth = JSON.stringify(value)
}
})
export const userStore = writable(stored || null)

View File

@ -5,17 +5,37 @@
import { Label } from "$lib/components/ui/label";
import { Input } from "$lib/components/ui/input";
import { userStore } from "$lib/state";
import {onMount} from "svelte";
// const user = {
// id: "helloworld",
// username: "Jar Allerton",
// };
let tokenInput = "";
/** @type {string} */
let authError = "";
onMount(async () => {
const me = await (await fetch("/api/me")).json();
function logIn() {
userStore.set({
id: tokenInput,
username: "joe"
})
if(me.success) {
userStore.set(me.user);
}
});
async function logIn() {
let result = await fetch("/api/login", {
method: "POST",
body: JSON.stringify({
token: tokenInput
})
});
let parsed = await result.json();
if(!parsed.success) return authError = "Invalid token";
userStore.set(parsed.user);
return;
}
</script>
@ -35,6 +55,7 @@
<Popover.Content class="w-80">
<div class="grid gap-4">
<div class="grid gap-2">
<p class="text-red-600">{authError}</p>
<div
class="grid grid-cols-3 items-center gap-4"
>

View File

@ -0,0 +1,19 @@
import db from '$lib/db/db.server';
import { usersTable } from '$lib/db/schema.server';
import { json } from '@sveltejs/kit';
import { eq } from 'drizzle-orm';
/** @type {import('./$types').RequestHandler} */
export async function POST(event) {
let data = await event.request.json();
if(!data.token) return json({success: false});
let [user] = await db.select().from(usersTable).where(eq(usersTable.id, data.token));
if(!user) return json({success:false});
event.cookies.set("token", user.id, {
path: "/"
});
return json({ success: true, user });
}

View File

@ -0,0 +1,15 @@
import db from '$lib/db/db.server';
import { usersTable } from '$lib/db/schema.server';
import { json } from '@sveltejs/kit';
import { eq } from 'drizzle-orm';
/** @type {import('./$types').RequestHandler} */
export async function GET(event) {
let token = event.cookies.get("token");
if(!token) return json({success: false});
let [user] = await db.select().from(usersTable).where(eq(usersTable.id, token));
if(!user) return json({success:false});
return json({ success: true, user });
}

View File

@ -0,0 +1,27 @@
import { json } from '@sveltejs/kit';
import db from '$lib/db/db.server';
import { usersTable } from '$lib/db/schema.server';
import { eq } from 'drizzle-orm';
import { getRole } from '$lib/auth/utils';
/** @type {import('./$types').RequestHandler} */
export async function GET(event) {
console.log(getRole(event))
const users = await db.select().from(usersTable);
return json({ success: true, users });
}
/** @type {import('./$types').RequestHandler} */
export async function PUT({ request }) {
const body = await request.json();
if (!body.role || !body.id) return new Response(null, {
status: 400
});
db.update(usersTable).set({
role: body.role
}).where(eq(usersTable.id, body.id));
return json({success: true});
}

View File

@ -0,0 +1,61 @@
import db from "$lib/db/db.server";
import { booksTable, usersTable } from "$lib/db/schema.server";
import { json, type Actions, type ServerLoadEvent } from "@sveltejs/kit";
import type { PageServerLoad } from "./$types";
import { eq } from "drizzle-orm";
export const load: PageServerLoad = async () => {
const books = await db.select().from(booksTable);
return { books };
}
export const actions: Actions = {
create: async (event) => {
// console.log("hello");
const data = await event.request.formData();
if(!data.has("name") || !data.has("userRole")) return json({
success: false,
message: "Missing property"
});
await db.insert(booksTable).values({
name: data.get("name")?.valueOf(),
type: data.get("type")?.valueOf()
});
return json({
success: true
})
},
updateType: async (event) => {
const data = await event.request.json();
if(!data.id || !data.type) return {
success: false,
message: "Missing property"
};
await db.update(booksTable).set({
type: data.type
}).where(eq(booksTable.id, data.id));
return {
success: true
}
},
delete: async (event) => {
const data = await event.request.json();
if(!data.id) return json({
success: false
});
await db.delete(booksTable).where(eq(data.id, booksTable.id));
return {
success: true
}
}
};

View File

@ -0,0 +1,81 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button/index.js";
import Nav from "$lib/admin/nav.svelte";
import * as Avatar from "$lib/components/ui/avatar";
import Dropdown from "$lib/admin/dropdown.svelte";
import * as Dialog from "$lib/components/ui/dialog";
import { Label } from "$lib/components/ui/label";
import {Input} from "$lib/components/ui/input";
import {X} from "lucide-svelte";
import type { PageServerData } from "./$types";
export let data: PageServerData;
let books = data.books;
</script>
<Nav title="Manga">
<Dialog.Root>
<Dialog.Trigger class="w-12">
<Button>
Add Manga
</Button>
</Dialog.Trigger>
<Dialog.Content class="sm:max-w-[425px]">
<Dialog.Header>
<Dialog.Title>Create manga</Dialog.Title>
</Dialog.Header>
<form method="POST" id="form" action="create">
<div class="grid gap-4 py-4">
<div class="grid grid-cols-4 items-center gap-4">
<Label for="name" class="text-right">Name</Label>
<Input id="name" value="Pedro Duarte" class="col-span-3" name="name" />
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right">Type</Label>
<Dropdown type="manga" name="type" selected="shonen" />
</div>
</div>
</form>
<Dialog.Footer>
<Button type="submit" form="form">Save changes</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>
<div class="grid gap-6 max-w-2xl">
{#each books as book}
<div class="flex items-center justify-between space-x-4">
<div class="flex items-center space-x-4">
<div>
<p class="text-sm font-medium leading-none">
{book.name}
</p>
</div>
</div>
<Dropdown selected={book.type} type="manga" on:select={val => {
console.log(val)
fetch("?/updateType", {
method: "POST",
body: JSON.stringify({
id: book.id,
type: val.detail
})
})
}} />
<Button variant="ghost" on:click={() => {
fetch("?/delete", {
method: "POST",
body: JSON.stringify({
id: book.id
})
});
books = books.filter(x=>x.id!==book.id);
}}>
<X class="w-5 h-5"/>
</Button>
</div>
{/each}
</div>
</Nav>

View File

@ -1,7 +1,53 @@
import type { Actions } from "@sveltejs/kit";
import db from "$lib/db/db.server";
import { usersTable } from "$lib/db/schema.server";
import { json, type Actions, type ServerLoadEvent } from "@sveltejs/kit";
import type { PageServerLoad } from "./$types";
import { eq } from "drizzle-orm";
export const load: PageServerLoad = async () => {
const users = await db.select().from(usersTable);
return { users };
}
export const actions: Actions = {
default: async (event) => {
console.log(event.request.body);
create: async (event) => {
// console.log("hello");
const data = await event.request.formData();
if(!data.has("name") || !data.has("userRole")) return json({
success: false,
message: "Missing property"
});
await db.insert(usersTable).values({
name: data.get("name")?.valueOf(),
role: data.get("userRole")?.valueOf(),
discord_id: data.get("discordid")?.valueOf()
});
},
updateRole: async (event) => {
const data = await event.request.json();
if(!data.user_id || !data.role) return json({
success: false,
message: "Missing property"
});
db.update(usersTable).set({
role: data.role
}).where(eq(usersTable.id, data.user_id));
},
delete: async (event) => {
const data = await event.request.json();
if(!data.id) return json({
success: false
});
await db.delete(usersTable).where(eq(data.id, usersTable.id));
return {
success: true
}
}
};

View File

@ -2,22 +2,17 @@
import { Button } from "$lib/components/ui/button/index.js";
import Nav from "$lib/admin/nav.svelte";
import * as Avatar from "$lib/components/ui/avatar";
import UserRoleDropdown from "$lib/admin/user-role-dropdown.svelte";
import Dropdown from "$lib/admin/dropdown.svelte";
import * as Dialog from "$lib/components/ui/dialog";
import { Label } from "$lib/components/ui/label";
import {Input} from "$lib/components/ui/input";
import {X} from "lucide-svelte";
import { onMount } from "svelte";
import type { PageServerLoad } from "./$types";
import type { PageServerData } from "./$types";
const users: { username: string; role: "Member" | "Admin"; }[] = [
{
username: "Joe Biden",
role: "Member",
},
{
username: "BoeJiden",
role: "Admin",
},
];
export let data: PageServerData;
let users = data.users;
</script>
<Nav title="Users">
@ -31,18 +26,25 @@
<Dialog.Header>
<Dialog.Title>Create user</Dialog.Title>
</Dialog.Header>
<div class="grid gap-4 py-4">
<div class="grid grid-cols-4 items-center gap-4">
<Label for="name" class="text-right">Username</Label>
<Input id="name" value="Pedro Duarte" class="col-span-3" />
<form method="POST" id="form" action="?/create">
<div class="grid gap-4 py-4">
<div class="grid grid-cols-4 items-center gap-4">
<Label for="name" class="text-right">Name</Label>
<Input id="name" value="Pedro Duarte" class="col-span-3" name="name" />
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="discordid" class="text-right">Discord ID</Label>
<Input id="discordid" class="col-span-3" name="discordid" />
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right">Role</Label>
<Dropdown name="role" type="role" placeholder="Select a role" />
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="username" class="text-right">Role</Label>
<UserRoleDropdown />
</div>
</div>
</form>
<Dialog.Footer>
<Button type="submit">Save changes</Button>
<Button type="submit" form="form">Save changes</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>
@ -53,16 +55,30 @@
<Avatar.Root>
<!-- <Avatar.Image src="/avatars/01.png" alt="Sofia Davis" /> -->
<Avatar.Fallback
>{user.username.substring(0, 2)}</Avatar.Fallback
>{user.name.substring(0, 2)}</Avatar.Fallback
>
</Avatar.Root>
<div>
<p class="text-sm font-medium leading-none">
{user.username}
{user.name}
</p>
</div>
</div>
<UserRoleDropdown selected={user.role} />
<Dropdown selected={user.role} type="role" on:select={(e) => {
console.log(e.detail.value);
}} />
<Button variant="ghost" on:click={() => {
fetch("?/delete", {
method: "POST",
body: JSON.stringify({
id: user.id
})
});
users = users.filter(x=>x.id!==user.id);
}}>
<X class="w-5 h-5"/>
</Button>
</div>
{/each}
</div>

View File

@ -0,0 +1,11 @@
import db from '$lib/db/db.server';
import { usersTable } from '$lib/db/schema.server';
import type { Handle } from '@sveltejs/kit';
import { eq } from 'drizzle-orm';
export const handle: Handle = async ({ event, resolve }) => {
let session = event.cookies.get("token");
if(session) event.locals.user = await db.select().from(usersTable).where(eq(usersTable.id, session));
const response = await resolve(event);
return response;
};