Skip to content
Merged

Dev #105

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
53 changes: 50 additions & 3 deletions api-gateway/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import healthRoutes from './routes/health';
import { patientAuthProxy } from './proxy/patient.auth.proxy';
import { patientDataProxy } from './proxy/patient.data.proxy';
import { staffAuthProxy } from './proxy/staff.auth.proxy';
import staffDataRouter from './routes/staff.data.router';
// import staffDataRouter from './routes/staff.data.router';
import { authenticate } from './middlewares/auth.middleware';
import { requirePatientSelf } from './middlewares/patient.guard';
import { createProxyMiddleware } from 'http-proxy-middleware';

import { requirePermission } from './middlewares/rbac.middleware';
import { staffDataProxy } from './proxy/staff.data.proxy';
const app = express();

app.use(
Expand Down Expand Up @@ -39,8 +40,38 @@ app.use(
);
// STAFF auth
app.use('/staff/public', staffAuthProxy);
app.use('/staff', authenticate, staffDataRouter);

const injectStaffHeaders = (req: any, _res: any, next: any) => {
if (req.user) {
if (req.user.sub) req.headers['x-user-id'] = String(req.user.sub);
if (req.user.role) req.headers['x-user-role'] = String(req.user.role);
if (req.user.type) req.headers['x-user-type'] = String(req.user.type);
}
next();
};

app.use('/staff', authenticate, injectStaffHeaders, staffDataProxy);

app.use('/admin', authenticate, injectStaffHeaders, staffDataProxy);

app.use(
'/admissions',
authenticate,
(req: any, _res, next) => {
if (req.user?.sub) req.headers['x-user-id'] = String(req.user.sub);

if (req.user?.role) req.headers['x-user-role'] = String(req.user.role);

if (req.user?.type) req.headers['x-user-type'] = String(req.user.type);

next();
},
createProxyMiddleware({
target: process.env.PATIENT_SERVICE_URL,
changeOrigin: true,
pathRewrite: (path) => `/admissions${path}`,
})
);
//Appointment routes (accessible by PATIENT + STAFF + ADMIN)

app.use(
Expand All @@ -64,4 +95,20 @@ app.use(
})
);

app.use(
'/prescriptions',
authenticate,
(req: any, _res, next) => {
if (req.user?.sub) {
req.headers['x-user-id'] = String(req.user.sub);
}
next();
},
createProxyMiddleware({
target: process.env.STAFF_SERVICE_URL,
changeOrigin: true,
pathRewrite: (path) => `/staff/pharmacy${path}`,
})
);

export default app;
27 changes: 20 additions & 7 deletions api-gateway/src/middlewares/rbac.middleware.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { Response, NextFunction } from 'express';
import { AuthenticatedRequest } from './auth.middleware';

// what actions exist in the system
export type Permission = 'CREATE_STAFF' | 'UPDATE_STAFF' | 'DEACTIVATE_STAFF';
export type Permission =
| 'CREATE_STAFF'
| 'UPDATE_STAFF'
| 'DEACTIVATE_STAFF'
| 'MANAGE_BEDS';

// staff role → permissions
const STAFF_ROLE_PERMISSIONS: Record<string, Permission[]> = {
DOCTOR: [],
NURSE: [],
NURSE: ['MANAGE_BEDS'],
RECEPTIONIST: ['MANAGE_BEDS'],
};

export function requirePermission(permission: Permission) {
Expand All @@ -18,12 +21,22 @@ export function requirePermission(permission: Permission) {
return res.status(401).json({ error: 'Unauthorized' });
}

// ADMIN can do everything
// ADMIN can do everything
if (user.type === 'ADMIN') {
return next();
}

// ❌ STAFF cannot do admin actions
return res.status(403).json({ error: 'Forbidden' });
// Only STAFF can have role permissions
if (user.type !== 'STAFF' || !user.role) {
return res.status(403).json({ error: 'Forbidden' });
}

const rolePermissions = STAFF_ROLE_PERMISSIONS[user.role] ?? [];

if (!rolePermissions.includes(permission)) {
return res.status(403).json({ error: 'Forbidden' });
}

next();
};
}
12 changes: 10 additions & 2 deletions api-gateway/src/proxy/staff.data.proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@ import { createProxyMiddleware } from 'http-proxy-middleware';
export const staffDataProxy = createProxyMiddleware({
target: 'http://localhost:3002',
changeOrigin: true,
pathRewrite: (path, req: any) => {
if (req.originalUrl.startsWith('/admin')) {
return `/admin${path}`;
}

// /staff/* → /staff/*
pathRewrite: (path) => `/staff${path}`,
if (req.originalUrl.startsWith('/staff')) {
return `/staff${path}`;
}

return path;
},
});
7 changes: 5 additions & 2 deletions api-gateway/src/routes/staff.data.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import { staffDataProxy } from '../proxy/staff.data.proxy';

const router = express.Router();

// 🔒 ADMIN ONLY: create staff
// 🔒 ADMIN ONLY
router.post('/', requirePermission('CREATE_STAFF'), staffDataProxy);

// everything else just passes through
// 🔒 BED MANAGEMENT
router.use('/beds', requirePermission('MANAGE_BEDS'), staffDataProxy);

// everything else
router.use(staffDataProxy);

export default router;
21 changes: 10 additions & 11 deletions frontend/src/app/(patient)/dashboard/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ const navItems = [
{ label: 'Dashboard', icon: LayoutDashboard, path: '/dashboard' },
{ label: 'Appointments', icon: Calendar, path: '/dashboard/appointments' },
{ label: 'Book Appointment', icon: PlusCircle, path: '/dashboard/book' },
{ label: 'Profile', icon: User, path: '/dashboard/profile' },
{ label: 'Documents', icon: FileText, path: '/dashboard/documents' },
{ label: 'Profile', icon: User, path: '/dashboard/profile' },
];

export default function PatientDashboardLayout({
Expand All @@ -48,11 +48,11 @@ export default function PatientDashboardLayout({
}
}, [auth.isRestoring, auth.accessToken, router]);

if (auth.isRestoring) return <div className="flex items-center justify-center min-h-[60vh]">
<div className="relative">
<div className="h-16 w-16 rounded-full border-4 border-slate-100 border-t-blue-600 animate-spin"></div>
</div>
</div>;
if (auth.isRestoring) return <div className="flex items-center justify-center min-h-[60vh]">
<div className="relative">
<div className="h-16 w-16 rounded-full border-4 border-slate-100 border-t-blue-600 animate-spin"></div>
</div>
</div>;
if (!auth.accessToken) return null;

// Safely extract initials
Expand Down Expand Up @@ -85,11 +85,10 @@ export default function PatientDashboardLayout({
<TooltipTrigger asChild>
<Link
href={item.path}
className={`flex h-11 w-11 items-center justify-center rounded-2xl transition-all duration-200 ${
isActive
? 'bg-white text-indigo-600 shadow-lg'
: 'text-white/60 hover:bg-white/10 hover:text-white'
}`}
className={`flex h-11 w-11 items-center justify-center rounded-2xl transition-all duration-200 ${isActive
? 'bg-white text-indigo-600 shadow-lg'
: 'text-white/60 hover:bg-white/10 hover:text-white'
}`}
>
<item.icon size={20} strokeWidth={isActive ? 2 : 1.5} />
</Link>
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/app/(patient)/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { getPatientDocuments } from '@/src/features/patient/api/getDocument';
import { getProfile } from '@/src/features/patient/api/getProfile';

import { Calendar, FileText, CalendarPlus, Upload, CloudCog } from 'lucide-react';
import AdmissionStatus from '@/src/features/patient/components/AdmissionStatus';

export default function DashboardHome() {

Expand All @@ -32,8 +33,8 @@ export default function DashboardHome() {
const docs = await getPatientDocuments();
const profileData = await getProfile();

console.log('profiledata',profileData);
console.log('profiledata', profileData);

setProfile(profileData);
setPatientName(profileData.name);

Expand Down Expand Up @@ -105,6 +106,9 @@ export default function DashboardHome() {

</div>

{/* Admission Status */}
<AdmissionStatus />

{/* Quick Actions */}

<div>
Expand Down
58 changes: 50 additions & 8 deletions frontend/src/app/admin/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,59 @@
import CreateStaffForm from "@/src/features/admin/components/CreateStaffForm";

import CreateWardForm from "@/src/features/admin/components/CreateWardForm";
import CreateRoomForm from "@/src/features/admin/components/CreateRoomForm";
import CreateBedForm from "@/src/features/admin/components/CreateBedForm";

export default function AdminDashboard() {
return (
<div>
<h1 className="text-2xl font-bold">
Admin Dashboard
</h1>
<div className="min-h-screen bg-gray-50 px-4 py-12 sm:px-6">
<div className="max-w-4xl mx-auto space-y-12"> {/* Narrower container for better readability */}

{/* Page Header */}
<header className="border-b pb-6">
<h1 className="text-3xl font-bold text-gray-900">Admin Management</h1>
<p className="text-gray-500">Add new staff members and hospital infrastructure</p>
</header>

{/* Form 1: Staff */}
<section className="bg-white shadow-md border rounded-xl overflow-hidden">
<div className="bg-blue-50 px-6 py-4 border-b border-blue-100">
<h2 className="text-lg font-bold text-blue-900">1. Staff Registration</h2>
</div>
<div className="p-8">
<CreateStaffForm />
</div>
</section>

{/* Form 2: Wards */}
<section className="bg-white shadow-md border rounded-xl overflow-hidden">
<div className="bg-green-50 px-6 py-4 border-b border-green-100">
<h2 className="text-lg font-bold text-green-900">2. Ward Management</h2>
</div>
<div className="p-8">
<CreateWardForm />
</div>
</section>

{/* Form 3: Rooms */}
<section className="bg-white shadow-md border rounded-xl overflow-hidden">
<div className="bg-purple-50 px-6 py-4 border-b border-purple-100">
<h2 className="text-lg font-bold text-purple-900">3. Room Assignment</h2>
</div>
<div className="p-8">
<CreateRoomForm />
</div>
</section>

<p>Welcome Admin</p>
{/* Form 4: Beds */}
<section className="bg-white shadow-md border rounded-xl overflow-hidden">
<div className="bg-amber-50 px-6 py-4 border-b border-amber-100">
<h2 className="text-lg font-bold text-amber-900">4. Bed Setup</h2>
</div>
<div className="p-8">
<CreateBedForm />
</div>
</section>

<div className="space-y-8" >
<CreateStaffForm />
</div>
</div>
);
Expand Down
18 changes: 18 additions & 0 deletions frontend/src/app/staff/dashboard/admissions/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use client'

import AdmissionQueue from '@/src/features/admissions/components/AdmissionQueue'

export default function StaffAdmissionsPage() {

return (
<div className="min-h-screen bg-gray-50 p-8">

<h1 className="text-2xl font-semibold mb-6">
Admissions
</h1>

<AdmissionQueue />

</div>
)
}
23 changes: 23 additions & 0 deletions frontend/src/app/staff/dashboard/beds/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use client'

import BedsBoard from '@/src/features/beds/components/BedsBoard'
import CreateWardForm from '@/src/features/admin/components/CreateWardForm'
import CreateRoomForm from '@/src/features/admin/components/CreateRoomForm'
import CreateBedForm from '@/src/features/admin/components/CreateBedForm'

export default function StaffBedsPage() {

return (

<div className="min-h-screen p-8 bg-gray-50 space-y-6">

<h1 className="text-2xl font-semibold">
Bed Management
</h1>

<BedsBoard />

</div>

)
}
14 changes: 14 additions & 0 deletions frontend/src/app/staff/dashboard/discharge/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import DischargeRequests from "@/src/features/admissions/components/DischargeRequests";

export default function DischargeRequestsPage(){
return(
<div className=" min-h-screen bg-gray-50 p-8 space-y-8" >
<h1 className="text-2xl font-semibold" >
Discharge Requests
</h1>
<div>
<DischargeRequests />
</div>
</div>
)
}
Loading
Loading