Skip to main content

Architecture

Garmint is a Next.js 15 application with a layered architecture designed for AI-first workflows.

High-Level Overview

┌─────────────────────────────────────────────────────────────┐
│                        Frontend                              │
│  Next.js App Router + React + Tailwind + shadcn/ui          │
├─────────────────────────────────────────────────────────────┤
│                      API Layer                               │
│  /api/chat  /api/upload  /api/production  /api/stripe       │
├─────────────────────────────────────────────────────────────┤
│                    Service Layer                             │
│  lib/ai/     lib/billing/     lib/image/     lib/shopify    │
├─────────────────────────────────────────────────────────────┤
│                   External Services                          │
│  Supabase    Replicate/fal    Stripe    Shopify    CDN      │
└─────────────────────────────────────────────────────────────┘

Directory Structure

garmint/
├── app/                      # Next.js App Router
│   ├── page.tsx              # Main chat studio
│   ├── garments/             # Garment browser
│   ├── gallery/              # Saved generations
│   ├── collections/          # Design collections
│   ├── production/           # Order tracking
│   ├── projects/             # Workspace settings
│   ├── ops/                  # Admin dashboard
│   ├── api/                  # API routes
│   │   ├── chat/             # AI generation endpoint
│   │   ├── upload/           # File upload
│   │   ├── production/       # Order management
│   │   ├── stripe/           # Payment webhooks
│   │   └── auth/             # Supabase auth callback
│   ├── components/           # Shared UI components
│   └── providers.tsx         # State management
├── lib/                      # Core business logic
│   ├── ai/                   # AI generation (Replicate/fal)
│   ├── billing/              # Token & subscription logic
│   ├── image/                # Upload & transformation
│   ├── production/           # Order processing
│   ├── supabase/             # Database clients
│   └── shopify.ts            # Shopify API client
├── hooks/                    # React hooks
├── types/                    # TypeScript definitions
├── supabase/                 # Database schema
│   ├── migrations/           # SQL migrations
│   └── seed.sql              # Initial data
└── docs/                     # This documentation

Data Flow

Generation Flow

Production Order Flow

Database Schema

Core Tables

TablePurpose
profilesUser accounts (synced from Supabase Auth)
projectsWorkspaces with billing & settings
project_membersUser-project associations
subscriptionsStripe subscription state
token_grantsCredit additions (subscription, purchases)
usage_ledgersToken consumption records

Chat & Generation

TablePurpose
prompt_threadsChat conversation containers
prompt_messagesIndividual messages in threads
render_jobsAI generation job records
render_assetsGenerated images (mockups, graphics)

Production

TablePurpose
production_ordersProduction order headers
production_line_itemsSize/quantity breakdown
fulfillment_eventsStatus change history

Views

ViewPurpose
project_token_balancesComputed token balances per project

Authentication

// middleware.ts - Auth flow
1. Check DEV_AUTH_USERNAME/PASSWORD (optional basic auth for previews)
2. Verify Supabase session via cookies
3. Redirect unauthenticated users to /login
4. Allow public routes: /landing, /login, /api/auth/*
Auth callback at /api/auth/callback completes PKCE exchange and sets session cookies.

Token System

// lib/billing/tokens.ts

// Token costs per operation (user-facing)
const TOKEN_COSTS = {
  "nano-banana": 12,         // Fast generation
  "nano-banana-pro": 45,     // High quality
  "nano-banana-pro-4k": 90,  // Print-ready 4K
  "remove-bg": 3,            // Background removal
  "upscale": 1,              // Image upscaling
};

// Balance check before generation
const balance = await getProjectTokenBalance(projectId);
if (balance < tokensNeeded) {
  return { error: "Insufficient tokens", code: 402 };
}

// Debit after successful generation
await recordUsageLedger(projectId, tokensSpent, jobId);

Image Generation

Two-step process for production-ready output:
// lib/ai/mockup.ts - generateUnified()

// Step 1: Generate isolated design
const designPrompt = `Create [design] on white background, 
  centered, ready for print. Square format 1:1.`;
const graphic = await replicate.run(model, { prompt: designPrompt });

// Step 2: Composite onto garment
const mockupPrompt = `Place this design on [garment], 
  professional product photo, design within bounds.`;
const mockup = await replicate.run(model, {
  prompt: mockupPrompt,
  image: graphic,
  image_2: garmentUrl,
});

return { mockups: [mockup], graphicUrl: graphic };

State Management

Client-side state via React Context (providers.tsx):
// StudioStateProvider
- collection: Garment[]           // Liked garments
- savedGenerations: Generation[]  // Gallery items
- designCollections: Collection[] // Organized groups
- activeProjectId: string         // Current workspace
All persisted to localStorage with garmint:* keys.

Realtime Updates

Production orders use Supabase Realtime:
// hooks/useProductionRealtime.ts
const channel = supabase
  .channel(`order:${orderId}`)
  .on('postgres_changes', {
    event: 'UPDATE',
    schema: 'public',
    table: 'production_orders',
    filter: `id=eq.${orderId}`,
  }, handleUpdate)
  .subscribe();

Security Model

  • RLS (Row Level Security): All tables use project-scoped policies
  • Service Role: Only for server-side admin operations
  • Anon Key: Safe for browser, limited to user’s own data
  • Stripe Webhooks: Verified via signature
  • Admin Routes: /ops/* gated by isOpsEmail() check