Files
falah-mobile/src/app/api/souq/upload/route.ts
T
root cfff74e2db feat: Souq native integration + auth simplification + CE-wide enhancements
Souq Marketplace:
- Native souq pages: browse+filters, create listing, detail+seller profile, orders+chat
- New API: reviews (POST), sellers/[id], upload (multipart), polar-checkout webhook
- Listings API enhanced: minPrice, maxPrice, sortBy, deliveryDays filters
- Seller workflow: start order, mark delivered, upload files, confirm delivery
- Wallet: 5 Polar.sh FLH tiers, production checkout, mock fallback, success banner
- Fix: order redirect /souq/history → /souq/orders

Auth System:
- Unified auth page, removed OAuth provider routing (2 files deleted)
- Deleted oauth.ts (319 lines) — simplified auth chain
- Streamlined login/register API routes

Prisma Schema (+179 lines):
- New models: Listing, Purchase, Review, Group/GroupMember
- Gamification: DailyStreak, XpTransaction, Achievement, UserLevel
- Learning: LearnCourse/Module/Enrollment/Certificate
- Notifications, Referrals, Dhikr, PremiumBenefits

Deleted legacy code:
- src/app/api/marketplace/* (3 files) — replaced by /api/souq/*
- src/app/api/files/[listingId]/route.ts — replaced by /api/souq/upload
- src/app/api/seller/[sellerId]/route.ts — replaced by /api/souq/sellers/[id]

Infrastructure:
- Dockerfile: standalone output, Prisma runtime migration
- docker-compose.yml: Polar env vars, healthcheck fix
- docker-compose.staging.yml: staging on port 4014
- .gitea/workflows/ci.yml: CI pipeline
2026-06-24 07:02:03 +02:00

94 lines
2.4 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { requireAuth } from "@/lib/auth";
import { writeFile } from "fs/promises";
import path from "path";
const MAX_SIZE = 10 * 1024 * 1024; // 10 MB
const ALLOWED_MIME = [
"image/jpeg",
"image/png",
"image/gif",
"image/webp",
"application/pdf",
"application/zip",
"application/x-zip-compressed",
"application/x-rar-compressed",
"application/x-7z-compressed",
"application/gzip",
];
// POST /api/souq/upload — upload a delivery file
export async function POST(request: NextRequest) {
try {
const auth = await requireAuth(request);
if (!auth) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const formData = await request.formData();
const file = formData.get("file") as File | null;
const orderId = formData.get("orderId") as string | null;
if (!file) {
return NextResponse.json({ error: "No file provided" }, { status: 400 });
}
if (!orderId) {
return NextResponse.json(
{ error: "orderId is required" },
{ status: 400 }
);
}
// Check file size
if (file.size > MAX_SIZE) {
return NextResponse.json(
{ error: "File too large. Maximum 10 MB." },
{ status: 413 }
);
}
// Only check mime type if available; allow fallback for unknown types
if (file.type && !ALLOWED_MIME.includes(file.type)) {
return NextResponse.json(
{
error:
"Invalid file type. Allowed: images, PDFs, ZIP, RAR, 7z, GZIP.",
},
{ status: 400 }
);
}
// Generate unique filename
const timestamp = Date.now();
const safeName = file.name.replace(/[^a-zA-Z0-9._-]/g, "_");
const fileName = `${orderId}-${timestamp}-${safeName}`;
// Save to disk
const uploadDir = path.join(
process.cwd(),
"public",
"uploads",
"deliveries"
);
const filePath = path.join(uploadDir, fileName);
const buffer = Buffer.from(await file.arrayBuffer());
await writeFile(filePath, buffer);
// Return the public URL path
const url = `/uploads/deliveries/${fileName}`;
return NextResponse.json({
url,
name: file.name,
size: file.size,
});
} catch (error) {
console.error("POST /api/souq/upload error:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}