Skip to content

Commit

Permalink
feat(nextjs): added permissions for the platform
Browse files Browse the repository at this point in the history
  • Loading branch information
not-ani committed Oct 14, 2024
1 parent d0d4c24 commit c4d20d4
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const schema = z.object({
target: z.string(),
}),
),
projectId: z.string(),
});

type InputProps = z.infer<typeof schema>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export const Flowchart: React.FC<FlowchartProps> = ({

function handleSubmit() {
const data = {
projectId: projectId,
tasks: nodes,
edges: edges.map((edge) => {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,37 +18,17 @@ import { api } from "~/trpc/react";
import CreateActionGuide from "./create-action-guide";

export default function Guides() {
const [search, setSearch] = useState<string | null>();
const { data: actionGuides } = api.actionGuides.getActionGuides.useQuery({
title: search ?? undefined,
});

const filteredGuides = useMemo(() => {
return actionGuides?.filter((guide) => {
const titleMatch =
guide.title ?? "".toLowerCase().includes(search?.toLowerCase() ?? "");
return titleMatch;
});
}, [search, actionGuides]);

const handleSearch = (e: any) => {
setSearch(e.target.value);
};
const [actionGuides] = api.actionGuides.getActionGuides.useSuspenseQuery();

return (
<div className="container mx-auto px-4 py-8 md:px-6">
<div className="mx-auto px-4 py-8 md:px-6">
<h1 className="mb-6 text-3xl font-bold">Action Guides</h1>
<div className="mb-6 flex flex-row justify-between">
<Input
placeholder="Search action guides..."
value={search ?? ""}
onChange={handleSearch}
className="w-full max-w-md"
/>
<div />
<CreateActionGuide />
</div>
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
{filteredGuides?.map((guide) => (
{actionGuides.map((guide) => (
<Link href={`/guide/${guide.embedId}`} key={guide.id}>
<Card className="h-[200px]">
<CardHeader>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from "react";

import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from "@amaxa/ui/card";
import { Input } from "@amaxa/ui/input";
import { Skeleton } from "@amaxa/ui/skeleton";

const GuidesSkeleton = () => {
return (
<div className="mx-auto px-4 py-8 md:px-6">
<h1 className="mb-6 text-3xl font-bold">Action Guides</h1>
<div className="mb-6 flex flex-row justify-between">
<Input
placeholder="Search action guides..."
className="w-full max-w-md"
disabled
/>
<Skeleton className="h-10 w-32" />
</div>
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
{[...Array(6)].map((_, index) => (
<Skeleton key={index} className="h-[200px]">
<Card className="h-full">
<CardHeader>
<CardTitle>
<Skeleton className="h-6 w-2/3" />
</CardTitle>
</CardHeader>
<CardContent>
<Skeleton className="mb-2 h-4 w-full" />
<Skeleton className="h-4 w-4/5" />
</CardContent>
<CardFooter>
<Skeleton className="h-4 w-1/4" />
</CardFooter>
</Card>
</Skeleton>
))}
</div>
</div>
);
};

export default GuidesSkeleton;
16 changes: 14 additions & 2 deletions apps/nextjs/src/app/(home)/guides/action-guides/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import { Suspense } from "react";

import { checkAuth } from "~/lib/auth";
import { api, HydrateClient } from "~/trpc/server";
import Guides from "./_components/GuideCard";
import GuidesSkeleton from "./_components/GuideCardSkeleton";

export default async function Page() {
await checkAuth();
void api.actionGuides.getActionGuides.prefetch();

export default function Page() {
return (
<div className="w-full">
<Guides />
<HydrateClient>
<Suspense fallback={<GuidesSkeleton />}>
<Guides />
</Suspense>
</HydrateClient>
</div>
);
}
2 changes: 1 addition & 1 deletion apps/nextjs/src/app/(home)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default async function Page() {

return (
<div className="min-h-screen p-4 sm:p-6 lg:p-8">
<div className="mx-auto max-w-screen-xl">
<div className="">
<div className="flex w-full flex-col gap-5">
<div className="flex flex-col gap-6">
<div className="flex flex-row justify-between gap-6">
Expand Down
48 changes: 48 additions & 0 deletions packages/api/src/permissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { Session } from "@amaxa/auth";

export function isProjectStudent(projectId: string, session: Session | null) {
if (!session) return false;
if (session.user.role === "Admin") return true;
const projectPermissions = session.user.project_permissions;
if (!projectPermissions) return false;
return (
projectPermissions[projectId] === "admin" ||
projectPermissions[projectId] === "coach" ||
projectPermissions[projectId] === "student"
);
}

/*
param projectId: string
@param session: Session | null
@returns boolean
Returns true if the user has admin or coach privileges for the project
*/
export function isProjectPrivileged(
projectId: string,
session: Session | null,
) {
if (!session) return false;
if (session.user.role === "Admin") return true;
const projectPermissions = session.user.project_permissions;
if (!projectPermissions) return false;
return (
projectPermissions[projectId] === "admin" ||
projectPermissions[projectId] === "coach"
);
}

export function isProjectAdmin(projectId: string, session: Session | null) {
if (!session) return false;
if (session.user.role === "Admin") return true;
const projectPermissions = session.user.project_permissions;
if (!projectPermissions) return false;
return projectPermissions[projectId] === "admin";
}

export function isAdmin(session: Session | null) {
if (!session) return false;
if (session.user.role === "Admin") return true;
return false;
}
17 changes: 3 additions & 14 deletions packages/api/src/router/action-guides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,9 @@ import { guides } from "@amaxa/db/schema";
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";

export const actionGuideRouter = createTRPCRouter({
getActionGuides: publicProcedure
.input(
z.object({
title: z.string().optional(),
skillId: z.string().optional(),
}),
)
.query(async ({ input, ctx }) => {
const { title } = input;
return await ctx.db.query.guides.findMany({
where: (guides, { and, ilike }) =>
and(title ? ilike(guides.title, `%${title}%`) : undefined),
});
}),
getActionGuides: publicProcedure.query(async ({ ctx }) => {
return await ctx.db.query.guides.findMany({});
}),

create: protectedProcedure
.input(
Expand Down
7 changes: 7 additions & 0 deletions packages/api/src/router/events.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { TRPCError } from "@trpc/server";
import { z } from "zod";

import { createEventSchema, events } from "@amaxa/db/schema";

import { isAdmin } from "../permissions";
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";

export const eventsRouter = createTRPCRouter({
Expand All @@ -21,6 +23,11 @@ export const eventsRouter = createTRPCRouter({
create: protectedProcedure
.input(createEventSchema)
.mutation(async ({ ctx, input }) => {
if (!isAdmin(ctx.session))
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You do not have permissions to create an event",
});
await ctx.db.insert(events).values(input);
}),
});
12 changes: 12 additions & 0 deletions packages/api/src/router/projects.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// import { and, ilike } from "@amaxa/db";
// import { Projects } from "@amaxa/db/schema";
import { TRPCError } from "@trpc/server";
import { z } from "zod";

import { eq } from "@amaxa/db";
import { createProjectSchema, Projects } from "@amaxa/db/schema";

import { isAdmin, isProjectPrivileged } from "../permissions";
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";

export const projectsRouter = createTRPCRouter({
Expand All @@ -20,6 +22,11 @@ export const projectsRouter = createTRPCRouter({
create: protectedProcedure
.input(createProjectSchema)
.mutation(async ({ ctx, input }) => {
if (!isAdmin(ctx.session))
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You do not have permissions to create a project",
});
await ctx.db.insert(Projects).values(input);
}),
forDashboard: protectedProcedure
Expand Down Expand Up @@ -52,6 +59,11 @@ export const projectsRouter = createTRPCRouter({
)
.mutation(async ({ ctx, input }) => {
const { id, ...update } = input;
if (!isProjectPrivileged(input.id, ctx.session))
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You do not have permissions to update this project",
});
await ctx.db.update(Projects).set(update).where(eq(Projects.id, id));
}),
});
13 changes: 13 additions & 0 deletions packages/api/src/router/tasks.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { TRPCError } from "@trpc/server";
import { z } from "zod";

import { and, buildConflictUpdateColumns, eq, sql } from "@amaxa/db";
import { edges, statusValues, tasks } from "@amaxa/db/schema";

import { isProjectStudent } from "../permissions";
import { createTRPCRouter, protectedProcedure } from "../trpc";

export const tasksRouter = createTRPCRouter({
Expand Down Expand Up @@ -100,6 +102,7 @@ export const tasksRouter = createTRPCRouter({
}),
}),
),
projectId: z.string(),

edges: z.array(
z.object({
Expand All @@ -112,6 +115,11 @@ export const tasksRouter = createTRPCRouter({
}),
)
.mutation(async ({ ctx, input }) => {
if (!isProjectStudent(input.projectId, ctx.session))
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You do not have permissions to create an event",
});
const formattedTasks = input.tasks.map((task) => ({
id: task.id,
type: task.type,
Expand Down Expand Up @@ -169,6 +177,11 @@ export const tasksRouter = createTRPCRouter({
}),
)
.mutation(async ({ ctx, input }) => {
if (!isProjectStudent(input.projectId, ctx.session))
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You do not have permissions to create an event",
});
await ctx.db.insert(tasks).values(input);
}),
update: protectedProcedure
Expand Down

0 comments on commit c4d20d4

Please sign in to comment.