Skip to content
Open
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
node_modules/
.next/
dist/
.env.local
.DS_Store
.next
playwright-report/
.env
124 changes: 124 additions & 0 deletions app/api/lab/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// /app/api/lab/[id]/route.ts

/**
* API Route for Lab Management by ID
* This file defines the API routes for managing lab entries in the inventory
* system based on their unique ID. It includes handlers for fetching,
* updating, and deleting lab entries by ID.
*/

'use server'

import { NextResponse } from "next/server";
import { z } from "zod";
import { getLab, updateLab, deleteLab } from "@/services/labs/labs";

type Params = { id: string };

// Define a Zod schema for validating lab updates, allowing for partial updates
const labUpdateSchema = z
.object({
name: z.string().min(1),
department: z.string().min(1),
createdAt: z.coerce.date(),
});

/**
* Get one lab entry by ID
* @param request request object
* @param context context object containing route parameters
* @return response with lab data or error message
*/
export async function GET(request: Request, context : { params: Params }) {
try {
const parsedParams = z.object({ id: z.string().min(1) })
.safeParse(context.params);
if (!parsedParams.success) {
return NextResponse.json({ message: "Invalid ID" },
{ status: 400 });
}
const item = await getLab(parsedParams.data.id);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good but let's update this to return a list of labs when we pass in user id. Because what if a user is a part of multiple labs?

if (!item) {
return NextResponse.json({ message: "Lab not found" },
{ status: 404 });
}
return NextResponse.json(item, { status: 200 });
} catch (err) {
console.error(err);
return NextResponse.json({ message: "Internal server error" },
{ status: 500 });
}
}

/**
* Update a lab entry by ID
* @param request request object
* @param context context object containing route parameters
* @return response after updating lab entry
*/
export async function PUT(request: Request, context : { params: Params }) {
try {
// Validate the ID parameter and request body, then attempt to update
// the lab entry
const parsedParams = z.object({ id: z.string().min(1) })
.safeParse(context.params);
if (!parsedParams.success) {
return NextResponse.json({ message: "Invalid ID" },
{ status: 400 });
}
// Validate the request body against the lab update schema, allowing
// for partial updates
const parsedBody = labUpdateSchema.partial().safeParse(
await request.json());
if (!parsedBody.success) {
return NextResponse.json({ message: "Invalid data" },
{ status: 400 });
}
// Attempt to update the lab entry and return appropriate response
// based on the result
const updatedLab = await updateLab(parsedParams.data.id,
parsedBody.data);
if (!updatedLab) {
return NextResponse.json({ message: "Lab not found" },
{ status: 404 });
}
// Return the updated lab entry if the update was successful
return NextResponse.json(updatedLab, { status: 200 });
} catch (err) {
console.error(err);
return NextResponse.json({ message: "Internal server error" },
{ status: 500 });
}
}

/**
* Delete a lab entry by ID
* @param request request object
* @param context context object containing route parameters
* @return response after deleting the lab entry
*/
export async function DELETE(request: Request, context : { params: Params }) {
try {
// Validate the ID parameter and attempt to delete the lab entry
const parsedParams = z.object({ id: z.string().min(1) }).safeParse(
context.params);
if (!parsedParams.success) {
return NextResponse.json({ message: "Invalid ID" },
{ status: 400 });
}
// Attempt to delete the lab entry and return appropriate response
// based on the result
const deleted = await deleteLab(parsedParams.data.id);
if (!deleted) {
return NextResponse.json({ message: "Lab not found" },
{ status: 404 });
}
// Return a success message if the lab entry was deleted successfully
return NextResponse.json({ message: "Lab deleted successfully" },
{ status: 200 });
} catch (err) {
console.error(err);
return NextResponse.json({ message: "Internal server error" },
{ status: 500 });
}
}
54 changes: 54 additions & 0 deletions app/api/lab/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// /app/api/lab/route.ts

/**
* API Route for Lab Management
* This file defines the API routes for managing lab entries in the inventory
* system. It includes handlers for fetching all labs and creating new lab
* entries.
*/

'use server'

import { NextResponse } from "next/server";
import { getLabs, addLab } from "@/services/labs/labs";

// Implement pagination later on (if necessary)
// const PAGE_SIZE = 10;

/**
* Fetch all lab entries
* @param request request object
* @return response with lab data
*/
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
/*
For later use in pagination:
const page = parseInt(searchParams.get("page") || "1");
const limit = parseInt(searchParams.get("limit") || "10");
*/
try {
// Fetch all lab entries from the database and return them in the response
const labs = await getLabs();
return NextResponse.json(labs, { status: 200 });
} catch (err) {
console.error(err);
return NextResponse.json({ message: "Internal server error" }, { status: 500 });
}
}

/**
* Create a new lab entry
* @param request request object
* @return response after creating new lab entry
*/
export async function POST(request: Request) {
try {
// Connect to the database and create a new lab entry with the request body
const newLab = await addLab(await request.json());
return NextResponse.json(newLab, { status: 201 });
} catch (err) {
console.error(err);
return NextResponse.json({ message: "Internal server error" }, { status: 500 });
}
}
58 changes: 51 additions & 7 deletions models/Lab.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,59 @@
import mongoose from "mongoose";
import {
HydratedDocument,
InferSchemaType,
Model,
Schema,
model,
models,
FlattenMaps,
Types
} from "mongoose";

// Lab Schema definition
// Each lab has a unique name, department, and a timestamp for when it was created
const labSchema = new mongoose.Schema(
const transformDocument = (_: unknown, ret: Record<string, unknown>) => {
ret.id = ret._id?.toString();
delete ret._id;
return ret;
};

/**
* Lab Schema Definition
* This schema defines the structure of a lab entry in the inventory system.
* Each lab has a unique name, department, and a timestamp for when it was
* created.
*/
const labSchema = new Schema(
{
name: { type: String, required: true },
department: { type: String, required: true },
createdAt: { type: Date, required: true, default: Date.now }
},
{
toJSON: { virtuals: true, versionKey: false,
transform: transformDocument },
toObject: { virtuals: true, versionKey: false,
transform: transformDocument },
}
);

// Create and export the Lab model
const Lab = mongoose.models.Lab || mongoose.model("Lab", labSchema);
export default Lab;
// Create and export the Lab model
export type LabInput = InferSchemaType<typeof labSchema>;
export type Lab = Omit<LabInput, "_id"> & { id: string };
export type LabDocument = HydratedDocument<LabInput>;
// Lean type for Lab, used when fetching data without Mongoose document methods
export type LabLean = FlattenMaps<LabInput> & { _id: Types.ObjectId };

const LabModel: Model<LabInput> =
(models.Lab as Model<LabInput>) || model<LabInput>("Lab", labSchema);

export default LabModel;

// Utility functions to convert Mongoose documents to plain JavaScript objects
// with the desired structure
export const toLab = (doc: LabDocument): Lab => doc.toObject<Lab>();
export const toLabFromLean = (obj: LabLean): Lab => {
const { _id, ...rest } = obj as any;
return {
...(rest as Omit<LabInput, "_id">),
id: String(_id),
};
};
76 changes: 76 additions & 0 deletions services/labs/labs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import type { HydratedDocument }from "mongoose";

import { connectToDatabase } from "@/lib/mongoose";
import LabModel, { Lab, LabInput, toLab, toLabFromLean } from "@/models/Lab";

type LabDocument = HydratedDocument<LabInput>;

/**
* Get all lab entries
* @returns an array of labs
*/
export async function getLabs(): Promise<Lab[]> {
await connectToDatabase();
const labs = await LabModel.find()
.sort({ createdAt: -1})
.lean()
.exec();
return labs.map(lab => toLabFromLean(lab));
}

/**
* Get a lab entry by ID
* @param id the ID of the lab to fetch
* @returns the lab
*/
export async function getLab(id: string): Promise<Lab | null> {
await connectToDatabase();
const lab = await LabModel.findById(id).lean().exec();
return lab ? toLabFromLean(lab) : null;
}

/**
* Add a new lab entry
* @param newLab the lab data to add
* @returns the created lab
*/
export async function addLab(newLab: Lab): Promise<Lab> {
await connectToDatabase();
const createdLab = await LabModel.create(newLab);
return toLab(createdLab);
}

/**
* Update a lab entry by ID
* @param id the ID of the lab to update
* @param data the data to update
* @returns the updated lab or null if not found
*/
export async function updateLab(
id: string,
data: Partial<Lab>,
): Promise<Lab | null> {
await connectToDatabase();
const updatedLab = await LabModel.findByIdAndUpdate(id, data, {
new: true,
runValidators: true,
}).exec();
return updatedLab ? toLab(updatedLab) : null;
}

/**
* Delete a lab entry by ID
* @param id the ID of the lab to delete
* @returns true if the lab was deleted, false otherwise
*/
// DON'T use this for tables that you don't actually need to potentially delete things from
// Could be used accidentally or misused maliciously to get rid of important data
export async function deleteLab(id: string): Promise<boolean> {
await connectToDatabase();
const lab = await LabModel.findById(id).exec();
if (!lab) {
throw new Error(`Lab with ID ${id} not found`);
}
const deleted = await lab.deleteOne();
return Boolean(deleted);
}