fixes to admin using client fetch
CI / test (push) Successful in 7s

This commit is contained in:
2026-01-29 21:52:12 -05:00
parent a13687635b
commit 45270f9b18
22 changed files with 828 additions and 52 deletions
+6
View File
@@ -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>
+4 -6
View File
@@ -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);
+30 -29
View File
@@ -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()}`);
+1 -1
View File
@@ -21,7 +21,7 @@ export default function AdminUsersPage() {
setLoading(true);
setError(null);
const res = await fetch(`${API_BASE_URL}/admin/users`, {
const res = await fetch(`/api/admin/users`, {
headers: {
...getAuthHeaders(),
},
+40
View File
@@ -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 }
);
}
}
@@ -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 }
);
}
}
+46
View File
@@ -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 }
);
}
}
+44
View File
@@ -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 }
);
}
}
+46
View File
@@ -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 }
);
}
}
+68
View File
@@ -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 });
}
}
+72
View File
@@ -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 }
);
}
}
+41
View File
@@ -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 }
);
}
}
+40
View File
@@ -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 }
);
}
}
+40
View File
@@ -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 }
);
}
}
+42
View File
@@ -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 }
);
}
}
+68
View File
@@ -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 });
}
}
+72
View File
@@ -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 }
);
}
}
+31
View File
@@ -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 }
);
}
}
+31
View File
@@ -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 }
);
}
}
+13 -16
View File
@@ -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) {
+43
View File
@@ -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;
}
+2
View File
@@ -47,6 +47,8 @@ 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)
// IMPORTANT: Use a server-only env var for middleware behavior.
// Set LAUNCH_ONLY_ROOT=true in your Docker compose.