fix: konvertera alla API route handlers till withAuth wrapper

Ersätter getAuthHeaders() + auth() standalone med withAuth() wrapper
i alla route handlers. Auth() standalone fungerar inte korrekt i
Next.js 16 + NextAuth beta.28 pga async cookies() kompatibilitet.
withAuth() använder auth() i wrapper-form sa att request.auth
populeras direkt av NextAuth.

Pavaerkade filer: 27 route handlers + ny lib/with-auth.ts
This commit is contained in:
Nils-Johan Gynther
2026-04-19 21:11:14 +02:00
parent 390e979cdb
commit 722440b9b5
28 changed files with 247 additions and 453 deletions
@@ -1,28 +1,15 @@
import { auth } from '../../../../auth'; import { withAuth } from '../../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
async function getAuthHeaders(): Promise<Record<string, string>> { export const POST = withAuth(async (req, session) => {
const session = await auth();
if (!session?.accessToken) return {};
return { Authorization: `Bearer ${session.accessToken}` };
}
// POST /api/admin/bulk-categorize
// Body: { productIds?: number[] }
export async function POST(req: Request) {
try { try {
const body = await req.json().catch(() => ({})); const body = await req.json().catch(() => ({}));
const { productIds } = body; const { productIds } = body;
const authHeaders = await getAuthHeaders();
if (!authHeaders.Authorization) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const res = await fetch(`${API_BASE}/api/products/ai-categorize-bulk`, { const res = await fetch(`${API_BASE}/api/products/ai-categorize-bulk`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders }, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify({ productIds }), body: JSON.stringify({ productIds }),
}); });
@@ -40,4 +27,4 @@ export async function POST(req: Request) {
{ status: 500 }, { status: 500 },
); );
} }
} });
+4 -13
View File
@@ -1,16 +1,8 @@
import { auth } from '../../../../auth'; import { withAuth } from '../../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
async function getAuthHeaders(): Promise<Record<string, string>> { export const POST = withAuth(async (req, session) => {
const session = await auth();
if (!session?.accessToken) {
return {};
}
return { Authorization: `Bearer ${session.accessToken}` };
}
export async function POST(req: Request) {
try { try {
const body = await req.json(); const body = await req.json();
const { name } = body; const { name } = body;
@@ -19,10 +11,9 @@ export async function POST(req: Request) {
return Response.json({ error: 'Name is required' }, { status: 400 }); return Response.json({ error: 'Name is required' }, { status: 400 });
} }
const authHeaders = await getAuthHeaders();
const res = await fetch(`${API_BASE}/api/products`, { const res = await fetch(`${API_BASE}/api/products`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders }, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify({ name }), body: JSON.stringify({ name }),
}); });
@@ -43,4 +34,4 @@ export async function POST(req: Request) {
{ status: 500 }, { status: 500 },
); );
} }
} });
@@ -1,29 +1,22 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../../lib/auth-headers'; import { withAuth } from '../../../../lib/with-auth';
const API_BASE = const API_BASE =
process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET(request: NextRequest) { export const GET = withAuth(async (request, session) => {
const authHeaders = await getAuthHeaders(); const { searchParams } = new URL(request.url);
const sourceProductId = request.nextUrl.searchParams.get('sourceProductId'); const sourceProductId = searchParams.get('sourceProductId');
const targetProductId = request.nextUrl.searchParams.get('targetProductId'); const targetProductId = searchParams.get('targetProductId');
const res = await fetch( const res = await fetch(
`${API_BASE}/api/products/merge-preview?sourceProductId=${sourceProductId}&targetProductId=${targetProductId}`, `${API_BASE}/api/products/merge-preview?sourceProductId=${sourceProductId}&targetProductId=${targetProductId}`,
{ {
method: 'GET', headers: { Authorization: `Bearer ${session.accessToken}` },
headers: { ...authHeaders },
cache: 'no-store', cache: 'no-store',
}, },
); );
const text = await res.text(); const text = await res.text();
return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
return new NextResponse(text, { });
status: res.status,
headers: {
'Content-Type': 'application/json',
},
});
}
+14 -42
View File
@@ -1,23 +1,10 @@
import { auth } from '../../../../../auth'; import { withAuth } from '../../../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
async function getAuthHeaders(): Promise<Record<string, string>> { export const PATCH = withAuth(async (req, session, context) => {
const session = await auth();
if (!session?.accessToken) {
return {};
}
return { Authorization: `Bearer ${session.accessToken}` };
}
// PATCH /api/admin/product/[id]
// Body: { name, canonicalName, category, subcategory, brand, categoryId, tags }
export async function PATCH(
req: Request,
{ params }: { params: Promise<{ id: string }> },
) {
try { try {
const { id } = await params; const { id } = await context.params;
const productId = Number(id); const productId = Number(id);
if (!productId) return Response.json({ error: 'Invalid id' }, { status: 400 }); if (!productId) return Response.json({ error: 'Invalid id' }, { status: 400 });
@@ -28,15 +15,11 @@ export async function PATCH(
return Response.json({ error: 'Namn får inte vara tomt.' }, { status: 400 }); return Response.json({ error: 'Namn får inte vara tomt.' }, { status: 400 });
} }
const authHeaders = await getAuthHeaders(); const authHeader = `Bearer ${session.accessToken}`;
if (!authHeaders.Authorization) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
// 1. Update product fields
const patchRes = await fetch(`${API_BASE}/api/products/${productId}`, { const patchRes = await fetch(`${API_BASE}/api/products/${productId}`, {
method: 'PATCH', method: 'PATCH',
headers: { 'Content-Type': 'application/json', ...authHeaders }, headers: { 'Content-Type': 'application/json', Authorization: authHeader },
body: JSON.stringify({ body: JSON.stringify({
name: name.trim(), name: name.trim(),
canonicalName: canonicalName?.trim() || undefined, canonicalName: canonicalName?.trim() || undefined,
@@ -53,10 +36,9 @@ export async function PATCH(
return Response.json({ error: `Kunde inte uppdatera produkt: ${text}` }, { status: patchRes.status }); return Response.json({ error: `Kunde inte uppdatera produkt: ${text}` }, { status: patchRes.status });
} }
// 2. Update tags
const tagsRes = await fetch(`${API_BASE}/api/products/${productId}/tags`, { const tagsRes = await fetch(`${API_BASE}/api/products/${productId}/tags`, {
method: 'PUT', method: 'PUT',
headers: { 'Content-Type': 'application/json', ...authHeaders }, headers: { 'Content-Type': 'application/json', Authorization: authHeader },
body: JSON.stringify({ tags: tags ?? [] }), body: JSON.stringify({ tags: tags ?? [] }),
}); });
@@ -66,17 +48,15 @@ export async function PATCH(
return Response.json({ error: `Kunde inte uppdatera taggar: ${text}` }, { status: tagsRes.status }); return Response.json({ error: `Kunde inte uppdatera taggar: ${text}` }, { status: tagsRes.status });
} }
// 3. Return the complete updated product
const fullRes = await fetch(`${API_BASE}/api/products/${productId}`, { const fullRes = await fetch(`${API_BASE}/api/products/${productId}`, {
headers: authHeaders, headers: { Authorization: authHeader },
}); });
if (!fullRes.ok) { if (!fullRes.ok) {
return Response.json({ error: 'Produkt uppdaterad men kunde inte hämtas' }, { status: 500 }); return Response.json({ error: 'Produkt uppdaterad men kunde inte hämtas' }, { status: 500 });
} }
const product = await fullRes.json(); return Response.json(await fullRes.json());
return Response.json(product);
} catch (err) { } catch (err) {
console.error('[api/admin/product] PATCH error:', err); console.error('[api/admin/product] PATCH error:', err);
return Response.json( return Response.json(
@@ -84,26 +64,17 @@ export async function PATCH(
{ status: 500 }, { status: 500 },
); );
} }
} });
// DELETE /api/admin/product/[id] export const DELETE = withAuth(async (_req, session, context) => {
export async function DELETE(
_req: Request,
{ params }: { params: Promise<{ id: string }> },
) {
try { try {
const { id } = await params; const { id } = await context.params;
const productId = Number(id); const productId = Number(id);
if (!productId) return Response.json({ error: 'Invalid id' }, { status: 400 }); if (!productId) return Response.json({ error: 'Invalid id' }, { status: 400 });
const authHeaders = await getAuthHeaders();
if (!authHeaders.Authorization) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const res = await fetch(`${API_BASE}/api/products/${productId}`, { const res = await fetch(`${API_BASE}/api/products/${productId}`, {
method: 'DELETE', method: 'DELETE',
headers: authHeaders, headers: { Authorization: `Bearer ${session.accessToken}` },
}); });
if (!res.ok) { if (!res.ok) {
@@ -120,4 +91,5 @@ export async function DELETE(
{ status: 500 }, { status: 500 },
); );
} }
} });
@@ -1,30 +1,15 @@
import { auth } from '../../../../../auth'; import { withAuth } from '../../../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
async function getAuthHeaders(): Promise<Record<string, string>> { export const GET = withAuth(async (_req, session, context) => {
const session = await auth();
if (!session?.accessToken) return {};
return { Authorization: `Bearer ${session.accessToken}` };
}
// GET /api/admin/suggest-category/[id]
export async function GET(
_req: Request,
{ params }: { params: Promise<{ id: string }> },
) {
try { try {
const { id } = await params; const { id } = await context.params;
const productId = Number(id); const productId = Number(id);
if (!productId) return Response.json({ error: 'Invalid id' }, { status: 400 }); if (!productId) return Response.json({ error: 'Invalid id' }, { status: 400 });
const authHeaders = await getAuthHeaders();
if (!authHeaders.Authorization) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const res = await fetch(`${API_BASE}/api/products/${productId}/suggest-category`, { const res = await fetch(`${API_BASE}/api/products/${productId}/suggest-category`, {
headers: authHeaders, headers: { Authorization: `Bearer ${session.accessToken}` },
}); });
if (!res.ok) { if (!res.ok) {
@@ -41,4 +26,4 @@ export async function GET(
{ status: 500 }, { status: 500 },
); );
} }
} });
@@ -1,29 +1,17 @@
import { auth } from '../../../../../auth'; import { withAuth } from '../../../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
async function getAuthHeaders(): Promise<Record<string, string>> { export const PATCH = withAuth(async (req, session, context) => {
const session = await auth();
if (!session?.accessToken) {
return {};
}
return { Authorization: `Bearer ${session.accessToken}` };
}
export async function PATCH(
req: Request,
{ params }: { params: Promise<{ id: string }> },
) {
try { try {
const { id } = await params; const { id } = await context.params;
const productId = parseInt(id, 10); const productId = parseInt(id, 10);
const body = await req.json(); const body = await req.json();
const { categoryId } = body; const { categoryId } = body;
const authHeaders = await getAuthHeaders();
const res = await fetch(`${API_BASE}/api/products/${productId}`, { const res = await fetch(`${API_BASE}/api/products/${productId}`, {
method: 'PATCH', method: 'PATCH',
headers: { 'Content-Type': 'application/json', ...authHeaders }, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify({ categoryId }), body: JSON.stringify({ categoryId }),
}); });
@@ -45,4 +33,4 @@ export async function PATCH(
{ status: 500 }, { status: 500 },
); );
} }
} });
@@ -1,25 +1,18 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../lib/auth-headers'; import { withAuth } from '../../../lib/with-auth';
const API_BASE = const API_BASE =
process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET(request: NextRequest) { export const GET = withAuth(async (request, session) => {
const authHeaders = await getAuthHeaders(); const { searchParams } = new URL(request.url);
const id = request.nextUrl.searchParams.get('id'); const id = searchParams.get('id');
const res = await fetch(`${API_BASE}/api/inventory/${id}/consumption-history`, { const res = await fetch(`${API_BASE}/api/inventory/${id}/consumption-history`, {
method: 'GET', headers: { Authorization: `Bearer ${session.accessToken}` },
headers: { ...authHeaders },
cache: 'no-store', cache: 'no-store',
}); });
const text = await res.text(); const text = await res.text();
return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
return new NextResponse(text, { });
status: res.status,
headers: {
'Content-Type': 'application/json',
},
});
}
+11 -19
View File
@@ -1,34 +1,26 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../lib/auth-headers'; import { withAuth } from '../../../lib/with-auth';
const API_BASE = const API_BASE =
process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET(request: NextRequest) { export const GET = withAuth(async (request, session) => {
const authHeaders = await getAuthHeaders(); const { search } = new URL(request.url);
const search = request.nextUrl.search;
const res = await fetch(`${API_BASE}/api/inventory${search}`, { const res = await fetch(`${API_BASE}/api/inventory${search}`, {
headers: { ...authHeaders }, headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store', cache: 'no-store',
}); });
const text = await res.text(); const text = await res.text();
return new NextResponse(text, { return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
status: res.status, });
headers: { 'Content-Type': 'application/json' },
});
}
export async function POST(request: NextRequest) { export const POST = withAuth(async (request, session) => {
const authHeaders = await getAuthHeaders();
const body = await request.json(); const body = await request.json();
const res = await fetch(`${API_BASE}/api/inventory`, { const res = await fetch(`${API_BASE}/api/inventory`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders }, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify(body), body: JSON.stringify(body),
}); });
const text = await res.text(); const text = await res.text();
return new NextResponse(text, { return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
status: res.status, });
headers: { 'Content-Type': 'application/json' },
});
}
@@ -1,20 +1,16 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../../lib/auth-headers'; import { withAuth } from '../../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET(request: NextRequest) { export const GET = withAuth(async (request, session) => {
const authHeaders = await getAuthHeaders(); const { searchParams } = new URL(request.url);
const { searchParams } = request.nextUrl;
const from = searchParams.get('from'); const from = searchParams.get('from');
const to = searchParams.get('to'); const to = searchParams.get('to');
const res = await fetch(`${API_BASE}/api/meal-plan/inventory-compare?from=${from}&to=${to}`, { const res = await fetch(`${API_BASE}/api/meal-plan/inventory-compare?from=${from}&to=${to}`, {
headers: { ...authHeaders }, headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store', cache: 'no-store',
}); });
const text = await res.text(); const text = await res.text();
return new NextResponse(text, { return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
status: res.status, });
headers: { 'Content-Type': 'application/json' },
});
}
+15 -26
View File
@@ -1,46 +1,35 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../lib/auth-headers'; import { withAuth } from '../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET(request: NextRequest) { export const GET = withAuth(async (request, session) => {
const authHeaders = await getAuthHeaders(); const { searchParams } = new URL(request.url);
const { searchParams } = request.nextUrl;
const query = searchParams.toString(); const query = searchParams.toString();
const res = await fetch(`${API_BASE}/api/meal-plan${query ? `?${query}` : ''}`, { const res = await fetch(`${API_BASE}/api/meal-plan${query ? `?${query}` : ''}`, {
headers: { ...authHeaders }, headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store', cache: 'no-store',
}); });
const text = await res.text(); const text = await res.text();
return new NextResponse(text, { return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
status: res.status, });
headers: { 'Content-Type': 'application/json' },
});
}
export async function POST(request: NextRequest) { export const POST = withAuth(async (request, session) => {
const authHeaders = await getAuthHeaders();
const body = await request.text(); const body = await request.text();
const res = await fetch(`${API_BASE}/api/meal-plan`, { const res = await fetch(`${API_BASE}/api/meal-plan`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders }, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body, body,
cache: 'no-store',
}); });
const text = await res.text(); const text = await res.text();
return new NextResponse(text, { return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
status: res.status, });
headers: { 'Content-Type': 'application/json' },
});
}
export async function DELETE(request: NextRequest) { export const DELETE = withAuth(async (request, session) => {
const authHeaders = await getAuthHeaders(); const date = new URL(request.url).searchParams.get('date');
const date = request.nextUrl.searchParams.get('date');
const res = await fetch(`${API_BASE}/api/meal-plan/${date}`, { const res = await fetch(`${API_BASE}/api/meal-plan/${date}`, {
method: 'DELETE', method: 'DELETE',
headers: { ...authHeaders }, headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store',
}); });
return new NextResponse(null, { status: res.status }); return new NextResponse(null, { status: res.status });
} });
@@ -1,20 +1,16 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../../lib/auth-headers'; import { withAuth } from '../../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET(request: NextRequest) { export const GET = withAuth(async (request, session) => {
const authHeaders = await getAuthHeaders(); const { searchParams } = new URL(request.url);
const { searchParams } = request.nextUrl;
const from = searchParams.get('from'); const from = searchParams.get('from');
const to = searchParams.get('to'); const to = searchParams.get('to');
const res = await fetch(`${API_BASE}/api/meal-plan/shopping-list?from=${from}&to=${to}`, { const res = await fetch(`${API_BASE}/api/meal-plan/shopping-list?from=${from}&to=${to}`, {
headers: { ...authHeaders }, headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store', cache: 'no-store',
}); });
const text = await res.text(); const text = await res.text();
return new NextResponse(text, { return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
status: res.status, });
headers: { 'Content-Type': 'application/json' },
});
}
+6 -11
View File
@@ -1,23 +1,18 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../lib/auth-headers'; import { withAuth } from '../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function POST(request: NextRequest) { export const POST = withAuth(async (request, session) => {
const authHeaders = await getAuthHeaders();
const body = await request.text(); const body = await request.text();
const res = await fetch(`${API_BASE}/api/recipes/parse-markdown`, { const res = await fetch(`${API_BASE}/api/recipes/parse-markdown`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders }, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body, body,
cache: 'no-store', cache: 'no-store',
}); });
const text = await res.text(); const text = await res.text();
return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
return new NextResponse(text, { });
status: res.status,
headers: { 'Content-Type': 'application/json' },
});
}
+4 -17
View File
@@ -1,16 +1,8 @@
import { auth } from '../../../auth'; import { withAuth } from '../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
async function getAuthHeaders(): Promise<Record<string, string>> { export const POST = withAuth(async (req, session) => {
const session = await auth();
if (!session?.accessToken) {
return {};
}
return { Authorization: `Bearer ${session.accessToken}` };
}
export async function POST(req: Request) {
try { try {
const body = await req.json(); const body = await req.json();
const { name } = body; const { name } = body;
@@ -19,12 +11,9 @@ export async function POST(req: Request) {
return Response.json({ error: 'Name is required' }, { status: 400 }); return Response.json({ error: 'Name is required' }, { status: 400 });
} }
const authHeaders = await getAuthHeaders();
console.log('[products-create] Auth headers:', authHeaders ? 'YES' : 'NO');
const res = await fetch(`${API_BASE}/api/products`, { const res = await fetch(`${API_BASE}/api/products`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders }, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify({ name }), body: JSON.stringify({ name }),
}); });
@@ -37,8 +26,6 @@ export async function POST(req: Request) {
} }
const product = await res.json(); const product = await res.json();
// Return only serializable fields
return Response.json({ return Response.json({
id: product.id, id: product.id,
name: product.name, name: product.name,
@@ -51,4 +38,4 @@ export async function POST(req: Request) {
{ status: 500 }, { status: 500 },
); );
} }
} });
+5 -21
View File
@@ -1,21 +1,10 @@
import { auth } from '../../../../auth'; import { withAuth } from '../../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
async function getAuthHeaders(): Promise<Record<string, string>> { export const PATCH = withAuth(async (req, session, context) => {
const session = await auth();
if (!session?.accessToken) {
return {};
}
return { Authorization: `Bearer ${session.accessToken}` };
}
export async function PATCH(
req: Request,
{ params }: { params: Promise<{ id: string }> },
) {
try { try {
const { id } = await params; const { id } = await context.params;
const productId = parseInt(id, 10); const productId = parseInt(id, 10);
const body = await req.json(); const body = await req.json();
const { categoryId } = body; const { categoryId } = body;
@@ -24,12 +13,9 @@ export async function PATCH(
return Response.json({ error: 'categoryId is required' }, { status: 400 }); return Response.json({ error: 'categoryId is required' }, { status: 400 });
} }
const authHeaders = await getAuthHeaders();
console.log('[products-update] Auth headers:', authHeaders ? 'YES' : 'NO');
const res = await fetch(`${API_BASE}/api/products/${productId}`, { const res = await fetch(`${API_BASE}/api/products/${productId}`, {
method: 'PATCH', method: 'PATCH',
headers: { 'Content-Type': 'application/json', ...authHeaders }, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify({ categoryId }), body: JSON.stringify({ categoryId }),
}); });
@@ -42,8 +28,6 @@ export async function PATCH(
} }
const product = await res.json(); const product = await res.json();
// Return only serializable fields
return Response.json({ return Response.json({
id: product.id, id: product.id,
name: product.name, name: product.name,
@@ -57,4 +41,4 @@ export async function PATCH(
{ status: 500 }, { status: 500 },
); );
} }
} });
+6 -7
View File
@@ -1,17 +1,16 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../../lib/auth-headers'; import { withAuth } from '../../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function PATCH(req: NextRequest, { params }: { params: Promise<{ id: string }> }) { export const PATCH = withAuth(async (req, session, context) => {
const { id } = await params; const { id } = await context.params;
const body = await req.json(); const body = await req.json();
const authHeaders = await getAuthHeaders();
const res = await fetch(`${API_BASE}/api/products/${id}`, { const res = await fetch(`${API_BASE}/api/products/${id}`, {
method: 'PATCH', method: 'PATCH',
headers: { 'Content-Type': 'application/json', ...authHeaders }, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify(body), body: JSON.stringify(body),
}); });
const data = await res.json().catch(() => ({})); const data = await res.json().catch(() => ({}));
return NextResponse.json(data, { status: res.status }); return NextResponse.json(data, { status: res.status });
} });
+5 -6
View File
@@ -1,16 +1,15 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../../lib/auth-headers'; import { withAuth } from '../../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function POST(req: NextRequest) { export const POST = withAuth(async (req, session) => {
const body = await req.json(); const body = await req.json();
const authHeaders = await getAuthHeaders();
const res = await fetch(`${API_BASE}/api/products/pending`, { const res = await fetch(`${API_BASE}/api/products/pending`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders }, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify(body), body: JSON.stringify(body),
}); });
const data = await res.json(); const data = await res.json();
return NextResponse.json(data, { status: res.status }); return NextResponse.json(data, { status: res.status });
} });
+4 -8
View File
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../lib/auth-headers'; import { withAuth } from '../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
@@ -15,17 +15,13 @@ export async function GET(req: NextRequest) {
return NextResponse.json(data, { status: res.status }); return NextResponse.json(data, { status: res.status });
} }
export async function POST(req: NextRequest) { export const POST = withAuth(async (req, session) => {
const body = await req.json(); const body = await req.json();
const authHeaders = await getAuthHeaders();
// Debug: log auth headers
// eslint-disable-next-line no-console
console.log('API /api/products POST: authHeaders =', authHeaders);
const res = await fetch(`${API_BASE}/api/products`, { const res = await fetch(`${API_BASE}/api/products`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders }, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify(body), body: JSON.stringify(body),
}); });
const data = await res.json(); const data = await res.json();
return NextResponse.json(data, { status: res.status }); return NextResponse.json(data, { status: res.status });
} });
+9 -17
View File
@@ -1,32 +1,24 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../lib/auth-headers'; import { withAuth } from '../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET() { export const GET = withAuth(async (_req, session) => {
const authHeaders = await getAuthHeaders();
const res = await fetch(`${API_BASE}/api/users/me`, { const res = await fetch(`${API_BASE}/api/users/me`, {
headers: { ...authHeaders }, headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store', cache: 'no-store',
}); });
const text = await res.text(); const text = await res.text();
return new NextResponse(text, { return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
status: res.status, });
headers: { 'Content-Type': 'application/json' },
});
}
export async function PATCH(request: NextRequest) { export const PATCH = withAuth(async (request: NextRequest, session) => {
const authHeaders = await getAuthHeaders();
const body = await request.json(); const body = await request.json();
const res = await fetch(`${API_BASE}/api/users/me`, { const res = await fetch(`${API_BASE}/api/users/me`, {
method: 'PATCH', method: 'PATCH',
headers: { 'Content-Type': 'application/json', ...authHeaders }, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify(body), body: JSON.stringify(body),
}); });
const text = await res.text(); const text = await res.text();
return new NextResponse(text, { return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
status: res.status, });
headers: { 'Content-Type': 'application/json' },
});
}
+9 -14
View File
@@ -1,35 +1,30 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../lib/auth-headers'; import { withAuth } from '../../../lib/with-auth';
export async function POST(request: NextRequest) { export const POST = withAuth(async (request, session) => {
try { try {
const contentType = request.headers.get('content-type') ?? ''; const contentType = request.headers.get('content-type') ?? '';
const isMultipart = contentType.includes('multipart/form-data'); const isMultipart = contentType.includes('multipart/form-data');
const backendUrl = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; const backendUrl = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
const authHeaders = await getAuthHeaders();
const response = await fetch(`${backendUrl}/api/quick-import`, { const response = await fetch(`${backendUrl}/api/quick-import`, {
method: 'POST', method: 'POST',
body: isMultipart body: isMultipart
? await request.formData() ? await request.formData()
: JSON.stringify(await request.json()), : JSON.stringify(await request.json()),
headers: isMultipart ? { ...authHeaders } : { 'Content-Type': 'application/json', ...authHeaders }, headers: isMultipart
? { Authorization: `Bearer ${session.accessToken}` }
: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store', cache: 'no-store',
}); });
const text = await response.text(); const text = await response.text();
return new NextResponse(text, { return new NextResponse(text, {
status: response.status, status: response.status,
headers: { headers: { 'Content-Type': response.headers.get('content-type') ?? 'application/json' },
'Content-Type': response.headers.get('content-type') ?? 'application/json',
},
}); });
} catch (error) { } catch (error) {
console.error('[QuickImportProxy] EXCEPTION:', error); console.error('[QuickImportProxy] EXCEPTION:', error);
return NextResponse.json( return NextResponse.json({ message: 'Kunde inte nå importtjänsten.' }, { status: 503 });
{ message: 'Kunde inte nå importtjänsten.' },
{ status: 503 },
);
} }
} });
+14 -23
View File
@@ -1,43 +1,34 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../lib/auth-headers'; import { withAuth } from '../../../lib/with-auth';
const API_BASE = const API_BASE =
process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET() { export const GET = withAuth(async (_request, session) => {
const authHeaders = await getAuthHeaders();
const res = await fetch(`${API_BASE}/api/receipt-aliases`, { const res = await fetch(`${API_BASE}/api/receipt-aliases`, {
headers: { ...authHeaders }, headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store', cache: 'no-store',
}); });
const text = await res.text(); const text = await res.text();
return new NextResponse(text, { return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
status: res.status, });
headers: { 'Content-Type': 'application/json' },
});
}
export async function POST(request: NextRequest) { export const POST = withAuth(async (request, session) => {
const authHeaders = await getAuthHeaders();
const body = await request.json(); const body = await request.json();
const res = await fetch(`${API_BASE}/api/receipt-aliases`, { const res = await fetch(`${API_BASE}/api/receipt-aliases`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders }, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify(body), body: JSON.stringify(body),
}); });
const text = await res.text(); const text = await res.text();
return new NextResponse(text, { return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
status: res.status, });
headers: { 'Content-Type': 'application/json' },
});
}
export async function DELETE(request: NextRequest) { export const DELETE = withAuth(async (request, session) => {
const authHeaders = await getAuthHeaders(); const id = new URL(request.url).searchParams.get('id');
const id = request.nextUrl.searchParams.get('id');
const res = await fetch(`${API_BASE}/api/receipt-aliases/${id}`, { const res = await fetch(`${API_BASE}/api/receipt-aliases/${id}`, {
method: 'DELETE', method: 'DELETE',
headers: { ...authHeaders }, headers: { Authorization: `Bearer ${session.accessToken}` },
}); });
return new NextResponse(null, { status: res.status }); return new NextResponse(null, { status: res.status });
} });
+6 -10
View File
@@ -1,22 +1,18 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../lib/auth-headers'; import { withAuth } from '../../../lib/with-auth';
const API_BASE = const API_BASE =
process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function POST(request: NextRequest) { export const POST = withAuth(async (request, session) => {
const authHeaders = await getAuthHeaders();
const formData = await request.formData(); const formData = await request.formData();
const res = await fetch(`${API_BASE}/api/receipt-import`, { const res = await fetch(`${API_BASE}/api/receipt-import`, {
method: 'POST', method: 'POST',
headers: { ...authHeaders }, headers: { Authorization: `Bearer ${session.accessToken}` },
body: formData, body: formData,
}); });
const text = await res.text(); const text = await res.text();
return new NextResponse(text, { return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
status: res.status, });
headers: { 'Content-Type': 'application/json' },
});
}
+8 -18
View File
@@ -1,31 +1,21 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../lib/auth-headers'; import { withAuth } from '../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET(request: NextRequest) { export const GET = withAuth(async (request, session) => {
const authHeaders = await getAuthHeaders(); const id = new URL(request.url).searchParams.get('id');
const id = request.nextUrl.searchParams.get('id');
if (!id) { if (!id) {
return NextResponse.json( return NextResponse.json({ error: 'Missing id parameter' }, { status: 400 });
{ error: 'Missing id parameter' },
{ status: 400 }
);
} }
const res = await fetch(`${API_BASE}/api/recipes/${id}/inventory-preview`, { const res = await fetch(`${API_BASE}/api/recipes/${id}/inventory-preview`, {
method: 'GET', headers: { Authorization: `Bearer ${session.accessToken}` },
headers: { ...authHeaders },
cache: 'no-store', cache: 'no-store',
}); });
const text = await res.text(); const text = await res.text();
return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
return new NextResponse(text, { });
status: res.status,
headers: {
'Content-Type': 'application/json',
},
});
} }
+7 -18
View File
@@ -1,27 +1,16 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../../../lib/auth-headers'; import { withAuth } from '../../../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function POST( export const POST = withAuth(async (request, session, context) => {
request: NextRequest, const { id } = await context.params;
{ params }: { params: Promise<{ id: string }> },
) {
const { id } = await params;
const authHeaders = await getAuthHeaders();
const body = await request.text(); const body = await request.text();
const res = await fetch(`${API_BASE}/api/recipes/${id}/image`, { const res = await fetch(`${API_BASE}/api/recipes/${id}/image`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders }, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body, body,
cache: 'no-store',
}); });
const text = await res.text(); const text = await res.text();
return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
return new NextResponse(text, { });
status: res.status,
headers: { 'Content-Type': 'application/json' },
});
}
+16 -36
View File
@@ -1,55 +1,35 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../../lib/auth-headers'; import { withAuth } from '../../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET( export const GET = withAuth(async (request, session, context) => {
_request: NextRequest, const { id } = await context.params;
{ params }: { params: Promise<{ id: string }> },
) {
const { id } = await params;
const authHeaders = await getAuthHeaders();
const res = await fetch(`${API_BASE}/api/recipes/${id}`, { const res = await fetch(`${API_BASE}/api/recipes/${id}`, {
headers: { ...authHeaders }, headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store', cache: 'no-store',
}); });
const text = await res.text(); const text = await res.text();
return new NextResponse(text, { return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
status: res.status, });
headers: { 'Content-Type': 'application/json' },
});
}
export async function PATCH( export const PATCH = withAuth(async (request, session, context) => {
request: NextRequest, const { id } = await context.params;
{ params }: { params: Promise<{ id: string }> },
) {
const { id } = await params;
const authHeaders = await getAuthHeaders();
const body = await request.json(); const body = await request.json();
const res = await fetch(`${API_BASE}/api/recipes/${id}`, { const res = await fetch(`${API_BASE}/api/recipes/${id}`, {
method: 'PATCH', method: 'PATCH',
headers: { 'Content-Type': 'application/json', ...authHeaders }, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify(body), body: JSON.stringify(body),
cache: 'no-store',
}); });
const text = await res.text(); const text = await res.text();
return new NextResponse(text, { return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
status: res.status, });
headers: { 'Content-Type': 'application/json' },
});
}
export async function DELETE( export const DELETE = withAuth(async (_request, session, context) => {
_request: NextRequest, const { id } = await context.params;
{ params }: { params: Promise<{ id: string }> },
) {
const { id } = await params;
const authHeaders = await getAuthHeaders();
const res = await fetch(`${API_BASE}/api/recipes/${id}`, { const res = await fetch(`${API_BASE}/api/recipes/${id}`, {
method: 'DELETE', method: 'DELETE',
headers: { ...authHeaders }, headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store',
}); });
return new NextResponse(null, { status: res.status }); return new NextResponse(null, { status: res.status });
} });
+8 -12
View File
@@ -1,31 +1,27 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../lib/auth-headers'; import { withAuth } from '../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET() { export const GET = withAuth(async (_request, session) => {
const authHeaders = await getAuthHeaders();
const res = await fetch(`${API_BASE}/api/recipes`, { const res = await fetch(`${API_BASE}/api/recipes`, {
headers: { ...authHeaders }, headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store', cache: 'no-store',
}); });
const data = await res.json(); const data = await res.json();
return NextResponse.json(data, { status: res.status }); return NextResponse.json(data, { status: res.status });
} });
export async function POST(request: NextRequest) { export const POST = withAuth(async (request, session) => {
const authHeaders = await getAuthHeaders();
const body = await request.json(); const body = await request.json();
const res = await fetch(`${API_BASE}/api/recipes`, { const res = await fetch(`${API_BASE}/api/recipes`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders }, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify(body), body: JSON.stringify(body),
cache: 'no-store',
}); });
const text = await res.text(); const text = await res.text();
return new NextResponse(text, { return new NextResponse(text, {
status: res.status, status: res.status,
headers: { 'Content-Type': res.headers.get('content-type') ?? 'application/json' }, headers: { 'Content-Type': res.headers.get('content-type') ?? 'application/json' },
}); });
} });
@@ -1,17 +1,13 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../../lib/auth-headers'; import { withAuth } from '../../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function DELETE( export const DELETE = withAuth(async (_request, session, context) => {
_request: NextRequest, const { productId } = await context.params;
{ params }: { params: Promise<{ productId: string }> },
) {
const { productId } = await params;
const authHeaders = await getAuthHeaders();
const res = await fetch(`${API_BASE}/api/user-products/${productId}`, { const res = await fetch(`${API_BASE}/api/user-products/${productId}`, {
method: 'DELETE', method: 'DELETE',
headers: { ...authHeaders }, headers: { Authorization: `Bearer ${session.accessToken}` },
}); });
return new NextResponse(null, { status: res.status }); return new NextResponse(null, { status: res.status });
} });
+10 -18
View File
@@ -1,32 +1,24 @@
import { NextRequest, NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../lib/auth-headers'; import { withAuth } from '../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET() { export const GET = withAuth(async (_request, session) => {
const authHeaders = await getAuthHeaders();
const res = await fetch(`${API_BASE}/api/user-products`, { const res = await fetch(`${API_BASE}/api/user-products`, {
headers: { ...authHeaders }, headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store', cache: 'no-store',
}); });
const text = await res.text(); const text = await res.text();
return new NextResponse(text, { return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
status: res.status, });
headers: { 'Content-Type': 'application/json' },
});
}
export async function POST(request: NextRequest) { export const POST = withAuth(async (request, session) => {
const authHeaders = await getAuthHeaders();
const body = await request.json(); const body = await request.json();
const res = await fetch(`${API_BASE}/api/user-products`, { const res = await fetch(`${API_BASE}/api/user-products`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders }, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify(body), body: JSON.stringify(body),
}); });
const text = await res.text(); const text = await res.text();
return new NextResponse(text, { return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
status: res.status, });
headers: { 'Content-Type': 'application/json' },
});
}
+35
View File
@@ -0,0 +1,35 @@
/**
* Hjälpfunktion för att wrappa Next.js Route Handlers med NextAuth auth().
* Löser problemet med att auth() standalone inte fungerar i route handlers
* med Next.js 15+/16 (async cookies-kompatibilitet i NextAuth beta).
*
* request.auth = session-objektet (inkl. accessToken)
*/
import { NextResponse } from 'next/server';
import { auth } from '../auth';
export type AuthedRequest = Request & { auth: { accessToken?: string; user?: any } | null };
/**
* Returnerar Authorization-headern från en autentiserad request.
* Kastar 401-svar om sessionen saknar accessToken.
*/
export function getBearer(session: AuthedRequest['auth']): string | null {
if (!session?.accessToken) return null;
return `Bearer ${session.accessToken}`;
}
/**
* Wrapper: export const GET = withAuth(async (req, session, context) => { ... })
*/
export function withAuth(
handler: (req: Request, session: NonNullable<AuthedRequest['auth']>, context: any) => Promise<Response>,
) {
return auth(async function (request: any, context: any) {
const session = request.auth;
if (!session?.accessToken) {
return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
}
return handler(request, session, context);
});
}