diff --git a/apps/api-service/src/routes/v1/generations.ts b/apps/api-service/src/routes/v1/generations.ts index 7574856..9a862bd 100644 --- a/apps/api-service/src/routes/v1/generations.ts +++ b/apps/api-service/src/routes/v1/generations.ts @@ -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, diff --git a/apps/api-service/src/routes/v1/images.ts b/apps/api-service/src/routes/v1/images.ts index 1a962ea..cf31e1e 100644 --- a/apps/api-service/src/routes/v1/images.ts +++ b/apps/api-service/src/routes/v1/images.ts @@ -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) => { + 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 diff --git a/apps/api-service/src/routes/v1/live.ts b/apps/api-service/src/routes/v1/live.ts index 342f503..eea1faf 100644 --- a/apps/api-service/src/routes/v1/live.ts +++ b/apps/api-service/src/routes/v1/live.ts @@ -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, diff --git a/apps/api-service/src/services/core/GenerationService.ts b/apps/api-service/src/services/core/GenerationService.ts index 8c674d4..0ef30ff 100644 --- a/apps/api-service/src/services/core/GenerationService.ts +++ b/apps/api-service/src/services/core/GenerationService.ts @@ -21,8 +21,8 @@ export interface CreateGenerationParams { referenceImages?: string[] | undefined; // Aliases to resolve aspectRatio?: string | undefined; flowId?: string | undefined; - outputAlias?: string | undefined; - flowAliases?: Record | undefined; + assignAlias?: string | undefined; + assignFlowAlias?: Record | undefined; autoEnhance?: boolean | undefined; enhancedPrompt?: string | undefined; meta?: Record | 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) { diff --git a/apps/api-service/src/services/core/ImageService.ts b/apps/api-service/src/services/core/ImageService.ts index 563f020..d6e7ece 100644 --- a/apps/api-service/src/services/core/ImageService.ts +++ b/apps/api-service/src/services/core/ImageService.ts @@ -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; } diff --git a/apps/api-service/src/types/requests.ts b/apps/api-service/src/types/requests.ts index 1b757fd..f397874 100644 --- a/apps/api-service/src/types/requests.ts +++ b/apps/api-service/src/types/requests.ts @@ -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; // Flow-scoped aliases to assign + assignAlias?: string; // Alias to assign to generated image + assignFlowAlias?: Record; // Flow-scoped aliases to assign autoEnhance?: boolean; enhancementOptions?: { template?: 'photorealistic' | 'illustration' | 'minimalist' | 'sticker' | 'product' | 'comic' | 'general'; diff --git a/apps/api-service/src/types/responses.ts b/apps/api-service/src/types/responses.ts index f230681..dd14bf1 100644 --- a/apps/api-service/src/types/responses.ts +++ b/apps/api-service/src/types/responses.ts @@ -89,7 +89,7 @@ export interface AliasResolutionResponse { alias: string; imageId: string; scope: AliasScope; - flowId?: string; + flowId?: string | undefined; image: ImageResponse; }