Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 140b83bd65 | |||
| 775ee0f691 | |||
| 4989bf6de4 | |||
| 4267c34399 | |||
| 6d1697e672 | |||
| 45270f9b18 | |||
| a13687635b | |||
| c2600b6a68 | |||
| b45493f5bd | |||
| cb46430ce4 |
+1
-1
@@ -2,7 +2,7 @@
|
||||
NEXT_PUBLIC_API_BASE_URL=http://battlbuilder-api:8080
|
||||
|
||||
# Middleware to limited site to only root page
|
||||
NEXT_PUBLIC_LAUNCH_ONLY_ROOT=true
|
||||
LAUNCH_ONLY_ROOT=true
|
||||
|
||||
NEXT_PUBLIC_SHORTLINK_BASE_URL=https://battl.build
|
||||
|
||||
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="db-tree-configuration">
|
||||
<option name="data" value="" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1523,25 +1523,25 @@ function GunbuilderPageContent() {
|
||||
)}
|
||||
|
||||
<div className="overflow-x-auto rounded-md border border-zinc-800 bg-zinc-950/80">
|
||||
<table className="min-w-full text-xs md:text-sm">
|
||||
<table className="min-w-full text-[11px] sm:text-xs md:text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-zinc-800 bg-zinc-900/60">
|
||||
<th className="px-3 py-2 text-left font-semibold text-zinc-400">
|
||||
<th className="px-2 sm:px-3 py-2 text-left font-semibold text-zinc-400">
|
||||
Component / Part Type
|
||||
</th>
|
||||
<th className="px-3 py-2 text-left font-semibold text-zinc-400">
|
||||
<th className="hidden sm:table-cell px-2 sm:px-3 py-2 text-left font-semibold text-zinc-400">
|
||||
Brand
|
||||
</th>
|
||||
<th className="px-3 py-2 text-right font-semibold text-zinc-400">
|
||||
<th className="px-2 sm:px-3 py-2 text-right font-semibold text-zinc-400">
|
||||
Price
|
||||
</th>
|
||||
<th className="px-3 py-2 text-right font-semibold text-zinc-400">
|
||||
<th className="hidden md:table-cell px-2 sm:px-3 py-2 text-right font-semibold text-zinc-400">
|
||||
Sale Price
|
||||
</th>
|
||||
<th className="px-3 py-2 text-left font-semibold text-zinc-400">
|
||||
<th className="hidden md:table-cell px-2 sm:px-3 py-2 text-left font-semibold text-zinc-400">
|
||||
Caliber
|
||||
</th>
|
||||
<th className="px-3 py-2 text-right font-semibold text-zinc-400">
|
||||
<th className="px-2 sm:px-3 py-2 text-right font-semibold text-zinc-400">
|
||||
Buy / Choose
|
||||
</th>
|
||||
</tr>
|
||||
@@ -1557,7 +1557,7 @@ function GunbuilderPageContent() {
|
||||
>
|
||||
<td
|
||||
colSpan={6}
|
||||
className="px-3 py-2 text-[0.7rem] text-zinc-500 bg-zinc-950/70"
|
||||
className="px-2 sm:px-3 py-2 text-[0.7rem] text-zinc-500 bg-zinc-950/70"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="font-semibold text-zinc-400">
|
||||
@@ -1606,7 +1606,7 @@ function GunbuilderPageContent() {
|
||||
row.tone === "muted" ? "opacity-70" : ""
|
||||
}`}
|
||||
>
|
||||
<td className="px-3 py-2 align-top">
|
||||
<td className="px-2 sm:px-3 py-2 align-top">
|
||||
<div className="flex items-start gap-2">
|
||||
{indent > 0 && (
|
||||
<div
|
||||
@@ -1654,24 +1654,24 @@ function GunbuilderPageContent() {
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td className="px-3 py-2 align-top text-zinc-300">
|
||||
<td className="hidden sm:table-cell px-2 sm:px-3 py-2 align-top text-zinc-300">
|
||||
{selectedPart ? selectedPart.brand : "—"}
|
||||
</td>
|
||||
|
||||
<td className="px-3 py-2 align-top text-right text-amber-300">
|
||||
<td className="px-2 sm:px-3 py-2 align-top text-right text-amber-300">
|
||||
{selectedPart
|
||||
? `$${selectedPart.price.toFixed(2)}`
|
||||
: "—"}
|
||||
</td>
|
||||
|
||||
<td className="px-3 py-2 align-top text-right text-zinc-400">
|
||||
<td className="hidden md:table-cell px-2 sm:px-3 py-2 align-top text-right text-zinc-400">
|
||||
—
|
||||
</td>
|
||||
<td className="px-3 py-2 align-top text-zinc-400">
|
||||
<td className="hidden md:table-cell px-2 sm:px-3 py-2 align-top text-zinc-400">
|
||||
—
|
||||
</td>
|
||||
|
||||
<td className="px-3 py-2 align-top">
|
||||
<td className="px-2 sm:px-3 py-2 align-top">
|
||||
<div className="flex justify-end gap-2">
|
||||
{isLocked ? (
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -417,9 +417,9 @@ export default function PrivacyPolicy() {
|
||||
<br />
|
||||
Battl Builder, LLC.
|
||||
<br />
|
||||
[Your Address]
|
||||
{/* [Your Address]
|
||||
<br />
|
||||
[City, State, ZIP]
|
||||
[City, State, ZIP] */}
|
||||
</p>
|
||||
</div>
|
||||
<p className="mt-4 text-sm">
|
||||
|
||||
@@ -168,12 +168,6 @@ export default function AdminLayout({
|
||||
</p>
|
||||
<p className="text-sm text-zinc-200">Battl Control Panel</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="rounded-full border border-amber-500/30 bg-amber-500/10 px-3 py-1 text-[11px] font-medium text-amber-300">
|
||||
Internal • v0.1
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* ✅ min-w-0 here prevents table min-width from widening the page */}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Plus, Pencil, Trash2, X } from "lucide-react";
|
||||
import { useAuth } from "@/context/AuthContext";
|
||||
import {
|
||||
createPlatform,
|
||||
deletePlatform,
|
||||
@@ -15,7 +14,6 @@ import {
|
||||
} from "@/lib/api/platforms";
|
||||
|
||||
export default function AdminPlatformsPage() {
|
||||
const { getAuthHeaders } = useAuth();
|
||||
|
||||
const [platforms, setPlatforms] = useState<Platform[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -36,7 +34,7 @@ export default function AdminPlatformsPage() {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const data = await fetchPlatforms(getAuthHeaders());
|
||||
const data = await fetchPlatforms({});
|
||||
setPlatforms(data);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
@@ -77,7 +75,7 @@ export default function AdminPlatformsPage() {
|
||||
try {
|
||||
setSaving(true);
|
||||
setError(null);
|
||||
await deletePlatform(getAuthHeaders(), p.id);
|
||||
await deletePlatform({}, p.id);
|
||||
await load();
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
@@ -103,7 +101,7 @@ export default function AdminPlatformsPage() {
|
||||
setError(null);
|
||||
|
||||
if (modalMode === "create") {
|
||||
await createPlatform(getAuthHeaders(), { ...form, key, label });
|
||||
await createPlatform({}, { ...form, key, label });
|
||||
} else if (selected) {
|
||||
const update: UpdatePlatformDto = {};
|
||||
if (key !== selected.key) update.key = key;
|
||||
@@ -111,7 +109,7 @@ export default function AdminPlatformsPage() {
|
||||
if ((form.isActive ?? true) !== selected.isActive)
|
||||
update.isActive = form.isActive;
|
||||
|
||||
await updatePlatform(getAuthHeaders(), selected.id, update);
|
||||
await updatePlatform({}, selected.id, update);
|
||||
}
|
||||
|
||||
setShowModal(false);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { API_BASE, getToken, qs } from "./auth";
|
||||
import { API_BASE, qs } from "./auth";
|
||||
import type {
|
||||
AdminProductRow,
|
||||
PageResponse,
|
||||
@@ -9,25 +9,14 @@ import type {
|
||||
|
||||
import type { CaliberDto } from "./types";
|
||||
|
||||
|
||||
async function authedFetch(url: string, init?: RequestInit) {
|
||||
const token = getToken();
|
||||
const res = await fetch(url, {
|
||||
credentials: "include",
|
||||
cache: "no-store",
|
||||
...(init ?? {}),
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
...(init?.headers ?? {}),
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
} as any,
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function fetchAdminRoles(params: Record<string, any>) {
|
||||
const res = await authedFetch(
|
||||
`${API_BASE}/api/v1/admin/products/roles?${qs(params)}`
|
||||
const queryString = qs(params);
|
||||
const res = await fetch(
|
||||
`/api/admin/products/roles${queryString ? `?${queryString}` : ''}`,
|
||||
{
|
||||
credentials: "include",
|
||||
cache: "no-store",
|
||||
}
|
||||
);
|
||||
if (!res.ok) throw new Error(`Failed to load roles (${res.status}): ${await res.text()}`);
|
||||
return (await res.json()) as string[];
|
||||
@@ -35,8 +24,13 @@ export async function fetchAdminRoles(params: Record<string, any>) {
|
||||
|
||||
// Facet/filtering can still use distinct values seen on products (optional)
|
||||
export async function fetchProductCaliberFacet(params: Record<string, any>) {
|
||||
const res = await authedFetch(
|
||||
`${API_BASE}/api/v1/admin/products/calibers?${qs(params)}`
|
||||
const queryString = qs(params);
|
||||
const res = await fetch(
|
||||
`/api/admin/products/calibers${queryString ? `?${queryString}` : ''}`,
|
||||
{
|
||||
credentials: "include",
|
||||
cache: "no-store",
|
||||
}
|
||||
);
|
||||
if (!res.ok)
|
||||
throw new Error(
|
||||
@@ -46,8 +40,13 @@ export async function fetchProductCaliberFacet(params: Record<string, any>) {
|
||||
}
|
||||
|
||||
export async function fetchAdminProducts(params: Record<string, any>) {
|
||||
const res = await authedFetch(
|
||||
`${API_BASE}/api/v1/admin/products?${qs(params)}`
|
||||
const queryString = qs(params);
|
||||
const res = await fetch(
|
||||
`/api/admin/products${queryString ? `?${queryString}` : ''}`,
|
||||
{
|
||||
credentials: "include",
|
||||
cache: "no-store",
|
||||
}
|
||||
);
|
||||
if (!res.ok) throw new Error(`Failed to load admin products (${res.status}): ${await res.text()}`);
|
||||
return (await res.json()) as PageResponse<AdminProductRow>;
|
||||
@@ -84,14 +83,11 @@ export async function bulkUpdate(
|
||||
classificationReason: "Admin bulk override";
|
||||
}>
|
||||
) {
|
||||
const token = getToken();
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/v1/admin/products/bulk`, {
|
||||
const res = await fetch(`/api/admin/products/bulk`, {
|
||||
method: "PATCH",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
},
|
||||
body: JSON.stringify({ productIds, ...set }),
|
||||
});
|
||||
@@ -103,8 +99,13 @@ export async function bulkUpdate(
|
||||
|
||||
|
||||
export async function fetchAdminCalibers(params: Record<string, any>) {
|
||||
const res = await authedFetch(
|
||||
`${API_BASE}/api/v1/admin/calibers?${qs(params)}`
|
||||
const queryString = qs(params);
|
||||
const res = await fetch(
|
||||
`/api/admin/calibers${queryString ? `?${queryString}` : ''}`,
|
||||
{
|
||||
credentials: "include",
|
||||
cache: "no-store",
|
||||
}
|
||||
);
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to load calibers (${res.status}): ${await res.text()}`);
|
||||
|
||||
@@ -17,11 +17,12 @@ export default function AdminUsersPage() {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
async function fetchUsers() {
|
||||
console.log("in the users fetch");
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const res = await fetch(`${API_BASE_URL}/admin/users`, {
|
||||
const res = await fetch(`/api/admin/users`, {
|
||||
headers: {
|
||||
...getAuthHeaders(),
|
||||
},
|
||||
|
||||
@@ -6,7 +6,9 @@ const API_BASE_URL =
|
||||
|
||||
// POST /api/admin/beta-invites?dryRun=true&limit=10&tokenMinutes=30
|
||||
export async function POST(req: Request) {
|
||||
const token = cookies().get("session_token")?.value;
|
||||
const cookieStore = await cookies();
|
||||
|
||||
const token = cookieStore.get("session_token")?.value;
|
||||
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: "Missing admin token" }, { status: 401 });
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
const API_BASE_URL =
|
||||
process.env.NEXT_PUBLIC_API_BASE_URL ?? "";
|
||||
|
||||
// POST /api/admin/beta-invites?dryRun=true&limit=10&tokenMinutes=30
|
||||
export async function POST(req: Request) {
|
||||
const token = cookies().get("session_token")?.value;
|
||||
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: "Missing admin token" }, { status: 401 });
|
||||
}
|
||||
|
||||
const url = new URL(req.url);
|
||||
const dryRun = url.searchParams.get("dryRun") ?? "true";
|
||||
const limit = url.searchParams.get("limit") ?? "0";
|
||||
const tokenMinutes = url.searchParams.get("tokenMinutes") ?? "30";
|
||||
|
||||
const upstream = `${API_BASE_URL}/api/v1/admin/beta/invites/send?dryRun=${dryRun}&limit=${limit}&tokenMinutes=${tokenMinutes}`;
|
||||
|
||||
const res = await fetch(upstream, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
cache: "no-store",
|
||||
});
|
||||
|
||||
const text = await res.text();
|
||||
return new NextResponse(text, {
|
||||
status: res.status,
|
||||
headers: { "Content-Type": res.headers.get("content-type") ?? "application/json" },
|
||||
});
|
||||
}
|
||||
@@ -5,19 +5,21 @@ const API_BASE_URL =
|
||||
process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get("session_token")?.value;
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get("session_token")?.value;
|
||||
if (!token) throw new Error("Unauthorized");
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
_req: Request,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const resolvedParams = await params;
|
||||
const headers = await getAuthHeader();
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/beta/requests/${params.id}/invite`,
|
||||
`${API_BASE_URL}/api/v1/admin/beta/requests/${resolvedParams.id}/invite`,
|
||||
{
|
||||
method: "POST",
|
||||
headers,
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
const API_BASE_URL =
|
||||
process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get("session_token")?.value;
|
||||
if (!token) throw new Error("Unauthorized");
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
_req: Request,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/beta/requests/${params.id}/invite`,
|
||||
{
|
||||
method: "POST",
|
||||
headers,
|
||||
cache: "no-store",
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,8 @@ const API_BASE_URL =
|
||||
process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get("session_token")?.value;
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get("session_token")?.value;
|
||||
if (!token) throw new Error("Unauthorized");
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
const API_BASE_URL =
|
||||
process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get("session_token")?.value;
|
||||
if (!token) throw new Error("Unauthorized");
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function GET(req: Request) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
const url = new URL(req.url);
|
||||
const search = url.searchParams.toString();
|
||||
|
||||
const upstream = `${API_BASE_URL}/api/v1/admin/beta/requests${
|
||||
search ? `?${search}` : ""
|
||||
}`;
|
||||
|
||||
const res = await fetch(upstream, {
|
||||
headers,
|
||||
cache: "no-store",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const cookieStore = await cookies();
|
||||
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const queryString = searchParams.toString();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/calibers${queryString ? `?${queryString}` : ''}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const queryString = searchParams.toString();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/calibers${queryString ? `?${queryString}` : ''}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,8 @@ import { cookies } from 'next/headers';
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
|
||||
const res = await fetch(`${API_BASE_URL}/api/admin/dashboard/overview`, {
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,20 +5,22 @@ const API_BASE_URL =
|
||||
process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get("session_token")?.value;
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get("session_token")?.value;
|
||||
if (!token) throw new Error("Unauthorized");
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const resolvedParams = await params;
|
||||
const headers = await getAuthHeader();
|
||||
const body = await request.json();
|
||||
|
||||
const res = await fetch(`${API_BASE_URL}/api/email/${params.id}`, {
|
||||
const res = await fetch(`${API_BASE_URL}/api/email/${resolvedParams.id}`, {
|
||||
method: "PUT",
|
||||
headers: { ...headers, "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
@@ -39,12 +41,13 @@ export async function PUT(
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const resolvedParams = await params;
|
||||
const headers = await getAuthHeader();
|
||||
|
||||
const res = await fetch(`${API_BASE_URL}/api/email/${params.id}`, {
|
||||
const res = await fetch(`${API_BASE_URL}/api/email/${resolvedParams.id}`, {
|
||||
method: "DELETE",
|
||||
headers,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
const API_BASE_URL =
|
||||
process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get("session_token")?.value;
|
||||
if (!token) throw new Error("Unauthorized");
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
const body = await request.json();
|
||||
|
||||
const res = await fetch(`${API_BASE_URL}/api/email/${params.id}`, {
|
||||
method: "PUT",
|
||||
headers: { ...headers, "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
|
||||
const res = await fetch(`${API_BASE_URL}/api/email/${params.id}`, {
|
||||
method: "DELETE",
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,8 @@ const API_BASE_URL =
|
||||
process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get("session_token")?.value;
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get("session_token")?.value;
|
||||
if (!token) throw new Error("Unauthorized");
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
const API_BASE_URL =
|
||||
process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get("session_token")?.value;
|
||||
if (!token) throw new Error("Unauthorized");
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
const res = await fetch(`${API_BASE_URL}/api/email`, {
|
||||
headers,
|
||||
cache: "no-store",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
const body = await request.json();
|
||||
|
||||
const res = await fetch(`${API_BASE_URL}/api/email`, {
|
||||
method: "POST",
|
||||
headers: { ...headers, "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string; action: string }> }
|
||||
) {
|
||||
try {
|
||||
const cookieStore = await cookies();
|
||||
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const resolvedParams = await params;
|
||||
|
||||
const { id, action } = resolvedParams;
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/admin/enrichment/${id}/${action}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
const contentType = res.headers.get('content-type');
|
||||
if (contentType?.includes('application/json')) {
|
||||
return NextResponse.json(await res.json());
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string; action: string } }
|
||||
) {
|
||||
try {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { id, action } = params;
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/admin/enrichment/${id}/${action}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
const contentType = res.headers.get('content-type');
|
||||
if (contentType?.includes('application/json')) {
|
||||
return NextResponse.json(await res.json());
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const cookieStore = await cookies();
|
||||
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const queryString = searchParams.toString();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/admin/enrichment/ai/run${queryString ? `?${queryString}` : ''}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
const contentType = res.headers.get('content-type');
|
||||
if (contentType?.includes('application/json')) {
|
||||
return NextResponse.json(await res.json());
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const queryString = searchParams.toString();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/admin/enrichment/ai/run${queryString ? `?${queryString}` : ''}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
const contentType = res.headers.get('content-type');
|
||||
if (contentType?.includes('application/json')) {
|
||||
return NextResponse.json(await res.json());
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
import { fixImageUrls } from '@/lib/server/image-utils';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const cookieStore = await cookies();
|
||||
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const queryString = searchParams.toString();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/admin/enrichment/queue2${queryString ? `?${queryString}` : ''}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
// Fix mixed content warnings: upgrade HTTP image URLs to HTTPS
|
||||
return NextResponse.json(fixImageUrls(data));
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
import { fixImageUrls } from '@/lib/server/image-utils';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const queryString = searchParams.toString();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/admin/enrichment/queue2${queryString ? `?${queryString}` : ''}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
// Fix mixed content warnings: upgrade HTTP image URLs to HTTPS
|
||||
return NextResponse.json(fixImageUrls(data));
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const cookieStore = await cookies();
|
||||
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const queryString = searchParams.toString();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/admin/enrichment/run${queryString ? `?${queryString}` : ''}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
const contentType = res.headers.get('content-type');
|
||||
if (contentType?.includes('application/json')) {
|
||||
return NextResponse.json(await res.json());
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const queryString = searchParams.toString();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/admin/enrichment/run${queryString ? `?${queryString}` : ''}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
const contentType = res.headers.get('content-type');
|
||||
if (contentType?.includes('application/json')) {
|
||||
return NextResponse.json(await res.json());
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,8 @@ import { cookies } from 'next/headers';
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
|
||||
const res = await fetch(`${API_BASE_URL}/api/admin/import-status/by-merchant`, {
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,8 @@ import { cookies } from 'next/headers';
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
|
||||
const res = await fetch(`${API_BASE_URL}/api/admin/import-status/summary`, {
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,8 @@ import { cookies } from 'next/headers';
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
const body = await request.json();
|
||||
|
||||
const res = await fetch(`${API_BASE_URL}/api/admin/mapping/apply`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,8 @@ import { cookies } from 'next/headers';
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
|
||||
const res = await fetch(`${API_BASE_URL}/api/admin/mapping/options`, {
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,8 @@ import { cookies } from 'next/headers';
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
|
||||
const res = await fetch(`${API_BASE_URL}/api/admin/mapping/pending-buckets`, {
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,8 @@ import { cookies } from 'next/headers';
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
const { searchParams } = new URL(request.url);
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/admin/mapping/raw-categories?${searchParams.toString()}`,
|
||||
{
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,8 @@ import { cookies } from 'next/headers';
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
const body = await request.json();
|
||||
|
||||
const res = await fetch(`${API_BASE_URL}/api/admin/mapping/upsert`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,20 +4,22 @@ import { cookies } from 'next/headers';
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const resolvedParams = await params;
|
||||
const headers = await getAuthHeader();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/merchants/${params.id}/import`,
|
||||
`${API_BASE_URL}/api/v1/admin/merchants/${resolvedParams.id}/import`,
|
||||
{ method: 'POST', headers }
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/merchants/${params.id}/import`,
|
||||
{ method: 'POST', headers }
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
}
|
||||
@@ -4,20 +4,22 @@ import { cookies } from 'next/headers';
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const resolvedParams = await params;
|
||||
const headers = await getAuthHeader();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/merchants/${params.id}/offer-sync`,
|
||||
`${API_BASE_URL}/api/v1/admin/merchants/${resolvedParams.id}/offer-sync`,
|
||||
{ method: 'POST', headers }
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/merchants/${params.id}/offer-sync`,
|
||||
{ method: 'POST', headers }
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
}
|
||||
@@ -4,20 +4,22 @@ import { cookies } from 'next/headers';
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const resolvedParams = await params;
|
||||
const headers = await getAuthHeader();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/merchants/${params.id}`,
|
||||
`${API_BASE_URL}/api/v1/admin/merchants/${resolvedParams.id}`,
|
||||
{ headers }
|
||||
);
|
||||
|
||||
@@ -36,14 +38,15 @@ export async function GET(
|
||||
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const resolvedParams = await params;
|
||||
const headers = await getAuthHeader();
|
||||
const body = await request.json();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/merchants/${params.id}`,
|
||||
`${API_BASE_URL}/api/v1/admin/merchants/${resolvedParams.id}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: { ...headers, 'Content-Type': 'application/json' },
|
||||
@@ -66,13 +69,14 @@ export async function PUT(
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const resolvedParams = await params;
|
||||
const headers = await getAuthHeader();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/merchants/${params.id}`,
|
||||
`${API_BASE_URL}/api/v1/admin/merchants/${resolvedParams.id}`,
|
||||
{ method: 'DELETE', headers }
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/merchants/${params.id}`,
|
||||
{ headers }
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
const body = await request.json();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/merchants/${params.id}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: { ...headers, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/merchants/${params.id}`,
|
||||
{ method: 'DELETE', headers }
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,8 @@ import { cookies } from 'next/headers';
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
const res = await fetch(`${API_BASE_URL}/api/v1/admin/merchants`, {
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
const body = await request.json();
|
||||
|
||||
const res = await fetch(`${API_BASE_URL}/api/v1/admin/merchants`, {
|
||||
method: 'POST',
|
||||
headers: { ...headers, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const resolvedParams = await params;
|
||||
const headers = await getAuthHeader();
|
||||
const body = await request.json();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/platforms/${resolvedParams.id}`,
|
||||
{
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
...headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const resolvedParams = await params;
|
||||
const headers = await getAuthHeader();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/platforms/${resolvedParams.id}`,
|
||||
{ method: 'DELETE', headers }
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
const body = await request.json();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/platforms/${params.id}`,
|
||||
{
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
...headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/platforms/${params.id}`,
|
||||
{ method: 'DELETE', headers }
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const queryString = searchParams.toString();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/platforms${queryString ? `?${queryString}` : ''}`,
|
||||
{
|
||||
headers: {
|
||||
...headers,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
const body = await request.json();
|
||||
|
||||
const res = await fetch(`${API_BASE_URL}/api/v1/admin/platforms`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const queryString = searchParams.toString();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/platforms${queryString ? `?${queryString}` : ''}`,
|
||||
{
|
||||
headers: {
|
||||
...headers,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
const body = await request.json();
|
||||
|
||||
const res = await fetch(`${API_BASE_URL}/api/v1/admin/platforms`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
export async function PATCH(request: NextRequest) {
|
||||
try {
|
||||
const cookieStore = await cookies();
|
||||
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/products/bulk`,
|
||||
{
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
export async function PATCH(request: NextRequest) {
|
||||
try {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/products/bulk`,
|
||||
{
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const cookieStore = await cookies();
|
||||
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const queryString = searchParams.toString();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/products/calibers${queryString ? `?${queryString}` : ''}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const queryString = searchParams.toString();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/products/calibers${queryString ? `?${queryString}` : ''}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const cookieStore = await cookies();
|
||||
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const queryString = searchParams.toString();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/products/roles${queryString ? `?${queryString}` : ''}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const queryString = searchParams.toString();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/products/roles${queryString ? `?${queryString}` : ''}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
import { fixImageUrls } from '@/lib/server/image-utils';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const cookieStore = await cookies();
|
||||
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const queryString = searchParams.toString();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/products${queryString ? `?${queryString}` : ''}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
return NextResponse.json(fixImageUrls(data));
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
import { fixImageUrls } from '@/lib/server/image-utils';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const queryString = searchParams.toString();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/products${queryString ? `?${queryString}` : ''}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
return NextResponse.json(fixImageUrls(data));
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const resolvedParams = await params;
|
||||
const headers = await getAuthHeader();
|
||||
const body = await request.json();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/platforms/${resolvedParams.id}`,
|
||||
{
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
...headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const resolvedParams = await params;
|
||||
const headers = await getAuthHeader();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/platforms/${resolvedParams.id}`,
|
||||
{ method: 'DELETE', headers }
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
const body = await request.json();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/platforms/${params.id}`,
|
||||
{
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
...headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/platforms/${params.id}`,
|
||||
{ method: 'DELETE', headers }
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const queryString = searchParams.toString();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/users${queryString ? `?${queryString}` : ''}`,
|
||||
{
|
||||
headers: {
|
||||
...headers,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
const data = await res.json();
|
||||
return NextResponse.json(data);
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
const body = await request.json();
|
||||
|
||||
const res = await fetch(`${API_BASE_URL}/api/v1/admin/users`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const queryString = searchParams.toString();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/admin/users${queryString ? `?${queryString}` : ''}`,
|
||||
{
|
||||
headers: {
|
||||
...headers,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
const data = await res.json();
|
||||
return NextResponse.json(data);
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const headers = await getAuthHeader();
|
||||
const body = await request.json();
|
||||
|
||||
const res = await fetch(`${API_BASE_URL}/api/v1/admin/users`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
|
||||
const res = await fetch(`${API_BASE_URL}/api/auth/beta/signup`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,8 @@ export async function POST(request: NextRequest) {
|
||||
const data = await res.json();
|
||||
|
||||
// Set HTTP-only cookie instead of returning token
|
||||
cookies().set('session_token', data.token, {
|
||||
const cookieStore = await cookies();
|
||||
cookieStore.set('session_token', data.token, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
|
||||
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
export async function POST() {
|
||||
cookies().delete('session_token');
|
||||
const cookieStore = await cookies();
|
||||
cookieStore.delete('session_token');
|
||||
return NextResponse.json({ success: true });
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
|
||||
const res = await fetch(`${API_BASE_URL}/api/auth/magic/exchange`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: await res.text() },
|
||||
{ status: res.status }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(await res.json());
|
||||
} catch (err: any) {
|
||||
return NextResponse.json(
|
||||
{ error: err?.message || 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,8 @@ import { cookies } from 'next/headers';
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
@@ -16,7 +17,7 @@ export async function GET(request: NextRequest) {
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
cookies().delete('session_token');
|
||||
cookieStore.delete('session_token');
|
||||
return NextResponse.json({ error: 'Session expired' }, { status: 401 });
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// Set HTTP-only cookie on successful registration
|
||||
if (data.token) {
|
||||
cookies().set('session_token', data.token, {
|
||||
const cookieStore = await cookies();
|
||||
cookieStore.set('session_token', data.token, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
|
||||
@@ -4,20 +4,22 @@ import { cookies } from 'next/headers';
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const { id } = await params;
|
||||
const headers = await getAuthHeader();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/gunbuilder/builds/${params.id}`,
|
||||
`${API_BASE_URL}/api/v1/gunbuilder/builds/${id}`,
|
||||
{ headers }
|
||||
);
|
||||
|
||||
@@ -36,14 +38,15 @@ export async function GET(
|
||||
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const { id } = await params;
|
||||
const headers = await getAuthHeader();
|
||||
const body = await request.json();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/gunbuilder/builds/${params.id}`,
|
||||
`${API_BASE_URL}/api/v1/gunbuilder/builds/${id}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: { ...headers, 'Content-Type': 'application/json' },
|
||||
@@ -66,13 +69,14 @@ export async function PUT(
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const { id } = await params;
|
||||
const headers = await getAuthHeader();
|
||||
|
||||
const res = await fetch(
|
||||
`${API_BASE_URL}/api/v1/gunbuilder/builds/${params.id}`,
|
||||
`${API_BASE_URL}/api/v1/gunbuilder/builds/${id}`,
|
||||
{ method: 'DELETE', headers }
|
||||
);
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import { cookies } from 'next/headers';
|
||||
const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
async function getAuthHeader() {
|
||||
const token = cookies().get('session_token')?.value;
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('session_token')?.value;
|
||||
if (!token) throw new Error('Unauthorized');
|
||||
return { Authorization: `Bearer ${token}` };
|
||||
}
|
||||
|
||||
@@ -29,11 +29,13 @@ function timeAgo(iso?: string | null) {
|
||||
export default async function BuildBreakdownPage({
|
||||
params,
|
||||
}: {
|
||||
params: { buildId: string };
|
||||
params: Promise<{ buildId: string }>;
|
||||
}) {
|
||||
const { buildId } = await params;
|
||||
|
||||
// Public detail endpoint - use Next.js API route
|
||||
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000';
|
||||
const res = await fetch(`${baseUrl}/api/builds/public/${params.buildId}`, {
|
||||
const res = await fetch(`${baseUrl}/api/builds/public/${buildId}`, {
|
||||
cache: "no-store",
|
||||
});
|
||||
|
||||
@@ -359,7 +361,7 @@ export default async function BuildBreakdownPage({
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-zinc-500">Build ID</span>
|
||||
<span className="text-zinc-300">
|
||||
{(build.uuid ?? params.buildId).slice(0, 8)}…
|
||||
{(build.uuid ?? buildId).slice(0, 8)}…
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
|
||||
@@ -2,20 +2,17 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { AuthProvider } from "@/context/AuthContext";
|
||||
import { BuilderNav } from "@/components/BuilderNav";
|
||||
import { TopNav } from "@/components/TopNav";
|
||||
|
||||
export default function BuilderLayout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<div className="min-h-screen bg-neutral-950 text-zinc-50">
|
||||
<AuthProvider>
|
||||
<TopNav />
|
||||
<Suspense fallback={<div className="h-[52px]" />}>
|
||||
<BuilderNav />
|
||||
</Suspense>
|
||||
<main className="min-h-screen">{children}</main>
|
||||
</AuthProvider>
|
||||
<TopNav />
|
||||
<Suspense fallback={<div className="h-[52px]" />}>
|
||||
<BuilderNav />
|
||||
</Suspense>
|
||||
<main className="min-h-screen">{children}</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
|
||||
<body className="bg-dark text-zinc-950 dark:bg-black dark:text-zinc-50">
|
||||
{" "}
|
||||
<Script src="https://umami.ash.gofwd.group/script.js" data-website-id="ad310b4a-aa5e-471a-938b-47a6c0f4108d"
|
||||
<Script src="https://umami.ash.gofwd.group/script.js" data-website-id="15c6b9ad-4119-4981-829c-26db69c07a15"
|
||||
strategy="lazyOnload"
|
||||
/>
|
||||
<AuthProvider>
|
||||
|
||||
+2
-1
@@ -13,7 +13,7 @@ const API_BASE_URL =
|
||||
function LoginPageContent() {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const { login, loading } = useAuth();
|
||||
const { login, refreshUser, loading } = useAuth();
|
||||
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
@@ -36,6 +36,7 @@ function LoginPageContent() {
|
||||
|
||||
try {
|
||||
await login({ email, password });
|
||||
await refreshUser();
|
||||
router.push(next);
|
||||
} catch (err: any) {
|
||||
setError(err?.message ?? "Failed to log in");
|
||||
|
||||
@@ -56,7 +56,7 @@ export function TopNav() {
|
||||
</Link>
|
||||
|
||||
{/* Right side actions */}
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center justify-end gap-3 sm:gap-4">
|
||||
<ThemeToggle />
|
||||
|
||||
{loading ? (
|
||||
@@ -83,7 +83,7 @@ export function TopNav() {
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex flex-col items-end gap-2 sm:flex-row sm:items-center sm:gap-4">
|
||||
<NavLink href="/login">Log In</NavLink>
|
||||
|
||||
<Link
|
||||
@@ -92,11 +92,11 @@ export function TopNav() {
|
||||
>
|
||||
Join Beta
|
||||
</Link>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
export default TopNav;
|
||||
export default TopNav;
|
||||
|
||||
@@ -3,17 +3,17 @@ import { useMemo } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import type React from "react";
|
||||
|
||||
import "react-quill/dist/quill.snow.css";
|
||||
// import "react-quill/dist/quill.snow.css";
|
||||
|
||||
const ReactQuill = dynamic(() => import("react-quill-new"), { ssr: false });
|
||||
|
||||
type RichTextEditorProps = {
|
||||
value: string;
|
||||
onChange: (html: string) => void;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const ReactQuill = dynamic(() => import("react-quill"), { ssr: false });
|
||||
|
||||
type RichTextEditorProps = {
|
||||
value: string;
|
||||
onChange: (html: string) => void;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export default function RichTextEditor({
|
||||
value,
|
||||
onChange,
|
||||
|
||||
+13
-16
@@ -14,9 +14,6 @@ export type CreatePlatformDto = {
|
||||
|
||||
export type UpdatePlatformDto = Partial<CreatePlatformDto>;
|
||||
|
||||
const API_BASE =
|
||||
process.env.NEXT_PUBLIC_API_BASE_URL ?? "";
|
||||
|
||||
function normalizePlatform(raw: any): Platform {
|
||||
const id = Number(raw?.id);
|
||||
const key = String(raw?.key ?? "").trim();
|
||||
@@ -33,9 +30,9 @@ function normalizePlatform(raw: any): Platform {
|
||||
return { id, key, label, isActive };
|
||||
}
|
||||
|
||||
export async function fetchPlatforms(tokenHeaders: HeadersInit): Promise<Platform[]> {
|
||||
const res = await fetch(`${API_BASE}/api/platforms`, {
|
||||
headers: { ...tokenHeaders },
|
||||
export async function fetchPlatforms(_tokenHeaders: HeadersInit): Promise<Platform[]> {
|
||||
const res = await fetch('/api/admin/platforms', {
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
@@ -48,15 +45,15 @@ export async function fetchPlatforms(tokenHeaders: HeadersInit): Promise<Platfor
|
||||
}
|
||||
|
||||
export async function createPlatform(
|
||||
tokenHeaders: HeadersInit,
|
||||
_tokenHeaders: HeadersInit,
|
||||
dto: CreatePlatformDto
|
||||
): Promise<Platform> {
|
||||
const res = await fetch(`${API_BASE}/api/platforms`, {
|
||||
const res = await fetch('/api/admin/platforms', {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...tokenHeaders,
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(dto),
|
||||
});
|
||||
|
||||
@@ -69,17 +66,17 @@ export async function createPlatform(
|
||||
}
|
||||
|
||||
export async function updatePlatform(
|
||||
tokenHeaders: HeadersInit,
|
||||
_tokenHeaders: HeadersInit,
|
||||
id: number,
|
||||
dto: UpdatePlatformDto
|
||||
): Promise<Platform> {
|
||||
const res = await fetch(`${API_BASE}/api/platforms/${id}`, {
|
||||
const res = await fetch(`/api/admin/platforms/${id}`, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...tokenHeaders,
|
||||
},
|
||||
body: JSON.stringify({ isActive: dto.isActive }),
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(dto),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
@@ -90,10 +87,10 @@ export async function updatePlatform(
|
||||
return normalizePlatform(await res.json());
|
||||
}
|
||||
|
||||
export async function deletePlatform(tokenHeaders: HeadersInit, id: number): Promise<void> {
|
||||
const res = await fetch(`${API_BASE}/api/platforms/${id}`, {
|
||||
export async function deletePlatform(_tokenHeaders: HeadersInit, id: number): Promise<void> {
|
||||
const res = await fetch(`/api/admin/platforms/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: { ...tokenHeaders },
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Fix mixed content warnings by upgrading HTTP image URLs to HTTPS
|
||||
* This handles image URLs stored in the database as http:// which cause
|
||||
* browser warnings when loaded from HTTPS pages.
|
||||
*/
|
||||
export function fixImageUrl(url: string | null | undefined): string | null | undefined {
|
||||
if (!url || typeof url !== 'string') return url;
|
||||
return url.replace(/^http:\/\//i, 'https://');
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively fix all image URL fields in an object
|
||||
* Common fields: imageUrl, mainImageUrl, coverImageUrl, thumbnailUrl, etc.
|
||||
*/
|
||||
export function fixImageUrls(data: any): any {
|
||||
if (!data) return data;
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
return data.map(item => fixImageUrls(item));
|
||||
}
|
||||
|
||||
if (typeof data === 'object') {
|
||||
const fixed: any = {};
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
// Fix any field that looks like an image URL
|
||||
if (
|
||||
(key.toLowerCase().includes('image') ||
|
||||
key.toLowerCase().includes('photo') ||
|
||||
key.toLowerCase().includes('picture')) &&
|
||||
typeof value === 'string'
|
||||
) {
|
||||
fixed[key] = fixImageUrl(value);
|
||||
} else if (typeof value === 'object') {
|
||||
fixed[key] = fixImageUrls(value);
|
||||
} else {
|
||||
fixed[key] = value;
|
||||
}
|
||||
}
|
||||
return fixed;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
+66
-16
@@ -2,42 +2,92 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
function redirectToLogin(req: NextRequest) {
|
||||
const url = req.nextUrl.clone();
|
||||
url.pathname = "/login";
|
||||
url.searchParams.set("next", req.nextUrl.pathname);
|
||||
return NextResponse.redirect(url);
|
||||
}
|
||||
|
||||
function isPublicPath(pathname: string) {
|
||||
// Public pages
|
||||
if (pathname === "/") return true;
|
||||
if (pathname === "/login") return true;
|
||||
if (pathname === "/tos") return true;
|
||||
if (pathname === "/privacy") return true;
|
||||
if (pathname === "/beta") return true;
|
||||
if (pathname.startsWith("/beta/")) return true;
|
||||
if (pathname.startsWith("/auth")) return true;
|
||||
if (pathname.startsWith("/api/auth")) return true;
|
||||
|
||||
// Static / framework assets
|
||||
if (pathname.startsWith("/_next")) return true;
|
||||
if (pathname === "/favicon.ico") return true;
|
||||
if (pathname === "/robots.txt") return true;
|
||||
if (pathname === "/sitemap.xml") return true;
|
||||
|
||||
// Health / misc APIs you may want public
|
||||
if (pathname === "/api/health") return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function isProtectedLaunchPath(pathname: string) {
|
||||
return (
|
||||
pathname === "/builder" ||
|
||||
pathname.startsWith("/builder/") ||
|
||||
pathname === "/builds" ||
|
||||
pathname.startsWith("/builds/") ||
|
||||
pathname === "/vault" ||
|
||||
pathname.startsWith("/vault/")
|
||||
);
|
||||
}
|
||||
|
||||
export function middleware(req: NextRequest) {
|
||||
const { pathname } = req.nextUrl;
|
||||
const token = req.cookies.get("session_token")?.value;
|
||||
const isLoggedIn = Boolean(token);
|
||||
console.log("is logged in: " + isLoggedIn);
|
||||
console.log("MW:", req.nextUrl.pathname)
|
||||
|
||||
// Protect admin API routes
|
||||
// IMPORTANT: Use a server-only env var for middleware behavior.
|
||||
// Set LAUNCH_ONLY_ROOT=true in your Docker compose.
|
||||
const launchOnlyRoot = process.env.LAUNCH_ONLY_ROOT === "true";
|
||||
|
||||
// In "launch only" mode, anonymous users are blocked only from gated app areas.
|
||||
if (launchOnlyRoot && !isLoggedIn && isProtectedLaunchPath(pathname)) {
|
||||
return redirectToLogin(req);
|
||||
}
|
||||
|
||||
// --- Protected API routes ---
|
||||
if (pathname.startsWith("/api/admin")) {
|
||||
if (!token) {
|
||||
if (!isLoggedIn) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
}
|
||||
|
||||
// Protect builds API routes
|
||||
if (pathname.startsWith("/api/builds")) {
|
||||
if (!token) {
|
||||
if (!isLoggedIn) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
}
|
||||
|
||||
// Protect admin pages
|
||||
// --- Protected pages ---
|
||||
if (pathname.startsWith("/admin")) {
|
||||
if (!token) {
|
||||
const url = req.nextUrl.clone();
|
||||
url.pathname = "/login";
|
||||
url.searchParams.set("next", pathname);
|
||||
return NextResponse.redirect(url);
|
||||
}
|
||||
if (!isLoggedIn) return redirectToLogin(req);
|
||||
}
|
||||
|
||||
// Protect the builder UI
|
||||
if (pathname === "/builder" || pathname.startsWith("/builder/")) {
|
||||
if (!isLoggedIn) return redirectToLogin(req);
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
export const config = {
|
||||
// Run middleware for all application routes, but avoid static assets.
|
||||
matcher: [
|
||||
"/api/admin/:path*",
|
||||
"/api/builds",
|
||||
"/api/builds/:path+",
|
||||
"/admin/:path*",
|
||||
"/((?!_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml).*)",
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
Vendored
+2
-1
@@ -1,5 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference path="./.next/types/routes.d.ts" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
appDir: true,
|
||||
},
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
|
||||
Generated
+801
-371
File diff suppressed because it is too large
Load Diff
+22
-7
@@ -1,7 +1,23 @@
|
||||
{
|
||||
"name": "gunbuilder-prototype",
|
||||
"version": "0.1.0",
|
||||
"name": "battl.builders",
|
||||
"version": "1.0",
|
||||
"private": true,
|
||||
"author": {
|
||||
"name": "Forward Group, LLC",
|
||||
"email": "info@goforward.group",
|
||||
"url": "https://goforward.group"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Sean Strawsburg",
|
||||
"email": "sean@goforward.group"
|
||||
},
|
||||
{
|
||||
"name": "Don Strawsburg",
|
||||
"email": "don@goforward.group"
|
||||
}
|
||||
],
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
@@ -13,12 +29,11 @@
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.555.0",
|
||||
"next": "14.2.3",
|
||||
"quill": "^2.0.3",
|
||||
"next": "15.5.11",
|
||||
"quill-image-resize-module": "^3.0.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-quill": "^2.0.0"
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-quill-new": "^3.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.12.7",
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
-- ============================================
|
||||
-- Gunbuilder / AvantLink Schema DDL
|
||||
-- ============================================
|
||||
|
||||
-- Enable extension for UUID generation (if not already enabled)
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
|
||||
-- ============================================
|
||||
-- 1. merchants
|
||||
-- ============================================
|
||||
CREATE TABLE merchants (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
avantlink_mid TEXT NOT NULL UNIQUE, -- AvantLink merchant ID
|
||||
feed_url TEXT NOT NULL, -- Product datafeed URL
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_merchants_active ON merchants (is_active);
|
||||
|
||||
|
||||
-- ============================================
|
||||
-- 2. part_categories
|
||||
-- Your internal Gunbuilder categories
|
||||
-- ============================================
|
||||
CREATE TABLE part_categories (
|
||||
id SERIAL PRIMARY KEY,
|
||||
slug TEXT NOT NULL UNIQUE, -- 'lower', 'upper', 'barrel', etc.
|
||||
name TEXT NOT NULL, -- 'Lower Receiver'
|
||||
description TEXT
|
||||
);
|
||||
|
||||
|
||||
-- ============================================
|
||||
-- 3. brands
|
||||
-- ============================================
|
||||
CREATE TABLE brands (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE, -- 'Aero Precision'
|
||||
website TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
|
||||
-- ============================================
|
||||
-- 4. products
|
||||
-- Canonical parts, independent of merchants
|
||||
-- ============================================
|
||||
CREATE TABLE products (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
brand_id INTEGER REFERENCES brands(id),
|
||||
part_category_id INTEGER NOT NULL REFERENCES part_categories(id),
|
||||
|
||||
name TEXT NOT NULL,
|
||||
slug TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
caliber TEXT,
|
||||
barrel_length_mm INTEGER,
|
||||
gas_system TEXT,
|
||||
handguard_length_mm INTEGER,
|
||||
image_url TEXT,
|
||||
spec_sheet_url TEXT,
|
||||
|
||||
msrp NUMERIC(10,2),
|
||||
current_lowest_price NUMERIC(10,2),
|
||||
current_lowest_merchant_id INTEGER REFERENCES merchants(id),
|
||||
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_products_category ON products (part_category_id);
|
||||
CREATE INDEX idx_products_brand ON products (brand_id);
|
||||
CREATE INDEX idx_products_active ON products (is_active);
|
||||
CREATE INDEX idx_products_lowest_price ON products (current_lowest_price);
|
||||
|
||||
|
||||
-- Optional: a "natural-ish" uniqueness constraint if you want it
|
||||
-- (Can be relaxed later if needed)
|
||||
CREATE UNIQUE INDEX uniq_products_brand_name_cat
|
||||
ON products (brand_id, name, part_category_id);
|
||||
|
||||
|
||||
-- ============================================
|
||||
-- 5. product_offers
|
||||
-- Merchant-specific offers for a product
|
||||
-- ============================================
|
||||
CREATE TABLE product_offers (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
product_id UUID NOT NULL REFERENCES products(id) ON DELETE CASCADE,
|
||||
merchant_id INTEGER NOT NULL REFERENCES merchants(id),
|
||||
avantlink_product_id TEXT NOT NULL,
|
||||
sku TEXT,
|
||||
upc TEXT,
|
||||
buy_url TEXT NOT NULL,
|
||||
|
||||
price NUMERIC(10,2) NOT NULL,
|
||||
original_price NUMERIC(10,2),
|
||||
currency TEXT NOT NULL DEFAULT 'USD',
|
||||
in_stock BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
|
||||
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
first_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Each merchant + avantlink_product_id combination must be unique
|
||||
CREATE UNIQUE INDEX uniq_product_offers_merchant_product
|
||||
ON product_offers (merchant_id, avantlink_product_id);
|
||||
|
||||
CREATE INDEX idx_product_offers_product ON product_offers (product_id);
|
||||
CREATE INDEX idx_product_offers_merchant ON product_offers (merchant_id);
|
||||
CREATE INDEX idx_product_offers_in_stock ON product_offers (in_stock);
|
||||
CREATE INDEX idx_product_offers_price ON product_offers (price);
|
||||
|
||||
|
||||
-- ============================================
|
||||
-- 6. price_history
|
||||
-- For tracking price changes over time
|
||||
-- ============================================
|
||||
CREATE TABLE price_history (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
product_offer_id UUID NOT NULL REFERENCES product_offers(id) ON DELETE CASCADE,
|
||||
price NUMERIC(10,2) NOT NULL,
|
||||
recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_price_history_offer ON price_history (product_offer_id);
|
||||
CREATE INDEX idx_price_history_recorded_at ON price_history (recorded_at);
|
||||
|
||||
|
||||
-- ============================================
|
||||
-- 7. category_mappings
|
||||
-- Map raw AvantLink categories to internal part_categories
|
||||
-- ============================================
|
||||
CREATE TABLE category_mappings (
|
||||
id SERIAL PRIMARY KEY,
|
||||
merchant_id INTEGER NOT NULL REFERENCES merchants(id),
|
||||
raw_category_path TEXT NOT NULL, -- e.g. 'Firearms > AR-15 Parts > Lower Receivers'
|
||||
part_category_id INTEGER NOT NULL REFERENCES part_categories(id),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
UNIQUE (merchant_id, raw_category_path)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_category_mappings_merchant ON category_mappings (merchant_id);
|
||||
CREATE INDEX idx_category_mappings_part_category ON category_mappings (part_category_id);
|
||||
|
||||
|
||||
-- ============================================
|
||||
-- 8. feed_imports
|
||||
-- Track each feed import run for observability
|
||||
-- ============================================
|
||||
CREATE TABLE feed_imports (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
merchant_id INTEGER NOT NULL REFERENCES merchants(id),
|
||||
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
finished_at TIMESTAMPTZ,
|
||||
rows_total INTEGER,
|
||||
rows_processed INTEGER,
|
||||
rows_new INTEGER,
|
||||
rows_updated INTEGER,
|
||||
status TEXT NOT NULL DEFAULT 'running', -- 'running','success','error'
|
||||
error_message TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX idx_feed_imports_merchant ON feed_imports (merchant_id);
|
||||
CREATE INDEX idx_feed_imports_status ON feed_imports (status);
|
||||
CREATE INDEX idx_feed_imports_started_at ON feed_imports (started_at);
|
||||
|
||||
|
||||
-- ============================================
|
||||
-- 9. Helpful seed data (optional)
|
||||
-- ============================================
|
||||
|
||||
-- Part categories seed (adjust as needed)
|
||||
INSERT INTO part_categories (slug, name) VALUES
|
||||
('lower', 'Lower Receiver'),
|
||||
('upper', 'Upper Receiver'),
|
||||
('barrel', 'Barrel'),
|
||||
('handguard', 'Handguard'),
|
||||
('stock', 'Stock'),
|
||||
('optic', 'Optic')
|
||||
ON CONFLICT (slug) DO NOTHING;
|
||||
|
||||
-- Example merchant skeleton (fill real datafeed URL + AvantLink MID)
|
||||
-- INSERT INTO merchants (name, avantlink_mid, feed_url)
|
||||
-- VALUES ('Example Merchant', '12345', 'https://www.avantlink.com/api/...');
|
||||
Reference in New Issue
Block a user