fix: add resolve endpoint and correct live path
CRITICAL FIXES: - Add GET /api/v1/images/resolve/:alias endpoint with 3-tier alias resolution - Supports optional flowId query parameter - Returns image, scope (technical/flow/project), and flowId - Placed before GET /:id to avoid route conflict - Change live endpoint from /api/v1/live/generate to /api/v1/live - Corrects path to match specification PARAMETER NAMING: - Rename outputAlias to assignAlias in requests and service - Rename flowAliases to assignFlowAlias in requests and service - Update generations route to use new parameter names FLOW TIMESTAMP UPDATES: - Add flow.updatedAt trigger in ImageService.create() - Updates flow timestamp when image is uploaded to a flow - Flow.updatedAt already updated in GenerationService.create() AUDIT TRAIL VERIFICATION: - Confirmed apiKeyId is properly saved in generations table - Confirmed apiKeyId is properly saved in images table (both upload and generation) TYPE FIXES: - Add explicit | undefined to AliasResolutionResponse.flowId 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
e55c02d158
commit
874cc4fcba
|
|
@ -41,8 +41,8 @@ generationsRouter.post(
|
|||
referenceImages,
|
||||
aspectRatio,
|
||||
flowId,
|
||||
outputAlias,
|
||||
flowAliases,
|
||||
assignAlias,
|
||||
assignFlowAlias,
|
||||
autoEnhance,
|
||||
meta,
|
||||
} = req.body;
|
||||
|
|
@ -68,8 +68,8 @@ generationsRouter.post(
|
|||
referenceImages,
|
||||
aspectRatio,
|
||||
flowId,
|
||||
outputAlias,
|
||||
flowAliases,
|
||||
assignAlias,
|
||||
assignFlowAlias,
|
||||
autoEnhance,
|
||||
meta,
|
||||
requestId: req.requestId,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Response, Router } from 'express';
|
||||
import type { Router as RouterType } from 'express';
|
||||
import { ImageService } from '@/services/core';
|
||||
import { ImageService, AliasService } from '@/services/core';
|
||||
import { StorageFactory } from '@/services/StorageFactory';
|
||||
import { asyncHandler } from '@/middleware/errorHandler';
|
||||
import { validateApiKey } from '@/middleware/auth/validateApiKey';
|
||||
|
|
@ -16,11 +16,13 @@ import type {
|
|||
GetImageResponse,
|
||||
UpdateImageResponse,
|
||||
DeleteImageResponse,
|
||||
ResolveAliasResponse,
|
||||
} from '@/types/responses';
|
||||
|
||||
export const imagesRouter: RouterType = Router();
|
||||
|
||||
let imageService: ImageService;
|
||||
let aliasService: AliasService;
|
||||
|
||||
const getImageService = (): ImageService => {
|
||||
if (!imageService) {
|
||||
|
|
@ -29,6 +31,13 @@ const getImageService = (): ImageService => {
|
|||
return imageService;
|
||||
};
|
||||
|
||||
const getAliasService = (): AliasService => {
|
||||
if (!aliasService) {
|
||||
aliasService = new AliasService();
|
||||
}
|
||||
return aliasService;
|
||||
};
|
||||
|
||||
/**
|
||||
* POST /api/v1/images/upload
|
||||
* Upload a single image file and create database record
|
||||
|
|
@ -162,6 +171,74 @@ imagesRouter.get(
|
|||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* GET /api/v1/images/resolve/:alias
|
||||
* Resolve an alias to an image using 3-tier precedence (technical -> flow -> project)
|
||||
*/
|
||||
imagesRouter.get(
|
||||
'/resolve/:alias',
|
||||
validateApiKey,
|
||||
requireProjectKey,
|
||||
asyncHandler(async (req: any, res: Response<ResolveAliasResponse>) => {
|
||||
const aliasServiceInstance = getAliasService();
|
||||
const { alias } = req.params;
|
||||
const { flowId } = req.query;
|
||||
|
||||
const projectId = req.apiKey.projectId;
|
||||
|
||||
try {
|
||||
const resolution = await aliasServiceInstance.resolve(
|
||||
alias,
|
||||
projectId,
|
||||
flowId as string | undefined
|
||||
);
|
||||
|
||||
if (!resolution) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: {
|
||||
message: `Alias '${alias}' not found`,
|
||||
code: 'ALIAS_NOT_FOUND',
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify project ownership
|
||||
if (resolution.image && resolution.image.projectId !== projectId) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: {
|
||||
message: 'Alias not found',
|
||||
code: 'ALIAS_NOT_FOUND',
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
alias,
|
||||
imageId: resolution.imageId,
|
||||
scope: resolution.scope,
|
||||
flowId: resolution.flowId,
|
||||
image: resolution.image ? toImageResponse(resolution.image) : ({} as any),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: {
|
||||
message: error instanceof Error ? error.message : 'Failed to resolve alias',
|
||||
code: 'RESOLUTION_ERROR',
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* GET /api/v1/images/:id
|
||||
* Get a single image by ID
|
||||
|
|
|
|||
|
|
@ -36,12 +36,12 @@ const getImageService = (): ImageService => {
|
|||
};
|
||||
|
||||
/**
|
||||
* GET /api/v1/live/generate
|
||||
* GET /api/v1/live
|
||||
* Generate image with prompt caching
|
||||
* Returns image bytes directly with cache headers
|
||||
*/
|
||||
liveRouter.get(
|
||||
'/generate',
|
||||
'/',
|
||||
validateApiKey,
|
||||
requireProjectKey,
|
||||
rateLimitByApiKey,
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ export interface CreateGenerationParams {
|
|||
referenceImages?: string[] | undefined; // Aliases to resolve
|
||||
aspectRatio?: string | undefined;
|
||||
flowId?: string | undefined;
|
||||
outputAlias?: string | undefined;
|
||||
flowAliases?: Record<string, string> | undefined;
|
||||
assignAlias?: string | undefined;
|
||||
assignFlowAlias?: Record<string, string> | undefined;
|
||||
autoEnhance?: boolean | undefined;
|
||||
enhancedPrompt?: string | undefined;
|
||||
meta?: Record<string, unknown> | undefined;
|
||||
|
|
@ -125,12 +125,12 @@ export class GenerationService {
|
|||
fileSize: 0, // TODO: Get actual file size from storage
|
||||
fileHash,
|
||||
source: 'generated',
|
||||
alias: params.outputAlias || null,
|
||||
alias: params.assignAlias || null,
|
||||
meta: params.meta || {},
|
||||
});
|
||||
|
||||
if (params.flowAliases && params.flowId) {
|
||||
await this.assignFlowAliases(params.flowId, params.flowAliases, imageRecord.id);
|
||||
if (params.assignFlowAlias && params.flowId) {
|
||||
await this.assignFlowAliases(params.flowId, params.assignFlowAlias, imageRecord.id);
|
||||
}
|
||||
|
||||
if (params.flowId) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { eq, and, isNull, desc, count, sql } from 'drizzle-orm';
|
||||
import { db } from '@/db';
|
||||
import { images } from '@banatie/database';
|
||||
import { images, flows } from '@banatie/database';
|
||||
import type { Image, NewImage, ImageFilters } from '@/types/models';
|
||||
import { buildWhereClause, buildEqCondition, withoutDeleted } from '@/utils/helpers';
|
||||
import { ERROR_MESSAGES } from '@/utils/constants';
|
||||
|
|
@ -18,6 +18,15 @@ export class ImageService {
|
|||
if (!image) {
|
||||
throw new Error('Failed to create image record');
|
||||
}
|
||||
|
||||
// Update flow timestamp if image is part of a flow
|
||||
if (image.flowId) {
|
||||
await db
|
||||
.update(flows)
|
||||
.set({ updatedAt: new Date() })
|
||||
.where(eq(flows.id, image.flowId));
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ export interface CreateGenerationRequest {
|
|||
referenceImages?: string[]; // Array of aliases to resolve
|
||||
aspectRatio?: string; // e.g., "1:1", "16:9", "3:2", "9:16"
|
||||
flowId?: string;
|
||||
outputAlias?: string; // Alias to assign to generated image
|
||||
flowAliases?: Record<string, string>; // Flow-scoped aliases to assign
|
||||
assignAlias?: string; // Alias to assign to generated image
|
||||
assignFlowAlias?: Record<string, string>; // Flow-scoped aliases to assign
|
||||
autoEnhance?: boolean;
|
||||
enhancementOptions?: {
|
||||
template?: 'photorealistic' | 'illustration' | 'minimalist' | 'sticker' | 'product' | 'comic' | 'general';
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ export interface AliasResolutionResponse {
|
|||
alias: string;
|
||||
imageId: string;
|
||||
scope: AliasScope;
|
||||
flowId?: string;
|
||||
flowId?: string | undefined;
|
||||
image: ImageResponse;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue