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
25 changes: 13 additions & 12 deletions AI_LOG.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
# AI Usage Log

Please be honest and detailed about your AI usage. Using AI is perfectly fine and expected, but we want to understand your workflow!

## Tools Used
*(e.g., GitHub Copilot, ChatGPT, Claude, Cursor, etc.)*

-
-
- Github Copilot
- ChatGPT

## How did you use AI for this assessment?

**Task 1: Pricing Logic Bug**
- *Did you use AI to find the bug? If so, what prompt did you use?*
- *Did you use AI to write the fix?*
-Bug discovery: Yes, I used AI to help locate the bug.
Prompt used: “There’s a bug in my pricing logic where discounts aren’t applied correctly. Can you help me trace the issue in this function?”

Fix: I asked AI to propose a corrected version of the function. It generated a fix, which I adapted slightly to match the project’s coding style and variable naming.

**Task 2: Dashboard Performance**
- *How did you use AI here? Did you use it to identify the N+1 issue, or just to write the optimized query?*
- Usage: I leaned on AI to confirm the presence of an N+1 query issue after suspecting it from slow load times.

Optimized query: AI suggested a more efficient query using eager loading. I directly implemented this, with minor adjustments to fit the ORM conventions in the project.
**Task 3: Duplicate Order Feature**
- *Did you use AI to write the API route, the client component, or both?*
- *Did AI make any mistakes you had to fix manually?*
- API route: AI drafted the initial Express.js route for duplicating an order.

Client component: I also used AI to scaffold the React component logic.

Corrections: AI’s first draft missed some validation checks and had a small bug in handling nested order items. I manually fixed those before finalizing.
## General Comments
*(Any other thoughts on how AI helped or hindered you during this assessment?)*
AI was extremely helpful for speeding up repetitive coding tasks and confirming suspicions (like the N+1 issue). However, I noticed that AI sometimes produced code that didn’t fully align with the project’s conventions or missed edge cases. I had to carefully review and adjust outputs rather than copy them blindly. Overall, AI acted as a strong accelerator but not a complete replacement for debugging and critical thinking.
150 changes: 112 additions & 38 deletions app/api/orders/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,121 @@
import { NextRequest, NextResponse } from 'next/server';
import { getSession } from '@/lib/auth/session';
import { getOrderById } from '@/lib/orders/queries';
import type { ApiResponse } from '@/types';

export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await getSession();
if (!session) {
return NextResponse.json<ApiResponse<null>>(
{ data: null, error: 'Unauthorized' },
{ status: 401 }
);
}
import { prisma } from '@/lib/db/prisma';
import type { Prisma } from '@prisma/client';

export async function getOrderWithItems(id: string) {
return prisma.order.findUnique({
where: { id },
include: {
items: {
include: { product: true },
},
},
});
}

export type OrderWithItems = Prisma.PromiseReturnType<typeof getOrderWithItems>;
import { prisma } from '@/lib/db/prisma';
import type { OrderWithItems } from '@/lib/orders/queries';

type DuplicateUnavailableItem = {
productId: string;
name: string;
reason: 'out_of_stock' | 'inactive' | 'missing';
requested: number;
available: number;
};

export type DuplicateResult = {
addedItems: { productId: string; name: string; quantity: number }[];
unavailableItems: DuplicateUnavailableItem[];
cart: { productId: string; quantity: number }[];
};

export async function addItemsToCart(
order: OrderWithItems,
userId: string
): Promise<DuplicateResult> {
if (!order) throw new Error('Order not found');

const addedItems: DuplicateResult['addedItems'] = [];
const unavailableItems: DuplicateUnavailableItem[] = [];

// Load the user's current cart so we can merge, not overwrite.
const user = await prisma.user.findUnique({ where: { id: userId } });
const cart = (user?.cart ?? []) as { productId: string; quantity: number }[];

const { id } = await params;
const order = await getOrderById(id);
for (const item of order.items) {
const product = item.product;

if (!order) {
return NextResponse.json<ApiResponse<null>>(
{ data: null, error: 'Order not found' },
{ status: 404 }
);
// 1) Validate availability before adding.
if (!product || !product.active) {
unavailableItems.push({
productId: item.productId,
name: item.name ?? product?.name ?? item.productId,
reason: product ? 'inactive' : 'missing',
requested: item.quantity,
available: 0,
});
continue;
}

if (order.userId !== session.id) {
return NextResponse.json<ApiResponse<null>>(
{ data: null, error: 'Forbidden' },
{ status: 403 }
);
if (product.stockQty < item.quantity) {
unavailableItems.push({
productId: item.productId,
name: product.name,
reason: 'out_of_stock',
requested: item.quantity,
available: product.stockQty,
});
continue;
}

return NextResponse.json<ApiResponse<typeof order>>({
data: order,
error: null,
// 2) Valid item — merge into cart, capping combined qty at stock.
const existing = cart.find((c) => c.productId === item.productId);
const cappedQty = Math.min(item.quantity + (existing?.quantity ?? 0), product.stockQty);

if (existing) {
existing.quantity = cappedQty;
} else {
cart.push({ productId: item.productId, quantity: item.quantity });
}

addedItems.push({
productId: item.productId,
name: product.name,
quantity: item.quantity,
});
} catch (err) {
console.error('[GET /api/orders/[id]]', err);
return NextResponse.json<ApiResponse<null>>(
{ data: null, error: 'Failed to load order' },
{ status: 500 }
);
}

// 3) Persist the merged cart in a single update.
await prisma.user.update({
where: { id: userId },
data: { cart },
});

return { addedItems, unavailableItems, cart };
}
import { NextRequest, NextResponse } from 'next/server';
import { getSession } from '@/lib/auth/session';
import { getOrderWithItems } from '@/lib/orders/queries';
import { addItemsToCart } from '@/lib/cart/actions';
import type { ApiResponse } from '@/types';

export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await getSession();
if (!session) {
return NextResponse.json<ApiResponse<null>>(
{ data: null, error: 'Unauthorized' },
{ status: 401 }
);
}

const { id } = await params;
const order = await getOrderWithItems(id);

if (!order) {
return NextResponse.json<ApiResponse<null>>(
{ data: null, error: 'Order not found
140 changes: 18 additions & 122 deletions app/api/orders/route.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/lib/db/client';
import { getSession } from '@/lib/auth/session';
import { listOrders } from '@/lib/orders/queries';
import { computeOrderTotals } from '@/lib/pricing/calculator';
import { getOrderById } from '@/lib/orders/queries';
import type { ApiResponse } from '@/types';

export async function GET(request: NextRequest) {
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await getSession();
if (!session) {
Expand All @@ -15,136 +16,31 @@ export async function GET(request: NextRequest) {
);
}

const orders = await listOrders(session.id);
const { id } = await params;
const order = await getOrderById(id);

return NextResponse.json<ApiResponse<typeof orders>>({
data: orders,
error: null,
});
} catch (err) {
console.error('[GET /api/orders]', err);
return NextResponse.json<ApiResponse<null>>(
{ data: null, error: 'Failed to load orders' },
{ status: 500 }
);
}
}

export async function POST(request: NextRequest) {
try {
const session = await getSession();
if (!session) {
return NextResponse.json<ApiResponse<null>>(
{ data: null, error: 'Unauthorized' },
{ status: 401 }
);
}

const body = await request.json();
const { items, discountCode: discountCodeStr } = body as {
items: { productId: string; quantity: number }[];
discountCode?: string;
};

if (!items || items.length === 0) {
if (!order) {
return NextResponse.json<ApiResponse<null>>(
{ data: null, error: 'Cart is empty' },
{ status: 400 }
{ data: null, error: 'Order not found' },
{ status: 404 }
);
}

const productIds = items.map((i) => i.productId);
const products = await prisma.product.findMany({
where: { id: { in: productIds }, active: true },
});

if (products.length !== items.length) {
if (order.userId !== session.id) {
return NextResponse.json<ApiResponse<null>>(
{ data: null, error: 'One or more products are unavailable' },
{ status: 400 }
{ data: null, error: 'Forbidden' },
{ status: 403 }
);
}

for (const item of items) {
const product = products.find((p) => p.id === item.productId)!;
if (product.stock < item.quantity) {
return NextResponse.json<ApiResponse<null>>(
{ data: null, error: `Insufficient stock for ${product.name}` },
{ status: 400 }
);
}
}

const subtotalCents = items.reduce((sum, item) => {
const product = products.find((p) => p.id === item.productId)!;
return sum + product.priceCents * item.quantity;
}, 0);

let discountCodeRecord = null;
if (discountCodeStr) {
discountCodeRecord = await prisma.discountCode.findUnique({
where: { code: discountCodeStr.toUpperCase() },
});
}

const pricing = computeOrderTotals({
subtotalCents,
discountCode: discountCodeRecord
? {
discountType: discountCodeRecord.discountType,
value: discountCodeRecord.value,
stackableWithFreeShipping: discountCodeRecord.stackableWithFreeShipping,
}
: null,
userTier: session.tier,
});

const order = await prisma.$transaction(async (tx) => {
const newOrder = await tx.order.create({
data: {
userId: session.id,
status: 'confirmed',
subtotalCents: pricing.subtotalCents,
discountCents: pricing.discountCents,
shippingCents: pricing.shippingCents,
totalCents: pricing.totalCents,
discountCodeId: discountCodeRecord?.id ?? null,
items: {
create: items.map((item) => {
const product = products.find((p) => p.id === item.productId)!;
return {
productId: item.productId,
quantity: item.quantity,
priceAtPurchase: product.priceCents,
};
}),
},
},
include: {
items: { include: { product: true } },
},
});

await Promise.all(
items.map((item) =>
tx.product.update({
where: { id: item.productId },
data: { stock: { decrement: item.quantity } },
})
)
);

return newOrder;
return NextResponse.json<ApiResponse<typeof order>>({
data: order,
error: null,
});

return NextResponse.json<ApiResponse<typeof order>>(
{ data: order, error: null },
{ status: 201 }
);
} catch (err) {
console.error('[POST /api/orders]', err);
console.error('[GET /api/orders/[id]]', err);
return NextResponse.json<ApiResponse<null>>(
{ data: null, error: 'Failed to create order' },
{ data: null, error: 'Failed to load order' },
{ status: 500 }
);
}
Expand Down
Loading