major functionalities are fixed

This commit is contained in:
2025-05-14 17:12:54 +05:30
parent 53a91dd5f9
commit b03b7efcb4
34 changed files with 4434 additions and 1082 deletions

View File

@@ -0,0 +1,5 @@
HOST="localhost"
PORT=3000
FRONTEND_URL="http://localhost:5173"
JWT_SECRET = 'dentalsecret'
DATABASE_URL=postgresql://postgres:mypassword@localhost:5432/dentalapp

40
apps/Backend/package.json Normal file
View File

@@ -0,0 +1,40 @@
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "ts-node-dev --respawn --transpile-only src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"bcrypt": "^5.1.1",
"cors": "^2.8.5",
"dotenv": "^16.0.3",
"express": "^4.21.2",
"express-session": "^1.18.1",
"jsonwebtoken": "^9.0.2",
"passport": "^0.7.0",
"passport-local": "^1.0.0",
"ws": "^8.18.0",
"zod": "^3.24.2",
"zod-validation-error": "^3.4.0"
},
"devDependencies": {
"@types/bcrypt": "^5.0.2",
"@types/cors": "^2.8.18",
"@types/express": "^5.0.1",
"@types/express-session": "^1.18.0",
"@types/jsonwebtoken": "^9.0.9",
"@types/node": "20.16.11",
"@types/passport": "^1.0.16",
"@types/passport-local": "^1.0.38",
"@types/ws": "^8.5.13",
"ts-node-dev": "^2.0.0"
}
}

36
apps/Backend/src/app.ts Normal file
View File

@@ -0,0 +1,36 @@
import express from 'express';
import cors from "cors";
import routes from './routes';
import { errorHandler } from './middlewares/error.middleware';
import { apiLogger } from './middlewares/logger.middleware';
import authRoutes from './routes/auth'
import { authenticateJWT } from './middlewares/auth.middleware';
import dotenv from 'dotenv';
dotenv.config();
const FRONTEND_URL = process.env.FRONTEND_URL;
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true })); // For form data
app.use(apiLogger);
console.log(FRONTEND_URL);
app.use(cors({
origin: FRONTEND_URL, // Make sure this matches the frontend URL
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // Allow these HTTP methods
allowedHeaders: ['Content-Type', 'Authorization'], // Allow necessary headers
credentials: true,
}));
app.use('/api/auth', authRoutes);
app.use('/api', authenticateJWT, routes);
app.use(errorHandler);
export default app;

11
apps/Backend/src/index.ts Normal file
View File

@@ -0,0 +1,11 @@
import app from './app';
import dotenv from 'dotenv';
dotenv.config();
const HOST = process.env.HOST;
const PORT = process.env.PORT;
app.listen(PORT, () => {
console.log(`Server running at http://${HOST}:${PORT}`);
});

View File

@@ -0,0 +1,26 @@
import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';
const JWT_SECRET = process.env.JWT_SECRET || 'your-jwt-secret'; // Secret used for signing JWTs
export function authenticateJWT(req: Request, res: Response, next: NextFunction): void{
// Check the Authorization header for the Bearer token
const token = req.header('Authorization')?.split(' ')[1]; // Extract token from Authorization header
if (!token) {
res.status(401).send("Access denied. No token provided.");
return;
}
// Verify JWT
jwt.verify(token, JWT_SECRET, (err, decoded) => {
if (err) {
return res.status(403).send("Forbidden. Invalid token.");
}
// Attach the decoded user data to the request object
req.user = decoded as Express.User;
next(); // Proceed to the next middleware or route handler
});
}

View File

@@ -0,0 +1,6 @@
import { Request, Response, NextFunction } from 'express';
export const errorHandler = (err: any, _req: Request, res: Response, _next: NextFunction) => {
console.error(err);
res.status(err.status || 500).json({ message: err.message || 'Internal Server Error' });
};

View File

@@ -0,0 +1,33 @@
import { Request, Response, NextFunction } from "express";
function log(message: string) {
console.log(`[${new Date().toISOString()}] ${message}`);
}
export function apiLogger(req: Request, res: Response, next: NextFunction) {
const start = Date.now();
const path = req.path;
let capturedJsonResponse: Record<string, any> | undefined = undefined;
const originalResJson = res.json;
res.json = function (bodyJson, ...args) {
capturedJsonResponse = bodyJson;
return originalResJson.apply(res, [bodyJson, ...args]);
};
res.on("finish", () => {
const duration = Date.now() - start;
if (path.startsWith("/api")) {
let logLine = `${req.method} ${path} ${res.statusCode} in ${duration}ms`;
if (capturedJsonResponse) {
logLine += ` :: ${JSON.stringify(capturedJsonResponse)}`;
}
if (logLine.length > 80) {
logLine = logLine.slice(0, 79) + "…";
}
log(logLine);
}
});
next();
}

View File

@@ -0,0 +1,351 @@
import { Router } from "express";
import type { Request, Response } from "express";
import { storage } from "../storage";
import {
AppointmentUncheckedCreateInputObjectSchema,
PatientUncheckedCreateInputObjectSchema,
} from "@repo/db/shared/schemas";
import { z } from "zod";
const router = Router();
//creating types out of schema auto generated.
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
const insertAppointmentSchema = (
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
id: true,
createdAt: true,
});
type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
const updateAppointmentSchema = (
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
)
.omit({
id: true,
createdAt: true,
})
.partial();
type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
const PatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
const insertPatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
id: true,
createdAt: true,
});
type InsertPatient = z.infer<typeof insertPatientSchema>;
const updatePatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
)
.omit({
id: true,
createdAt: true,
userId: true,
})
.partial();
type UpdatePatient = z.infer<typeof updatePatientSchema>;
// Get all appointments
router.get("/all", async (req: Request, res: Response): Promise<any> => {
try {
const appointments = await storage.getAllAppointments();
res.json(appointments);
} catch (error) {
res.status(500).json({ message: "Failed to retrieve all appointments" });
}
});
// Get a single appointment by ID
router.get(
"/:id",
async (req: Request, res: Response): Promise<any> => {
try {
const appointmentIdParam = req.params.id;
// Ensure that patientIdParam exists and is a valid number
if (!appointmentIdParam) {
return res.status(400).json({ message: "Appointment ID is required" });
}
const appointmentId = parseInt(appointmentIdParam);
const appointment = await storage.getAppointment(appointmentId);
if (!appointment) {
return res.status(404).json({ message: "Appointment not found" });
}
// Ensure the appointment belongs to the logged-in user
if (appointment.userId !== req.user!.id) {
return res.status(403).json({ message: "Forbidden" });
}
res.json(appointment);
} catch (error) {
res.status(500).json({ message: "Failed to retrieve appointment" });
}
}
);
// Create a new appointment
router.post(
"/",
async (req: Request, res: Response): Promise<any> => {
try {
console.log("Appointment creation request body:", req.body);
// Validate request body
const appointmentData = insertAppointmentSchema.parse({
...req.body,
userId: req.user!.id,
});
console.log("Validated appointment data:", appointmentData);
// Verify patient exists and belongs to user
const patient = await storage.getPatient(appointmentData.patientId);
if (!patient) {
console.log("Patient not found:", appointmentData.patientId);
return res.status(404).json({ message: "Patient not found" });
}
if (patient.userId !== req.user!.id) {
console.log(
"Patient belongs to another user. Patient userId:",
patient.userId,
"Request userId:",
req.user!.id
);
return res.status(403).json({ message: "Forbidden" });
}
// Check if there's already an appointment at this time slot
const existingAppointments = await storage.getAppointmentsByUserId(
req.user!.id
);
const conflictingAppointment = existingAppointments.find(
(apt) =>
apt.date === appointmentData.date &&
apt.startTime === appointmentData.startTime &&
apt.notes?.includes(
appointmentData.notes.split("Appointment with ")[1]
)
);
if (conflictingAppointment) {
console.log(
"Time slot already booked:",
appointmentData.date,
appointmentData.startTime
);
return res.status(409).json({
message:
"This time slot is already booked. Please select another time or staff member.",
});
}
// Create appointment
const appointment = await storage.createAppointment(appointmentData);
console.log("Appointment created successfully:", appointment);
res.status(201).json(appointment);
} catch (error) {
console.error("Error creating appointment:", error);
if (error instanceof z.ZodError) {
console.log(
"Validation error details:",
JSON.stringify(error.format(), null, 2)
);
return res.status(400).json({
message: "Validation error",
errors: error.format(),
});
}
res.status(500).json({
message: "Failed to create appointment",
error: error instanceof Error ? error.message : String(error),
});
}
}
);
// Update an existing appointment
router.put(
"/:id",
async (req: Request, res: Response): Promise<any> => {
try {
const appointmentIdParam = req.params.id;
if (!appointmentIdParam) {
return res.status(400).json({ message: "Appointment ID is required" });
}
const appointmentId = parseInt(appointmentIdParam);
console.log(
"Update appointment request. ID:",
appointmentId,
"Body:",
req.body
);
// Check if appointment exists and belongs to user
const existingAppointment = await storage.getAppointment(appointmentId);
if (!existingAppointment) {
console.log("Appointment not found:", appointmentId);
return res.status(404).json({ message: "Appointment not found" });
}
if (existingAppointment.userId !== req.user!.id) {
console.log(
"Appointment belongs to another user. Appointment userId:",
existingAppointment.userId,
"Request userId:",
req.user!.id
);
return res.status(403).json({ message: "Forbidden" });
}
// Validate request body
const appointmentData = updateAppointmentSchema.parse(req.body);
console.log("Validated appointment update data:", appointmentData);
// If patient ID is being updated, verify the new patient belongs to user
if (
appointmentData.patientId &&
appointmentData.patientId !== existingAppointment.patientId
) {
const patient = await storage.getPatient(appointmentData.patientId);
if (!patient) {
console.log("New patient not found:", appointmentData.patientId);
return res.status(404).json({ message: "Patient not found" });
}
if (patient.userId !== req.user!.id) {
console.log(
"New patient belongs to another user. Patient userId:",
patient.userId,
"Request userId:",
req.user!.id
);
return res.status(403).json({ message: "Forbidden" });
}
}
// Check if there's already an appointment at this time slot (if time is being changed)
if (
appointmentData.date &&
appointmentData.startTime &&
(appointmentData.date !== existingAppointment.date ||
appointmentData.startTime !== existingAppointment.startTime)
) {
// Extract staff name from notes
const staffInfo =
appointmentData.notes?.split("Appointment with ")[1] ||
existingAppointment.notes?.split("Appointment with ")[1];
const existingAppointments = await storage.getAppointmentsByUserId(
req.user!.id
);
const conflictingAppointment = existingAppointments.find(
(apt) =>
apt.id !== appointmentId && // Don't match with itself
apt.date === (appointmentData.date || existingAppointment.date) &&
apt.startTime ===
(appointmentData.startTime || existingAppointment.startTime) &&
apt.notes?.includes(staffInfo)
);
if (conflictingAppointment) {
console.log(
"Time slot already booked:",
appointmentData.date,
appointmentData.startTime
);
return res.status(409).json({
message:
"This time slot is already booked. Please select another time or staff member.",
});
}
}
// Update appointment
const updatedAppointment = await storage.updateAppointment(
appointmentId,
appointmentData
);
console.log("Appointment updated successfully:", updatedAppointment);
res.json(updatedAppointment);
} catch (error) {
console.error("Error updating appointment:", error);
if (error instanceof z.ZodError) {
console.log(
"Validation error details:",
JSON.stringify(error.format(), null, 2)
);
return res.status(400).json({
message: "Validation error",
errors: error.format(),
});
}
res.status(500).json({
message: "Failed to update appointment",
error: error instanceof Error ? error.message : String(error),
});
}
}
);
// Delete an appointment
router.delete(
"/:id",
async (req: Request, res: Response): Promise<any> => {
try {
const appointmentIdParam = req.params.id;
if (!appointmentIdParam) {
return res.status(400).json({ message: "Appointment ID is required" });
}
const appointmentId = parseInt(appointmentIdParam);
// Check if appointment exists and belongs to user
const existingAppointment = await storage.getAppointment(appointmentId);
if (!existingAppointment) {
return res.status(404).json({ message: "Appointment not found" });
}
if (existingAppointment.userId !== req.user!.id) {
return res.status(403).json({ message: "Forbidden" });
}
// Delete appointment
await storage.deleteAppointment(appointmentId);
res.status(204).send();
} catch (error) {
res.status(500).json({ message: "Failed to delete appointment" });
}
}
);
export default router;

View File

@@ -0,0 +1,87 @@
import express, { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import { storage } from '../storage';
import { UserUncheckedCreateInputObjectSchema } from '@repo/db/shared/schemas';
import { z } from 'zod';
type SelectUser = z.infer<typeof UserUncheckedCreateInputObjectSchema>;
const JWT_SECRET = process.env.JWT_SECRET || 'your-jwt-secret';
const JWT_EXPIRATION = '24h'; // JWT expiration time (1 day)
// Function to hash password using bcrypt
async function hashPassword(password: string) {
const saltRounds = 10; // Salt rounds for bcrypt
const hashedPassword = await bcrypt.hash(password, saltRounds);
return hashedPassword;
}
// Function to compare passwords using bcrypt
async function comparePasswords(supplied: string, stored: string) {
const isMatch = await bcrypt.compare(supplied, stored);
return isMatch;
}
// Function to generate JWT
function generateToken(user: SelectUser) {
return jwt.sign({ id: user.id, username: user.username }, JWT_SECRET, {
expiresIn: JWT_EXPIRATION,
});
}
const router = express.Router();
// User registration route
router.post("/register", async (req: Request, res: Response, next: NextFunction): Promise<any> => {
try {
const existingUser = await storage.getUserByUsername(req.body.username);
if (existingUser) {
return res.status(400).send("Username already exists");
}
const hashedPassword = await hashPassword(req.body.password);
const user = await storage.createUser({
...req.body,
password: hashedPassword,
});
// Generate a JWT token for the user after successful registration
const token = generateToken(user);
const { password, ...safeUser } = user;
return res.status(201).json({ user: safeUser, token });
} catch (error) {
next(error);
}
});
// User login route
router.post("/login", async (req: Request, res: Response, next: NextFunction): Promise<any> => {
try {
const user = await storage.getUserByUsername(req.body.username);
if (!user || !(await comparePasswords(req.body.password, user.password))) {
return res.status(401).send("Invalid username or password");
}
// Generate a JWT token for the user after successful login
const token = generateToken(user);
const { password, ...safeUser } = user;
return res.status(200).json({ user: safeUser, token });
} catch (error) {
next(error);
}
});
// Logout route (client-side action to remove the token)
router.post("/logout", (req: Request, res: Response) => {
// For JWT-based auth, logout is handled on the client (by removing token)
res.status(200).send("Logged out successfully");
});
export default router;

View File

@@ -0,0 +1,14 @@
import { Router } from 'express';
import patientRoutes from './patients';
import appointmentRoutes from './appointements'
import userRoutes from './users'
import staffRoutes from './staffs'
const router = Router();
router.use('/patients', patientRoutes);
router.use('/appointments', appointmentRoutes);
router.use('/users', userRoutes);
router.use('/staffs', staffRoutes);
export default router;

View File

@@ -0,0 +1,252 @@
import { Router } from "express";
import type { Request, Response } from "express";
import { storage } from "../storage";
import {
AppointmentUncheckedCreateInputObjectSchema,
PatientUncheckedCreateInputObjectSchema,
} from "@repo/db/shared/schemas";
import { z } from "zod";
const router = Router();
//creating types out of schema auto generated.
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
const insertAppointmentSchema = (
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
id: true,
createdAt: true,
});
type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
const updateAppointmentSchema = (
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
)
.omit({
id: true,
createdAt: true,
})
.partial();
type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
const PatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
const insertPatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
id: true,
createdAt: true,
});
type InsertPatient = z.infer<typeof insertPatientSchema>;
const updatePatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
)
.omit({
id: true,
createdAt: true,
userId: true,
})
.partial();
type UpdatePatient = z.infer<typeof updatePatientSchema>;
// Patient Routes
// Get all patients for the logged-in user
router.get("/", async (req, res) => {
try {
const patients = await storage.getPatientsByUserId(req.user!.id);
res.json(patients);
} catch (error) {
res.status(500).json({ message: "Failed to retrieve patients" });
}
});
// Get a single patient by ID
router.get(
"/:id",
async (req: Request, res: Response): Promise<any> => {
try {
const patientIdParam = req.params.id;
// Ensure that patientIdParam exists and is a valid number
if (!patientIdParam) {
return res.status(400).json({ message: "Patient ID is required" });
}
const patientId = parseInt(patientIdParam);
const patient = await storage.getPatient(patientId);
if (!patient) {
return res.status(404).json({ message: "Patient not found" });
}
// Ensure the patient belongs to the logged-in user
if (patient.userId !== req.user!.id) {
return res.status(403).json({ message: "Forbidden" });
}
res.json(patient);
} catch (error) {
res.status(500).json({ message: "Failed to retrieve patient" });
}
}
);
// Create a new patient
router.post("/", async (req: Request, res: Response): Promise<any> => {
try {
// Validate request body
const patientData = insertPatientSchema.parse({
...req.body,
userId: req.user!.id,
});
// Create patient
const patient = await storage.createPatient(patientData);
res.status(201).json(patient);
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({
message: "Validation error",
errors: error.format(),
});
}
res.status(500).json({ message: "Failed to create patient" });
}
});
// Update an existing patient
router.put(
"/:id",
async (req: Request, res: Response): Promise<any> => {
try {
const patientIdParam = req.params.id;
// Ensure that patientIdParam exists and is a valid number
if (!patientIdParam) {
return res.status(400).json({ message: "Patient ID is required" });
}
const patientId = parseInt(patientIdParam);
// Check if patient exists and belongs to user
const existingPatient = await storage.getPatient(patientId);
if (!existingPatient) {
return res.status(404).json({ message: "Patient not found" });
}
if (existingPatient.userId !== req.user!.id) {
return res.status(403).json({ message: "Forbidden" });
}
// Validate request body
const patientData = updatePatientSchema.parse(req.body);
// Update patient
const updatedPatient = await storage.updatePatient(
patientId,
patientData
);
res.json(updatedPatient);
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({
message: "Validation error",
errors: error.format(),
});
}
res.status(500).json({ message: "Failed to update patient" });
}
}
);
// Delete a patient
router.delete(
"/:id",
async (req: Request, res: Response): Promise<any> => {
try {
const patientIdParam = req.params.id;
// Ensure that patientIdParam exists and is a valid number
if (!patientIdParam) {
return res.status(400).json({ message: "Patient ID is required" });
}
const patientId = parseInt(patientIdParam);
// Check if patient exists and belongs to user
const existingPatient = await storage.getPatient(patientId);
if (!existingPatient) {
return res.status(404).json({ message: "Patient not found" });
}
if (existingPatient.userId !== req.user!.id) {
return res.status(403).json({ message: "Forbidden" });
}
// Delete patient
await storage.deletePatient(patientId);
res.status(204).send();
} catch (error) {
res.status(500).json({ message: "Failed to delete patient" });
}
}
);
// Appointment Routes
// Get all appointments for the logged-in user
router.get("/appointments", async (req, res) => {
try {
const appointments = await storage.getAppointmentsByUserId(req.user!.id);
res.json(appointments);
} catch (error) {
res.status(500).json({ message: "Failed to retrieve appointments" });
}
});
// Get appointments for a specific patient
router.get(
"/:patientId/appointments",
async (req: Request, res: Response): Promise<any> => {
try {
const patientIdParam = req.params.id;
// Ensure that patientIdParam exists and is a valid number
if (!patientIdParam) {
return res.status(400).json({ message: "Patient ID is required" });
}
const patientId = parseInt(patientIdParam);
// Check if patient exists and belongs to user
const patient = await storage.getPatient(patientId);
if (!patient) {
return res.status(404).json({ message: "Patient not found" });
}
if (patient.userId !== req.user!.id) {
return res.status(403).json({ message: "Forbidden" });
}
const appointments = await storage.getAppointmentsByPatientId(patientId);
res.json(appointments);
} catch (error) {
res.status(500).json({ message: "Failed to retrieve appointments" });
}
}
);
export default router;

View File

@@ -0,0 +1,77 @@
import { Router } from "express";
import type { Request, Response } from "express";
import { storage } from "../storage";
import { z } from "zod";
import { StaffUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
const staffCreateSchema = StaffUncheckedCreateInputObjectSchema;
const staffUpdateSchema = (
StaffUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).partial();
const router = Router();
router.post("/", async (req: Request, res: Response): Promise<any> => {
try {
const validatedData = staffCreateSchema.parse(req.body);
const newStaff = await storage.createStaff(validatedData);
res.status(201).json(newStaff);
} catch (error) {
console.error("Failed to create staff:", error);
res.status(500).send("Failed to create staff");
}
});
router.get("/", async (req: Request, res: Response): Promise<any> => {
try {
const staff = await storage.getAllStaff();
if (!staff) return res.status(404).send("Staff not found");
res.status(201).json(staff);
} catch (error) {
console.error("Failed to fetch staff:", error);
res.status(500).send("Failed to fetch staff");
}
});
router.put("/:id", async (req: Request, res: Response): Promise<any> => {
try {
const parsedStaffId = Number(req.params.id);
if (isNaN(parsedStaffId)) {
return res.status(400).send("Invalid staff ID");
}
const validatedData = staffUpdateSchema.parse(req.body);
const updatedStaff = await storage.updateStaff(
parsedStaffId,
validatedData
);
if (!updatedStaff) return res.status(404).send("Staff not found");
res.json(updatedStaff);
} catch (error) {
console.error("Failed to update staff:", error);
res.status(500).send("Failed to update staff");
}
});
router.delete("/:id", async (req: Request, res: Response): Promise<any> => {
try {
const parsedStaffId = Number(req.params.id);
if (isNaN(parsedStaffId)) {
return res.status(400).send("Invalid staff ID");
}
const deleted = await storage.deleteStaff(parsedStaffId);
if (!deleted) return res.status(404).send("Staff not found");
res.status(200).send("Staff deleted successfully");
} catch (error) {
console.error("Failed to delete staff:", error);
res.status(500).send("Failed to delete staff");
}
});
export default router;

View File

@@ -0,0 +1,108 @@
import { Router } from "express";
import type { Request, Response } from "express";
import { storage } from "../storage";
import { z } from "zod";
import { UserUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
const router = Router();
// Type based on shared schema
type SelectUser = z.infer<typeof UserUncheckedCreateInputObjectSchema>;
// Zod validation
const userCreateSchema = UserUncheckedCreateInputObjectSchema;
const userUpdateSchema = (UserUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).partial();
router.get("/", async (req: Request, res: Response): Promise<any> => {
try {
const userId = req.user?.id;
if (!userId) return res.status(401).send("Unauthorized UserId");
const user = await storage.getUser(userId);
if (!user) return res.status(404).send("User not found");
const { password, ...safeUser } = user;
res.json(safeUser);
} catch (error) {
console.error(error);
res.status(500).send("Failed to fetch user");
}
});
// GET: User by ID
router.get("/:id", async (req: Request, res: Response): Promise<any> => {
try {
const idParam = req.params.id;
if (!idParam) return res.status(400).send("User ID is required");
const id = parseInt(idParam);
if (isNaN(id)) return res.status(400).send("Invalid user ID");
const user = await storage.getUser(id);
if (!user) return res.status(404).send("User not found");
const { password, ...safeUser } = user;
res.json(safeUser);
} catch (error) {
console.error(error);
res.status(500).send("Failed to fetch user");
}
});
// POST: Create new user
router.post("/", async (req: Request, res: Response) => {
try {
const input = userCreateSchema.parse(req.body);
const newUser = await storage.createUser(input);
const { password, ...safeUser } = newUser;
res.status(201).json(safeUser);
} catch (err) {
console.error(err);
res.status(400).json({ error: "Invalid user data", details: err });
}
});
// PUT: Update user
router.put("/:id", async (req: Request, res: Response):Promise<any> => {
try {
const idParam = req.params.id;
if (!idParam) return res.status(400).send("User ID is required");
const id = parseInt(idParam);
if (isNaN(id)) return res.status(400).send("Invalid user ID");
const updates = userUpdateSchema.parse(req.body);
const updatedUser = await storage.updateUser(id, updates);
if (!updatedUser) return res.status(404).send("User not found");
const { password, ...safeUser } = updatedUser;
res.json(safeUser);
} catch (err) {
console.error(err);
res.status(400).json({ error: "Invalid update data", details: err });
}
});
// DELETE: Delete user
router.delete("/:id", async (req: Request, res: Response):Promise<any> => {
try {
const idParam = req.params.id;
if (!idParam) return res.status(400).send("User ID is required");
const id = parseInt(idParam);
if (isNaN(id)) return res.status(400).send("Invalid user ID");
const success = await storage.deleteUser(id);
if (!success) return res.status(404).send("User not found");
res.status(204).send();
} catch (error) {
console.error(error);
res.status(500).send("Failed to delete user");
}
});
export default router;

View File

@@ -0,0 +1,284 @@
import { prisma as db } from "@repo/db/client";
import {
AppointmentUncheckedCreateInputObjectSchema,
PatientUncheckedCreateInputObjectSchema,
UserUncheckedCreateInputObjectSchema,
StaffUncheckedCreateInputObjectSchema,
} from "@repo/db/shared/schemas";
import { z } from "zod";
//creating types out of schema auto generated.
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
const insertAppointmentSchema = (
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
id: true,
createdAt: true,
});
type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
const updateAppointmentSchema = (
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
)
.omit({
id: true,
createdAt: true,
})
.partial();
type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
//patient types
const PatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientUncheckedCreateInputObjectSchema>;
type Patient2 = z.infer<typeof PatientSchema>;
const insertPatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
id: true,
createdAt: true,
});
type InsertPatient = z.infer<typeof insertPatientSchema>;
const updatePatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
)
.omit({
id: true,
createdAt: true,
userId: true,
})
.partial();
type UpdatePatient = z.infer<typeof updatePatientSchema>;
//user types
type User = z.infer<typeof UserUncheckedCreateInputObjectSchema>;
const insertUserSchema = (
UserUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).pick({
username: true,
password: true,
});
const loginSchema = (insertUserSchema as unknown as z.ZodObject<any>).extend({
rememberMe: z.boolean().optional(),
});
const registerSchema = (insertUserSchema as unknown as z.ZodObject<any>)
.extend({
confirmPassword: z.string().min(6, {
message: "Password must be at least 6 characters long",
}),
agreeTerms: z.literal(true, {
errorMap: () => ({
message: "You must agree to the terms and conditions",
}),
}),
})
.refine((data: any) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ["confirmPassword"],
});
type InsertUser = z.infer<typeof insertUserSchema>;
type LoginFormValues = z.infer<typeof loginSchema>;
type RegisterFormValues = z.infer<typeof registerSchema>;
// staff types:
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
export interface IStorage {
// User methods
getUser(id: number): Promise<User | undefined>;
getUserByUsername(username: string): Promise<User | undefined>;
createUser(user: InsertUser): Promise<User>;
updateUser(id: number, updates: Partial<User>): Promise<User | undefined>;
deleteUser(id: number): Promise<boolean>;
// Patient methods
getPatient(id: number): Promise<Patient | undefined>;
getPatientsByUserId(userId: number): Promise<Patient[]>;
createPatient(patient: InsertPatient): Promise<Patient>;
updatePatient(id: number, patient: UpdatePatient): Promise<Patient>;
deletePatient(id: number): Promise<void>;
// Appointment methods
getAppointment(id: number): Promise<Appointment | undefined>;
getAllAppointments(): Promise<Appointment[]>;
getAppointmentsByUserId(userId: number): Promise<Appointment[]>;
getAppointmentsByPatientId(patientId: number): Promise<Appointment[]>;
createAppointment(appointment: InsertAppointment): Promise<Appointment>;
updateAppointment(
id: number,
appointment: UpdateAppointment
): Promise<Appointment>;
deleteAppointment(id: number): Promise<void>;
// Staff methods
getStaff(id: number): Promise<Staff | undefined>;
getAllStaff(): Promise<Staff[]>;
createStaff(staff: Staff): Promise<Staff>;
updateStaff(id: number, updates: Partial<Staff>): Promise<Staff | undefined>;
deleteStaff(id: number): Promise<boolean>;
}
export const storage: IStorage = {
// User methods
async getUser(id: number): Promise<User | undefined> {
const user = await db.user.findUnique({ where: { id } });
return user ?? undefined;
},
async getUserByUsername(username: string): Promise<User | undefined> {
const user = await db.user.findUnique({ where: { username } });
return user ?? undefined;
},
async createUser(user: InsertUser): Promise<User> {
return await db.user.create({ data: user as User });
},
async updateUser(
id: number,
updates: Partial<User>
): Promise<User | undefined> {
try {
return await db.user.update({ where: { id }, data: updates });
} catch {
return undefined;
}
},
async deleteUser(id: number): Promise<boolean> {
try {
await db.user.delete({ where: { id } });
return true;
} catch {
return false;
}
},
// Patient methods
async getPatient(id: number): Promise<Patient | undefined> {
const patient = await db.patient.findUnique({ where: { id } });
return patient ?? undefined;
},
async getPatientsByUserId(userId: number): Promise<Patient[]> {
return await db.patient.findMany({ where: { userId } });
},
async createPatient(patient: InsertPatient): Promise<Patient> {
return await db.patient.create({ data: patient as Patient });
},
async updatePatient(id: number, updateData: UpdatePatient): Promise<Patient> {
try {
return await db.patient.update({
where: { id },
data: updateData,
});
} catch (err) {
throw new Error(`Patient with ID ${id} not found`);
}
},
async deletePatient(id: number): Promise<void> {
try {
await db.patient.delete({ where: { id } });
} catch (err) {
throw new Error(`Patient with ID ${id} not found`);
}
},
// Appointment methods
async getAppointment(id: number): Promise<Appointment | undefined> {
const appointment = await db.appointment.findUnique({ where: { id } });
return appointment ?? undefined;
},
async getAllAppointments(): Promise<Appointment[]> {
return await db.appointment.findMany();
},
async getAppointmentsByUserId(userId: number): Promise<Appointment[]> {
return await db.appointment.findMany({ where: { userId } });
},
async getAppointmentsByPatientId(patientId: number): Promise<Appointment[]> {
return await db.appointment.findMany({ where: { patientId } });
},
async createAppointment(
appointment: InsertAppointment
): Promise<Appointment> {
return await db.appointment.create({ data: appointment as Appointment });
},
async updateAppointment(
id: number,
updateData: UpdateAppointment
): Promise<Appointment> {
try {
return await db.appointment.update({
where: { id },
data: updateData,
});
} catch (err) {
throw new Error(`Appointment with ID ${id} not found`);
}
},
async deleteAppointment(id: number): Promise<void> {
try {
await db.appointment.delete({ where: { id } });
} catch (err) {
throw new Error(`Appointment with ID ${id} not found`);
}
},
async getStaff(id: number): Promise<Staff | undefined> {
const staff = await db.staff.findUnique({ where: { id } });
return staff ?? undefined;
},
async getAllStaff(): Promise<Staff[]> {
const staff = await db.staff.findMany();
return staff;
},
async createStaff(staff: Staff): Promise<Staff> {
const createdStaff = await db.staff.create({
data: staff,
});
return createdStaff;
},
async updateStaff(
id: number,
updates: Partial<Staff>
): Promise<Staff | undefined> {
const updatedStaff = await db.staff.update({
where: { id },
data: updates,
});
return updatedStaff ?? undefined;
},
async deleteStaff(id: number): Promise<boolean> {
try {
await db.staff.delete({ where: { id } });
return true;
} catch (error) {
console.error("Error deleting staff:", error);
return false;
}
},
};

View File

@@ -0,0 +1,10 @@
import { User } from "@repo/db/client";
declare global {
namespace Express {
interface User {
id: number;
// include any other properties
}
}
}

View File

@@ -0,0 +1,10 @@
{
"extends": "@repo/typescript-config/base.json",
"compilerOptions": {
"outDir":"dist"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}