feat: remix integration
This commit is contained in:
parent
7045e582c7
commit
082bd78bf7
5 changed files with 348 additions and 3 deletions
124
apps/remix-template/app/routes/api.products.tsx
Normal file
124
apps/remix-template/app/routes/api.products.tsx
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { getProducts, createProduct } from "@repo/db/index";
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
const page = parseInt(url.searchParams.get("page") || "1");
|
||||
const limit = parseInt(url.searchParams.get("limit") || "10");
|
||||
|
||||
if (page < 1 || limit < 1 || limit > 100) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: "Invalid pagination parameters. Page must be >= 1, limit must be 1-100",
|
||||
}),
|
||||
{
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const result = await getProducts(page, limit);
|
||||
return new Response(JSON.stringify(result), {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("API Error:", error);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: "Failed to fetch products",
|
||||
message: error.message,
|
||||
}),
|
||||
{
|
||||
status: 500,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
if (request.method !== "POST") {
|
||||
return new Response(JSON.stringify({ error: "Method not allowed" }), {
|
||||
status: 405,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { name, description, price, imageUrl, isPublished } = body;
|
||||
|
||||
// Validation
|
||||
if (!name || typeof name !== "string" || !name.trim()) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: "Product name is required and must be a non-empty string",
|
||||
}),
|
||||
{
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (price === undefined || price === null || isNaN(Number(price))) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: "Price is required and must be a valid number",
|
||||
}),
|
||||
{
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const priceValue = Number(price);
|
||||
if (priceValue < 0) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Price must be non-negative" }),
|
||||
{
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const productData = {
|
||||
name: name.trim(),
|
||||
description: description?.trim() || null,
|
||||
price: priceValue.toString(),
|
||||
imageUrl: imageUrl?.trim() || null,
|
||||
isPublished: Boolean(isPublished),
|
||||
};
|
||||
|
||||
const newProduct = await createProduct(productData);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: "Product created successfully",
|
||||
data: newProduct,
|
||||
}),
|
||||
{
|
||||
status: 201,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error("API Error:", error);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: "Failed to create product",
|
||||
message: error.message || "An unexpected error occurred",
|
||||
}),
|
||||
{
|
||||
status: 500,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue