Compare commits
3 Commits
39781de4e8
...
b0e6304e07
| Author | SHA1 | Date |
|---|---|---|
|
|
b0e6304e07 | |
|
|
7e04fcbbb0 | |
|
|
0a42a32817 |
|
|
@ -422,6 +422,8 @@ cdnRouter.get(
|
||||||
const generation = await genService.create({
|
const generation = await genService.create({
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
apiKeyId: null as unknown as string, // System generation for live URLs
|
apiKeyId: null as unknown as string, // System generation for live URLs
|
||||||
|
organizationSlug: orgSlug,
|
||||||
|
projectSlug: projectSlug,
|
||||||
prompt,
|
prompt,
|
||||||
aspectRatio: (aspectRatio as string) || GENERATION_LIMITS.DEFAULT_ASPECT_RATIO,
|
aspectRatio: (aspectRatio as string) || GENERATION_LIMITS.DEFAULT_ASPECT_RATIO,
|
||||||
autoEnhance: normalizedAutoEnhance,
|
autoEnhance: normalizedAutoEnhance,
|
||||||
|
|
|
||||||
|
|
@ -40,11 +40,11 @@ uploadRouter.post(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract org/project slugs from validated API key
|
// Extract org/project slugs from validated API key
|
||||||
const orgId = req.apiKey?.organizationSlug || 'default';
|
const orgSlug = req.apiKey?.organizationSlug || process.env['DEFAULT_ORG_SLUG'] || 'default';
|
||||||
const projectId = req.apiKey?.projectSlug!; // Guaranteed by requireProjectKey middleware
|
const projectSlug = req.apiKey?.projectSlug || process.env['DEFAULT_PROJECT_SLUG'] || 'main'; // Guaranteed by requireProjectKey middleware
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`[${timestamp}] [${requestId}] Starting file upload for org:${orgId}, project:${projectId}`,
|
`[${timestamp}] [${requestId}] Starting file upload for org:${orgSlug}, project:${projectSlug}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const file = req.file;
|
const file = req.file;
|
||||||
|
|
@ -59,8 +59,8 @@ uploadRouter.post(
|
||||||
);
|
);
|
||||||
|
|
||||||
const uploadResult = await storageService.uploadFile(
|
const uploadResult = await storageService.uploadFile(
|
||||||
orgId,
|
orgSlug,
|
||||||
projectId,
|
projectSlug,
|
||||||
'uploads',
|
'uploads',
|
||||||
file.originalname,
|
file.originalname,
|
||||||
file.buffer,
|
file.buffer,
|
||||||
|
|
|
||||||
|
|
@ -114,10 +114,14 @@ generationsRouter.post(
|
||||||
|
|
||||||
const projectId = req.apiKey.projectId;
|
const projectId = req.apiKey.projectId;
|
||||||
const apiKeyId = req.apiKey.id;
|
const apiKeyId = req.apiKey.id;
|
||||||
|
const organizationSlug = req.apiKey.organizationSlug || process.env['DEFAULT_ORG_SLUG'] || 'default';
|
||||||
|
const projectSlug = req.apiKey.projectSlug || process.env['DEFAULT_PROJECT_SLUG'] || 'main';
|
||||||
|
|
||||||
const generation = await service.create({
|
const generation = await service.create({
|
||||||
projectId,
|
projectId,
|
||||||
apiKeyId,
|
apiKeyId,
|
||||||
|
organizationSlug,
|
||||||
|
projectSlug,
|
||||||
prompt,
|
prompt,
|
||||||
referenceImages,
|
referenceImages,
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,8 @@ liveRouter.get(
|
||||||
|
|
||||||
const projectId = req.apiKey.projectId;
|
const projectId = req.apiKey.projectId;
|
||||||
const apiKeyId = req.apiKey.id;
|
const apiKeyId = req.apiKey.id;
|
||||||
|
const organizationSlug = req.apiKey.organizationSlug || process.env['DEFAULT_ORG_SLUG'] || 'default';
|
||||||
|
const projectSlug = req.apiKey.projectSlug || process.env['DEFAULT_PROJECT_SLUG'] || 'main';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Compute prompt hash for cache lookup
|
// Compute prompt hash for cache lookup
|
||||||
|
|
@ -122,6 +124,8 @@ liveRouter.get(
|
||||||
const generation = await genService.create({
|
const generation = await genService.create({
|
||||||
projectId,
|
projectId,
|
||||||
apiKeyId,
|
apiKeyId,
|
||||||
|
organizationSlug,
|
||||||
|
projectSlug,
|
||||||
prompt,
|
prompt,
|
||||||
aspectRatio: (aspectRatio as string) || GENERATION_LIMITS.DEFAULT_ASPECT_RATIO,
|
aspectRatio: (aspectRatio as string) || GENERATION_LIMITS.DEFAULT_ASPECT_RATIO,
|
||||||
requestId: req.requestId,
|
requestId: req.requestId,
|
||||||
|
|
|
||||||
|
|
@ -268,6 +268,13 @@ export class MinioStorageService implements StorageService {
|
||||||
await this.client.removeObject(this.bucketName, filePath);
|
await this.client.removeObject(this.bucketName, filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get public URL for file access
|
||||||
|
* Returns CDN URL if MINIO_PUBLIC_URL is configured (production),
|
||||||
|
* otherwise falls back to API endpoint URL (development)
|
||||||
|
*
|
||||||
|
* @returns {string} URL for accessing the file
|
||||||
|
*/
|
||||||
getPublicUrl(
|
getPublicUrl(
|
||||||
orgId: string,
|
orgId: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
|
|
@ -275,9 +282,21 @@ export class MinioStorageService implements StorageService {
|
||||||
filename: string,
|
filename: string,
|
||||||
): string {
|
): string {
|
||||||
this.validateFilePath(orgId, projectId, category, filename);
|
this.validateFilePath(orgId, projectId, category, filename);
|
||||||
// Production-ready: Return API URL for presigned URL access
|
|
||||||
|
// If MINIO_PUBLIC_URL is configured, use direct CDN access
|
||||||
|
// This provides better performance and reduces API server load
|
||||||
|
if (this.publicUrl && process.env['USE_DIRECT_CDN'] !== 'false') {
|
||||||
|
const filePath = this.getFilePath(orgId, projectId, category, filename);
|
||||||
|
const cdnUrl = `${this.publicUrl}/${this.bucketName}/${filePath}`;
|
||||||
|
console.log(`[MinIO] Using CDN URL: ${cdnUrl}`);
|
||||||
|
return cdnUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to API URL for local development or when CDN is disabled
|
||||||
const apiBaseUrl = process.env['API_BASE_URL'] || 'http://localhost:3000';
|
const apiBaseUrl = process.env['API_BASE_URL'] || 'http://localhost:3000';
|
||||||
return `${apiBaseUrl}/api/images/${orgId}/${projectId}/${category}/${filename}`;
|
const apiUrl = `${apiBaseUrl}/api/images/${orgId}/${projectId}/${category}/${filename}`;
|
||||||
|
console.log(`[MinIO] Using API URL: ${apiUrl}`);
|
||||||
|
return apiUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPresignedUploadUrl(
|
async getPresignedUploadUrl(
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
import { eq, desc, count, and, isNull, inArray } from 'drizzle-orm';
|
import { eq, desc, count, and, isNull, inArray } from 'drizzle-orm';
|
||||||
import { db } from '@/db';
|
import { db } from '@/db';
|
||||||
import { generations, flows, images } from '@banatie/database';
|
import { generations, flows, images, projects } from '@banatie/database';
|
||||||
import type {
|
import type {
|
||||||
Generation,
|
Generation,
|
||||||
NewGeneration,
|
NewGeneration,
|
||||||
|
|
@ -20,6 +20,8 @@ import type { ReferenceImage } from '@/types/api';
|
||||||
export interface CreateGenerationParams {
|
export interface CreateGenerationParams {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
apiKeyId: string;
|
apiKeyId: string;
|
||||||
|
organizationSlug: string; // For storage paths (orgSlug/projectSlug/category/file)
|
||||||
|
projectSlug: string; // For storage paths
|
||||||
prompt: string;
|
prompt: string;
|
||||||
referenceImages?: string[] | undefined; // Aliases to resolve
|
referenceImages?: string[] | undefined; // Aliases to resolve
|
||||||
aspectRatio?: string | undefined;
|
aspectRatio?: string | undefined;
|
||||||
|
|
@ -151,8 +153,8 @@ export class GenerationService {
|
||||||
filename: `gen_${generation.id}`,
|
filename: `gen_${generation.id}`,
|
||||||
referenceImages: referenceImageBuffers,
|
referenceImages: referenceImageBuffers,
|
||||||
aspectRatio: params.aspectRatio || GENERATION_LIMITS.DEFAULT_ASPECT_RATIO,
|
aspectRatio: params.aspectRatio || GENERATION_LIMITS.DEFAULT_ASPECT_RATIO,
|
||||||
orgId: 'default',
|
orgId: params.organizationSlug, // Use slug for storage path
|
||||||
projectId: params.projectId,
|
projectId: params.projectSlug, // Use slug for storage path
|
||||||
meta: params.meta || {},
|
meta: params.meta || {},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -377,6 +379,27 @@ export class GenerationService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get organization and project slugs for storage paths
|
||||||
|
*/
|
||||||
|
private async getSlugs(projectId: string): Promise<{ orgSlug: string; projectSlug: string }> {
|
||||||
|
const project = await db.query.projects.findFirst({
|
||||||
|
where: eq(projects.id, projectId),
|
||||||
|
with: {
|
||||||
|
organization: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!project) {
|
||||||
|
throw new Error('Project not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
orgSlug: project.organization.slug,
|
||||||
|
projectSlug: project.slug,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async updateStatus(
|
private async updateStatus(
|
||||||
id: string,
|
id: string,
|
||||||
status: 'pending' | 'processing' | 'success' | 'failed',
|
status: 'pending' | 'processing' | 'success' | 'failed',
|
||||||
|
|
@ -491,14 +514,17 @@ export class GenerationService {
|
||||||
// Update status to processing
|
// Update status to processing
|
||||||
await this.updateStatus(id, 'processing');
|
await this.updateStatus(id, 'processing');
|
||||||
|
|
||||||
|
// Get slugs for storage paths
|
||||||
|
const { orgSlug, projectSlug } = await this.getSlugs(generation.projectId);
|
||||||
|
|
||||||
// Use EXACT same parameters as original (no overrides)
|
// Use EXACT same parameters as original (no overrides)
|
||||||
const genResult = await this.imageGenService.generateImage({
|
const genResult = await this.imageGenService.generateImage({
|
||||||
prompt: generation.prompt,
|
prompt: generation.prompt,
|
||||||
filename: `gen_${id}`,
|
filename: `gen_${id}`,
|
||||||
referenceImages: [], // TODO: Re-resolve referenced images if needed
|
referenceImages: [], // TODO: Re-resolve referenced images if needed
|
||||||
aspectRatio: generation.aspectRatio || GENERATION_LIMITS.DEFAULT_ASPECT_RATIO,
|
aspectRatio: generation.aspectRatio || GENERATION_LIMITS.DEFAULT_ASPECT_RATIO,
|
||||||
orgId: 'default',
|
orgId: orgSlug,
|
||||||
projectId: generation.projectId,
|
projectId: projectSlug,
|
||||||
meta: generation.meta as Record<string, unknown> || {},
|
meta: generation.meta as Record<string, unknown> || {},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -605,14 +631,17 @@ export class GenerationService {
|
||||||
const promptToUse = updates.prompt || generation.prompt;
|
const promptToUse = updates.prompt || generation.prompt;
|
||||||
const aspectRatioToUse = updates.aspectRatio || generation.aspectRatio || GENERATION_LIMITS.DEFAULT_ASPECT_RATIO;
|
const aspectRatioToUse = updates.aspectRatio || generation.aspectRatio || GENERATION_LIMITS.DEFAULT_ASPECT_RATIO;
|
||||||
|
|
||||||
|
// Get slugs for storage paths
|
||||||
|
const { orgSlug, projectSlug } = await this.getSlugs(generation.projectId);
|
||||||
|
|
||||||
// Regenerate image
|
// Regenerate image
|
||||||
const genResult = await this.imageGenService.generateImage({
|
const genResult = await this.imageGenService.generateImage({
|
||||||
prompt: promptToUse,
|
prompt: promptToUse,
|
||||||
filename: `gen_${id}`,
|
filename: `gen_${id}`,
|
||||||
referenceImages: [],
|
referenceImages: [],
|
||||||
aspectRatio: aspectRatioToUse,
|
aspectRatio: aspectRatioToUse,
|
||||||
orgId: 'default',
|
orgId: orgSlug,
|
||||||
projectId: generation.projectId,
|
projectId: projectSlug,
|
||||||
meta: updates.meta || generation.meta || {},
|
meta: updates.meta || generation.meta || {},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,11 @@ STORAGE_TYPE=minio
|
||||||
# Public URL for CDN access (used in API responses)
|
# Public URL for CDN access (used in API responses)
|
||||||
MINIO_PUBLIC_URL=https://cdn.banatie.app
|
MINIO_PUBLIC_URL=https://cdn.banatie.app
|
||||||
|
|
||||||
|
# Use direct CDN URLs instead of API proxy (recommended for production)
|
||||||
|
# Set to 'false' to force API URLs even when MINIO_PUBLIC_URL is configured
|
||||||
|
# Default: true (CDN enabled when MINIO_PUBLIC_URL is present)
|
||||||
|
USE_DIRECT_CDN=true
|
||||||
|
|
||||||
# ----------------------------------------
|
# ----------------------------------------
|
||||||
# API Configuration
|
# API Configuration
|
||||||
# ----------------------------------------
|
# ----------------------------------------
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue