From fcc8646ee40aaaaf80b9aec6155aa046dcbe5d34 Mon Sep 17 00:00:00 2001
From: Daniel Adu-Gyan <26229521+danieladugyan@users.noreply.github.com>
Date: Sun, 16 Nov 2025 10:52:11 +0100
Subject: [PATCH 1/9] Move door loading from page to layout
---
.../(app)/admin/doors/{+page.server.ts => +layout.server.ts} | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
rename src/routes/(app)/admin/doors/{+page.server.ts => +layout.server.ts} (69%)
diff --git a/src/routes/(app)/admin/doors/+page.server.ts b/src/routes/(app)/admin/doors/+layout.server.ts
similarity index 69%
rename from src/routes/(app)/admin/doors/+page.server.ts
rename to src/routes/(app)/admin/doors/+layout.server.ts
index 5b6457849..140fdaa65 100644
--- a/src/routes/(app)/admin/doors/+page.server.ts
+++ b/src/routes/(app)/admin/doors/+layout.server.ts
@@ -1,8 +1,8 @@
import apiNames from "$lib/utils/apiNames";
import { authorize } from "$lib/utils/authorization";
-import type { PageServerLoad } from "./$types";
+import type { LayoutServerLoad } from "./$types";
-export const load: PageServerLoad = async ({ locals }) => {
+export const load: LayoutServerLoad = async ({ locals }) => {
const { prisma, user } = locals;
authorize(apiNames.DOOR.READ, user);
From f8998c904dc47cd0b2e512dd0198ce81a8af22f5 Mon Sep 17 00:00:00 2001
From: Daniel Adu-Gyan <26229521+danieladugyan@users.noreply.github.com>
Date: Sun, 16 Nov 2025 11:36:41 +0100
Subject: [PATCH 2/9] Implement /admin/doors layout
---
.../(app)/admin/doors/+layout.server.ts | 3 +-
src/routes/(app)/admin/doors/+layout.svelte | 46 +++++++++++++++++++
2 files changed, 48 insertions(+), 1 deletion(-)
create mode 100644 src/routes/(app)/admin/doors/+layout.svelte
diff --git a/src/routes/(app)/admin/doors/+layout.server.ts b/src/routes/(app)/admin/doors/+layout.server.ts
index 140fdaa65..732f5677f 100644
--- a/src/routes/(app)/admin/doors/+layout.server.ts
+++ b/src/routes/(app)/admin/doors/+layout.server.ts
@@ -2,12 +2,13 @@ import apiNames from "$lib/utils/apiNames";
import { authorize } from "$lib/utils/authorization";
import type { LayoutServerLoad } from "./$types";
-export const load: LayoutServerLoad = async ({ locals }) => {
+export const load: LayoutServerLoad = async ({ locals, params }) => {
const { prisma, user } = locals;
authorize(apiNames.DOOR.READ, user);
const doors = await prisma.door.findMany();
return {
doors,
+ slug: params.slug,
};
};
diff --git a/src/routes/(app)/admin/doors/+layout.svelte b/src/routes/(app)/admin/doors/+layout.svelte
new file mode 100644
index 000000000..4c1c16c76
--- /dev/null
+++ b/src/routes/(app)/admin/doors/+layout.svelte
@@ -0,0 +1,46 @@
+
+
+
+ Doors
+
+
+
From 6f2bc2620fae0d7eed04ab0f92f13e7f447c7090 Mon Sep 17 00:00:00 2001
From: Daniel Adu-Gyan <26229521+danieladugyan@users.noreply.github.com>
Date: Sun, 16 Nov 2025 11:37:22 +0100
Subject: [PATCH 3/9] Remove whitespace in doors +page.server.ts
---
.../admin/doors/edit/[slug]/+page.server.ts | 28 +++----------------
1 file changed, 4 insertions(+), 24 deletions(-)
diff --git a/src/routes/(app)/admin/doors/edit/[slug]/+page.server.ts b/src/routes/(app)/admin/doors/edit/[slug]/+page.server.ts
index 0fabe0e68..f5c626516 100644
--- a/src/routes/(app)/admin/doors/edit/[slug]/+page.server.ts
+++ b/src/routes/(app)/admin/doors/edit/[slug]/+page.server.ts
@@ -13,32 +13,12 @@ export const load: PageServerLoad = async ({ locals, params }) => {
const doorAccessPolicies = await prisma.doorAccessPolicy.findMany({
where: {
doorName: params.slug,
- OR: [
- {
- endDatetime: {
- gte: new Date(),
- },
- },
- {
- endDatetime: null,
- },
- ],
+ OR: [{ endDatetime: { gte: new Date() } }, { endDatetime: null }],
},
- include: {
- member: true,
- },
- orderBy: [
- {
- startDatetime: "asc",
- },
- {
- role: "asc",
- },
- {
- studentId: "asc",
- },
- ],
+ include: { member: true },
+ orderBy: [{ startDatetime: "asc" }, { role: "asc" }, { studentId: "asc" }],
});
+
return {
doorAccessPolicies,
createForm: await superValidate(zod4(createSchema), { id: "createForm" }),
From 2f6fdf1fc49c933d64e2cec20e08d447cb52dac3 Mon Sep 17 00:00:00 2001
From: Daniel Adu-Gyan <26229521+danieladugyan@users.noreply.github.com>
Date: Sun, 16 Nov 2025 11:37:55 +0100
Subject: [PATCH 4/9] Add /admin/doors empty state page
---
src/routes/(app)/admin/doors/+page.svelte | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/src/routes/(app)/admin/doors/+page.svelte b/src/routes/(app)/admin/doors/+page.svelte
index 0a8eebb03..6f04679fa 100644
--- a/src/routes/(app)/admin/doors/+page.svelte
+++ b/src/routes/(app)/admin/doors/+page.svelte
@@ -1,5 +1,15 @@
-
-
+
+
+
+
+
+ Välj en dörr för att hantera åtkomstregler
+
+
+
+
From 0542c9aab16d11e2a07dd018ef122be8b498167c Mon Sep 17 00:00:00 2001
From: Daniel Adu-Gyan <26229521+danieladugyan@users.noreply.github.com>
Date: Mon, 17 Nov 2025 23:26:24 +0100
Subject: [PATCH 5/9] Add door access rule form
---
.../admin/doors/edit/[slug]/+page.server.ts | 82 +++++----
.../admin/doors/edit/[slug]/+page.svelte | 156 +++++++++++++++++-
2 files changed, 190 insertions(+), 48 deletions(-)
diff --git a/src/routes/(app)/admin/doors/edit/[slug]/+page.server.ts b/src/routes/(app)/admin/doors/edit/[slug]/+page.server.ts
index f5c626516..e8dc79773 100644
--- a/src/routes/(app)/admin/doors/edit/[slug]/+page.server.ts
+++ b/src/routes/(app)/admin/doors/edit/[slug]/+page.server.ts
@@ -1,10 +1,11 @@
import apiNames from "$lib/utils/apiNames";
import { z } from "zod";
import type { Actions, PageServerLoad } from "./$types";
-import { message, setError, superValidate } from "sveltekit-superforms/server";
+import { message, superValidate } from "sveltekit-superforms/server";
import { zod4 } from "sveltekit-superforms/adapters";
import { fail } from "@sveltejs/kit";
import { authorize } from "$lib/utils/authorization";
+import authorizedPrismaClient from "$lib/server/authorizedPrisma";
export const load: PageServerLoad = async ({ locals, params }) => {
const { prisma, user } = locals;
@@ -21,23 +22,43 @@ export const load: PageServerLoad = async ({ locals, params }) => {
return {
doorAccessPolicies,
- createForm: await superValidate(zod4(createSchema), { id: "createForm" }),
- banForm: await superValidate(zod4(createSchema), { id: "banForm" }),
+ createForm: await superValidate(zod4(createSchema)),
deleteForm: await superValidate(zod4(deleteSchema)),
};
};
const createSchema = z
.object({
- studentId: z.string().min(1).optional(),
- role: z.string().min(1).optional(),
+ subject: z.string().min(1),
+ type: z.enum(["member", "role"]).default("member"),
+ mode: z.enum(["allow", "deny"]).default("allow"),
startDatetime: z.date().optional(),
endDatetime: z.date().optional(),
- information: z.string().optional(),
+ reason: z.string().optional(),
})
- .refine((data) => data.studentId != null || data.role != null, {
- message: "Du måste ange roll eller student id",
- });
+ .refine(
+ (data) => (data.startDatetime ?? 0) < (data.endDatetime ?? Infinity),
+ {
+ message: "Slutdatum kan inte vara före startdatum",
+ path: ["endDatetime"],
+ },
+ )
+ .refine(
+ async ({ type, subject }) => {
+ if (type === "member") {
+ // check if member exists
+ return await authorizedPrismaClient.member.findFirst({
+ where: { studentId: subject },
+ });
+ } else {
+ // check if role exists
+ return await authorizedPrismaClient.position.findFirst({
+ where: { id: { startsWith: `${subject}%` } },
+ });
+ }
+ },
+ { message: "Medlemmen/rollen finns inte", path: ["subject"] },
+ );
const deleteSchema = z.object({
id: z.string(),
@@ -49,47 +70,18 @@ export const actions: Actions = {
const form = await superValidate(request, zod4(createSchema));
if (!form.valid) return fail(400, { form });
const doorName = params.slug;
- const { studentId } = form.data;
- if (
- studentId &&
- (await prisma.member.count({
- where: { studentId },
- })) <= 0
- ) {
- return setError(form, "studentId", "Medlemmen finns inte");
- }
- await prisma.doorAccessPolicy.create({
- data: {
- doorName,
- ...form.data,
- },
- });
- return message(form, {
- message: "Dörrpolicy skapad",
- type: "success",
- });
- },
- ban: async ({ request, locals, params }) => {
- const { prisma } = locals;
- const form = await superValidate(request, zod4(createSchema));
- if (!form.valid) return fail(400, { form });
- const doorName = params.slug;
- const { studentId } = form.data;
- if (
- studentId &&
- (await prisma.member.count({
- where: { studentId },
- })) <= 0
- ) {
- return setError(form, "studentId", "Medlemmen finns inte");
- }
+ const { mode, subject, type, startDatetime, endDatetime } = form.data;
+
await prisma.doorAccessPolicy.create({
data: {
doorName,
- isBan: true,
- ...form.data,
+ startDatetime,
+ endDatetime,
+ isBan: mode === "deny",
+ ...(type === "member" ? { studentId: subject } : { role: subject }),
},
});
+
return message(form, {
message: "Dörrpolicy skapad",
type: "success",
diff --git a/src/routes/(app)/admin/doors/edit/[slug]/+page.svelte b/src/routes/(app)/admin/doors/edit/[slug]/+page.svelte
index 0a8eebb03..fc0459e20 100644
--- a/src/routes/(app)/admin/doors/edit/[slug]/+page.svelte
+++ b/src/routes/(app)/admin/doors/edit/[slug]/+page.svelte
@@ -1,5 +1,155 @@
-
-
+
+
+
+
+ Add Access Rule
+
+ Grant or restrict access to {page.params["slug"]}
+
+
+
+
+
+
+
+
+
From eaa616d03206053747d9322b2a98f91f9d703f4a Mon Sep 17 00:00:00 2001
From: Daniel Adu-Gyan <26229521+danieladugyan@users.noreply.github.com>
Date: Tue, 18 Nov 2025 00:14:41 +0100
Subject: [PATCH 6/9] Add door access list
---
.../admin/doors/edit/[slug]/+page.server.ts | 2 +
.../admin/doors/edit/[slug]/+page.svelte | 65 +++++++++++++++++++
2 files changed, 67 insertions(+)
diff --git a/src/routes/(app)/admin/doors/edit/[slug]/+page.server.ts b/src/routes/(app)/admin/doors/edit/[slug]/+page.server.ts
index e8dc79773..5209ce226 100644
--- a/src/routes/(app)/admin/doors/edit/[slug]/+page.server.ts
+++ b/src/routes/(app)/admin/doors/edit/[slug]/+page.server.ts
@@ -27,6 +27,8 @@ export const load: PageServerLoad = async ({ locals, params }) => {
};
};
+// TODO: require reason and end date for member rules
+// TODO: don't allow banning groups since this is not implemented
const createSchema = z
.object({
subject: z.string().min(1),
diff --git a/src/routes/(app)/admin/doors/edit/[slug]/+page.svelte b/src/routes/(app)/admin/doors/edit/[slug]/+page.svelte
index fc0459e20..85fc2baac 100644
--- a/src/routes/(app)/admin/doors/edit/[slug]/+page.svelte
+++ b/src/routes/(app)/admin/doors/edit/[slug]/+page.svelte
@@ -18,8 +18,12 @@
SelectTrigger,
} from "$lib/components/ui/select";
import { superForm } from "$lib/utils/client/superForms";
+ import TrashIcon from "@lucide/svelte/icons/trash";
+ import { getFullName } from "$lib/utils/client/member";
+ import { Badge } from "$lib/components/ui/badge";
let { data }: PageProps = $props();
+ let policies = $derived(data.doorAccessPolicies);
const { form, errors, constraints, enhance } = superForm(data.createForm);
const types = [
@@ -52,6 +56,7 @@
diff --git a/src/routes/(app)/admin/doors/edit/[slug]/+page.server.ts b/src/routes/(app)/admin/doors/edit/[slug]/+page.server.ts
index 5209ce226..14bc2a184 100644
--- a/src/routes/(app)/admin/doors/edit/[slug]/+page.server.ts
+++ b/src/routes/(app)/admin/doors/edit/[slug]/+page.server.ts
@@ -3,14 +3,20 @@ import { z } from "zod";
import type { Actions, PageServerLoad } from "./$types";
import { message, superValidate } from "sveltekit-superforms/server";
import { zod4 } from "sveltekit-superforms/adapters";
-import { fail } from "@sveltejs/kit";
+import { error, fail } from "@sveltejs/kit";
import { authorize } from "$lib/utils/authorization";
import authorizedPrismaClient from "$lib/server/authorizedPrisma";
+import * as m from "$paraglide/messages";
-export const load: PageServerLoad = async ({ locals, params }) => {
+export const load: PageServerLoad = async ({ locals, params, parent }) => {
const { prisma, user } = locals;
authorize(apiNames.DOOR.READ, user);
+ const { doors } = await parent();
+ const door = doors.find((door) => door.name === params.slug);
+
+ if (!door) error(404, m.admin_doors_notFound());
+
const doorAccessPolicies = await prisma.doorAccessPolicy.findMany({
where: {
doorName: params.slug,
@@ -21,6 +27,7 @@ export const load: PageServerLoad = async ({ locals, params }) => {
});
return {
+ door,
doorAccessPolicies,
createForm: await superValidate(zod4(createSchema)),
deleteForm: await superValidate(zod4(deleteSchema)),
@@ -41,7 +48,7 @@ const createSchema = z
.refine(
(data) => (data.startDatetime ?? 0) < (data.endDatetime ?? Infinity),
{
- message: "Slutdatum kan inte vara före startdatum",
+ message: m.admin_doors_endDateBeforeStart(),
path: ["endDatetime"],
},
)
@@ -59,7 +66,7 @@ const createSchema = z
});
}
},
- { message: "Medlemmen/rollen finns inte", path: ["subject"] },
+ { message: m.admin_doors_memberOrRoleNotFound(), path: ["subject"] },
);
const deleteSchema = z.object({
@@ -85,7 +92,7 @@ export const actions: Actions = {
});
return message(form, {
- message: "Dörrpolicy skapad",
+ message: m.admin_doors_ruleCreated(),
type: "success",
});
},
@@ -98,7 +105,7 @@ export const actions: Actions = {
where: { id },
});
return message(form, {
- message: "Dörrpolicy raderad",
+ message: m.admin_doors_ruleDeleted(),
type: "success",
});
},
diff --git a/src/routes/(app)/admin/doors/edit/[slug]/+page.svelte b/src/routes/(app)/admin/doors/edit/[slug]/+page.svelte
index 30c9e3be6..448221b4a 100644
--- a/src/routes/(app)/admin/doors/edit/[slug]/+page.svelte
+++ b/src/routes/(app)/admin/doors/edit/[slug]/+page.svelte
@@ -1,6 +1,5 @@