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
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 });
}
136 changes: 136 additions & 0 deletions app/api/inventory/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
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][]]);

export async function GET(_: Request, { params }: { params: { id: string } }) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should connectToDB here instead of in services for consistency across all our services. We used a cached conn from:

import { connectToDatabase } from "@/lib/mongoose";

Put this before calling the services functions
try {
await connectToDatabase();
} catch {
return NextResponse.json(
{ success: false, message: "Error connecting to database." },
{ status: 500 }
);
}

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 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 }
);
}
}
85 changes: 85 additions & 0 deletions app/api/inventory/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { NextResponse } from "next/server";
import { z } from "zod";
import {
categoryValues,
notificationAudienceValues,
notificationEventValues,
} from "@/models/Item";
import { addItem, getItems } from "@/services/items";
import { connectToDatabase } from "@/lib/mongoose";

// Only returns a Next response upon failed connection
async function connect() {
try {
await connectToDatabase();
} catch {
return NextResponse.json(
{ success: false, message: "Error connecting to database" },
{ status: 500 }
);
}
}

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
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
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

const connectionResponse = await connect();
// if (connectionResponse) return connectionResponse; ?

try {
const items = await getItems();
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 connectionResponse = await connect();

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