Skip to content
Open
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
node_modules/
.next/
.env.local
.env
.env.test
.DS_Store
.env
10 changes: 10 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"tabWidth": 4,
"useTabs": false,
"semi": true,
"singleQuote": false,
"quoteProps": "as-needed",
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "avoid"
}
11 changes: 11 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"editor.tabSize": 4,
"editor.insertSpaces": true,
"editor.detectIndentation": false,
"[typescript]": {
"editor.tabSize": 4
},
"[typescriptreact]": {
"editor.tabSize": 4
}
}
29 changes: 23 additions & 6 deletions app/api/demo/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,17 @@ import { NextResponse } from "next/server";

import { z } from "zod";
import { statusValues } from "@/models/Product";
import { addProduct, deleteProduct, getProduct, getProducts, updateProduct } from "@/services/demo";
import {
addProduct,
deleteProduct,
getProduct,
getProducts,
updateProduct,
} from "@/services/demo";

const objectIdSchema = z.string().regex(/^[0-9a-fA-F]{24}$/, "Invalid MongoDB ObjectId");
const objectIdSchema = z
.string()
.regex(/^[0-9a-fA-F]{24}$/, "Invalid MongoDB ObjectId");
const productBaseSchema = z.object({
image_url: z.url(),
name: z.string().min(1),
Expand All @@ -28,14 +36,17 @@ export async function GET(request: Request) {
if (!parsedId.success) {
return NextResponse.json(
{ message: parsedId.error.issues[0]?.message ?? "Invalid id" },
{ status: 400 },
{ status: 400 }
);
}

// There was an id in the search, so find the product that corresponds to it
const product = await getProduct(parsedId.data);
if (!product) {
return NextResponse.json({ message: "Product not found" }, { status: 404 });
return NextResponse.json(
{ message: "Product not found" },
{ status: 404 }
);
}
return NextResponse.json(product, { status: 200 });
} else {
Expand Down Expand Up @@ -63,7 +74,10 @@ export async function PUT(request: Request) {
const updatedData = parsedRequest.update;
const updatedProduct = await updateProduct(id, updatedData);
if (!updatedProduct) {
return NextResponse.json({ message: "Product not found" }, { status: 404 });
return NextResponse.json(
{ message: "Product not found" },
{ status: 404 }
);
}
return NextResponse.json(updatedProduct, { status: 200 });
}
Expand All @@ -74,7 +88,10 @@ export async function DELETE(request: Request) {
const { id } = validator.parse(await request.json());
const deleted = await deleteProduct(id);
if (!deleted) {
return NextResponse.json({ message: "Product not found" }, { status: 404 });
return NextResponse.json(
{ message: "Product not found" },
{ status: 404 }
);
}
return NextResponse.json({ message: "Product deleted" }, { status: 200 });
}
137 changes: 137 additions & 0 deletions app/api/inventory/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { NextResponse } from "next/server";
import { z } from "zod";
import { getItem, updateItem, deleteItem } from "@/services/items";
import {
categoryValues,
notificationAudienceValues,
notificationEventValues,
} from "@/models/Item";

const objectIdSchema = z
.string()
.regex(/^[0-9a-fA-F]{24}$/, "Invalid MongoDB ObjectId");

const zEnumFromConst = <T extends readonly [string, ...string[]]>(values: T) =>
z.enum(values as unknown as [T[number], ...T[number][]]);

// GET: get an item by id
export async function GET(_: Request, { params }: { params: { id: string } }) {
const parsedId = objectIdSchema.safeParse(params.id);
if (!parsedId.success) {
return NextResponse.json(
{ message: parsedId.error.issues[0]?.message ?? "Invalid id" },
{ status: 400 }
);
}

try {
const item = await getItem(parsedId.data);
if (!item) {
return NextResponse.json(
{ message: "Item not found" },
{ status: 404 }
);
}
return NextResponse.json(item, { status: 200 });
} catch {
return NextResponse.json(
{ success: false, message: "Error occured while retrieving items" },
{ status: 500 }
);
}
}

// PUT: update an item by id
export async function PUT(
request: Request,
{ params }: { params: { id: string } }
) {
const parsedId = objectIdSchema.safeParse(params.id);
if (!parsedId.success) {
return NextResponse.json({ message: "Invalid id" }, { status: 400 });
}

const thresholdFullSchema = z.object({
minQuantity: z.number().min(0),
enabled: z.boolean(),
lastAlertSentAt: z.coerce.date(),
});

const notificationPolicyFullSchema = z.object({
event: zEnumFromConst(notificationEventValues),
audience: zEnumFromConst(notificationAudienceValues),
});

const updateSchema = z
.object({
name: z.string().trim().min(1).optional(),
category: zEnumFromConst(categoryValues).optional(),
quantity: z.number().min(0).optional(),

threshold: thresholdFullSchema.optional(),
notificationPolicy: notificationPolicyFullSchema.optional(),
})
.strict()
.refine(obj => Object.keys(obj).length > 0, {
message: "Body must include at least one field to update",
});

// Assuming updateSchema
const json = await request.json();
const parsedUpdate = updateSchema.safeParse(json);
if (!parsedUpdate.success) {
return NextResponse.json(
{
message: "Update doesn't follow schema",
issues: parsedUpdate.error.flatten(),
},
{ status: 400 }
);
}

try {
const updated = await updateItem(parsedId.data, parsedUpdate.data);
if (!updated) {
return NextResponse.json(
{ message: "Item not found" },
{ status: 404 }
);
}

return NextResponse.json(updated, { status: 200 });
} catch {
return NextResponse.json(
{ success: false, message: "Error while updating data" },
{ status: 500 }
);
}
}

// In the future, check for auth before usage to prevent unauthorized deletes
// DELETE: Delete a product by id
export async function DELETE(
_: Request,
{ params }: { params: { id: string } }
) {
const parsedId = objectIdSchema.safeParse(params.id);
if (!parsedId.success) {
return NextResponse.json({ message: "Invalid id" }, { status: 400 });
}

try {
const deleted = await deleteItem(parsedId.data);
if (!deleted) {
return NextResponse.json(
{ message: "Item not found" },
{ status: 404 }
);
}

return NextResponse.json(deleted, { status: 200 });
} catch {
return NextResponse.json(
{ success: false, message: "Error while deleting data" },
{ status: 500 }
);
}
}
70 changes: 70 additions & 0 deletions app/api/inventory/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { NextResponse } from "next/server";
import { z } from "zod";
import {
categoryValues,
notificationAudienceValues,
notificationEventValues,
} from "@/models/Item";
import { addItem, filteredGet } from "@/services/items";

const thresholdSchema = z.object({
minQuantity: z.number().int().nonnegative(),
enabled: z.boolean(),
lastAlertSentAt: z.coerce.date(),
});

const notificationPolicySchema = z.object({
event: z.enum(notificationEventValues),
audience: z.enum(notificationAudienceValues),
});

const itemCreateSchema = z.object({
labId: z.string().min(1),
name: z.string().min(1),
category: z.enum(categoryValues),
quantity: z.number().int().nonnegative(),
threshold: thresholdSchema,
notificationPolicy: notificationPolicySchema,
});

// GET: fetch all items
export async function GET() {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Same thing, lets do the try catch with connectToDB before thsi try catch in all functs

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This should also be filtered like they should be able to get by item name as well

try {
const items = await filteredGet({
page: 1,
limit: 10,
}); // swap to getItems() if needed
return NextResponse.json(items, { status: 200 });
} catch {
return NextResponse.json(
{ message: "Failed to fetch items" },
{ status: 500 }
);
}
}

// POST: add a new item
export async function POST(request: Request) {
const body = await request.json();
const parsedBody = itemCreateSchema.safeParse(body);

if (!parsedBody.success) {
return NextResponse.json(
{
success: false,
message: "Invalid request body.",
},
{ status: 400 }
);
}

try {
const created = await addItem(parsedBody.data);
return NextResponse.json(created, { status: 201 });
} catch {
return NextResponse.json(
{ message: "Error occured while creating item" },
{ status: 500 }
);
}
}
Loading