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';
async function getAuthHeaders(): Promise<Record<string, string>> {
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) {
export const POST = withAuth(async (req, session) => {
try {
const body = await req.json().catch(() => ({}));
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`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders },
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify({ productIds }),
});
@@ -40,4 +27,4 @@ export async function POST(req: Request) {
{ 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';
async function getAuthHeaders(): Promise<Record<string, string>> {
const session = await auth();
if (!session?.accessToken) {
return {};
}
return { Authorization: `Bearer ${session.accessToken}` };
}
export async function POST(req: Request) {
export const POST = withAuth(async (req, session) => {
try {
const body = await req.json();
const { name } = body;
@@ -19,10 +11,9 @@ export async function POST(req: Request) {
return Response.json({ error: 'Name is required' }, { status: 400 });
}
const authHeaders = await getAuthHeaders();
const res = await fetch(`${API_BASE}/api/products`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders },
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify({ name }),
});
@@ -43,4 +34,4 @@ export async function POST(req: Request) {
{ status: 500 },
);
}
}
});
@@ -1,29 +1,22 @@
import { NextRequest, NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../../lib/auth-headers';
import { NextResponse } from 'next/server';
import { withAuth } from '../../../../lib/with-auth';
const API_BASE =
process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET(request: NextRequest) {
const authHeaders = await getAuthHeaders();
const sourceProductId = request.nextUrl.searchParams.get('sourceProductId');
const targetProductId = request.nextUrl.searchParams.get('targetProductId');
export const GET = withAuth(async (request, session) => {
const { searchParams } = new URL(request.url);
const sourceProductId = searchParams.get('sourceProductId');
const targetProductId = searchParams.get('targetProductId');
const res = await fetch(
`${API_BASE}/api/products/merge-preview?sourceProductId=${sourceProductId}&targetProductId=${targetProductId}`,
{
method: 'GET',
headers: { ...authHeaders },
headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store',
},
);
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';
async function getAuthHeaders(): Promise<Record<string, string>> {
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 }> },
) {
export const PATCH = withAuth(async (req, session, context) => {
try {
const { id } = await params;
const { id } = await context.params;
const productId = Number(id);
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 });
}
const authHeaders = await getAuthHeaders();
if (!authHeaders.Authorization) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const authHeader = `Bearer ${session.accessToken}`;
// 1. Update product fields
const patchRes = await fetch(`${API_BASE}/api/products/${productId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json', ...authHeaders },
headers: { 'Content-Type': 'application/json', Authorization: authHeader },
body: JSON.stringify({
name: name.trim(),
canonicalName: canonicalName?.trim() || undefined,
@@ -53,10 +36,9 @@ export async function PATCH(
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`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json', ...authHeaders },
headers: { 'Content-Type': 'application/json', Authorization: authHeader },
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 });
}
// 3. Return the complete updated product
const fullRes = await fetch(`${API_BASE}/api/products/${productId}`, {
headers: authHeaders,
headers: { Authorization: authHeader },
});
if (!fullRes.ok) {
return Response.json({ error: 'Produkt uppdaterad men kunde inte hämtas' }, { status: 500 });
}
const product = await fullRes.json();
return Response.json(product);
return Response.json(await fullRes.json());
} catch (err) {
console.error('[api/admin/product] PATCH error:', err);
return Response.json(
@@ -84,26 +64,17 @@ export async function PATCH(
{ status: 500 },
);
}
}
});
// DELETE /api/admin/product/[id]
export async function DELETE(
_req: Request,
{ params }: { params: Promise<{ id: string }> },
) {
export const DELETE = withAuth(async (_req, session, context) => {
try {
const { id } = await params;
const { id } = await context.params;
const productId = Number(id);
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}`, {
method: 'DELETE',
headers: authHeaders,
headers: { Authorization: `Bearer ${session.accessToken}` },
});
if (!res.ok) {
@@ -120,4 +91,5 @@ export async function DELETE(
{ 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';
async function getAuthHeaders(): Promise<Record<string, string>> {
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 }> },
) {
export const GET = withAuth(async (_req, session, context) => {
try {
const { id } = await params;
const { id } = await context.params;
const productId = Number(id);
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`, {
headers: authHeaders,
headers: { Authorization: `Bearer ${session.accessToken}` },
});
if (!res.ok) {
@@ -41,4 +26,4 @@ export async function GET(
{ 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';
async function getAuthHeaders(): Promise<Record<string, string>> {
const session = await auth();
if (!session?.accessToken) {
return {};
}
return { Authorization: `Bearer ${session.accessToken}` };
}
export async function PATCH(
req: Request,
{ params }: { params: Promise<{ id: string }> },
) {
export const PATCH = withAuth(async (req, session, context) => {
try {
const { id } = await params;
const { id } = await context.params;
const productId = parseInt(id, 10);
const body = await req.json();
const { categoryId } = body;
const authHeaders = await getAuthHeaders();
const res = await fetch(`${API_BASE}/api/products/${productId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json', ...authHeaders },
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify({ categoryId }),
});
@@ -45,4 +33,4 @@ export async function PATCH(
{ status: 500 },
);
}
}
});
@@ -1,25 +1,18 @@
import { NextRequest, NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../lib/auth-headers';
import { NextResponse } from 'next/server';
import { withAuth } from '../../../lib/with-auth';
const API_BASE =
process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET(request: NextRequest) {
const authHeaders = await getAuthHeaders();
const id = request.nextUrl.searchParams.get('id');
export const GET = withAuth(async (request, session) => {
const { searchParams } = new URL(request.url);
const id = searchParams.get('id');
const res = await fetch(`${API_BASE}/api/inventory/${id}/consumption-history`, {
method: 'GET',
headers: { ...authHeaders },
headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store',
});
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 { getAuthHeaders } from '../../../lib/auth-headers';
import { NextResponse } from 'next/server';
import { withAuth } from '../../../lib/with-auth';
const API_BASE =
process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET(request: NextRequest) {
const authHeaders = await getAuthHeaders();
const search = request.nextUrl.search;
export const GET = withAuth(async (request, session) => {
const { search } = new URL(request.url);
const res = await fetch(`${API_BASE}/api/inventory${search}`, {
headers: { ...authHeaders },
headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store',
});
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' } });
});
export async function POST(request: NextRequest) {
const authHeaders = await getAuthHeaders();
export const POST = withAuth(async (request, session) => {
const body = await request.json();
const res = await fetch(`${API_BASE}/api/inventory`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders },
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify(body),
});
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' } });
});
@@ -1,20 +1,16 @@
import { NextRequest, NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../../lib/auth-headers';
import { NextResponse } from 'next/server';
import { withAuth } from '../../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET(request: NextRequest) {
const authHeaders = await getAuthHeaders();
const { searchParams } = request.nextUrl;
export const GET = withAuth(async (request, session) => {
const { searchParams } = new URL(request.url);
const from = searchParams.get('from');
const to = searchParams.get('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',
});
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' } });
});
+15 -26
View File
@@ -1,46 +1,35 @@
import { NextRequest, NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../lib/auth-headers';
import { NextResponse } from 'next/server';
import { withAuth } from '../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET(request: NextRequest) {
const authHeaders = await getAuthHeaders();
const { searchParams } = request.nextUrl;
export const GET = withAuth(async (request, session) => {
const { searchParams } = new URL(request.url);
const query = searchParams.toString();
const res = await fetch(`${API_BASE}/api/meal-plan${query ? `?${query}` : ''}`, {
headers: { ...authHeaders },
headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store',
});
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' } });
});
export async function POST(request: NextRequest) {
const authHeaders = await getAuthHeaders();
export const POST = withAuth(async (request, session) => {
const body = await request.text();
const res = await fetch(`${API_BASE}/api/meal-plan`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders },
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body,
cache: 'no-store',
});
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' } });
});
export async function DELETE(request: NextRequest) {
const authHeaders = await getAuthHeaders();
const date = request.nextUrl.searchParams.get('date');
export const DELETE = withAuth(async (request, session) => {
const date = new URL(request.url).searchParams.get('date');
const res = await fetch(`${API_BASE}/api/meal-plan/${date}`, {
method: 'DELETE',
headers: { ...authHeaders },
cache: 'no-store',
headers: { Authorization: `Bearer ${session.accessToken}` },
});
return new NextResponse(null, { status: res.status });
}
});
@@ -1,20 +1,16 @@
import { NextRequest, NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../../lib/auth-headers';
import { NextResponse } from 'next/server';
import { withAuth } from '../../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET(request: NextRequest) {
const authHeaders = await getAuthHeaders();
const { searchParams } = request.nextUrl;
export const GET = withAuth(async (request, session) => {
const { searchParams } = new URL(request.url);
const from = searchParams.get('from');
const to = searchParams.get('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',
});
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' } });
});
+6 -11
View File
@@ -1,23 +1,18 @@
import { NextRequest, NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../lib/auth-headers';
import { NextResponse } from 'next/server';
import { withAuth } from '../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function POST(request: NextRequest) {
const authHeaders = await getAuthHeaders();
export const POST = withAuth(async (request, session) => {
const body = await request.text();
const res = await fetch(`${API_BASE}/api/recipes/parse-markdown`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders },
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body,
cache: 'no-store',
});
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';
async function getAuthHeaders(): Promise<Record<string, string>> {
const session = await auth();
if (!session?.accessToken) {
return {};
}
return { Authorization: `Bearer ${session.accessToken}` };
}
export async function POST(req: Request) {
export const POST = withAuth(async (req, session) => {
try {
const body = await req.json();
const { name } = body;
@@ -19,12 +11,9 @@ export async function POST(req: Request) {
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`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders },
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify({ name }),
});
@@ -37,8 +26,6 @@ export async function POST(req: Request) {
}
const product = await res.json();
// Return only serializable fields
return Response.json({
id: product.id,
name: product.name,
@@ -51,4 +38,4 @@ export async function POST(req: Request) {
{ 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';
async function getAuthHeaders(): Promise<Record<string, string>> {
const session = await auth();
if (!session?.accessToken) {
return {};
}
return { Authorization: `Bearer ${session.accessToken}` };
}
export async function PATCH(
req: Request,
{ params }: { params: Promise<{ id: string }> },
) {
export const PATCH = withAuth(async (req, session, context) => {
try {
const { id } = await params;
const { id } = await context.params;
const productId = parseInt(id, 10);
const body = await req.json();
const { categoryId } = body;
@@ -24,12 +13,9 @@ export async function PATCH(
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}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json', ...authHeaders },
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify({ categoryId }),
});
@@ -42,8 +28,6 @@ export async function PATCH(
}
const product = await res.json();
// Return only serializable fields
return Response.json({
id: product.id,
name: product.name,
@@ -57,4 +41,4 @@ export async function PATCH(
{ status: 500 },
);
}
}
});
+6 -7
View File
@@ -1,17 +1,16 @@
import { NextRequest, NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../../lib/auth-headers';
import { NextResponse } from 'next/server';
import { withAuth } from '../../../../lib/with-auth';
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 }> }) {
const { id } = await params;
export const PATCH = withAuth(async (req, session, context) => {
const { id } = await context.params;
const body = await req.json();
const authHeaders = await getAuthHeaders();
const res = await fetch(`${API_BASE}/api/products/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json', ...authHeaders },
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify(body),
});
const data = await res.json().catch(() => ({}));
return NextResponse.json(data, { status: res.status });
}
});
+5 -6
View File
@@ -1,16 +1,15 @@
import { NextRequest, NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../../lib/auth-headers';
import { NextResponse } from 'next/server';
import { withAuth } from '../../../../lib/with-auth';
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 authHeaders = await getAuthHeaders();
const res = await fetch(`${API_BASE}/api/products/pending`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders },
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify(body),
});
const data = await res.json();
return NextResponse.json(data, { status: res.status });
}
});
+4 -8
View File
@@ -1,5 +1,5 @@
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';
@@ -15,17 +15,13 @@ export async function GET(req: NextRequest) {
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 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`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders },
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify(body),
});
const data = await res.json();
return NextResponse.json(data, { status: res.status });
}
});
+9 -17
View File
@@ -1,32 +1,24 @@
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';
export async function GET() {
const authHeaders = await getAuthHeaders();
export const GET = withAuth(async (_req, session) => {
const res = await fetch(`${API_BASE}/api/users/me`, {
headers: { ...authHeaders },
headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store',
});
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' } });
});
export async function PATCH(request: NextRequest) {
const authHeaders = await getAuthHeaders();
export const PATCH = withAuth(async (request: NextRequest, session) => {
const body = await request.json();
const res = await fetch(`${API_BASE}/api/users/me`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json', ...authHeaders },
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify(body),
});
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' } });
});
+9 -14
View File
@@ -1,35 +1,30 @@
import { NextRequest, NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../lib/auth-headers';
import { NextResponse } from 'next/server';
import { withAuth } from '../../../lib/with-auth';
export async function POST(request: NextRequest) {
export const POST = withAuth(async (request, session) => {
try {
const contentType = request.headers.get('content-type') ?? '';
const isMultipart = contentType.includes('multipart/form-data');
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`, {
method: 'POST',
body: isMultipart
? await request.formData()
: 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',
});
const text = await response.text();
return new NextResponse(text, {
status: response.status,
headers: {
'Content-Type': response.headers.get('content-type') ?? 'application/json',
},
headers: { 'Content-Type': response.headers.get('content-type') ?? 'application/json' },
});
} catch (error) {
console.error('[QuickImportProxy] EXCEPTION:', error);
return NextResponse.json(
{ message: 'Kunde inte nå importtjänsten.' },
{ status: 503 },
);
return NextResponse.json({ message: 'Kunde inte nå importtjänsten.' }, { status: 503 });
}
}
});
+14 -23
View File
@@ -1,43 +1,34 @@
import { NextRequest, NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../lib/auth-headers';
import { NextResponse } from 'next/server';
import { withAuth } from '../../../lib/with-auth';
const API_BASE =
process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET() {
const authHeaders = await getAuthHeaders();
export const GET = withAuth(async (_request, session) => {
const res = await fetch(`${API_BASE}/api/receipt-aliases`, {
headers: { ...authHeaders },
headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store',
});
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' } });
});
export async function POST(request: NextRequest) {
const authHeaders = await getAuthHeaders();
export const POST = withAuth(async (request, session) => {
const body = await request.json();
const res = await fetch(`${API_BASE}/api/receipt-aliases`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders },
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify(body),
});
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' } });
});
export async function DELETE(request: NextRequest) {
const authHeaders = await getAuthHeaders();
const id = request.nextUrl.searchParams.get('id');
export const DELETE = withAuth(async (request, session) => {
const id = new URL(request.url).searchParams.get('id');
const res = await fetch(`${API_BASE}/api/receipt-aliases/${id}`, {
method: 'DELETE',
headers: { ...authHeaders },
headers: { Authorization: `Bearer ${session.accessToken}` },
});
return new NextResponse(null, { status: res.status });
}
});
+6 -10
View File
@@ -1,22 +1,18 @@
import { NextRequest, NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../lib/auth-headers';
import { NextResponse } from 'next/server';
import { withAuth } from '../../../lib/with-auth';
const API_BASE =
process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function POST(request: NextRequest) {
const authHeaders = await getAuthHeaders();
export const POST = withAuth(async (request, session) => {
const formData = await request.formData();
const res = await fetch(`${API_BASE}/api/receipt-import`, {
method: 'POST',
headers: { ...authHeaders },
headers: { Authorization: `Bearer ${session.accessToken}` },
body: formData,
});
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' } });
});
+8 -18
View File
@@ -1,31 +1,21 @@
import { NextRequest, NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../lib/auth-headers';
import { NextResponse } from 'next/server';
import { withAuth } from '../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET(request: NextRequest) {
const authHeaders = await getAuthHeaders();
const id = request.nextUrl.searchParams.get('id');
export const GET = withAuth(async (request, session) => {
const id = new URL(request.url).searchParams.get('id');
if (!id) {
return NextResponse.json(
{ error: 'Missing id parameter' },
{ status: 400 }
);
return NextResponse.json({ error: 'Missing id parameter' }, { status: 400 });
}
const res = await fetch(`${API_BASE}/api/recipes/${id}/inventory-preview`, {
method: 'GET',
headers: { ...authHeaders },
headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store',
});
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 { getAuthHeaders } from '../../../../../lib/auth-headers';
import { NextResponse } from 'next/server';
import { withAuth } from '../../../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) {
const { id } = await params;
const authHeaders = await getAuthHeaders();
export const POST = withAuth(async (request, session, context) => {
const { id } = await context.params;
const body = await request.text();
const res = await fetch(`${API_BASE}/api/recipes/${id}/image`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders },
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body,
cache: 'no-store',
});
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 { getAuthHeaders } from '../../../../lib/auth-headers';
import { NextResponse } from 'next/server';
import { withAuth } from '../../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET(
_request: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) {
const { id } = await params;
const authHeaders = await getAuthHeaders();
export const GET = withAuth(async (request, session, context) => {
const { id } = await context.params;
const res = await fetch(`${API_BASE}/api/recipes/${id}`, {
headers: { ...authHeaders },
headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store',
});
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' } });
});
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) {
const { id } = await params;
const authHeaders = await getAuthHeaders();
export const PATCH = withAuth(async (request, session, context) => {
const { id } = await context.params;
const body = await request.json();
const res = await fetch(`${API_BASE}/api/recipes/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json', ...authHeaders },
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify(body),
cache: 'no-store',
});
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' } });
});
export async function DELETE(
_request: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) {
const { id } = await params;
const authHeaders = await getAuthHeaders();
export const DELETE = withAuth(async (_request, session, context) => {
const { id } = await context.params;
const res = await fetch(`${API_BASE}/api/recipes/${id}`, {
method: 'DELETE',
headers: { ...authHeaders },
cache: 'no-store',
headers: { Authorization: `Bearer ${session.accessToken}` },
});
return new NextResponse(null, { status: res.status });
}
});
+8 -12
View File
@@ -1,31 +1,27 @@
import { NextRequest, NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../lib/auth-headers';
import { NextResponse } from 'next/server';
import { withAuth } from '../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET() {
const authHeaders = await getAuthHeaders();
export const GET = withAuth(async (_request, session) => {
const res = await fetch(`${API_BASE}/api/recipes`, {
headers: { ...authHeaders },
headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store',
});
const data = await res.json();
return NextResponse.json(data, { status: res.status });
}
});
export async function POST(request: NextRequest) {
const authHeaders = await getAuthHeaders();
export const POST = withAuth(async (request, session) => {
const body = await request.json();
const res = await fetch(`${API_BASE}/api/recipes`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders },
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify(body),
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' },
});
}
});
@@ -1,17 +1,13 @@
import { NextRequest, NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../../lib/auth-headers';
import { NextResponse } from 'next/server';
import { withAuth } from '../../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function DELETE(
_request: NextRequest,
{ params }: { params: Promise<{ productId: string }> },
) {
const { productId } = await params;
const authHeaders = await getAuthHeaders();
export const DELETE = withAuth(async (_request, session, context) => {
const { productId } = await context.params;
const res = await fetch(`${API_BASE}/api/user-products/${productId}`, {
method: 'DELETE',
headers: { ...authHeaders },
headers: { Authorization: `Bearer ${session.accessToken}` },
});
return new NextResponse(null, { status: res.status });
}
});
+10 -18
View File
@@ -1,32 +1,24 @@
import { NextRequest, NextResponse } from 'next/server';
import { getAuthHeaders } from '../../../lib/auth-headers';
import { NextResponse } from 'next/server';
import { withAuth } from '../../../lib/with-auth';
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
export async function GET() {
const authHeaders = await getAuthHeaders();
export const GET = withAuth(async (_request, session) => {
const res = await fetch(`${API_BASE}/api/user-products`, {
headers: { ...authHeaders },
headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store',
});
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' } });
});
export async function POST(request: NextRequest) {
const authHeaders = await getAuthHeaders();
export const POST = withAuth(async (request, session) => {
const body = await request.json();
const res = await fetch(`${API_BASE}/api/user-products`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders },
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
body: JSON.stringify(body),
});
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' } });
});
+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);
});
}