From 37e75afd22b970a07f62a4fd9b2b6c8baa52b85b Mon Sep 17 00:00:00 2001 From: Samuel Shuert Date: Wed, 1 Jul 2026 00:07:34 +0000 Subject: [PATCH] feat(frontend): implement permissions --- frontend/lib/session.ts | 16 ++- frontend/lib/userPermissions.ts | 175 ++++++++++++++++++++++++++++++++ frontend/tsconfig.json | 14 +-- 3 files changed, 196 insertions(+), 9 deletions(-) create mode 100644 frontend/lib/userPermissions.ts diff --git a/frontend/lib/session.ts b/frontend/lib/session.ts index 0013f5e..c49c1ac 100644 --- a/frontend/lib/session.ts +++ b/frontend/lib/session.ts @@ -1,4 +1,5 @@ import { cookies } from "next/headers" +import { UserPermissions } from "./userPermissions" const BACKEND_PORT = process.env.BACKEND_PORT ?? 8632 const BACKEND_URL = `http://${process.env.BACKEND_URL ?? "localhost"}:${BACKEND_PORT}` @@ -7,7 +8,14 @@ export interface User { id: string full_name: string username: string - permissions: number + permissions: UserPermissions +} + +interface UserResponse { + id: string + full_name: string + username: string + permissions: bigint } export async function getSession(): Promise { @@ -23,5 +31,9 @@ export async function getSession(): Promise { }) if (!res.ok) return null - return res.json() + const res_data: UserResponse = await res.json() + return { + ...res_data, + permissions: UserPermissions.fromBits(res_data.permissions), + } } diff --git a/frontend/lib/userPermissions.ts b/frontend/lib/userPermissions.ts new file mode 100644 index 0000000..24559c4 --- /dev/null +++ b/frontend/lib/userPermissions.ts @@ -0,0 +1,175 @@ +export class UserPermissions { + readonly bits: bigint + + private constructor(bits: bigint) { + this.bits = bits + } + + private static of(bits: bigint): UserPermissions { + return new UserPermissions(bits) + } + + static readonly BatchRead = UserPermissions.of(1n << 0n) + static readonly BatchCreate = UserPermissions.of(1n << 1n) + static readonly BatchUpdate = UserPermissions.of(1n << 2n) + static readonly BatchDelete = UserPermissions.of(1n << 3n) + + static readonly SupplierRead = UserPermissions.of(1n << 4n) + static readonly SupplierCreate = UserPermissions.of(1n << 5n) + static readonly SupplierUpdate = UserPermissions.of(1n << 6n) + static readonly SupplierDelete = UserPermissions.of(1n << 7n) + + static readonly IngredientRead = UserPermissions.of(1n << 8n) + static readonly IngredientCreate = UserPermissions.of(1n << 9n) + static readonly IngredientUpdate = UserPermissions.of(1n << 10n) + static readonly IngredientDelete = UserPermissions.of(1n << 11n) + + static readonly FlavorRead = UserPermissions.of(1n << 12n) + static readonly FlavorCreate = UserPermissions.of(1n << 13n) + static readonly FlavorUpdate = UserPermissions.of(1n << 14n) + static readonly FlavorDelete = UserPermissions.of(1n << 15n) + + static readonly UserRead = UserPermissions.of(1n << 16n) + static readonly UserCreate = UserPermissions.of(1n << 17n) + static readonly UserUpdate = UserPermissions.of(1n << 18n) + static readonly UserDelete = UserPermissions.of(1n << 19n) + + static readonly CustomerRead = UserPermissions.of(1n << 20n) + static readonly CustomerCreate = UserPermissions.of(1n << 21n) + static readonly CustomerUpdate = UserPermissions.of(1n << 22n) + static readonly CustomerDelete = UserPermissions.of(1n << 23n) + + static readonly OrderRead = UserPermissions.of(1n << 24n) + static readonly OrderCreate = UserPermissions.of(1n << 25n) + static readonly OrderUpdate = UserPermissions.of(1n << 26n) + static readonly OrderDelete = UserPermissions.of(1n << 27n) + + static readonly Batch = UserPermissions.BatchRead.union( + UserPermissions.BatchCreate, + UserPermissions.BatchUpdate, + UserPermissions.BatchDelete + ) + static readonly Supplier = UserPermissions.SupplierRead.union( + UserPermissions.SupplierCreate, + UserPermissions.SupplierUpdate, + UserPermissions.SupplierDelete + ) + static readonly Ingredient = UserPermissions.IngredientRead.union( + UserPermissions.IngredientCreate, + UserPermissions.IngredientUpdate, + UserPermissions.IngredientDelete + ) + static readonly Flavor = UserPermissions.FlavorRead.union( + UserPermissions.FlavorCreate, + UserPermissions.FlavorUpdate, + UserPermissions.FlavorDelete + ) + static readonly User = UserPermissions.UserRead.union( + UserPermissions.UserCreate, + UserPermissions.UserUpdate, + UserPermissions.UserDelete + ) + static readonly Customer = UserPermissions.CustomerRead.union( + UserPermissions.CustomerCreate, + UserPermissions.CustomerUpdate, + UserPermissions.CustomerDelete + ) + static readonly Order = UserPermissions.OrderRead.union( + UserPermissions.OrderCreate, + UserPermissions.OrderUpdate, + UserPermissions.OrderDelete + ) + + static readonly Cook = UserPermissions.BatchRead.union( + UserPermissions.BatchCreate, + UserPermissions.BatchUpdate, + UserPermissions.IngredientRead, + UserPermissions.IngredientCreate, + UserPermissions.IngredientUpdate, + UserPermissions.FlavorRead, + UserPermissions.SupplierRead, + UserPermissions.CustomerRead, + UserPermissions.OrderRead, + UserPermissions.OrderCreate, + UserPermissions.OrderUpdate + ) + + static readonly Manager = UserPermissions.Batch.union( + UserPermissions.Supplier, + UserPermissions.Ingredient, + UserPermissions.Flavor, + UserPermissions.UserRead, + UserPermissions.Customer, + UserPermissions.Order + ) + + static readonly Admin = UserPermissions.Batch.union( + UserPermissions.Supplier, + UserPermissions.Ingredient, + UserPermissions.Flavor, + UserPermissions.User, + UserPermissions.Customer, + UserPermissions.Order + ) + + static readonly SuperAdmin = UserPermissions.of((1n << 64n) - 1n) + static readonly Empty = UserPermissions.of(0n) + + static fromBits(bits: bigint): UserPermissions { + return UserPermissions.of(bits & UserPermissions.SuperAdmin.bits) + } + + union(...others: UserPermissions[]): UserPermissions { + return UserPermissions.of( + others.reduce((acc, o) => acc | o.bits, this.bits) + ) + } + + intersection(other: UserPermissions): UserPermissions { + return UserPermissions.of(this.bits & other.bits) + } + + difference(other: UserPermissions): UserPermissions { + return UserPermissions.of(this.bits & ~other.bits) + } + + complement(): UserPermissions { + return UserPermissions.of(~this.bits & UserPermissions.SuperAdmin.bits) + } + + contains(other: UserPermissions): boolean { + return (this.bits & other.bits) === other.bits + } + + intersects(other: UserPermissions): boolean { + return (this.bits & other.bits) !== 0n + } + + isEmpty(): boolean { + return this.bits === 0n + } + + equals(other: UserPermissions): boolean { + return this.bits === other.bits + } + + insert(other: UserPermissions): UserPermissions { + return UserPermissions.of(this.bits | other.bits) + } + + remove(other: UserPermissions): UserPermissions { + return UserPermissions.of(this.bits & ~other.bits) + } + + toggle(other: UserPermissions): UserPermissions { + return UserPermissions.of(this.bits ^ other.bits) + } + + toString(): string { + return `0x${this.bits.toString(16)}` + } + + toJSON(): string { + return this.bits.toString() + } +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index bca0e21..83c639d 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "ES2017", + "target": "ESNext", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, @@ -15,12 +15,12 @@ "incremental": true, "plugins": [ { - "name": "next" - } + "name": "next", + }, ], "paths": { - "@/*": ["./*"] - } + "@/*": ["./*"], + }, }, "include": [ "next-env.d.ts", @@ -29,7 +29,7 @@ "**/*.tsx", "**/*.mts", ".next/types/**/*.ts", - ".next/dev/types/**/*.ts" + ".next/dev/types/**/*.ts", ], - "exclude": ["node_modules"] + "exclude": ["node_modules"], } -- 2.51.2