feat(api): tokens on all routes; items expose claims/claimedQuantity/remainingQuantity
This commit is contained in:
@@ -1,12 +1,19 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
import { db, wishlists } from '@/lib/db';
|
import { db, wishlists } from '@/lib/db';
|
||||||
|
import { getGuestFromRequest, verifyAdminToken } from '@/lib/auth/tokens';
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
{ params }: { params: Promise<{ slug: string }> }
|
{ params }: { params: Promise<{ slug: string }> }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
|
const isAdmin = verifyAdminToken(request);
|
||||||
|
const guest = await getGuestFromRequest(request);
|
||||||
|
if (!isAdmin && !guest) {
|
||||||
|
return NextResponse.json({ error: 'Convite necessário' }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
|
|
||||||
const wishlist = await db
|
const wishlist = await db
|
||||||
@@ -22,8 +29,8 @@ export async function GET(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only return public wishlists
|
// Only return public wishlists (admin can see all)
|
||||||
if (!wishlist[0].isPublic) {
|
if (!wishlist[0].isPublic && !isAdmin) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Wishlist not found' },
|
{ error: 'Wishlist not found' },
|
||||||
{ status: 404 }
|
{ status: 404 }
|
||||||
|
|||||||
@@ -1,28 +1,15 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
import { db, wishlistItems } from '@/lib/db';
|
import { db, wishlistItems } from '@/lib/db';
|
||||||
import { verifyAccessToken } from '@/lib/auth/utils';
|
import { verifyAdminToken } from '@/lib/auth/tokens';
|
||||||
|
|
||||||
export async function POST(
|
export async function POST(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
{ params }: { params: Promise<{ id: string }> }
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const token = request.cookies.get('access_token')?.value;
|
if (!verifyAdminToken(request)) {
|
||||||
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
if (!token) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Not authenticated' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = verifyAccessToken(token);
|
|
||||||
if (!payload) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Invalid or expired token' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
import { db, wishlistItems, wishlists } from '@/lib/db';
|
import { db, wishlistItems, wishlists } from '@/lib/db';
|
||||||
import { verifyAccessToken } from '@/lib/auth/utils';
|
import { verifyAdminToken, getGuestFromRequest } from '@/lib/auth/tokens';
|
||||||
|
import { attachClaimsToItems } from '@/lib/items-with-claims';
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
@@ -10,10 +11,11 @@ export async function GET(
|
|||||||
try {
|
try {
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
|
|
||||||
// Check for auth token
|
const isAdmin = verifyAdminToken(request);
|
||||||
const token = request.cookies.get('access_token')?.value;
|
const guest = await getGuestFromRequest(request);
|
||||||
const payload = token ? verifyAccessToken(token) : null;
|
if (!isAdmin && !guest) {
|
||||||
const isAuthenticated = payload !== null;
|
return NextResponse.json({ error: 'Convite necessário' }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
// Get item
|
// Get item
|
||||||
const item = await db
|
const item = await db
|
||||||
@@ -43,17 +45,18 @@ export async function GET(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check permissions
|
if (!wishlist[0].isPublic && !isAdmin) {
|
||||||
if (!wishlist[0].isPublic && !isAuthenticated) {
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'This item is private' },
|
{ error: 'This item is private' },
|
||||||
{ status: 403 }
|
{ status: 403 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [withClaims] = await attachClaimsToItems(item);
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
item: item[0],
|
item: withClaims,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching item:', error);
|
console.error('Error fetching item:', error);
|
||||||
@@ -69,21 +72,8 @@ export async function PATCH(
|
|||||||
{ params }: { params: Promise<{ id: string }> }
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const token = request.cookies.get('access_token')?.value;
|
if (!verifyAdminToken(request)) {
|
||||||
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
if (!token) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Not authenticated' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = verifyAccessToken(token);
|
|
||||||
if (!payload) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Invalid or expired token' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
@@ -114,7 +104,7 @@ export async function PATCH(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build update object (only include provided fields)
|
// Build update object (only include provided fields)
|
||||||
const updateData: any = {
|
const updateData: Record<string, unknown> = {
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -152,21 +142,8 @@ export async function DELETE(
|
|||||||
{ params }: { params: Promise<{ id: string }> }
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const token = request.cookies.get('access_token')?.value;
|
if (!verifyAdminToken(request)) {
|
||||||
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
if (!token) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Not authenticated' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = verifyAccessToken(token);
|
|
||||||
if (!payload) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Invalid or expired token' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { eq, asc } from 'drizzle-orm';
|
import { eq, asc } from 'drizzle-orm';
|
||||||
import { db, wishlists } from '@/lib/db';
|
import { db, wishlists } from '@/lib/db';
|
||||||
|
import { getGuestFromRequest, verifyAdminToken } from '@/lib/auth/tokens';
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
|
const isAdmin = verifyAdminToken(request);
|
||||||
|
const guest = await getGuestFromRequest(request);
|
||||||
|
if (!isAdmin && !guest) {
|
||||||
|
return NextResponse.json({ error: 'Convite necessário' }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch only public wishlists
|
// Fetch only public wishlists
|
||||||
const publicWishlists = await db
|
const publicWishlists = await db
|
||||||
.select()
|
.select()
|
||||||
|
|||||||
@@ -1,24 +1,11 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { verifyAccessToken } from '@/lib/auth/utils';
|
import { verifyAdminToken } from '@/lib/auth/tokens';
|
||||||
import { scrapeUrl } from '@/lib/scraping/service';
|
import { scrapeUrl } from '@/lib/scraping/service';
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const token = request.cookies.get('access_token')?.value;
|
if (!verifyAdminToken(request)) {
|
||||||
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
if (!token) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Not authenticated' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = verifyAccessToken(token);
|
|
||||||
if (!payload) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Invalid or expired token' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
import { db, settings } from '@/lib/db';
|
import { db, settings } from '@/lib/db';
|
||||||
import { verifyAccessToken } from '@/lib/auth/utils';
|
import { verifyAdminToken } from '@/lib/auth/tokens';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
|
|
||||||
// GET /api/settings - Get all settings (public endpoint for reading only)
|
// GET /api/settings - Get all settings (public endpoint for reading only)
|
||||||
@@ -42,21 +42,8 @@ export async function GET(request: NextRequest) {
|
|||||||
// PUT /api/settings - Update settings (admin only)
|
// PUT /api/settings - Update settings (admin only)
|
||||||
export async function PUT(request: NextRequest) {
|
export async function PUT(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const token = request.cookies.get('access_token')?.value;
|
if (!verifyAdminToken(request)) {
|
||||||
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
if (!token) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Not authenticated' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = verifyAccessToken(token);
|
|
||||||
if (!payload) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Invalid or expired token' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { eq, and, desc } from 'drizzle-orm';
|
import { eq, and, desc } from 'drizzle-orm';
|
||||||
import { db, wishlistItems, wishlists } from '@/lib/db';
|
import { db, wishlistItems, wishlists } from '@/lib/db';
|
||||||
import { verifyAccessToken } from '@/lib/auth/utils';
|
import { verifyAdminToken, getGuestFromRequest } from '@/lib/auth/tokens';
|
||||||
|
import { attachClaimsToItems } from '@/lib/items-with-claims';
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
@@ -10,10 +11,11 @@ export async function GET(
|
|||||||
try {
|
try {
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
|
|
||||||
// Check for auth token
|
const isAdmin = verifyAdminToken(request);
|
||||||
const token = request.cookies.get('access_token')?.value;
|
const guest = await getGuestFromRequest(request);
|
||||||
const payload = token ? verifyAccessToken(token) : null;
|
if (!isAdmin && !guest) {
|
||||||
const isAuthenticated = payload !== null;
|
return NextResponse.json({ error: 'Convite necessário' }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
// Check if wishlist exists
|
// Check if wishlist exists
|
||||||
const wishlist = await db
|
const wishlist = await db
|
||||||
@@ -29,20 +31,20 @@ export async function GET(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check permissions
|
// Permissions: guest can only see public wishlists; admin sees all
|
||||||
if (!wishlist[0].isPublic && !isAuthenticated) {
|
if (!wishlist[0].isPublic && !isAdmin) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'This wishlist is private' },
|
{ error: 'This wishlist is private' },
|
||||||
{ status: 403 }
|
{ status: 403 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all items (exclude archived unless authenticated)
|
// Get all items (exclude archived unless admin)
|
||||||
const items = await db
|
const raw = await db
|
||||||
.select()
|
.select()
|
||||||
.from(wishlistItems)
|
.from(wishlistItems)
|
||||||
.where(
|
.where(
|
||||||
isAuthenticated
|
isAdmin
|
||||||
? eq(wishlistItems.wishlistId, id)
|
? eq(wishlistItems.wishlistId, id)
|
||||||
: and(
|
: and(
|
||||||
eq(wishlistItems.wishlistId, id),
|
eq(wishlistItems.wishlistId, id),
|
||||||
@@ -51,12 +53,11 @@ export async function GET(
|
|||||||
)
|
)
|
||||||
.orderBy(wishlistItems.sortOrder);
|
.orderBy(wishlistItems.sortOrder);
|
||||||
|
|
||||||
// Return items
|
const items = await attachClaimsToItems(raw);
|
||||||
const responseItems = items;
|
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
items: responseItems,
|
items,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching items:', error);
|
console.error('Error fetching items:', error);
|
||||||
@@ -72,21 +73,8 @@ export async function POST(
|
|||||||
{ params }: { params: Promise<{ id: string }> }
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const token = request.cookies.get('access_token')?.value;
|
if (!verifyAdminToken(request)) {
|
||||||
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
if (!token) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Not authenticated' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = verifyAccessToken(token);
|
|
||||||
if (!payload) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Invalid or expired token' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
|
|||||||
@@ -1,28 +1,15 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
import { db, wishlists } from '@/lib/db';
|
import { db, wishlists } from '@/lib/db';
|
||||||
import { verifyAccessToken } from '@/lib/auth/utils';
|
import { verifyAdminToken } from '@/lib/auth/tokens';
|
||||||
|
|
||||||
export async function POST(
|
export async function POST(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
{ params }: { params: Promise<{ id: string }> }
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const token = request.cookies.get('access_token')?.value;
|
if (!verifyAdminToken(request)) {
|
||||||
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
if (!token) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Not authenticated' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = verifyAccessToken(token);
|
|
||||||
if (!payload) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Invalid or expired token' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
|
|||||||
@@ -1,28 +1,15 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
import { db, wishlists } from '@/lib/db';
|
import { db, wishlists } from '@/lib/db';
|
||||||
import { verifyAccessToken } from '@/lib/auth/utils';
|
import { verifyAdminToken } from '@/lib/auth/tokens';
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
{ params }: { params: Promise<{ id: string }> }
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const token = request.cookies.get('access_token')?.value;
|
if (!verifyAdminToken(request)) {
|
||||||
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
if (!token) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Not authenticated' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = verifyAccessToken(token);
|
|
||||||
if (!payload) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Invalid or expired token' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
@@ -58,21 +45,8 @@ export async function PATCH(
|
|||||||
{ params }: { params: Promise<{ id: string }> }
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const token = request.cookies.get('access_token')?.value;
|
if (!verifyAdminToken(request)) {
|
||||||
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
if (!token) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Not authenticated' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = verifyAccessToken(token);
|
|
||||||
if (!payload) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Invalid or expired token' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
@@ -145,21 +119,8 @@ export async function DELETE(
|
|||||||
{ params }: { params: Promise<{ id: string }> }
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const token = request.cookies.get('access_token')?.value;
|
if (!verifyAdminToken(request)) {
|
||||||
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
if (!token) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Not authenticated' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = verifyAccessToken(token);
|
|
||||||
if (!payload) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Invalid or expired token' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
|
|||||||
@@ -1,25 +1,12 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { eq, asc } from 'drizzle-orm';
|
import { eq, asc } from 'drizzle-orm';
|
||||||
import { db, wishlists } from '@/lib/db';
|
import { db, wishlists } from '@/lib/db';
|
||||||
import { verifyAccessToken } from '@/lib/auth/utils';
|
import { verifyAdminToken } from '@/lib/auth/tokens';
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const token = request.cookies.get('access_token')?.value;
|
if (!verifyAdminToken(request)) {
|
||||||
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
if (!token) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Not authenticated' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = verifyAccessToken(token);
|
|
||||||
if (!payload) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Invalid or expired token' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const allWishlists = await db
|
const allWishlists = await db
|
||||||
@@ -42,21 +29,8 @@ export async function GET(request: NextRequest) {
|
|||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const token = request.cookies.get('access_token')?.value;
|
if (!verifyAdminToken(request)) {
|
||||||
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||||
if (!token) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Not authenticated' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = verifyAccessToken(token);
|
|
||||||
if (!payload) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Invalid or expired token' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
|
|||||||
16
lib/api.ts
16
lib/api.ts
@@ -71,6 +71,15 @@ export interface Wishlist {
|
|||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ItemClaim {
|
||||||
|
id: string;
|
||||||
|
quantity: number;
|
||||||
|
note: string | null;
|
||||||
|
isPurchased: boolean;
|
||||||
|
claimedAt: string;
|
||||||
|
guest: { id: string; name: string };
|
||||||
|
}
|
||||||
|
|
||||||
export interface Item {
|
export interface Item {
|
||||||
id: string;
|
id: string;
|
||||||
wishlistId: string;
|
wishlistId: string;
|
||||||
@@ -82,10 +91,9 @@ export interface Item {
|
|||||||
imageUrl: string | null;
|
imageUrl: string | null;
|
||||||
purchaseUrls: Array<{ label: string; url: string }> | null;
|
purchaseUrls: Array<{ label: string; url: string }> | null;
|
||||||
isArchived: boolean;
|
isArchived: boolean;
|
||||||
claimedByName: string | null;
|
claims: ItemClaim[];
|
||||||
claimedByNote: string | null;
|
claimedQuantity: number;
|
||||||
claimedAt: string | null;
|
remainingQuantity: number;
|
||||||
isPurchased: boolean;
|
|
||||||
sortOrder: number;
|
sortOrder: number;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
|||||||
55
lib/items-with-claims.ts
Normal file
55
lib/items-with-claims.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { eq, inArray } from 'drizzle-orm';
|
||||||
|
import { db, wishlistItems, itemClaims, guests } from '@/lib/db';
|
||||||
|
|
||||||
|
type RawItem = typeof wishlistItems.$inferSelect;
|
||||||
|
|
||||||
|
export async function attachClaimsToItems(items: RawItem[]) {
|
||||||
|
if (items.length === 0) return [];
|
||||||
|
const itemIds = items.map((i) => i.id);
|
||||||
|
const rows = await db
|
||||||
|
.select({
|
||||||
|
id: itemClaims.id,
|
||||||
|
itemId: itemClaims.itemId,
|
||||||
|
quantity: itemClaims.quantity,
|
||||||
|
note: itemClaims.note,
|
||||||
|
isPurchased: itemClaims.isPurchased,
|
||||||
|
claimedAt: itemClaims.claimedAt,
|
||||||
|
guestId: guests.id,
|
||||||
|
guestName: guests.name,
|
||||||
|
})
|
||||||
|
.from(itemClaims)
|
||||||
|
.innerJoin(guests, eq(itemClaims.guestId, guests.id))
|
||||||
|
.where(inArray(itemClaims.itemId, itemIds));
|
||||||
|
|
||||||
|
const byItem = new Map<string, ReturnType<typeof shapeClaim>[]>();
|
||||||
|
for (const r of rows) {
|
||||||
|
const arr = byItem.get(r.itemId) ?? [];
|
||||||
|
arr.push(shapeClaim(r));
|
||||||
|
byItem.set(r.itemId, arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return items.map((it) => {
|
||||||
|
const claims = byItem.get(it.id) ?? [];
|
||||||
|
const claimedQuantity = claims.reduce((s, c) => s + c.quantity, 0);
|
||||||
|
return {
|
||||||
|
...it,
|
||||||
|
claims,
|
||||||
|
claimedQuantity,
|
||||||
|
remainingQuantity: Math.max(0, it.quantity - claimedQuantity),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function shapeClaim(r: {
|
||||||
|
id: string; quantity: number; note: string | null; isPurchased: boolean;
|
||||||
|
claimedAt: Date; guestId: string; guestName: string;
|
||||||
|
}) {
|
||||||
|
return {
|
||||||
|
id: r.id,
|
||||||
|
quantity: r.quantity,
|
||||||
|
note: r.note,
|
||||||
|
isPurchased: r.isPurchased,
|
||||||
|
claimedAt: r.claimedAt,
|
||||||
|
guest: { id: r.guestId, name: r.guestName },
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user