Skip to content
Merged

Dev #104

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions api-gateway/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,18 @@ app.use('/health', healthRoutes);
app.use('/patients/public', patientAuthProxy);

// PATIENT self routes
app.use('/patients', authenticate, requirePatientSelf, patientDataProxy);

app.use(
'/patients',
authenticate,
requirePatientSelf,
(req: any, _res, next) => {
if (req.user?.sub) {
req.headers['x-user-id'] = String(req.user.sub);
}
next();
},
patientDataProxy
);
// STAFF auth
app.use('/staff/public', staffAuthProxy);
app.use('/staff', authenticate, staffDataRouter);
Expand Down
19 changes: 15 additions & 4 deletions api-gateway/src/middlewares/patient.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,24 @@ export function requirePatientSelf(
return res.status(401).json({ error: 'Unauthorized' });
}

// URL will be /:id after /patients is stripped
const patientIdFromPath = req.path.split('/')[1];
const pathParts = req.path.split('/');
const patientIdFromPath = pathParts[1];

if (!patientIdFromPath) {
return res.status(400).json({ error: 'Invalid patient path' });
/**
* Allow special routes
*/
if (
patientIdFromPath === 'me' ||
patientIdFromPath === 'upload' ||
patientIdFromPath === 'profile-image' ||
patientIdFromPath === 'documents'
) {
return next();
}

/**
* Protect /patients/:id
*/
if (req.user.sub !== patientIdFromPath) {
return res.status(403).json({ error: 'Access denied' });
}
Expand Down
25 changes: 25 additions & 0 deletions frontend/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "radix-nova",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"rtl": false,
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"menuColor": "default",
"menuAccent": "subtle",
"registries": {}
}
57 changes: 57 additions & 0 deletions frontend/components/ui/tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"use client"

import * as React from "react"
import { Tooltip as TooltipPrimitive } from "radix-ui"

import { cn } from "@/lib/utils"

function TooltipProvider({
delayDuration = 0,
...props
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
return (
<TooltipPrimitive.Provider
data-slot="tooltip-provider"
delayDuration={delayDuration}
{...props}
/>
)
}

function Tooltip({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
}

function TooltipTrigger({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
}

function TooltipContent({
className,
sideOffset = 0,
children,
...props
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
return (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
data-slot="tooltip-content"
sideOffset={sideOffset}
className={cn(
"z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
className
)}
{...props}
>
{children}
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" />
</TooltipPrimitive.Content>
</TooltipPrimitive.Portal>
)
}

export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }
6 changes: 6 additions & 0 deletions frontend/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
7 changes: 7 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,18 @@
"dependencies": {
"@tanstack/react-query": "^5.90.21",
"axios": "^1.13.5",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"framer-motion": "^12.35.0",
"lucide-react": "^0.575.0",
"next": "16.1.6",
"radix-ui": "^1.4.3",
"react": "19.2.3",
"react-dom": "19.2.3",
"shadcn": "^4.0.0",
"tailwind-merge": "^3.5.0",
"tw-animate-css": "^1.4.0",
"zod": "^4.3.6"
},
"devDependencies": {
Expand Down
Loading
Loading