feat: testing references
This commit is contained in:
parent
3e3f15cd9c
commit
2656b208c5
|
|
@ -0,0 +1,817 @@
|
||||||
|
# Banatie API v1 - Technical Changes and Refactoring
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Project is in active development with no existing clients. All changes can be made without backward compatibility concerns. **Priority: high-quality and correct API implementation.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Parameter Naming Cleanup ✅
|
||||||
|
|
||||||
|
### 1.1 POST /api/v1/generations
|
||||||
|
|
||||||
|
**Current parameters:**
|
||||||
|
- `assignAlias` → rename to `alias`
|
||||||
|
- `assignFlowAlias` → rename to `flowAlias`
|
||||||
|
|
||||||
|
**Rationale:** Shorter, clearer, no need for "assign" prefix when assignment is obvious from endpoint context.
|
||||||
|
|
||||||
|
**Affected areas:**
|
||||||
|
- Request type definitions
|
||||||
|
- Route handlers
|
||||||
|
- Service methods
|
||||||
|
- API documentation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Enhanced Prompt Support - Logic Redesign
|
||||||
|
|
||||||
|
### 2.1 Database Schema Changes
|
||||||
|
|
||||||
|
**Required schema modifications:**
|
||||||
|
|
||||||
|
1. **Rename field:** `enhancedPrompt` → `originalPrompt`
|
||||||
|
2. **Change field semantics:**
|
||||||
|
- `prompt` - ALWAYS contains the prompt that was used for generation (enhanced or original)
|
||||||
|
- `originalPrompt` - contains user's original input ONLY if autoEnhance was used (nullable)
|
||||||
|
|
||||||
|
**Field population logic:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Case 1: autoEnhance = false
|
||||||
|
prompt = user input
|
||||||
|
originalPrompt = NULL
|
||||||
|
|
||||||
|
Case 2: autoEnhance = true
|
||||||
|
prompt = enhanced prompt (used for generation)
|
||||||
|
originalPrompt = user input (preserved)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 API Response Format
|
||||||
|
|
||||||
|
**Response structure:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"prompt": "detailed enhanced prompt...", // Always the prompt used for generation
|
||||||
|
"originalPrompt": "sunset" // Only present if enhancement was used
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Affected endpoints:**
|
||||||
|
- `POST /api/v1/generations` response
|
||||||
|
- `GET /api/v1/generations/:id` response
|
||||||
|
- `GET /api/v1/generations` list response
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Regeneration Endpoint Refactoring ✅
|
||||||
|
|
||||||
|
### 3.1 Endpoint Rename
|
||||||
|
|
||||||
|
**Change:**
|
||||||
|
- ❌ OLD: `POST /api/v1/generations/:id/retry`
|
||||||
|
- ✅ NEW: `POST /api/v1/generations/:id/regenerate`
|
||||||
|
|
||||||
|
### 3.2 Remove Status Checks
|
||||||
|
|
||||||
|
- Remove `if (original.status === 'success') throw error` check
|
||||||
|
- Remove `GENERATION_ALREADY_SUCCEEDED` error constant
|
||||||
|
- Allow regeneration for any status (pending, processing, success, failed)
|
||||||
|
|
||||||
|
### 3.3 Remove Retry Logic
|
||||||
|
|
||||||
|
- Remove `retryCount >= MAX_RETRY_COUNT` check
|
||||||
|
- Remove retryCount increment
|
||||||
|
- Remove `MAX_RETRY_COUNT` constant
|
||||||
|
|
||||||
|
### 3.4 Remove Override Parameters
|
||||||
|
|
||||||
|
- Remove `prompt` and `aspectRatio` parameters from request body
|
||||||
|
- Always regenerate with exact same parameters as original
|
||||||
|
|
||||||
|
### 3.5 Image Update Behavior
|
||||||
|
|
||||||
|
**Update existing image instead of creating new:**
|
||||||
|
|
||||||
|
**Preserve:**
|
||||||
|
- `imageId` (UUID remains the same)
|
||||||
|
- `storageKey` (MinIO path)
|
||||||
|
- `storageUrl`
|
||||||
|
- `alias` (if assigned)
|
||||||
|
- `createdAt` (original creation timestamp)
|
||||||
|
|
||||||
|
**Update:**
|
||||||
|
- Physical file in MinIO (overwrite)
|
||||||
|
- `fileSize` (if changed)
|
||||||
|
- `updatedAt` timestamp
|
||||||
|
|
||||||
|
**Generation record:**
|
||||||
|
- Update `status` → processing → success/failed
|
||||||
|
- Update `processingTimeMs`
|
||||||
|
- Keep `outputImageId` (same value)
|
||||||
|
- Keep `flowId` (if present)
|
||||||
|
|
||||||
|
### 3.6 Additional Endpoint
|
||||||
|
|
||||||
|
**Add for Flow:**
|
||||||
|
- `POST /api/v1/flows/:id/regenerate`
|
||||||
|
- Regenerates the most recent generation in flow
|
||||||
|
- Returns `FLOW_HAS_NO_GENERATIONS` error if flow is empty
|
||||||
|
- Uses parameters from the last generation in flow
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Flow Auto-Creation (Lazy Flow Pattern)
|
||||||
|
|
||||||
|
### 4.1 Lazy Flow Creation Strategy
|
||||||
|
|
||||||
|
**Concept:**
|
||||||
|
1. **First request without flowId** → return generated `flowId` in response, but **DO NOT create in DB**
|
||||||
|
2. **Any request with valid flowId** → create flow in DB if doesn't exist, add this request to flow
|
||||||
|
3. **If flowAlias specified in request** → create flow immediately (eager creation)
|
||||||
|
|
||||||
|
### 4.2 Implementation Details
|
||||||
|
|
||||||
|
**Flow ID Generation:**
|
||||||
|
- When generation/upload has no flowId, generate UUID for potential flow
|
||||||
|
- Return this flowId in response
|
||||||
|
- Save `flowId` in generation/image record, but DO NOT create flow record
|
||||||
|
|
||||||
|
**Flow Creation in DB:**
|
||||||
|
|
||||||
|
**Trigger:** ANY request with valid flowId value
|
||||||
|
|
||||||
|
**Logic:**
|
||||||
|
1. Check if flow record exists in DB
|
||||||
|
2. Check if there are existing generations/images with this flowId
|
||||||
|
3. If flow doesn't exist:
|
||||||
|
- Create flow record with provided flowId
|
||||||
|
- Include all existing records with this flowId
|
||||||
|
- Maintain chronological order based on createdAt timestamps
|
||||||
|
4. If flow exists:
|
||||||
|
- Add new record to existing flow
|
||||||
|
|
||||||
|
**Eager creation:**
|
||||||
|
- If request includes `flowAlias` → create flow immediately
|
||||||
|
- Set alias in `flow.aliases` object
|
||||||
|
|
||||||
|
**Database Schema:**
|
||||||
|
- `generations` table already has `flowId` field (foreign key to flows.id)
|
||||||
|
- `images` table already has `flowId` field (foreign key to flows.id)
|
||||||
|
- No schema changes needed
|
||||||
|
|
||||||
|
**Orphan flowId handling:**
|
||||||
|
- If `flowId` exists in generation/image record but not in `flows` table - this is normal
|
||||||
|
- Such records are called "orphans" and simply not shown in `GET /api/v1/flows` list
|
||||||
|
- No cleanup job needed
|
||||||
|
- Do NOT delete such records automatically
|
||||||
|
- System works correctly with orphan flowIds until flow record is created
|
||||||
|
|
||||||
|
### 4.3 Endpoint Changes
|
||||||
|
|
||||||
|
**Remove:**
|
||||||
|
- ❌ `POST /api/v1/flows` endpoint (no longer needed)
|
||||||
|
|
||||||
|
**Modify responses:**
|
||||||
|
- `POST /api/v1/generations` → always return `flowId` in response (see section 10.1)
|
||||||
|
- `POST /api/v1/images/upload` → always return `flowId` in response (see section 10.1)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Upload Image Enhancements
|
||||||
|
|
||||||
|
### 5.1 Add Parameters
|
||||||
|
|
||||||
|
**POST /api/v1/images/upload:**
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `alias` (optional, string) - project-scoped alias
|
||||||
|
- `flowAlias` (optional, string) - flow-scoped alias for uploaded image
|
||||||
|
- `flowId` (optional, string) - flow association
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
- If `flowAlias` and `flowId` specified:
|
||||||
|
- Ensure flow exists (or create via lazy pattern)
|
||||||
|
- Add alias to `flow.aliases` object
|
||||||
|
- If `flowAlias` WITHOUT `flowId`:
|
||||||
|
- Apply lazy flow creation with eager pattern
|
||||||
|
- Create flow immediately, set flowAlias
|
||||||
|
- If only `alias` specified:
|
||||||
|
- Set project-scoped alias on image
|
||||||
|
|
||||||
|
### 5.2 Alias Conflict Resolution
|
||||||
|
|
||||||
|
**Validation rules:**
|
||||||
|
|
||||||
|
1. **Technical aliases are forbidden:**
|
||||||
|
- Cannot use: `@last`, `@first`, `@upload` or any reserved technical alias
|
||||||
|
- Return validation error if attempted
|
||||||
|
|
||||||
|
2. **Alias override behavior:**
|
||||||
|
- If alias already exists → new request has higher priority
|
||||||
|
- Alias points to new image
|
||||||
|
- Previous image loses its alias but is NOT deleted
|
||||||
|
- Same logic applies to both project aliases and flow aliases
|
||||||
|
|
||||||
|
3. **Applies to both:**
|
||||||
|
- Image upload with alias
|
||||||
|
- Generation with alias/flowAlias
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```
|
||||||
|
State: Image A has alias "@hero"
|
||||||
|
Request: Upload Image B with alias "@hero"
|
||||||
|
Result:
|
||||||
|
- Image B now has alias "@hero"
|
||||||
|
- Image A loses alias (alias = NULL)
|
||||||
|
- Image A is NOT deleted
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Image Alias Management Refactoring
|
||||||
|
|
||||||
|
### 6.1 Endpoint Consolidation
|
||||||
|
|
||||||
|
**Remove alias handling from:**
|
||||||
|
- ❌ `PUT /api/v1/images/:id` (body: { alias, focalPoint, meta })
|
||||||
|
- Remove `alias` parameter
|
||||||
|
- Keep only `focalPoint` and `meta`
|
||||||
|
|
||||||
|
**Single method for project-scoped alias management:**
|
||||||
|
- ✅ `PUT /api/v1/images/:id/alias` (body: { alias })
|
||||||
|
- Set new alias
|
||||||
|
- Change existing alias
|
||||||
|
- Remove alias (pass `alias: null`)
|
||||||
|
|
||||||
|
**Rationale:** Explicit intent, dedicated endpoint for alias operations, simpler validation.
|
||||||
|
|
||||||
|
### 6.2 Alias as Image Identifier
|
||||||
|
|
||||||
|
**Support alias in path parameters:**
|
||||||
|
|
||||||
|
**Syntax:**
|
||||||
|
- UUID: `GET /api/v1/images/550e8400-e29b-41d4-a716-446655440000`
|
||||||
|
- Alias: `GET /api/v1/images/@hero`
|
||||||
|
- `@` symbol distinguishes alias from UUID (UUIDs never contain `@`)
|
||||||
|
|
||||||
|
**UUID validation:** UUIDs can NEVER contain `@` symbol - this guarantees no conflicts
|
||||||
|
|
||||||
|
**Flow-scoped resolution:**
|
||||||
|
- `GET /api/v1/images/@hero?flowId=uuid-123`
|
||||||
|
- Searches for alias `@hero` in context of flow `uuid-123`
|
||||||
|
- Uses 3-tier precedence (technical → flow → project)
|
||||||
|
|
||||||
|
**Endpoints with alias support:**
|
||||||
|
- `GET /api/v1/images/:id_or_alias`
|
||||||
|
- `PUT /api/v1/images/:id_or_alias` (for focalPoint, meta)
|
||||||
|
- `PUT /api/v1/images/:id_or_alias/alias`
|
||||||
|
- `DELETE /api/v1/images/:id_or_alias`
|
||||||
|
|
||||||
|
**Implementation:**
|
||||||
|
- Check first character of path parameter
|
||||||
|
- If starts with `@` → resolve via AliasService
|
||||||
|
- If doesn't start with `@` → treat as UUID
|
||||||
|
- After resolution, work with imageId as usual
|
||||||
|
|
||||||
|
### 6.3 CDN-style Image URLs with Alias Support
|
||||||
|
|
||||||
|
**Current URL format must be changed.**
|
||||||
|
|
||||||
|
**New standardized URL patterns:**
|
||||||
|
|
||||||
|
**For all generated and uploaded images:**
|
||||||
|
```
|
||||||
|
GET /cdn/:orgSlug/:projectSlug/img/:filenameOrAlias
|
||||||
|
```
|
||||||
|
|
||||||
|
**For live URLs:**
|
||||||
|
```
|
||||||
|
GET /cdn/:orgSlug/:projectSlug/live/:scope?prompt=...&aspectRatio=...
|
||||||
|
```
|
||||||
|
|
||||||
|
**All image URLs returned by API must follow this pattern.**
|
||||||
|
|
||||||
|
**Resolution Logic:**
|
||||||
|
1. Check if `:filenameOrAlias` starts with `@`
|
||||||
|
2. If yes → resolve alias via AliasService
|
||||||
|
3. If no → search by filename/storageKey
|
||||||
|
4. Return image bytes with proper content-type headers
|
||||||
|
|
||||||
|
**Response Headers:**
|
||||||
|
- Content-Type: image/jpeg (or appropriate MIME type)
|
||||||
|
- Cache-Control: public, max-age=31536000
|
||||||
|
- ETag: based on imageId or fileHash
|
||||||
|
|
||||||
|
**URL Encoding for prompts:**
|
||||||
|
- Spaces can be replaced with underscores `_` for convenience
|
||||||
|
- Both `prompt=beautiful%20sunset` and `prompt=beautiful_sunset` are valid
|
||||||
|
- System should handle both formats
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
```
|
||||||
|
GET /cdn/acme/website/img/@hero → resolve @hero alias
|
||||||
|
GET /cdn/acme/website/img/logo.png → find by filename
|
||||||
|
GET /cdn/acme/website/img/@product-1 → resolve @product-1 alias
|
||||||
|
```
|
||||||
|
|
||||||
|
**Error Handling:**
|
||||||
|
- Alias not found → 404
|
||||||
|
- Filename not found → 404
|
||||||
|
- Multiple matches → alias takes priority over filename
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Deletion Strategy Overhaul
|
||||||
|
|
||||||
|
### 7.1 Image Deletion (Hard Delete)
|
||||||
|
|
||||||
|
**DELETE /api/v1/images/:id**
|
||||||
|
|
||||||
|
**Operations:**
|
||||||
|
1. Delete physical file from MinIO storage
|
||||||
|
2. Delete record from `images` table (hard delete)
|
||||||
|
3. Cascade: set `outputImageId = NULL` in related generations
|
||||||
|
4. Cascade: **completely remove alias entries** from all `flow.aliases` where imageId is referenced
|
||||||
|
- Remove entire key-value pairs, not just values
|
||||||
|
5. Cascade: remove imageId from `generation.referencedImages` JSON arrays
|
||||||
|
|
||||||
|
**Example cascade for flow.aliases:**
|
||||||
|
```
|
||||||
|
Before: flow.aliases = { "@hero": "img-123", "@product": "img-456" }
|
||||||
|
Delete img-123
|
||||||
|
After: flow.aliases = { "@product": "img-456" }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rationale:** User wants to delete - remove completely, free storage. Alias entries are also completely removed.
|
||||||
|
|
||||||
|
### 7.2 Generation Deletion (Conditional)
|
||||||
|
|
||||||
|
**DELETE /api/v1/generations/:id**
|
||||||
|
|
||||||
|
**Behavior depends on output image alias:**
|
||||||
|
|
||||||
|
**Case 1: Output image WITHOUT project alias**
|
||||||
|
1. Delete output image completely (hard delete with MinIO cleanup)
|
||||||
|
2. Delete generation record (hard delete)
|
||||||
|
|
||||||
|
**Case 2: Output image WITH project alias**
|
||||||
|
1. Keep output image (do not delete)
|
||||||
|
2. Delete only generation record (hard delete)
|
||||||
|
3. Set `generationId = NULL` in image record
|
||||||
|
|
||||||
|
**Decision Logic:**
|
||||||
|
- If `outputImage.alias !== null` → keep image, delete only generation
|
||||||
|
- If `outputImage.alias === null` → delete both image and generation
|
||||||
|
|
||||||
|
**Rationale:**
|
||||||
|
- Image with project alias is used as standalone asset, preserve it
|
||||||
|
- Image without alias was created only for this generation, delete together
|
||||||
|
|
||||||
|
**No regeneration of deleted generations** - deleted generations cannot be regenerated
|
||||||
|
|
||||||
|
### 7.3 Flow Deletion (Cascade with Alias Protection)
|
||||||
|
|
||||||
|
**DELETE /api/v1/flows/:id**
|
||||||
|
|
||||||
|
**Operations:**
|
||||||
|
1. Delete flow record from DB
|
||||||
|
2. Cascade: delete all generations associated with this flowId
|
||||||
|
3. Cascade: delete all images associated with this flowId **EXCEPT** images with project alias
|
||||||
|
|
||||||
|
**Detailed Cascade Logic:**
|
||||||
|
|
||||||
|
**For Generations:**
|
||||||
|
- Delete each generation (follows conditional delete from 7.2)
|
||||||
|
- If output image has no alias → delete image
|
||||||
|
- If output image has alias → keep image, set generationId = NULL, set flowId = NULL
|
||||||
|
|
||||||
|
**For Images (uploaded):**
|
||||||
|
- If image has no alias → delete (with MinIO cleanup)
|
||||||
|
- If image has alias → keep, set flowId = NULL
|
||||||
|
|
||||||
|
**Summary:**
|
||||||
|
- Flow record → DELETE
|
||||||
|
- All generations → DELETE
|
||||||
|
- Images without alias → DELETE (with MinIO cleanup)
|
||||||
|
- Images with project alias → KEEP (unlink: flowId = NULL)
|
||||||
|
|
||||||
|
**Rationale:**
|
||||||
|
Flow deletion removes all content except images with project aliases (used globally in project).
|
||||||
|
|
||||||
|
### 7.4 Transactional Delete Pattern
|
||||||
|
|
||||||
|
**All delete operations must be transactional:**
|
||||||
|
|
||||||
|
1. Delete from MinIO storage first
|
||||||
|
2. Delete from database (with cascades)
|
||||||
|
3. If MinIO delete fails → rollback DB transaction
|
||||||
|
4. If DB delete fails → cleanup MinIO file (or rollback if possible)
|
||||||
|
5. Log all delete operations for audit trail
|
||||||
|
|
||||||
|
**Principle:** System must be designed so orphaned files in MinIO NEVER occur.
|
||||||
|
|
||||||
|
**Database Constraints:**
|
||||||
|
- ON DELETE CASCADE for appropriate foreign keys
|
||||||
|
- ON DELETE SET NULL where related records must be preserved
|
||||||
|
- Proper referential integrity
|
||||||
|
|
||||||
|
**No background cleanup jobs needed** - system is self-sufficient and always consistent.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Live URL System
|
||||||
|
|
||||||
|
### 8.1 Core Concept
|
||||||
|
|
||||||
|
**Purpose:** Permanent URLs that can be immediately inserted into HTML and work forever.
|
||||||
|
|
||||||
|
**Use Case:**
|
||||||
|
```html
|
||||||
|
<img src="https://banatie.app/cdn/acme/website/live/hero-section?prompt=beautiful_sunset&aspectRatio=16:9"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Points:**
|
||||||
|
- URL is constructed immediately and used permanently
|
||||||
|
- No preliminary generation through API needed
|
||||||
|
- No signed URLs or tokens in query params
|
||||||
|
- First request → generation, subsequent → cache
|
||||||
|
|
||||||
|
### 8.2 URL Format & Structure
|
||||||
|
|
||||||
|
**URL Pattern:**
|
||||||
|
```
|
||||||
|
/cdn/:orgSlug/:projectSlug/live/:scope?prompt=...&aspectRatio=...
|
||||||
|
```
|
||||||
|
|
||||||
|
**URL Components:**
|
||||||
|
```
|
||||||
|
/cdn/acme/website/live/hero-section?prompt=beautiful_sunset&aspectRatio=16:9
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │ │ │ └─ Generation params (query string)
|
||||||
|
│ │ │ └─ Scope identifier
|
||||||
|
│ │ └─ "live" prefix
|
||||||
|
│ └─ Project slug
|
||||||
|
└─ Organization slug
|
||||||
|
```
|
||||||
|
|
||||||
|
**Scope Parameter:**
|
||||||
|
- Name: `scope` (confirmed)
|
||||||
|
- Purpose: logical separation of live URLs within project
|
||||||
|
- Format: alphanumeric + hyphens + underscores
|
||||||
|
- Any user can specify any scope (no validation/signature required)
|
||||||
|
|
||||||
|
### 8.3 First Request Flow
|
||||||
|
|
||||||
|
**Cache MISS (first request):**
|
||||||
|
1. Parse orgSlug, projectSlug, scope from URL
|
||||||
|
2. Compute cache key: hash(projectId + scope + prompt + params)
|
||||||
|
3. Check if image exists in cache
|
||||||
|
4. If NOT found:
|
||||||
|
- Check scope settings (allowNewGenerations, limit)
|
||||||
|
- Trigger image generation
|
||||||
|
- Create database records (generation, image, cache entry)
|
||||||
|
- Wait for generation to complete
|
||||||
|
- Return image bytes
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
- Content-Type: image/jpeg
|
||||||
|
- Cache-Control: public, max-age=31536000
|
||||||
|
- X-Cache-Status: MISS
|
||||||
|
- X-Scope: hero-section
|
||||||
|
- X-Image-Id: uuid
|
||||||
|
|
||||||
|
**Cache HIT (subsequent requests):**
|
||||||
|
1. Same cache key lookup
|
||||||
|
2. Found existing image
|
||||||
|
3. Return cached image bytes immediately
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
- Content-Type: image/jpeg
|
||||||
|
- Cache-Control: public, max-age=31536000
|
||||||
|
- X-Cache-Status: HIT
|
||||||
|
- X-Image-Id: uuid
|
||||||
|
|
||||||
|
**Generation in Progress:**
|
||||||
|
- If image is not in cache but generation is already running:
|
||||||
|
- System must have internal status to track this
|
||||||
|
- Wait for generation to complete
|
||||||
|
- Return image bytes immediately when ready
|
||||||
|
- This ensures consistent behavior for concurrent requests
|
||||||
|
|
||||||
|
### 8.4 Scope Management
|
||||||
|
|
||||||
|
**Database Table: `live_scopes`**
|
||||||
|
|
||||||
|
Create dedicated table with fields:
|
||||||
|
- `id` (UUID, primary key)
|
||||||
|
- `project_id` (UUID, foreign key to projects)
|
||||||
|
- `slug` (TEXT, unique within project) - used in URL
|
||||||
|
- `allowNewGenerations` (BOOLEAN, default: true) - controls if new generations can be triggered
|
||||||
|
- `newGenerationsLimit` (INTEGER, default: 30) - max number of generations in this scope
|
||||||
|
- `created_at` (TIMESTAMP)
|
||||||
|
- `updated_at` (TIMESTAMP)
|
||||||
|
|
||||||
|
**Scope Behavior:**
|
||||||
|
|
||||||
|
**allowNewGenerations:**
|
||||||
|
- Controls whether new generations can be triggered in this scope
|
||||||
|
- Already generated images are ALWAYS served publicly regardless of this setting
|
||||||
|
- Default: true
|
||||||
|
|
||||||
|
**newGenerationsLimit:**
|
||||||
|
- Limit on number of generations in this scope
|
||||||
|
- Only affects NEW generations, does not affect regeneration
|
||||||
|
- Default: 30
|
||||||
|
|
||||||
|
**Scope Creation:**
|
||||||
|
- Manual: via dedicated endpoint (see below)
|
||||||
|
- Automatic: when new scope is used in live URL (if project allows)
|
||||||
|
|
||||||
|
**Project-level Settings:**
|
||||||
|
|
||||||
|
Add to projects table or settings:
|
||||||
|
- `allowNewLiveScopes` (BOOLEAN, default: true) - allows creating new scopes via live URLs
|
||||||
|
- If false: new scopes cannot be created via live URL
|
||||||
|
- If false: scopes can still be created via API endpoint
|
||||||
|
- `newLiveScopesGenerationLimit` (INTEGER, default: 30) - generation limit for auto-created scopes
|
||||||
|
- This value is set as `newGenerationsLimit` for newly created scopes
|
||||||
|
|
||||||
|
### 8.5 Scope Management API
|
||||||
|
|
||||||
|
**Create scope (manual):**
|
||||||
|
```
|
||||||
|
POST /api/v1/live/scopes
|
||||||
|
Headers: X-API-Key: bnt_project_key
|
||||||
|
Body: {
|
||||||
|
"slug": "hero-section",
|
||||||
|
"allowNewGenerations": true,
|
||||||
|
"newGenerationsLimit": 50
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**List scopes:**
|
||||||
|
```
|
||||||
|
GET /api/v1/live/scopes
|
||||||
|
Headers: X-API-Key: bnt_project_key
|
||||||
|
Response: {
|
||||||
|
"scopes": [
|
||||||
|
{
|
||||||
|
"id": "uuid",
|
||||||
|
"slug": "hero-section",
|
||||||
|
"allowNewGenerations": true,
|
||||||
|
"newGenerationsLimit": 50,
|
||||||
|
"currentGenerations": 23,
|
||||||
|
"lastGeneratedAt": "2024-01-15T10:30:00Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Get scope details:**
|
||||||
|
```
|
||||||
|
GET /api/v1/live/scopes/:slug
|
||||||
|
Headers: X-API-Key: bnt_project_key
|
||||||
|
Response: {
|
||||||
|
"id": "uuid",
|
||||||
|
"slug": "hero-section",
|
||||||
|
"allowNewGenerations": true,
|
||||||
|
"newGenerationsLimit": 50,
|
||||||
|
"currentGenerations": 23,
|
||||||
|
"images": [...]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Update scope:**
|
||||||
|
```
|
||||||
|
PUT /api/v1/live/scopes/:slug
|
||||||
|
Headers: X-API-Key: bnt_project_key
|
||||||
|
Body: {
|
||||||
|
"allowNewGenerations": false,
|
||||||
|
"newGenerationsLimit": 100
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Regenerate scope images:**
|
||||||
|
```
|
||||||
|
POST /api/v1/live/scopes/:slug/regenerate
|
||||||
|
Headers: X-API-Key: bnt_project_key
|
||||||
|
Body: { "imageId": "uuid" } // Optional: regenerate specific image
|
||||||
|
Response: {
|
||||||
|
"regenerated": 1,
|
||||||
|
"images": [...]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Delete scope:**
|
||||||
|
```
|
||||||
|
DELETE /api/v1/live/scopes/:slug
|
||||||
|
Headers: X-API-Key: bnt_project_key
|
||||||
|
```
|
||||||
|
|
||||||
|
**Deletion behavior:** Deletes all images in this scope (follows standard image deletion with alias protection).
|
||||||
|
|
||||||
|
### 8.6 Security & Rate Limiting
|
||||||
|
|
||||||
|
**Rate Limiting by IP:**
|
||||||
|
- Aggressive limits for live URLs (e.g., 10 new generations per hour per IP)
|
||||||
|
- Separate from API key limits
|
||||||
|
- Cache hits do NOT count toward limit
|
||||||
|
- Only new generations count
|
||||||
|
|
||||||
|
**Scope Quotas:**
|
||||||
|
- Maximum N unique prompts per scope (newGenerationsLimit)
|
||||||
|
- After limit reached → return existing images, do not generate new
|
||||||
|
- Regeneration does not count toward limit
|
||||||
|
|
||||||
|
### 8.7 Caching Strategy
|
||||||
|
|
||||||
|
**Cache Key:**
|
||||||
|
```
|
||||||
|
cacheKey = hash(projectId + scope + prompt + aspectRatio + otherParams)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cache Invalidation:**
|
||||||
|
- Manual: via API endpoint regenerate
|
||||||
|
- Automatic: never (images cached forever unless explicitly regenerated)
|
||||||
|
|
||||||
|
**Scope Naming:** `scope` (confirmed)
|
||||||
|
|
||||||
|
**URL Encoding:**
|
||||||
|
- Prompt in query string: URL-encoded or underscores for spaces
|
||||||
|
- Both formats supported: `prompt=beautiful%20sunset` and `prompt=beautiful_sunset`
|
||||||
|
- Scope in path: alphanumeric + hyphens + underscores
|
||||||
|
|
||||||
|
### 8.8 Error Handling
|
||||||
|
|
||||||
|
**Detailed errors for live URLs:**
|
||||||
|
|
||||||
|
- Invalid scope format → 400 "Invalid scope format. Use alphanumeric characters, hyphens, and underscores"
|
||||||
|
- New scope creation disabled → 403 "Creating new live scopes is disabled for this project"
|
||||||
|
- Generation limit exceeded → 429 "Scope generation limit exceeded. Maximum N generations per scope"
|
||||||
|
- Generation fails → 500 with retry logic
|
||||||
|
- Rate limit by IP exceeded → 429 "Rate limit exceeded. Try again in X seconds" with Retry-After header
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Generation Modification
|
||||||
|
|
||||||
|
### 9.1 Update Generation Endpoint
|
||||||
|
|
||||||
|
**New endpoint:**
|
||||||
|
```
|
||||||
|
PUT /api/v1/generations/:id
|
||||||
|
```
|
||||||
|
|
||||||
|
**Modifiable Fields:**
|
||||||
|
- `prompt` - change prompt
|
||||||
|
- `aspectRatio` - change aspect ratio
|
||||||
|
- `flowId` - change/remove/add flow association
|
||||||
|
- `meta` - update metadata
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
|
||||||
|
**Case 1: Non-generative parameters (flowId, meta)**
|
||||||
|
- Simply update fields in DB
|
||||||
|
- Do NOT regenerate image
|
||||||
|
|
||||||
|
**Case 2: Generative parameters (prompt, aspectRatio)**
|
||||||
|
- Update fields in DB
|
||||||
|
- Automatically trigger regeneration
|
||||||
|
- Update existing image (same imageId, path, URL)
|
||||||
|
|
||||||
|
### 9.2 FlowId Management
|
||||||
|
|
||||||
|
**FlowId handling:**
|
||||||
|
- `flowId: null` → detach from flow
|
||||||
|
- `flowId: "new-uuid"` → attach to different flow
|
||||||
|
- If flow doesn't exist → create new flow eagerly (with this flowId)
|
||||||
|
- If flow exists → add generation to existing flow
|
||||||
|
- `flowId: undefined` → do not change current value
|
||||||
|
|
||||||
|
**Use Case - "Detach from Flow":**
|
||||||
|
- Set `flowId: null` to detach generation from flow
|
||||||
|
- Output image is preserved (if has alias)
|
||||||
|
- Useful before deleting flow to protect important generations
|
||||||
|
|
||||||
|
### 9.3 Validation Rules
|
||||||
|
|
||||||
|
**Use existing validation logic from generation creation:**
|
||||||
|
- Prompt validation (existing rules)
|
||||||
|
- AspectRatio validation (existing rules)
|
||||||
|
- FlowId validation:
|
||||||
|
- If provided (not null): must be valid UUID format
|
||||||
|
- Flow does NOT need to exist (will be created eagerly if missing)
|
||||||
|
- Allow null explicitly (for detachment)
|
||||||
|
|
||||||
|
### 9.4 Response Format
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"id": "gen-uuid",
|
||||||
|
"prompt": "updated prompt",
|
||||||
|
"aspectRatio": "16:9",
|
||||||
|
"flowId": null,
|
||||||
|
"status": "processing", // If regeneration triggered
|
||||||
|
"regenerated": true, // Flag indicating regeneration started
|
||||||
|
"outputImage": { ... } // Current image (updates when regeneration completes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Response Format Consistency
|
||||||
|
|
||||||
|
### 10.1 FlowId in Responses
|
||||||
|
|
||||||
|
**Rule for flowId in generation and upload responses:**
|
||||||
|
|
||||||
|
**If request has `flowId: undefined` (not provided):**
|
||||||
|
- Generate new flowId
|
||||||
|
- Return in response: `"flowId": "new-uuid"`
|
||||||
|
|
||||||
|
**If request has `flowId: null` (explicitly null):**
|
||||||
|
- Do NOT generate flowId
|
||||||
|
- Flow is definitely not needed
|
||||||
|
- Return in response: `"flowId": null`
|
||||||
|
|
||||||
|
**If request has `flowId: "uuid"` (specific value):**
|
||||||
|
- Use provided flowId
|
||||||
|
- Return in response: `"flowId": "uuid"`
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
```json
|
||||||
|
// Request without flowId
|
||||||
|
POST /api/v1/generations
|
||||||
|
Body: { "prompt": "sunset" }
|
||||||
|
Response: { "flowId": "generated-uuid", ... }
|
||||||
|
|
||||||
|
// Request with explicit null
|
||||||
|
POST /api/v1/generations
|
||||||
|
Body: { "prompt": "sunset", "flowId": null }
|
||||||
|
Response: { "flowId": null, ... }
|
||||||
|
|
||||||
|
// Request with specific flowId
|
||||||
|
POST /api/v1/generations
|
||||||
|
Body: { "prompt": "sunset", "flowId": "my-flow-uuid" }
|
||||||
|
Response: { "flowId": "my-flow-uuid", ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Error Messages Updates
|
||||||
|
|
||||||
|
**Remove constants:**
|
||||||
|
- `GENERATION_ALREADY_SUCCEEDED` (no longer needed)
|
||||||
|
- `MAX_RETRY_COUNT_EXCEEDED` (no longer needed)
|
||||||
|
|
||||||
|
**Add constants:**
|
||||||
|
- `SCOPE_INVALID_FORMAT` - "Invalid scope format. Use alphanumeric characters, hyphens, and underscores"
|
||||||
|
- `SCOPE_CREATION_DISABLED` - "Creating new live scopes is disabled for this project"
|
||||||
|
- `SCOPE_GENERATION_LIMIT_EXCEEDED` - "Scope generation limit exceeded. Maximum {limit} generations per scope"
|
||||||
|
- `STORAGE_DELETE_FAILED` - "Failed to delete file from storage"
|
||||||
|
|
||||||
|
**Update constants:**
|
||||||
|
- `GENERATION_FAILED` - include details about network/storage errors
|
||||||
|
- `IMAGE_NOT_FOUND` - distinguish between deleted and never existed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary of Changes
|
||||||
|
|
||||||
|
### Database Changes
|
||||||
|
1. Rename `enhancedPrompt` → `originalPrompt` in generations table
|
||||||
|
2. Create `live_scopes` table with fields: id, project_id, slug, allowNewGenerations, newGenerationsLimit
|
||||||
|
3. Add project settings: allowNewLiveScopes, newLiveScopesGenerationLimit
|
||||||
|
4. Add `scope` and `isLiveUrl` fields to images table (optional, can use meta)
|
||||||
|
|
||||||
|
### API Changes
|
||||||
|
1. Rename parameters: assignAlias → alias, assignFlowAlias → flowAlias
|
||||||
|
2. Rename endpoint: POST /generations/:id/retry → /generations/:id/regenerate
|
||||||
|
3. Remove endpoint: POST /api/v1/flows (no longer needed)
|
||||||
|
4. Add endpoint: POST /api/v1/flows/:id/regenerate
|
||||||
|
5. Add endpoint: PUT /api/v1/generations/:id (modification)
|
||||||
|
6. Add CDN endpoints:
|
||||||
|
- GET /cdn/:org/:project/img/:filenameOrAlias (all images)
|
||||||
|
- GET /cdn/:org/:project/live/:scope (live URLs)
|
||||||
|
7. Add scope management endpoints (CRUD for live_scopes)
|
||||||
|
8. Update all image URLs in API responses to use CDN format
|
||||||
|
|
||||||
|
### Behavior Changes
|
||||||
|
1. Lazy flow creation (create on second request or when flowAlias present)
|
||||||
|
2. Alias conflict resolution (new overwrites old)
|
||||||
|
3. Regenerate updates existing image (same ID, path, URL)
|
||||||
|
4. Hard delete for images (with MinIO cleanup)
|
||||||
|
5. Conditional delete for generations (based on alias)
|
||||||
|
6. Cascade delete for flows (with alias protection)
|
||||||
|
7. Live URL caching and scope management
|
||||||
|
8. FlowId in responses (generate if undefined, keep if null)
|
||||||
|
|
||||||
|
### Validation Changes
|
||||||
|
1. @ symbol distinguishes aliases from UUIDs
|
||||||
|
2. Technical aliases forbidden in user input
|
||||||
|
3. Flow creation on-the-fly for non-existent flowIds
|
||||||
|
4. Scope format validation for live URLs
|
||||||
|
|
@ -208,5 +208,5 @@ X-API-Key: {{apiKey}}
|
||||||
### Generate with Prompt Caching
|
### Generate with Prompt Caching
|
||||||
# Generate images with intelligent caching based on prompt hash
|
# Generate images with intelligent caching based on prompt hash
|
||||||
# Returns raw image bytes (not JSON)
|
# Returns raw image bytes (not JSON)
|
||||||
GET {{base}}/api/v1/live?prompt=A beautiful sunset&aspectRatio=16:9
|
GET {{base}}/api/v1/live?prompt=грузовик едет по горной дороге&aspectRatio=16:9
|
||||||
X-API-Key: {{apiKey}}
|
X-API-Key: {{apiKey}}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
@base = http://localhost:3000
|
||||||
|
@apiKey = bnt_71e7e16732ac5e21f597edc56e99e8c3696e713552ec9d1f44dfeffb2ef7c495
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# IMAGE REFERENCES & ALIASES TESTING
|
||||||
|
# This file demonstrates the complete flow of:
|
||||||
|
# 1. Generating an image with an alias
|
||||||
|
# 2. Verifying the alias is assigned
|
||||||
|
# 3. Using that alias as a reference in another generation
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# STEP 1: Generate Simple Logo (1:1 aspect ratio)
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
# @name generateLogo
|
||||||
|
POST {{base}}/api/v1/generations
|
||||||
|
Content-Type: application/json
|
||||||
|
X-API-Key: {{apiKey}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"prompt": "A sleek and modern company logo featuring a stylized character @ turning it into a snail in blue and brown colors, minimalist design, vector art",
|
||||||
|
"aspectRatio": "1:1",
|
||||||
|
"assignAlias": "@logo-snail",
|
||||||
|
"autoEnhance": false
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
@logoGenerationId = {{generateLogo.response.body.$.data.id}}
|
||||||
|
@logoImageId = {{generateLogo.response.body.$.data.outputImageId}}
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# STEP 2: Verify Logo Alias Assignment
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
### Resolve @logo Alias
|
||||||
|
# Confirm that @logo alias is properly assigned and retrieve image metadata
|
||||||
|
GET {{base}}/api/v1/images/resolve/@logo-snail
|
||||||
|
X-API-Key: {{apiKey}}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
### Get Logo Generation Details
|
||||||
|
# View complete generation record with output image
|
||||||
|
GET {{base}}/api/v1/generations/{{logoGenerationId}}
|
||||||
|
X-API-Key: {{apiKey}}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
### Get Logo Image Details
|
||||||
|
# View image record directly by ID
|
||||||
|
GET {{base}}/api/v1/images/{{logoImageId}}
|
||||||
|
X-API-Key: {{apiKey}}
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# STEP 3: Generate Lorry with Logo Reference
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
# @name generateLorry
|
||||||
|
POST {{base}}/api/v1/generations
|
||||||
|
Content-Type: application/json
|
||||||
|
X-API-Key: {{apiKey}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"prompt": "A modern lorry truck driving on a winding mountain road during sunset, the truck has a large @logo-snail prominently displayed on its side panel, photorealistic style, golden hour lighting, detailed commercial vehicle, scenic mountain landscape",
|
||||||
|
"aspectRatio": "16:9",
|
||||||
|
"referenceImages": ["@logo-snail"],
|
||||||
|
"assignAlias": "@lorry-branded",
|
||||||
|
"autoEnhance": false
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
@lorryGenerationId = {{generateLorry.response.body.$.data.id}}
|
||||||
|
@lorryImageId = {{generateLorry.response.body.$.data.outputImageId}}
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# VERIFICATION: Check Both Generations
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
### List All Generations
|
||||||
|
# View both logo and lorry generations in the project
|
||||||
|
GET {{base}}/api/v1/generations?limit=10&offset=0
|
||||||
|
X-API-Key: {{apiKey}}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
### Resolve @lorry-branded Alias
|
||||||
|
# Confirm the lorry image alias is assigned
|
||||||
|
GET {{base}}/api/v1/images/resolve/@lorry-branded
|
||||||
|
X-API-Key: {{apiKey}}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
### Get Lorry Generation Details
|
||||||
|
# View complete generation with reference images
|
||||||
|
GET {{base}}/api/v1/generations/{{lorryGenerationId}}
|
||||||
|
X-API-Key: {{apiKey}}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
### List All Images
|
||||||
|
# View both logo and lorry images
|
||||||
|
GET {{base}}/api/v1/images?limit=10&offset=0
|
||||||
|
X-API-Key: {{apiKey}}
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# BONUS: Test Technical Aliases
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
### Resolve @last (Most Recent Image)
|
||||||
|
# Should return the lorry image (most recently generated)
|
||||||
|
GET {{base}}/api/v1/images/resolve/@last
|
||||||
|
X-API-Key: {{apiKey}}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
### Resolve @first (First Generated Image)
|
||||||
|
# Should return the logo image (first generated in this flow)
|
||||||
|
GET {{base}}/api/v1/images/resolve/@first
|
||||||
|
X-API-Key: {{apiKey}}
|
||||||
Loading…
Reference in New Issue