- Add AI Dental Shopping to sidebar with Search/Tag and Login Info sub-pages - Build full-stack Login Info CRUD: save vendor name, website, username, password per user - Add ShoppingVendor Prisma model, run db push, regenerate client and Zod schemas - Add storage layer, REST API at /api/shopping-vendors/, and frontend table with add/edit/delete modal Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
61 lines
2.5 KiB
TypeScript
61 lines
2.5 KiB
TypeScript
import express, { Request, Response } from "express";
|
|
import { storage } from "../storage";
|
|
import { insertShoppingVendorSchema, ShoppingVendor } from "@repo/db/types";
|
|
|
|
const router = express.Router();
|
|
|
|
router.get("/", async (req: Request, res: Response): Promise<any> => {
|
|
try {
|
|
if (!req.user?.id) return res.status(401).json({ message: "Unauthorized" });
|
|
const vendors = await storage.getShoppingVendorsByUser(req.user.id);
|
|
return res.status(200).json(vendors);
|
|
} catch (err) {
|
|
return res.status(500).json({ error: "Failed to fetch vendors", details: String(err) });
|
|
}
|
|
});
|
|
|
|
router.post("/", async (req: Request, res: Response): Promise<any> => {
|
|
try {
|
|
if (!req.user?.id) return res.status(401).json({ message: "Unauthorized" });
|
|
const parseResult = insertShoppingVendorSchema.safeParse({ ...req.body, userId: req.user.id });
|
|
if (!parseResult.success) {
|
|
const firstError = Object.values(parseResult.error.flatten().fieldErrors)[0]?.[0] || "Invalid input";
|
|
return res.status(400).json({ message: firstError });
|
|
}
|
|
const vendor = await storage.createShoppingVendor(parseResult.data);
|
|
return res.status(201).json(vendor);
|
|
} catch (err) {
|
|
return res.status(500).json({ error: "Failed to create vendor", details: String(err) });
|
|
}
|
|
});
|
|
|
|
router.put("/:id", async (req: Request, res: Response): Promise<any> => {
|
|
try {
|
|
const id = Number(req.params.id);
|
|
if (isNaN(id)) return res.status(400).send("Invalid vendor ID");
|
|
const vendor = await storage.updateShoppingVendor(id, req.body as Partial<ShoppingVendor>);
|
|
return res.status(200).json(vendor);
|
|
} catch (err) {
|
|
return res.status(500).json({ error: "Failed to update vendor", details: String(err) });
|
|
}
|
|
});
|
|
|
|
router.delete("/:id", async (req: Request, res: Response): Promise<any> => {
|
|
try {
|
|
const userId = req.user?.id;
|
|
if (!userId) return res.status(401).json({ message: "Unauthorized" });
|
|
const id = Number(req.params.id);
|
|
if (isNaN(id)) return res.status(400).send("Invalid ID");
|
|
const existing = await storage.getShoppingVendor(id);
|
|
if (!existing) return res.status(404).json({ message: "Vendor not found" });
|
|
if (existing.userId !== userId) return res.status(403).json({ message: "Forbidden" });
|
|
const ok = await storage.deleteShoppingVendor(userId, id);
|
|
if (!ok) return res.status(404).json({ message: "Vendor not found or already deleted" });
|
|
return res.status(204).send();
|
|
} catch (err) {
|
|
return res.status(500).json({ error: "Failed to delete vendor", details: String(err) });
|
|
}
|
|
});
|
|
|
|
export default router;
|