feat: add TypeScript definitions for next-auth session with accessToken and user details
Test Suite / test (24.15.0) (push) Has been cancelled
Test Suite / test (24.15.0) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { auth } from '../../../../../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 session = await auth();
|
||||
if (!session || (session.user as any)?.role !== 'admin') {
|
||||
return NextResponse.json({ message: 'Förbjuden' }, { status: 403 });
|
||||
}
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/users/${id}/reset-password`, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${session.accessToken}` },
|
||||
});
|
||||
const data = await res.json();
|
||||
return NextResponse.json(data, { status: res.status });
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { auth } from '../../../../auth';
|
||||
|
||||
const API_BASE =
|
||||
process.env.NEXT_PUBLIC_API_URL_INTERNAL ?? 'http://recipe-api:8080';
|
||||
|
||||
async function getAdminSession() {
|
||||
const session = await auth();
|
||||
if (!session || (session.user as any)?.role !== 'admin') return null;
|
||||
return session;
|
||||
}
|
||||
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
const { id } = await params;
|
||||
const session = await getAdminSession();
|
||||
if (!session) return NextResponse.json({ message: 'Förbjuden' }, { status: 403 });
|
||||
|
||||
const body = await request.json();
|
||||
|
||||
// Om body innehåller isPremium → anropa /premium-endpoint
|
||||
if ('isPremium' in body) {
|
||||
const res = await fetch(`${API_BASE}/api/users/${id}/premium`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${session.accessToken}`,
|
||||
},
|
||||
body: JSON.stringify({ isPremium: body.isPremium }),
|
||||
});
|
||||
const data = await res.json();
|
||||
return NextResponse.json(data, { status: res.status });
|
||||
}
|
||||
|
||||
// Annars → roll-byte
|
||||
const res = await fetch(`${API_BASE}/api/users/${id}/role`, {
|
||||
method: 'PATCH',
|
||||
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 });
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
_request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
const { id } = await params;
|
||||
const session = await getAdminSession();
|
||||
if (!session) return NextResponse.json({ message: 'Förbjuden' }, { status: 403 });
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/users/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: { Authorization: `Bearer ${session.accessToken}` },
|
||||
});
|
||||
const data = await res.json().catch(() => ({ deleted: true }));
|
||||
return NextResponse.json(data, { status: res.status });
|
||||
}
|
||||
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
// PUT används för e-postbyte (PATCH /api/users/:id/email)
|
||||
const { id } = await params;
|
||||
const session = await getAdminSession();
|
||||
if (!session) return NextResponse.json({ message: 'Förbjuden' }, { status: 403 });
|
||||
|
||||
const body = await request.json();
|
||||
const res = await fetch(`${API_BASE}/api/users/${id}/email`, {
|
||||
method: 'PATCH',
|
||||
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 });
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { auth } from '../../../auth';
|
||||
|
||||
const API_BASE =
|
||||
process.env.NEXT_PUBLIC_API_URL_INTERNAL ?? 'http://recipe-api:8080';
|
||||
|
||||
export async function GET() {
|
||||
const session = await auth();
|
||||
if (!session || (session.user as any)?.role !== 'admin') {
|
||||
return NextResponse.json({ message: 'Förbjuden' }, { status: 403 });
|
||||
}
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/users`, {
|
||||
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 session = await auth();
|
||||
if (!session || (session.user as any)?.role !== 'admin') {
|
||||
return NextResponse.json({ message: 'Förbjuden' }, { status: 403 });
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const res = await fetch(`${API_BASE}/api/users`, {
|
||||
method: 'POST',
|
||||
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 });
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { withAuth } from '../../../../lib/with-auth';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
|
||||
|
||||
export const POST = withAuth(async (req, session) => {
|
||||
try {
|
||||
const body = await req.json().catch(() => ({}));
|
||||
const { productIds } = body;
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/products/ai-categorize-bulk`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
|
||||
body: JSON.stringify({ productIds }),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
console.error('[api/admin/bulk-categorize] failed:', res.status, text);
|
||||
return Response.json({ error: `Bulk-AI-kategorisering misslyckades: ${text}` }, { status: res.status });
|
||||
}
|
||||
|
||||
return Response.json(await res.json());
|
||||
} catch (err) {
|
||||
console.error('[api/admin/bulk-categorize] error:', err);
|
||||
return Response.json(
|
||||
{ error: err instanceof Error ? err.message : 'Unknown error' },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { auth } from '../../../../auth';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const session = await auth();
|
||||
if (!session?.accessToken) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const body = await req.json();
|
||||
const { ids, categoryId } = body as { ids: number[]; categoryId: number | null };
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/products/bulk-update`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${session.accessToken}`,
|
||||
},
|
||||
body: JSON.stringify({ ids, categoryId }),
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
return NextResponse.json({ error: text || 'Bulk-uppdatering misslyckades' }, { status: res.status });
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { withAuth } from '../../../../lib/with-auth';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
|
||||
|
||||
export const POST = withAuth(async (req, session) => {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { name } = body;
|
||||
|
||||
if (!name || typeof name !== 'string') {
|
||||
return Response.json({ error: 'Name is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/products`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
|
||||
body: JSON.stringify({ name }),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const e = await res.json().catch(() => ({}));
|
||||
return Response.json({ error: e.message ?? `HTTP ${res.status}` }, { status: res.status });
|
||||
}
|
||||
|
||||
const product = await res.json();
|
||||
return Response.json({
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
canonicalName: product.canonicalName ?? null,
|
||||
});
|
||||
} catch (err) {
|
||||
return Response.json(
|
||||
{ error: err instanceof Error ? err.message : 'Unknown error' },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { withAuth } from '../../../../../lib/with-auth';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
|
||||
|
||||
export const POST = withAuth(async (_req, session, context) => {
|
||||
const { id } = await context.params;
|
||||
const productId = Number(id);
|
||||
if (!productId) return Response.json({ error: 'Ogiltigt id' }, { status: 400 });
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/products/${productId}/restore`, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${session.accessToken}` },
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
return Response.json(data, { status: res.status });
|
||||
});
|
||||
|
||||
export const DELETE = withAuth(async (_req, session, context) => {
|
||||
const { id } = await context.params;
|
||||
const productId = Number(id);
|
||||
if (!productId) return Response.json({ error: 'Ogiltigt id' }, { status: 400 });
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/products/${productId}/permanent`, {
|
||||
method: 'DELETE',
|
||||
headers: { Authorization: `Bearer ${session.accessToken}` },
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
return Response.json(data, { status: res.status });
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { withAuth } from '../../../../lib/with-auth';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
|
||||
|
||||
export const GET = withAuth(async (_req, session) => {
|
||||
const res = await fetch(`${API_BASE}/api/products/deleted`, {
|
||||
headers: { Authorization: `Bearer ${session.accessToken}` },
|
||||
});
|
||||
const data = await res.json().catch(() => ([]));
|
||||
return Response.json(data, { status: res.status });
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { auth } from '../../../../../../auth';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
|
||||
|
||||
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const session = await auth();
|
||||
if (!session?.accessToken) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { id } = await params;
|
||||
const body = await req.json();
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/inventory/${id}/consume`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${session.accessToken}`,
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
return NextResponse.json({ error: text || 'Kunde inte förbruka inventory-rad' }, { status: res.status });
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { auth } from '../../../../../auth';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
|
||||
|
||||
export async function PATCH(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const session = await auth();
|
||||
if (!session?.accessToken) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { id } = await params;
|
||||
const body = await req.json();
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/inventory/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${session.accessToken}`,
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
return NextResponse.json({ error: text || 'Kunde inte uppdatera inventory-rad' }, { status: res.status });
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
|
||||
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const session = await auth();
|
||||
if (!session?.accessToken) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { id } = await params;
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/inventory/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.accessToken}`,
|
||||
},
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
return NextResponse.json({ error: text || 'Kunde inte ta bort inventory-rad' }, { status: res.status });
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { auth } from '../../../../auth';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const session = await auth();
|
||||
if (!session?.accessToken) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const body = await req.json();
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/inventory`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${session.accessToken}`,
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
return NextResponse.json({ error: text || 'Kunde inte skapa inventory-rad' }, { status: res.status });
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
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 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}`,
|
||||
{
|
||||
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' } });
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { auth } from '../../../../auth';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const session = await auth();
|
||||
if (!session?.accessToken) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const body = await req.json();
|
||||
const { sourceProductId, targetProductId } = body as {
|
||||
sourceProductId: number;
|
||||
targetProductId: number;
|
||||
};
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/products/merge`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${session.accessToken}`,
|
||||
},
|
||||
body: JSON.stringify({ sourceProductId, targetProductId }),
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
return NextResponse.json({ error: text || 'Sammanslagning misslyckades' }, { status: res.status });
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { auth } from '../../../../../auth';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
|
||||
|
||||
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const session = await auth();
|
||||
if (!session?.accessToken) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { id } = await params;
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/pantry/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.accessToken}`,
|
||||
},
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
return NextResponse.json({ error: text || 'Kunde inte ta bort baslager-vara' }, { status: res.status });
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { auth } from '../../../../auth';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const session = await auth();
|
||||
if (!session?.accessToken) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const body = await req.json();
|
||||
const { productId } = body as { productId: number };
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/pantry`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${session.accessToken}`,
|
||||
},
|
||||
body: JSON.stringify({ productId }),
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
return NextResponse.json({ error: text || 'Kunde inte lägga till baslager-vara' }, { status: res.status });
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { auth } from '../../../../../auth';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
|
||||
|
||||
export async function PATCH(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const session = await auth();
|
||||
if (!session?.accessToken) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { id } = await params;
|
||||
const body = await req.json();
|
||||
const { status } = body as { status: 'active' | 'rejected' };
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/products/${id}/status`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${session.accessToken}`,
|
||||
},
|
||||
body: JSON.stringify({ status }),
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
return NextResponse.json({ error: text || 'Kunde inte uppdatera status' }, { status: res.status });
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { withAuth } from '../../../../../lib/with-auth';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
|
||||
|
||||
export const PATCH = withAuth(async (req, session, context) => {
|
||||
try {
|
||||
const { id } = await context.params;
|
||||
const productId = Number(id);
|
||||
if (!productId) return Response.json({ error: 'Invalid id' }, { status: 400 });
|
||||
|
||||
const body = await req.json();
|
||||
const { name, canonicalName, category, subcategory, brand, categoryId, tags } = body;
|
||||
|
||||
if (!name || typeof name !== 'string' || !name.trim()) {
|
||||
return Response.json({ error: 'Namn får inte vara tomt.' }, { status: 400 });
|
||||
}
|
||||
|
||||
const authHeader = `Bearer ${session.accessToken}`;
|
||||
|
||||
const patchRes = await fetch(`${API_BASE}/api/products/${productId}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json', Authorization: authHeader },
|
||||
body: JSON.stringify({
|
||||
name: name.trim(),
|
||||
canonicalName: canonicalName?.trim() || undefined,
|
||||
category: category?.trim() || null,
|
||||
subcategory: subcategory?.trim() || null,
|
||||
brand: brand?.trim() || null,
|
||||
categoryId: categoryId ?? null,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!patchRes.ok) {
|
||||
const text = await patchRes.text();
|
||||
console.error('[api/admin/product] PATCH failed:', patchRes.status, text);
|
||||
return Response.json({ error: `Kunde inte uppdatera produkt: ${text}` }, { status: patchRes.status });
|
||||
}
|
||||
|
||||
const tagsRes = await fetch(`${API_BASE}/api/products/${productId}/tags`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json', Authorization: authHeader },
|
||||
body: JSON.stringify({ tags: tags ?? [] }),
|
||||
});
|
||||
|
||||
if (!tagsRes.ok) {
|
||||
const text = await tagsRes.text();
|
||||
console.error('[api/admin/product] tags PUT failed:', tagsRes.status, text);
|
||||
return Response.json({ error: `Kunde inte uppdatera taggar: ${text}` }, { status: tagsRes.status });
|
||||
}
|
||||
|
||||
const fullRes = await fetch(`${API_BASE}/api/products/${productId}`, {
|
||||
headers: { Authorization: authHeader },
|
||||
});
|
||||
|
||||
if (!fullRes.ok) {
|
||||
return Response.json({ error: 'Produkt uppdaterad men kunde inte hämtas' }, { status: 500 });
|
||||
}
|
||||
|
||||
return Response.json(await fullRes.json());
|
||||
} catch (err) {
|
||||
console.error('[api/admin/product] PATCH error:', err);
|
||||
return Response.json(
|
||||
{ error: err instanceof Error ? err.message : 'Unknown error' },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export const DELETE = withAuth(async (_req, session, context) => {
|
||||
try {
|
||||
const { id } = await context.params;
|
||||
const productId = Number(id);
|
||||
if (!productId) return Response.json({ error: 'Invalid id' }, { status: 400 });
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/products/${productId}`, {
|
||||
method: 'DELETE',
|
||||
headers: { Authorization: `Bearer ${session.accessToken}` },
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
console.error('[api/admin/product] DELETE failed:', res.status, text);
|
||||
return Response.json({ error: `Kunde inte ta bort produkt: ${text}` }, { status: res.status });
|
||||
}
|
||||
|
||||
return new Response(null, { status: 204 });
|
||||
} catch (err) {
|
||||
console.error('[api/admin/product] DELETE error:', err);
|
||||
return Response.json(
|
||||
{ error: err instanceof Error ? err.message : 'Unknown error' },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { auth } from '../../../../auth';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
|
||||
|
||||
export async function POST() {
|
||||
const session = await auth();
|
||||
if (!session?.accessToken) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/products/reset-all`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${session.accessToken}`,
|
||||
},
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
return NextResponse.json({ error: text || 'Återställning misslyckades' }, { status: res.status });
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { withAuth } from '../../../../../lib/with-auth';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
|
||||
|
||||
export const GET = withAuth(async (_req, session, context) => {
|
||||
try {
|
||||
const { id } = await context.params;
|
||||
const productId = Number(id);
|
||||
if (!productId) return Response.json({ error: 'Invalid id' }, { status: 400 });
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/products/${productId}/suggest-category`, {
|
||||
headers: { Authorization: `Bearer ${session.accessToken}` },
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
console.error('[api/admin/suggest-category] failed:', res.status, text);
|
||||
return Response.json({ error: `AI-kategorisering misslyckades: ${text}` }, { status: res.status });
|
||||
}
|
||||
|
||||
return Response.json(await res.json());
|
||||
} catch (err) {
|
||||
console.error('[api/admin/suggest-category] error:', err);
|
||||
return Response.json(
|
||||
{ error: err instanceof Error ? err.message : 'Unknown error' },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { withAuth } from '../../../../../lib/with-auth';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
|
||||
|
||||
export const PATCH = withAuth(async (req, session, context) => {
|
||||
try {
|
||||
const { id } = await context.params;
|
||||
const productId = parseInt(id, 10);
|
||||
const body = await req.json();
|
||||
const { categoryId } = body;
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/products/${productId}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
|
||||
body: JSON.stringify({ categoryId }),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const e = await res.json().catch(() => ({}));
|
||||
return Response.json({ error: e.message ?? `HTTP ${res.status}` }, { status: res.status });
|
||||
}
|
||||
|
||||
const product = await res.json();
|
||||
return Response.json({
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
canonicalName: product.canonicalName ?? null,
|
||||
categoryId: product.categoryId ?? null,
|
||||
});
|
||||
} catch (err) {
|
||||
return Response.json(
|
||||
{ error: err instanceof Error ? err.message : 'Unknown error' },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { auth } from '../../../auth';
|
||||
|
||||
export async function GET() {
|
||||
const session = await auth();
|
||||
if ((session?.user as any)?.role !== 'admin') {
|
||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||
}
|
||||
|
||||
const key = process.env.MISTRAL_API_KEY ?? '';
|
||||
const keyHint = key.length >= 4 ? key.slice(-4) : '????';
|
||||
const hasKey = key.length > 0;
|
||||
|
||||
return NextResponse.json({ keyHint, hasKey });
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
|
||||
|
||||
export async function GET() {
|
||||
const res = await fetch(`${API_BASE}/api/ai/models`, { cache: 'no-store' });
|
||||
const text = await res.text();
|
||||
return new NextResponse(text, {
|
||||
status: res.status,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const body = await request.json();
|
||||
const res = await fetch(`${API_BASE}/api/auth/register`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const text = await res.text();
|
||||
return new NextResponse(text, {
|
||||
status: res.status,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { handlers } from '../../../../auth';
|
||||
|
||||
export const { GET, POST } = handlers;
|
||||
@@ -0,0 +1,14 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const isTree = req.nextUrl.searchParams.has('tree');
|
||||
const endpoint = isTree ? '/api/categories/tree' : '/api/categories';
|
||||
const res = await fetch(`${API_BASE}${endpoint}`, { cache: 'no-store' });
|
||||
const text = await res.text();
|
||||
return new NextResponse(text, {
|
||||
status: res.status,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
// turbopackIgnore: true — IMAGE_DIR är en runtime env-variabel, inte statisk sökväg
|
||||
const IMAGE_DIR: string = /* turbopackIgnore: true */ (process.env.IMAGE_DIR || '/app/public/images') as string;
|
||||
|
||||
export async function GET(
|
||||
_request: NextRequest,
|
||||
{ params }: { params: Promise<{ filename: string }> },
|
||||
) {
|
||||
const { filename } = await params;
|
||||
|
||||
// Förhindra path traversal
|
||||
if (!filename || filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
|
||||
return new NextResponse('Not found', { status: 404 });
|
||||
}
|
||||
|
||||
const filePath = path.join(IMAGE_DIR, filename);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return new NextResponse('Not found', { status: 404 });
|
||||
}
|
||||
|
||||
const file = fs.readFileSync(filePath);
|
||||
return new NextResponse(file, {
|
||||
headers: {
|
||||
'Content-Type': 'image/jpeg',
|
||||
'Cache-Control': 'public, max-age=31536000, immutable',
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
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 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`, {
|
||||
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' } });
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
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 const GET = withAuth(async (request, session) => {
|
||||
const { search } = new URL(request.url);
|
||||
const res = await fetch(`${API_BASE}/api/inventory${search}`, {
|
||||
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' } });
|
||||
});
|
||||
|
||||
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', 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' } });
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
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 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: { Authorization: `Bearer ${session.accessToken}` },
|
||||
cache: 'no-store',
|
||||
});
|
||||
const text = await res.text();
|
||||
return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
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 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: { Authorization: `Bearer ${session.accessToken}` },
|
||||
cache: 'no-store',
|
||||
});
|
||||
const text = await res.text();
|
||||
return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
|
||||
});
|
||||
|
||||
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', Authorization: `Bearer ${session.accessToken}` },
|
||||
body,
|
||||
});
|
||||
const text = await res.text();
|
||||
return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
|
||||
});
|
||||
|
||||
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: { Authorization: `Bearer ${session.accessToken}` },
|
||||
});
|
||||
return new NextResponse(null, { status: res.status });
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
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 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: { Authorization: `Bearer ${session.accessToken}` },
|
||||
cache: 'no-store',
|
||||
});
|
||||
const text = await res.text();
|
||||
return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
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 const GET = withAuth(async (_request, session) => {
|
||||
const res = await fetch(`${API_BASE}/api/pantry`, {
|
||||
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' } });
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
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 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', 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' } });
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { withAuth } from '../../../lib/with-auth';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
|
||||
|
||||
export const POST = withAuth(async (req, session) => {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { name } = body;
|
||||
|
||||
if (!name || typeof name !== 'string') {
|
||||
return Response.json({ error: 'Name is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/products`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
|
||||
body: JSON.stringify({ name }),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const e = await res.json().catch(() => ({}));
|
||||
return Response.json(
|
||||
{ error: e.message ?? `HTTP ${res.status}` },
|
||||
{ status: res.status },
|
||||
);
|
||||
}
|
||||
|
||||
const product = await res.json();
|
||||
return Response.json({
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
canonicalName: product.canonicalName ?? null,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('[products-create] Error:', err);
|
||||
return Response.json(
|
||||
{ error: err instanceof Error ? err.message : 'Unknown error' },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
import { withAuth } from '../../../../lib/with-auth';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080';
|
||||
|
||||
export const PATCH = withAuth(async (req, session, context) => {
|
||||
try {
|
||||
const { id } = await context.params;
|
||||
const productId = parseInt(id, 10);
|
||||
const body = await req.json();
|
||||
const { categoryId } = body;
|
||||
|
||||
if (!categoryId || typeof categoryId !== 'number') {
|
||||
return Response.json({ error: 'categoryId is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/products/${productId}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
|
||||
body: JSON.stringify({ categoryId }),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const e = await res.json().catch(() => ({}));
|
||||
return Response.json(
|
||||
{ error: e.message ?? `HTTP ${res.status}` },
|
||||
{ status: res.status },
|
||||
);
|
||||
}
|
||||
|
||||
const product = await res.json();
|
||||
return Response.json({
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
canonicalName: product.canonicalName ?? null,
|
||||
categoryId: product.categoryId ?? null,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('[products-update] Error:', err);
|
||||
return Response.json(
|
||||
{ error: err instanceof Error ? err.message : 'Unknown error' },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
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 const PATCH = withAuth(async (req, session, context) => {
|
||||
const { id } = await context.params;
|
||||
const body = await req.json();
|
||||
const res = await fetch(`${API_BASE}/api/products/${id}`, {
|
||||
method: 'PATCH',
|
||||
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 });
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
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 const POST = withAuth(async (req, session) => {
|
||||
const body = await req.json();
|
||||
const res = await fetch(`${API_BASE}/api/products/pending`, {
|
||||
method: 'POST',
|
||||
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 });
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { NextRequest, 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 const dynamic = 'force-dynamic';
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const url = new URL(req.url);
|
||||
const query = url.searchParams.toString();
|
||||
const res = await fetch(`${API_BASE}/api/products${query ? `?${query}` : ''}`, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
const data = await res.json();
|
||||
return NextResponse.json(data, { status: res.status });
|
||||
}
|
||||
|
||||
export const POST = withAuth(async (req, session) => {
|
||||
const body = await req.json();
|
||||
const res = await fetch(`${API_BASE}/api/products`, {
|
||||
method: 'POST',
|
||||
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 });
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
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 const GET = withAuth(async (_req, session) => {
|
||||
const res = await fetch(`${API_BASE}/api/users/me`, {
|
||||
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' } });
|
||||
});
|
||||
|
||||
export const PATCH = withAuth(async (request, session) => {
|
||||
const body = await request.json();
|
||||
const res = await fetch(`${API_BASE}/api/users/me`, {
|
||||
method: 'PATCH',
|
||||
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' } });
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { withAuth } from '../../../lib/with-auth';
|
||||
|
||||
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 response = await fetch(`${backendUrl}/api/quick-import`, {
|
||||
method: 'POST',
|
||||
body: isMultipart
|
||||
? await request.formData()
|
||||
: JSON.stringify(await request.json()),
|
||||
headers: isMultipart
|
||||
? { Authorization: `Bearer ${session.accessToken}` }
|
||||
: { 'Content-Type': 'application/json', Authorization: `Bearer ${session.accessToken}` },
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
const text = await response.text();
|
||||
try {
|
||||
const parsed = JSON.parse(text);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[QuickImportProxy] backend response', {
|
||||
status: response.status,
|
||||
hasMarkdown: Boolean(parsed?.markdown),
|
||||
imageUrl: parsed?.imageUrl ?? null,
|
||||
imageWarning: parsed?.imageWarning ?? null,
|
||||
});
|
||||
} catch {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[QuickImportProxy] backend non-json response', {
|
||||
status: response.status,
|
||||
contentType: response.headers.get('content-type'),
|
||||
});
|
||||
}
|
||||
return new NextResponse(text, {
|
||||
status: response.status,
|
||||
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 });
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
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 const GET = withAuth(async (_request, session) => {
|
||||
const res = await fetch(`${API_BASE}/api/receipt-aliases`, {
|
||||
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' } });
|
||||
});
|
||||
|
||||
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', 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' } });
|
||||
});
|
||||
|
||||
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: { Authorization: `Bearer ${session.accessToken}` },
|
||||
});
|
||||
return new NextResponse(null, { status: res.status });
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
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 const POST = withAuth(async (request, session) => {
|
||||
const formData = await request.formData();
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/receipt-import`, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${session.accessToken}` },
|
||||
body: formData,
|
||||
});
|
||||
|
||||
const text = await res.text();
|
||||
return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
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 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 });
|
||||
}
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/recipes/${id}/inventory-preview`, {
|
||||
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' } });
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
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 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', Authorization: `Bearer ${session.accessToken}` },
|
||||
body,
|
||||
});
|
||||
const text = await res.text();
|
||||
return new NextResponse(text, { status: res.status, headers: { 'Content-Type': 'application/json' } });
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
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 const GET = withAuth(async (request, session, context) => {
|
||||
const { id } = await context.params;
|
||||
const res = await fetch(`${API_BASE}/api/recipes/${id}`, {
|
||||
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' } });
|
||||
});
|
||||
|
||||
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', 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' } });
|
||||
});
|
||||
|
||||
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: { Authorization: `Bearer ${session.accessToken}` },
|
||||
});
|
||||
return new NextResponse(null, { status: res.status });
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
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 const GET = withAuth(async (_request, session) => {
|
||||
const res = await fetch(`${API_BASE}/api/recipes`, {
|
||||
headers: { Authorization: `Bearer ${session.accessToken}` },
|
||||
cache: 'no-store',
|
||||
});
|
||||
const data = await res.json();
|
||||
return NextResponse.json(data, { status: res.status });
|
||||
});
|
||||
|
||||
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', Authorization: `Bearer ${session.accessToken}` },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const text = await res.text();
|
||||
return new NextResponse(text, {
|
||||
status: res.status,
|
||||
headers: { 'Content-Type': res.headers.get('content-type') ?? 'application/json' },
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
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 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: { Authorization: `Bearer ${session.accessToken}` },
|
||||
});
|
||||
return new NextResponse(null, { status: res.status });
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
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 const GET = withAuth(async (_request, session) => {
|
||||
const res = await fetch(`${API_BASE}/api/user-products`, {
|
||||
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' } });
|
||||
});
|
||||
|
||||
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', 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' } });
|
||||
});
|
||||
Reference in New Issue
Block a user