feature/api-development #1

Merged
usulpro merged 47 commits from feature/api-development into main 2025-11-29 23:03:01 +07:00
2 changed files with 427 additions and 0 deletions
Showing only changes of commit fba243cfbd - Show all commits

View File

@ -672,6 +672,101 @@ The v1 API supports a 3-tier alias resolution system for referencing images.
--- ---
## Response Formats
### Generation Response
Generation responses include fields for prompt enhancement tracking:
```json
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"projectId": "57c7f7f4d-47de-4d70-9ebd-3807a0b63746",
"prompt": "A photorealistic establishing shot of a luxurious motor yacht...",
"originalPrompt": "шикарная моторная яхта движется по живописному озеру...",
"autoEnhance": true,
"aspectRatio": "16:9",
"status": "success",
"outputImageId": "7c4ccf47-41ce-4718-afbc-8c553b2c631a",
"outputImage": { ... },
"referenceImages": [],
"flowId": null,
"processingTimeMs": 8980,
"cost": null,
"retryCount": 0,
"errorMessage": null,
"meta": {},
"createdAt": "2025-11-23T13:47:00.127Z",
"updatedAt": "2025-11-23T13:47:09.107Z"
}
```
**Prompt Enhancement Fields:**
- `prompt` - The actual prompt used for generation (enhanced if `autoEnhance: true`, original if `autoEnhance: false`)
- `originalPrompt` - The user's original input prompt (always populated for transparency)
- `autoEnhance` - Boolean indicating if prompt enhancement was applied
**Semantics:**
- When `autoEnhance: true`:
- `originalPrompt` = user's original input
- `prompt` = AI-enhanced version used for generation
- Example: Original "a cat" → Enhanced "A photorealistic close-up portrait of a cat..."
- When `autoEnhance: false`:
- `originalPrompt` = user's original input
- `prompt` = same as originalPrompt (no enhancement)
- Both fields contain identical values
**Default Behavior:** `autoEnhance` defaults to `true` if not explicitly set to `false`.
---
### Image Response
Image responses include actual dimensions and storage access:
```json
{
"id": "7c4ccf47-41ce-4718-afbc-8c553b2c631a",
"projectId": "57c7f7f4d-47de-4d70-9ebd-3807a0b63746",
"flowId": null,
"storageKey": "default/57c7f7f4d-47de-4d70-9ebd-3807a0b63746/generated/2025-11/gen_fd14839b.png",
"storageUrl": "http://localhost:9000/banatie/default/57c7f7f4d-47de-4d70-9ebd-3807a0b63746/generated/gen_fd14839b.png",
"mimeType": "image/jpeg",
"fileSize": 1909246,
"width": 1792,
"height": 1024,
"source": "generated",
"alias": null,
"focalPoint": null,
"fileHash": null,
"generationId": "fd14839b-d42e-4be9-93b3-e2fb67f7af0d",
"apiKeyId": "988cb71e-0021-4217-a536-734b097a87b3",
"meta": {},
"createdAt": "2025-11-23T13:47:00.127Z",
"updatedAt": "2025-11-23T13:47:00.127Z",
"deletedAt": null
}
```
**Key Fields:**
- `width` / `height` - Actual image dimensions in pixels (automatically extracted from generated images)
- `storageUrl` - Direct URL to access the image file (use this for image display)
- `storageKey` - Internal MinIO storage path
- `source` - Image origin: "generated" (AI-generated) or "uploaded" (user upload)
- `alias` - Project-scoped alias (e.g., "@logo"), null if not assigned
- `focalPoint` - Cropping focal point {x: 0.0-1.0, y: 0.0-1.0}, null if not set
**Image Access:**
To display an image, use the `storageUrl` field which provides direct access to the image file in MinIO storage. For public CDN access, use the `/cdn/:orgSlug/:projectSlug/img/:alias` endpoint.
**Note:** Previous versions included a `url` field that pointed to a non-existent `/download` endpoint. This field has been removed. Always use `storageUrl` for direct image access.
---
## Error Codes ## Error Codes
| Code | Description | | Code | Description |

332
tests/api/02-basic.rest Normal file
View File

@ -0,0 +1,332 @@
@base = http://localhost:3000
@apiKey = bnt_727d2f4f72bd03ed96da5278bb971a00cb0a2454d4d70f9748b5c39f3f69d88d
###############################################################################
# IMAGE UPLOAD & CRUD TESTS
# Tests: Upload, list, filter, pagination, metadata updates, alias management
###############################################################################
### Test 1.1: Upload image with project-scoped alias
# @name uploadWithAlias
POST {{base}}/api/v1/images/upload
X-API-Key: {{apiKey}}
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="test-image.png"
Content-Type: image/png
< ./fixture/test-image.png
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="alias"
@test-logo
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="description"
Test logo image for CRUD tests
------WebKitFormBoundary7MA4YWxkTrZu0gW--
###
@uploadedImageId = {{uploadWithAlias.response.body.$.data.id}}
@uploadedImageAlias = {{uploadWithAlias.response.body.$.data.alias}}
@uploadedImageSource = {{uploadWithAlias.response.body.$.data.source}}
### Test 1.2: Verify uploaded image details
# Expected: alias = @test-logo, source = uploaded
GET {{base}}/api/v1/images/{{uploadedImageId}}
X-API-Key: {{apiKey}}
###
### Test 2.1: Upload image without alias
# @name uploadWithoutAlias
POST {{base}}/api/v1/images/upload
X-API-Key: {{apiKey}}
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="test-image.png"
Content-Type: image/png
< ./fixture/test-image.png
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="description"
Image without alias
------WebKitFormBoundary7MA4YWxkTrZu0gW--
###
@uploadedImageId2 = {{uploadWithoutAlias.response.body.$.data.id}}
### Test 2.2: Verify image has no alias
# Expected: alias = null
GET {{base}}/api/v1/images/{{uploadedImageId2}}
X-API-Key: {{apiKey}}
###
### Test 3: List all images
# Expected: Returns array with pagination
GET {{base}}/api/v1/images
X-API-Key: {{apiKey}}
###
### Test 4: List images - filter by source=uploaded
# Expected: All results have source="uploaded"
GET {{base}}/api/v1/images?source=uploaded
X-API-Key: {{apiKey}}
###
### Test 5: List images with pagination
# Expected: limit=3, offset=0, hasMore=true/false
GET {{base}}/api/v1/images?limit=3&offset=0
X-API-Key: {{apiKey}}
###
### Test 6: Get image by ID
# Expected: Returns full image details
GET {{base}}/api/v1/images/{{uploadedImageId}}
X-API-Key: {{apiKey}}
###
### Test 7: Resolve project-scoped alias
# Expected: Resolves to uploadedImageId, scope=project
GET {{base}}/api/v1/images/resolve/@test-logo
X-API-Key: {{apiKey}}
###
### Test 8.1: Update image metadata (focal point + meta)
# @name updateMetadata
PUT {{base}}/api/v1/images/{{uploadedImageId}}
X-API-Key: {{apiKey}}
Content-Type: application/json
{
"focalPoint": {
"x": 0.5,
"y": 0.3
},
"meta": {
"description": "Updated description",
"tags": ["test", "logo", "updated"]
}
}
###
### Test 8.2: Verify metadata update
# Expected: focalPoint x=0.5, y=0.3, meta has tags
GET {{base}}/api/v1/images/{{uploadedImageId}}
X-API-Key: {{apiKey}}
###
### Test 9.1: Update image alias (dedicated endpoint)
# @name updateAlias
PUT {{base}}/api/v1/images/{{uploadedImageId}}/alias
X-API-Key: {{apiKey}}
Content-Type: application/json
{
"alias": "@new-test-logo"
}
###
### Test 9.2: Verify new alias works
# Expected: Resolves to same uploadedImageId
GET {{base}}/api/v1/images/resolve/@new-test-logo
X-API-Key: {{apiKey}}
###
### Test 10: Verify old alias doesn't work after update
# Expected: 404 - Alias not found
GET {{base}}/api/v1/images/resolve/@test-logo
X-API-Key: {{apiKey}}
###
### Test 11.1: Remove image alias
# @name removeAlias
PUT {{base}}/api/v1/images/{{uploadedImageId}}/alias
X-API-Key: {{apiKey}}
Content-Type: application/json
{
"alias": null
}
###
### Test 11.2: Verify image exists but has no alias
# Expected: alias = null
GET {{base}}/api/v1/images/{{uploadedImageId}}
X-API-Key: {{apiKey}}
###
### Test 11.3: Verify alias resolution fails
# Expected: 404 - Alias not found
GET {{base}}/api/v1/images/resolve/@new-test-logo
X-API-Key: {{apiKey}}
###
### Test 12.1: Reassign alias for reference image test
# @name reassignAlias
PUT {{base}}/api/v1/images/{{uploadedImageId}}/alias
X-API-Key: {{apiKey}}
Content-Type: application/json
{
"alias": "@reference-logo"
}
###
### Test 12.2: Generate with manual reference image
# @name genWithReference
POST {{base}}/api/v1/generations
X-API-Key: {{apiKey}}
Content-Type: application/json
{
"prompt": "A product photo with the logo in corner",
"aspectRatio": "1:1",
"referenceImages": ["@reference-logo"]
}
###
@genWithReferenceId = {{genWithReference.response.body.$.data.id}}
### Test 12.3: Poll generation status
# Run this multiple times until status = success
GET {{base}}/api/v1/generations/{{genWithReferenceId}}
X-API-Key: {{apiKey}}
###
### Test 12.4: Verify referenced images tracked
# Expected: referencedImages array contains @reference-logo
GET {{base}}/api/v1/generations/{{genWithReferenceId}}
X-API-Key: {{apiKey}}
###
### Test 13.1: Generate with auto-detected reference in prompt
# @name genAutoDetect
POST {{base}}/api/v1/generations
X-API-Key: {{apiKey}}
Content-Type: application/json
{
"prompt": "Create banner using @reference-logo with blue background",
"aspectRatio": "16:9"
}
###
@genAutoDetectId = {{genAutoDetect.response.body.$.data.id}}
### Test 13.2: Poll until complete
GET {{base}}/api/v1/generations/{{genAutoDetectId}}
X-API-Key: {{apiKey}}
###
### Test 13.3: Verify auto-detection worked
# Expected: referencedImages contains @reference-logo
GET {{base}}/api/v1/generations/{{genAutoDetectId}}
X-API-Key: {{apiKey}}
###
### Test 14.1: Generate with project alias assignment
# @name genWithAlias
POST {{base}}/api/v1/generations
X-API-Key: {{apiKey}}
Content-Type: application/json
{
"prompt": "A hero banner image",
"aspectRatio": "21:9",
"alias": "@hero-banner"
}
###
@genWithAliasId = {{genWithAlias.response.body.$.data.id}}
### Test 14.2: Poll until complete
GET {{base}}/api/v1/generations/{{genWithAliasId}}
X-API-Key: {{apiKey}}
###
@heroImageId = {{genWithAlias.response.body.$.data.outputImageId}}
### Test 14.3: Verify alias assigned to output image
# Expected: alias = @hero-banner
GET {{base}}/api/v1/images/{{heroImageId}}
X-API-Key: {{apiKey}}
###
### Test 14.4: Verify alias resolution works
# Expected: Resolves to heroImageId
GET {{base}}/api/v1/images/resolve/@hero-banner
X-API-Key: {{apiKey}}
###
### Test 15.1: Alias conflict - create second generation with same alias
# @name genConflict
POST {{base}}/api/v1/generations
X-API-Key: {{apiKey}}
Content-Type: application/json
{
"prompt": "A different hero image",
"aspectRatio": "21:9",
"alias": "@hero-banner"
}
###
@genConflictId = {{genConflict.response.body.$.data.id}}
### Test 15.2: Poll until complete
GET {{base}}/api/v1/generations/{{genConflictId}}
X-API-Key: {{apiKey}}
###
@secondHeroImageId = {{genConflict.response.body.$.data.outputImageId}}
### Test 15.3: Verify second image has the alias
# Expected: Resolves to secondHeroImageId (not heroImageId)
GET {{base}}/api/v1/images/resolve/@hero-banner
X-API-Key: {{apiKey}}
###
### Test 15.4: Verify first image lost the alias but still exists
# Expected: alias = null, image still exists
GET {{base}}/api/v1/images/{{heroImageId}}
X-API-Key: {{apiKey}}
###
###############################################################################
# END OF IMAGE UPLOAD & CRUD TESTS
###############################################################################