Support missing roles and deleted user
This commit is contained in:
@@ -274,7 +274,7 @@ const app = new Elysia()
|
||||
);
|
||||
|
||||
return Option.match(res, {
|
||||
onNone: () => error("Not Found", undefined),
|
||||
onNone: () => error("Not Found", new Response() as unknown as void),
|
||||
onSome: ({ displayName }) => ({ userId, displayName }),
|
||||
});
|
||||
}, {
|
||||
@@ -290,13 +290,13 @@ const app = new Elysia()
|
||||
|
||||
// --- MARK: PIECE CRUD ------------------------------------------------
|
||||
|
||||
.post("/piece", async ({ db, body: { name, composer, lyricist, arranger }, session: { idToken } }) => {
|
||||
.post("/piece", async ({ db, body: { name, composer, lyricist, arranger }, session: { idToken, roles } }) => {
|
||||
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
if (!idToken.value.payload.roles.includes("Editor")) {
|
||||
if (!roles.includes("Editor")) {
|
||||
return error("Forbidden", "Must be an Editor");
|
||||
}
|
||||
|
||||
@@ -369,7 +369,7 @@ const app = new Elysia()
|
||||
.executeTakeFirst();
|
||||
|
||||
if (piece === undefined) {
|
||||
return error("Not Found", undefined);
|
||||
return error("Not Found", new Response() as unknown as void);
|
||||
}
|
||||
|
||||
const attachments = await db
|
||||
@@ -396,13 +396,13 @@ const app = new Elysia()
|
||||
},
|
||||
})
|
||||
|
||||
.put("/piece/:pieceId", async ({ db, body: { name, composer, lyricist, arranger }, params: { pieceId }, session: { idToken } }) => {
|
||||
.put("/piece/:pieceId", async ({ db, body: { name, composer, lyricist, arranger }, params: { pieceId }, session: { idToken, roles } }) => {
|
||||
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
if (!idToken.value.payload.roles.includes("Editor")) {
|
||||
if (!roles.includes("Editor")) {
|
||||
return error("Forbidden", "Must be an Editor");
|
||||
}
|
||||
|
||||
@@ -414,7 +414,7 @@ const app = new Elysia()
|
||||
.execute();
|
||||
|
||||
if (res.length === 0) {
|
||||
return error("Not Found", undefined);
|
||||
return error("Not Found", new Response() as unknown as void);
|
||||
}
|
||||
|
||||
const attachments = await db
|
||||
@@ -448,13 +448,13 @@ const app = new Elysia()
|
||||
},
|
||||
})
|
||||
|
||||
.delete("/piece/:pieceId", async ({ db, params: { pieceId }, set, session: { idToken } }) => {
|
||||
.delete("/piece/:pieceId", async ({ db, params: { pieceId }, set, session: { idToken, roles } }) => {
|
||||
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
if (!idToken.value.payload.roles.includes("Editor")) {
|
||||
if (!roles.includes("Editor")) {
|
||||
return error("Forbidden", "Must be an Editor");
|
||||
}
|
||||
|
||||
@@ -465,7 +465,7 @@ const app = new Elysia()
|
||||
.execute();
|
||||
|
||||
if (res.length === 0) {
|
||||
return error("Not Found", undefined);
|
||||
return error("Not Found", new Response() as unknown as void);
|
||||
}
|
||||
|
||||
set.status = "No Content";
|
||||
@@ -482,13 +482,13 @@ const app = new Elysia()
|
||||
|
||||
// --- MARK: ATTACHMENT CRUD -------------------------------------------
|
||||
|
||||
.post("/piece/:pieceId/attachment", async ({ db, body: { filename, mediaType, data }, params: { pieceId }, session: { idToken } }) => {
|
||||
.post("/piece/:pieceId/attachment", async ({ db, body: { filename, mediaType, data }, params: { pieceId }, session: { idToken, roles } }) => {
|
||||
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
if (!idToken.value.payload.roles.includes("Editor")) {
|
||||
if (!roles.includes("Editor")) {
|
||||
return error("Forbidden", "Must be an Editor");
|
||||
}
|
||||
|
||||
@@ -554,7 +554,7 @@ const app = new Elysia()
|
||||
.executeTakeFirst();
|
||||
|
||||
if (res === undefined) {
|
||||
return error("Not Found", undefined);
|
||||
return error("Not Found", new Response() as unknown as void);
|
||||
}
|
||||
|
||||
set.headers["content-disposition"] = `attachment; filename*=UTF-8''${encodeURIComponent(res.filename)}`;
|
||||
@@ -572,13 +572,13 @@ const app = new Elysia()
|
||||
},
|
||||
})
|
||||
|
||||
.put("/piece/:pieceId/attachment/:attachmentId", async ({ db, body: { filename }, params: { pieceId, attachmentId }, session: { idToken } }) => {
|
||||
.put("/piece/:pieceId/attachment/:attachmentId", async ({ db, body: { filename }, params: { pieceId, attachmentId }, session: { idToken, roles } }) => {
|
||||
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
if (!idToken.value.payload.roles.includes("Editor")) {
|
||||
if (!roles.includes("Editor")) {
|
||||
return error("Forbidden", "Must be an Editor");
|
||||
}
|
||||
|
||||
@@ -593,7 +593,7 @@ const app = new Elysia()
|
||||
.execute();
|
||||
|
||||
if (res.length === 0) {
|
||||
return error("Not Found", undefined);
|
||||
return error("Not Found", new Response() as unknown as void);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -616,13 +616,13 @@ const app = new Elysia()
|
||||
},
|
||||
})
|
||||
|
||||
.delete("/piece/:pieceId/attachment/:attachmentId", async ({ db, params: { pieceId, attachmentId }, set, session: { idToken } }) => {
|
||||
.delete("/piece/:pieceId/attachment/:attachmentId", async ({ db, params: { pieceId, attachmentId }, set, session: { idToken, roles } }) => {
|
||||
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
if (!idToken.value.payload.roles.includes("Editor")) {
|
||||
if (!roles.includes("Editor")) {
|
||||
return error("Forbidden", "Must be an Editor");
|
||||
}
|
||||
|
||||
@@ -636,7 +636,7 @@ const app = new Elysia()
|
||||
.execute();
|
||||
|
||||
if (res.length === 0) {
|
||||
return error("Not Found", undefined);
|
||||
return error("Not Found", new Response() as unknown as void);
|
||||
}
|
||||
|
||||
set.status = "No Content";
|
||||
@@ -655,13 +655,13 @@ const app = new Elysia()
|
||||
|
||||
// --- MARK: REPERTOIRE CRUD -------------------------------------------
|
||||
|
||||
.post("/repertoire", async ({ db, body: { name, entries }, session: { idToken } }) => {
|
||||
.post("/repertoire", async ({ db, body: { name, entries }, session: { idToken, roles } }) => {
|
||||
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
if (!idToken.value.payload.roles.includes("Editor")) {
|
||||
if (!roles.includes("Editor")) {
|
||||
return error("Forbidden", "Must be an Editor");
|
||||
}
|
||||
|
||||
@@ -741,7 +741,7 @@ const app = new Elysia()
|
||||
.executeTakeFirst();
|
||||
|
||||
if (repertoire === undefined) {
|
||||
return error("Not Found", undefined);
|
||||
return error("Not Found", new Response() as unknown as void);
|
||||
}
|
||||
|
||||
const entries = await db
|
||||
@@ -766,13 +766,13 @@ const app = new Elysia()
|
||||
},
|
||||
})
|
||||
|
||||
.put("/repertoire/:repertoireId", async ({ db, body: { name, entries }, params: { repertoireId }, session: { idToken } }) => {
|
||||
.put("/repertoire/:repertoireId", async ({ db, body: { name, entries }, params: { repertoireId }, session: { idToken, roles } }) => {
|
||||
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
if (!idToken.value.payload.roles.includes("Editor")) {
|
||||
if (!roles.includes("Editor")) {
|
||||
return error("Forbidden", "Must be an Editor");
|
||||
}
|
||||
|
||||
@@ -784,7 +784,7 @@ const app = new Elysia()
|
||||
.execute();
|
||||
|
||||
if (res.length === 0) {
|
||||
return error("Not Found", undefined);
|
||||
return error("Not Found", new Response() as unknown as void);
|
||||
}
|
||||
|
||||
await db
|
||||
@@ -821,13 +821,13 @@ const app = new Elysia()
|
||||
},
|
||||
})
|
||||
|
||||
.delete("/repertoire/:repertoireId", async ({ db, params: { repertoireId }, set, session: { idToken } }) => {
|
||||
.delete("/repertoire/:repertoireId", async ({ db, params: { repertoireId }, set, session: { idToken, roles } }) => {
|
||||
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
if (!idToken.value.payload.roles.includes("Editor")) {
|
||||
if (!roles.includes("Editor")) {
|
||||
return error("Forbidden", "Must be an Editor");
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ export const revalidateTokens = Effect.fn("revaildateTokens")(
|
||||
userId: Option.map(it, ({ payload: { oid } }) => oid),
|
||||
roles: Option.match(it, {
|
||||
onNone: constant(Object.freeze([])),
|
||||
onSome: ({ payload: { roles } }) => roles,
|
||||
onSome: ({ payload: { roles } }) => roles ?? Object.freeze([]),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface IdTokenPayload {
|
||||
readonly exp: number;
|
||||
readonly name: string;
|
||||
readonly oid: Common.UserId;
|
||||
readonly roles: readonly string[];
|
||||
readonly roles?: readonly string[];
|
||||
}
|
||||
|
||||
const brandedString = <T>() => t.Transform(t.String())
|
||||
|
||||
@@ -9,9 +9,9 @@ export interface User {
|
||||
}
|
||||
|
||||
export interface SystemInformation {
|
||||
readonly createdBy: Option.Option<User>;
|
||||
readonly createdBy: Option.Option<User | null>;
|
||||
readonly createdAt: string;
|
||||
readonly modifiedBy: Option.Option<User>;
|
||||
readonly modifiedBy: Option.Option<User | null>;
|
||||
readonly modifiedAt: Option.Option<string>;
|
||||
}
|
||||
|
||||
@@ -113,7 +113,8 @@ const RepertoireSemaphore = Effect.unsafeMakeSemaphore(1);
|
||||
export const userLookup = (userId: UserId) => pipe(
|
||||
Effect.promise((signal) => client.user({ userId }).get({ fetch: { signal } })),
|
||||
Effect.flatMap(mapResponse),
|
||||
Effect.map((x): User => x), // safely coerce to interface
|
||||
Effect.catchAll((error) => error.status === 404 ? Effect.succeed(null) : Effect.fail(error)),
|
||||
Effect.map((x): User | null => x), // safely coerce to interface
|
||||
UserSemaphore.withPermits(1),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { client } from "@/client";
|
||||
import { useLoading } from "@/hooks/useLoading.ts";
|
||||
import { AttachmentId, PieceId } from "common";
|
||||
import { Match } from "effect";
|
||||
import JSZip from "jszip";
|
||||
import { OpenSheetMusicDisplay } from "opensheetmusicdisplay";
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
@@ -102,7 +103,13 @@ export default function Attachment() {
|
||||
if (error !== null) {
|
||||
return (
|
||||
<div className="w-full h-full overflow-hidden flex items-center justify-center">
|
||||
<div>Wystąpił błąd: {error.status === 422 ? error.value.message : error.value}</div>
|
||||
<div>
|
||||
Wystąpił błąd: {Match.value(error).pipe(
|
||||
Match.when({ status: 422 }, ({ value }) => value.message),
|
||||
Match.when({ status: 404 }, () => "Załącznik nie istnieje"),
|
||||
Match.exhaustive,
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export function Piece() {
|
||||
return (
|
||||
<div className="p-4 overflow-y-auto flex flex-wrap items-start gap-4">
|
||||
{error !== null ? (
|
||||
Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${error.value}`
|
||||
Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${JSON.stringify(error.value)}`
|
||||
) : (<>
|
||||
<div className="flex flex-col gap-4 p-4 border rounded">
|
||||
<h3 className="font-bold text-lg">Utwór</h3>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@
|
||||
import { useLoadingEffect } from "@/hooks/useLoading";
|
||||
import { authors, created, DEBOUNCE, modified } from "@/snippets";
|
||||
import { PieceId } from "common";
|
||||
import { Cause, Effect } from "effect";
|
||||
import { Cause, Effect, Match } from "effect";
|
||||
import { Loader2, Plus } from "lucide-react";
|
||||
import { FormEventHandler, useId, useRef, useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
@@ -91,7 +91,7 @@ export function Pieces() {
|
||||
) : error !== null ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="text-center">
|
||||
{Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${error.value}`}
|
||||
{Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${JSON.stringify(error.value)}`}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
@@ -124,7 +124,14 @@ function PieceRow(props: PieceRow.Props) {
|
||||
if (error !== null) {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4}>Wystąpił błąd: {error.status === 422 ? error.value.message : error.value}</TableCell>
|
||||
<TableCell colSpan={4}>
|
||||
Wystąpił błąd: {Match.value(error).pipe(
|
||||
Match.when({ status: 401 }, () => "Zaloguj się ponownie"),
|
||||
Match.when({ status: 422 }, ({ value }) => value.message),
|
||||
Match.when({ status: 404 }, () => "Utwór nie istnieje"),
|
||||
Match.exhaustive,
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useLoadingEffect } from "@/hooks/useLoading";
|
||||
import { mapProp, Update, Updater } from "@/hooks/useStore";
|
||||
import { authors, DEBOUNCE, saveDelay } from "@/snippets";
|
||||
import { PieceId, RepertoireId } from "common";
|
||||
import { Array, Cause, Effect, Option, pipe } from "effect";
|
||||
import { Array, Cause, Effect, Match, Option, pipe } from "effect";
|
||||
import { ChevronDown, ChevronUp, CircleMinus, Loader2, Plus } from "lucide-react";
|
||||
import { FormEventHandler, useCallback, useId, useMemo, useRef, useState } from "react";
|
||||
import { Link, useNavigate, useParams } from "react-router-dom";
|
||||
@@ -36,7 +36,7 @@ export function Repertoire() {
|
||||
return (
|
||||
<div className="p-4 overflow-y-auto flex flex-wrap items-start gap-4">
|
||||
{error !== null ? (
|
||||
Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${error.value}`
|
||||
Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${JSON.stringify(error.value)}`
|
||||
) : (<>
|
||||
<div className="flex flex-col gap-4 p-4 border rounded">
|
||||
<h3 className="font-bold">Repertuar</h3>
|
||||
@@ -343,7 +343,7 @@ function AddEntryDialogContent(props: AddEntryDialogContent.Props) {
|
||||
) : error !== null ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="text-center">
|
||||
{Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${error.value}`}
|
||||
{Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${JSON.stringify(error.value)}`}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
@@ -410,7 +410,14 @@ function EntryDialogPieceRow(props: EntryDialogPieceRow.Props) {
|
||||
if (error !== null) {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={2}>Wystąpił błąd: {error.status === 422 ? error.value.message : error.value}</TableCell>
|
||||
<TableCell colSpan={2}>
|
||||
Wystąpił błąd: {Match.value(error).pipe(
|
||||
Match.when({ status: 401 }, () => "Zaloguj się ponownie"),
|
||||
Match.when({ status: 422 }, ({ value }) => value.message),
|
||||
Match.when({ status: 404 }, () => "Utwór nie istnieje"),
|
||||
Match.exhaustive,
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@
|
||||
import { useLoadingEffect } from "@/hooks/useLoading";
|
||||
import { created, DEBOUNCE, modified } from "@/snippets";
|
||||
import { RepertoireId } from "common";
|
||||
import { Cause, Effect } from "effect";
|
||||
import { Cause, Effect, Match } from "effect";
|
||||
import { Loader2, Plus } from "lucide-react";
|
||||
import { FormEventHandler, ReactNode, useId, useRef, useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
@@ -79,7 +79,7 @@ export function Repertoires() {
|
||||
) : error !== null ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="text-center">
|
||||
{Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${error.value}`}
|
||||
{Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${JSON.stringify(error.value)}`}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
@@ -112,7 +112,14 @@ function RepertoireRow(props: RepertoireRow.Props) {
|
||||
if (error !== null) {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4}>Wystąpił błąd: {error.status === 422 ? error.value.message : error.value}</TableCell>
|
||||
<TableCell colSpan={4}>
|
||||
Wystąpił błąd: {Match.value(error).pipe(
|
||||
Match.when({ status: 401 }, () => "Zaloguj się ponownie"),
|
||||
Match.when({ status: 422 }, ({ value }) => value.message),
|
||||
Match.when({ status: 404 }, () => "Repertuar nie istnieje"),
|
||||
Match.exhaustive,
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,11 @@ export function created({ createdAt, createdBy }: SystemInformation): ReactNode
|
||||
|
||||
if (Option.isSome(createdBy)) {
|
||||
nodes.push(<br />);
|
||||
nodes.push(`przez ${createdBy.value.displayName}`);
|
||||
if (createdBy.value !== null) {
|
||||
nodes.push(`przez ${createdBy.value.displayName}`);
|
||||
} else {
|
||||
nodes.push("przez nieznanego użytkownika");
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
@@ -48,7 +52,11 @@ export function modified({ modifiedAt, modifiedBy }: SystemInformation): ReactNo
|
||||
if (Option.isNone(modifiedBy)) {
|
||||
return "\u2014";
|
||||
} else {
|
||||
return `przez ${modifiedBy.value.displayName}`;
|
||||
if (modifiedBy.value !== null) {
|
||||
return `przez ${modifiedBy.value.displayName}`;
|
||||
} else {
|
||||
return "przez nieznanego użytkownika";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +64,11 @@ export function modified({ modifiedAt, modifiedBy }: SystemInformation): ReactNo
|
||||
|
||||
if (Option.isSome(modifiedBy)) {
|
||||
nodes.push(<br />);
|
||||
nodes.push(`przez ${modifiedBy.value.displayName}`);
|
||||
if (modifiedBy.value !== null) {
|
||||
nodes.push(`przez ${modifiedBy.value.displayName}`);
|
||||
} else {
|
||||
nodes.push("przez nieznanego użytkownika");
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
|
||||
Reference in New Issue
Block a user