united-tattoo/types/database.ts
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

310 lines
6.1 KiB
TypeScript

// Cloudflare Types
declare global {
interface D1Database {
prepare(query: string): D1PreparedStatement;
exec(query: string): Promise<D1ExecResult>;
batch(statements: D1PreparedStatement[]): Promise<D1Result[]>;
dump(): Promise<ArrayBuffer>;
}
interface D1PreparedStatement {
bind(...values: any[]): D1PreparedStatement;
first<T = any>(): Promise<T | null>;
run(): Promise<D1Result>;
all<T = any>(): Promise<D1Result<T>>;
}
interface D1Result<T = any> {
results: T[];
success: boolean;
meta: {
duration: number;
size_after: number;
rows_read: number;
rows_written: number;
};
}
interface D1ExecResult {
count: number;
duration: number;
}
interface R2Bucket {
put(key: string, value: ReadableStream | ArrayBuffer | string, options?: R2PutOptions): Promise<R2Object | null>;
get(key: string, options?: R2GetOptions): Promise<R2Object | null>;
delete(keys: string | string[]): Promise<void>;
list(options?: R2ListOptions): Promise<R2Objects>;
}
interface R2Object {
key: string;
version: string;
size: number;
etag: string;
httpEtag: string;
uploaded: Date;
checksums: R2Checksums;
httpMetadata?: R2HTTPMetadata;
customMetadata?: Record<string, string>;
body?: ReadableStream;
bodyUsed?: boolean;
arrayBuffer(): Promise<ArrayBuffer>;
text(): Promise<string>;
json<T = any>(): Promise<T>;
blob(): Promise<Blob>;
}
interface R2PutOptions {
httpMetadata?: R2HTTPMetadata;
customMetadata?: Record<string, string>;
}
interface R2GetOptions {
onlyIf?: R2Conditional;
range?: R2Range;
}
interface R2ListOptions {
limit?: number;
prefix?: string;
cursor?: string;
delimiter?: string;
startAfter?: string;
include?: ('httpMetadata' | 'customMetadata')[];
}
interface R2Objects {
objects: R2Object[];
truncated: boolean;
cursor?: string;
delimitedPrefixes: string[];
}
interface R2HTTPMetadata {
contentType?: string;
contentLanguage?: string;
contentDisposition?: string;
contentEncoding?: string;
cacheControl?: string;
cacheExpiry?: Date;
}
interface R2Checksums {
md5?: ArrayBuffer;
sha1?: ArrayBuffer;
sha256?: ArrayBuffer;
sha384?: ArrayBuffer;
sha512?: ArrayBuffer;
}
interface R2Conditional {
etagMatches?: string;
etagDoesNotMatch?: string;
uploadedBefore?: Date;
uploadedAfter?: Date;
}
interface R2Range {
offset?: number;
length?: number;
suffix?: number;
}
}
// User Management Types
export interface User {
id: string
email: string
name: string
role: UserRole
avatar?: string
createdAt: Date
updatedAt: Date
}
export enum UserRole {
SUPER_ADMIN = 'SUPER_ADMIN',
SHOP_ADMIN = 'SHOP_ADMIN',
ARTIST = 'ARTIST',
CLIENT = 'CLIENT'
}
// Artist Management Types
export interface Artist {
id: string
userId: string
slug: string
name: string
bio: string
specialties: string[]
instagramHandle?: string
portfolioImages: PortfolioImage[]
isActive: boolean
hourlyRate?: number
availability: Availability[]
createdAt: Date
updatedAt: Date
}
export interface ArtistWithPortfolio extends Artist {
portfolioImages: PortfolioImage[]
user?: {
name: string
email: string
avatar?: string
}
}
export interface PublicArtist {
id: string
slug: string
name: string
bio: string
specialties: string[]
instagramHandle?: string
portfolioImages: PortfolioImage[]
isActive: boolean
hourlyRate?: number
}
export interface ArtistDashboardStats {
totalImages: number
activeImages: number
profileViews?: number
lastUpdated: Date
}
export interface ArtistFilters {
specialty?: string
search?: string
isActive?: boolean
limit?: number
offset?: number
}
export interface PortfolioImage {
id: string
artistId: string
url: string
caption?: string
tags: string[]
orderIndex: number
isPublic: boolean
createdAt: Date
}
// Calendar & Booking Types
export interface Appointment {
id: string
artistId: string
clientId: string
title: string
description?: string
startTime: Date
endTime: Date
status: AppointmentStatus
depositAmount?: number
totalAmount?: number
notes?: string
createdAt: Date
updatedAt: Date
}
export enum AppointmentStatus {
PENDING = 'PENDING',
CONFIRMED = 'CONFIRMED',
IN_PROGRESS = 'IN_PROGRESS',
COMPLETED = 'COMPLETED',
CANCELLED = 'CANCELLED'
}
export interface Availability {
id: string
artistId: string
dayOfWeek: number // 0-6 (Sunday-Saturday)
startTime: string // HH:mm format
endTime: string // HH:mm format
isActive: boolean
}
// Content Management Types
export interface SiteSettings {
id: string
studioName: string
description: string
address: string
phone: string
email: string
socialMedia: SocialMediaLinks
businessHours: BusinessHours[]
heroImage?: string
logoUrl?: string
updatedAt: Date
}
export interface SocialMediaLinks {
instagram?: string
facebook?: string
twitter?: string
tiktok?: string
}
export interface BusinessHours {
dayOfWeek: number
openTime: string
closeTime: string
isClosed: boolean
}
// File Upload Types
export interface FileUpload {
id: string
filename: string
originalName: string
mimeType: string
size: number
url: string
uploadedBy: string
createdAt: Date
}
// API Input Types
export interface CreateArtistInput {
name: string
bio: string
specialties: string[]
instagramHandle?: string
hourlyRate?: number
isActive?: boolean
userId?: string
email?: string
}
export interface UpdateArtistInput extends Partial<CreateArtistInput> {
id: string
}
export interface CreateAppointmentInput {
artistId: string
clientId: string
title: string
description?: string
startTime: Date
endTime: Date
status?: AppointmentStatus
depositAmount?: number
totalAmount?: number
notes?: string
}
export interface UpdateSiteSettingsInput extends Partial<Omit<SiteSettings, 'id' | 'updatedAt'>> {}
export interface AppointmentFilters {
artistId?: string
clientId?: string
status?: AppointmentStatus
startDate?: Date
endDate?: Date
}