generated from CSES-UCSD/nextjs-boilerplate
-
Notifications
You must be signed in to change notification settings - Fork 0
Implement REST API endpoints for Labs #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mateokalafatovich
wants to merge
4
commits into
main
Choose a base branch
from
task/lab-APIs
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
c8e496b
Implement REST API endpoints for Labs
mateokalafatovich 5336123
Created lean type for lab
mateokalafatovich 379e346
Fixed API endpoints to use lean and sort
mateokalafatovich b17629b
Restore accidentally removed auth/test files and config updates
mateokalafatovich File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| 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 }); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 }); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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), | ||
| }; | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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?