Nicholai 43b336acf9 feat: Phase 1 - Artist profile database refactor with API foundation
Implements backend infrastructure for loading artist profiles from Cloudflare D1 database instead of static data.

Database Changes:
- Add slug column migration for SEO-friendly URLs (0001_add_artist_slug.sql)
- Enhanced data migration script with slug generation
- Support for all artist fields from data/artists.ts

Type Definitions:
- Add slug field to Artist interface
- Create ArtistWithPortfolio type for full artist data
- Create PublicArtist type for sanitized API responses
- Add ArtistFilters type for query parameters
- Add ArtistDashboardStats for analytics

Database Functions (lib/db.ts):
- getPublicArtists() - fetch active artists with portfolio and filtering
- getArtistWithPortfolio() - fetch single artist with full portfolio
- getArtistBySlug() - fetch by URL-friendly slug
- getArtistByUserId() - fetch by user ID for dashboard
- Enhanced getArtists() with JSON parsing

API Endpoints:
- Updated GET /api/artists - filtering, pagination, portfolio images
- Created GET /api/artists/[id] - fetch by ID or slug
- Created PUT /api/artists/[id] - update with authorization
- Created DELETE /api/artists/[id] - soft delete (admin only)
- Created GET /api/artists/me - current artist profile

React Hooks (hooks/use-artist-data.ts):
- useArtists() - fetch with filtering
- useArtist() - fetch single artist
- useCurrentArtist() - logged-in artist
- useUpdateArtist(), useCreateArtist(), useDeleteArtist() - mutations

Frontend Components:
- Refactored artists-grid.tsx to use API with loading/error states
- Use database field names (slug, specialties, portfolioImages)
- Display profile images from portfolio
- Client-side filtering by specialty

Files Modified:
- sql/migrations/0001_add_artist_slug.sql (new)
- types/database.ts (enhanced)
- lib/data-migration.ts (enhanced)
- lib/db.ts (enhanced)
- app/api/artists/route.ts (updated)
- app/api/artists/[id]/route.ts (new)
- app/api/artists/me/route.ts (new)
- hooks/use-artist-data.ts (new)
- components/artists-grid.tsx (refactored)

Remaining work: Artist portfolio page, artist dashboard, admin enhancements

Ref: artist_profile_refactor_implementation_plan.md
2025-10-06 03:53:28 -06:00

144 lines
3.9 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server"
import { requireAuth } from "@/lib/auth"
import { UserRole } from "@/types/database"
import { updateArtistSchema } from "@/lib/validations"
import { getArtistWithPortfolio, getArtistBySlug, updateArtist, deleteArtist } from "@/lib/db"
export const dynamic = "force-dynamic";
// GET /api/artists/[id] - Fetch single artist with portfolio
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } },
context?: any
) {
try {
const { id } = params
// Try to fetch by ID first, then by slug
let artist = await getArtistWithPortfolio(id, context?.env)
if (!artist) {
artist = await getArtistBySlug(id, context?.env)
}
if (!artist) {
return NextResponse.json(
{ error: "Artist not found" },
{ status: 404 }
)
}
return NextResponse.json(artist)
} catch (error) {
console.error("Error fetching artist:", error)
return NextResponse.json(
{ error: "Failed to fetch artist" },
{ status: 500 }
)
}
}
// PUT /api/artists/[id] - Update artist (admin or artist themselves)
export async function PUT(
request: NextRequest,
{ params }: { params: { id: string } },
context?: any
) {
try {
const { id } = params
const session = await requireAuth()
// Get the artist to check ownership
const artist = await getArtistWithPortfolio(id, context?.env)
if (!artist) {
return NextResponse.json(
{ error: "Artist not found" },
{ status: 404 }
)
}
// Check authorization: must be the artist themselves or an admin
const isOwner = artist.userId === session.user.id
const isAdmin = [UserRole.SUPER_ADMIN, UserRole.SHOP_ADMIN].includes(session.user.role)
if (!isOwner && !isAdmin) {
return NextResponse.json(
{ error: "Insufficient permissions" },
{ status: 403 }
)
}
const body = await request.json()
const validatedData = updateArtistSchema.parse(body)
// If artist is updating themselves (not admin), restrict what they can change
let updateData = validatedData
if (isOwner && !isAdmin) {
// Artists can only update: bio, specialties, instagramHandle, hourlyRate
const { bio, specialties, instagramHandle, hourlyRate } = validatedData
updateData = { bio, specialties, instagramHandle, hourlyRate }
}
const updatedArtist = await updateArtist(id, updateData, context?.env)
return NextResponse.json(updatedArtist)
} catch (error) {
console.error("Error updating artist:", error)
if (error instanceof Error) {
if (error.message.includes("Authentication required")) {
return NextResponse.json(
{ error: "Authentication required" },
{ status: 401 }
)
}
}
return NextResponse.json(
{ error: "Failed to update artist" },
{ status: 500 }
)
}
}
// DELETE /api/artists/[id] - Soft delete artist (admin only)
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } },
context?: any
) {
try {
const { id } = params
// Require admin authentication
await requireAuth(UserRole.SHOP_ADMIN)
await deleteArtist(id, context?.env)
return NextResponse.json({ success: true })
} catch (error) {
console.error("Error deleting artist:", error)
if (error instanceof Error) {
if (error.message.includes("Authentication required")) {
return NextResponse.json(
{ error: "Authentication required" },
{ status: 401 }
)
}
if (error.message.includes("Insufficient permissions")) {
return NextResponse.json(
{ error: "Insufficient permissions" },
{ status: 403 }
)
}
}
return NextResponse.json(
{ error: "Failed to delete artist" },
{ status: 500 }
)
}
}