fix: basic
This commit is contained in:
parent
8623442157
commit
6803a23aa3
|
|
@ -13,7 +13,7 @@ import { validateAndNormalizePagination } from '@/utils/validators';
|
||||||
import { buildPaginatedResponse } from '@/utils/helpers';
|
import { buildPaginatedResponse } from '@/utils/helpers';
|
||||||
import { toImageResponse } from '@/types/responses';
|
import { toImageResponse } from '@/types/responses';
|
||||||
import { db } from '@/db';
|
import { db } from '@/db';
|
||||||
import { flows } from '@banatie/database';
|
import { flows, type Image } from '@banatie/database';
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
import type {
|
import type {
|
||||||
UploadImageResponse,
|
UploadImageResponse,
|
||||||
|
|
@ -704,13 +704,14 @@ imagesRouter.put(
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign a project-scoped alias to an image
|
* Assign or remove a project-scoped alias from an image
|
||||||
*
|
*
|
||||||
* Sets or updates the project-scoped alias for an image:
|
* Sets, updates, or removes the project-scoped alias for an image:
|
||||||
* - Alias must start with @ symbol
|
* - Alias must start with @ symbol (when assigning)
|
||||||
* - Must be unique within the project
|
* - Must be unique within the project
|
||||||
* - Replaces existing alias if image already has one
|
* - Replaces existing alias if image already has one
|
||||||
* - Used for alias resolution in generations and CDN access
|
* - Used for alias resolution in generations and CDN access
|
||||||
|
* - Set alias to null to remove existing alias
|
||||||
*
|
*
|
||||||
* This is a dedicated endpoint introduced in Section 6.1 to separate
|
* This is a dedicated endpoint introduced in Section 6.1 to separate
|
||||||
* alias assignment from general metadata updates.
|
* alias assignment from general metadata updates.
|
||||||
|
|
@ -722,24 +723,30 @@ imagesRouter.put(
|
||||||
* @param {string} req.params.id_or_alias - Image ID (UUID) or alias (@-prefixed)
|
* @param {string} req.params.id_or_alias - Image ID (UUID) or alias (@-prefixed)
|
||||||
* @param {string} [req.query.flowId] - Flow ID for flow-scoped alias resolution
|
* @param {string} [req.query.flowId] - Flow ID for flow-scoped alias resolution
|
||||||
* @param {object} req.body - Request body
|
* @param {object} req.body - Request body
|
||||||
* @param {string} req.body.alias - Project-scoped alias (e.g., "@hero-bg")
|
* @param {string|null} req.body.alias - Project-scoped alias (e.g., "@hero-bg") or null to remove
|
||||||
*
|
*
|
||||||
* @returns {UpdateImageResponse} 200 - Updated image with new alias
|
* @returns {UpdateImageResponse} 200 - Updated image with new/removed alias
|
||||||
* @returns {object} 404 - Image not found or access denied
|
* @returns {object} 404 - Image not found or access denied
|
||||||
* @returns {object} 400 - Missing or invalid alias
|
* @returns {object} 400 - Invalid alias format
|
||||||
* @returns {object} 401 - Missing or invalid API key
|
* @returns {object} 401 - Missing or invalid API key
|
||||||
* @returns {object} 409 - Alias already exists
|
* @returns {object} 409 - Alias already exists
|
||||||
*
|
*
|
||||||
* @throws {Error} IMAGE_NOT_FOUND - Image does not exist
|
* @throws {Error} IMAGE_NOT_FOUND - Image does not exist
|
||||||
* @throws {Error} VALIDATION_ERROR - Alias is required
|
* @throws {Error} VALIDATION_ERROR - Invalid alias format
|
||||||
* @throws {Error} ALIAS_CONFLICT - Alias already assigned to another image
|
* @throws {Error} ALIAS_CONFLICT - Alias already assigned to another image
|
||||||
*
|
*
|
||||||
* @example UUID identifier
|
* @example Assign alias
|
||||||
* PUT /api/v1/images/550e8400-e29b-41d4-a716-446655440000/alias
|
* PUT /api/v1/images/550e8400-e29b-41d4-a716-446655440000/alias
|
||||||
* {
|
* {
|
||||||
* "alias": "@hero-background"
|
* "alias": "@hero-background"
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
|
* @example Remove alias
|
||||||
|
* PUT /api/v1/images/550e8400-e29b-41d4-a716-446655440000/alias
|
||||||
|
* {
|
||||||
|
* "alias": null
|
||||||
|
* }
|
||||||
|
*
|
||||||
* @example Project-scoped alias identifier
|
* @example Project-scoped alias identifier
|
||||||
* PUT /api/v1/images/@old-hero/alias
|
* PUT /api/v1/images/@old-hero/alias
|
||||||
* {
|
* {
|
||||||
|
|
@ -762,11 +769,12 @@ imagesRouter.put(
|
||||||
const { flowId } = req.query;
|
const { flowId } = req.query;
|
||||||
const { alias } = req.body;
|
const { alias } = req.body;
|
||||||
|
|
||||||
if (!alias || typeof alias !== 'string') {
|
// Validate: alias must be null (to remove) or a non-empty string
|
||||||
|
if (alias !== null && (typeof alias !== 'string' || alias.trim() === '')) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: {
|
error: {
|
||||||
message: 'Alias is required and must be a string',
|
message: 'Alias must be null (to remove) or a non-empty string',
|
||||||
code: 'VALIDATION_ERROR',
|
code: 'VALIDATION_ERROR',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -815,7 +823,16 @@ imagesRouter.put(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updated = await service.assignProjectAlias(imageId, alias);
|
// Either remove alias (null) or assign new one (override behavior per Section 5.2)
|
||||||
|
let updated: Image;
|
||||||
|
if (alias === null) {
|
||||||
|
// Remove alias
|
||||||
|
updated = await service.update(imageId, { alias: null });
|
||||||
|
} else {
|
||||||
|
// Reassign alias (clears from any existing image, then assigns to this one)
|
||||||
|
await service.reassignProjectAlias(alias, imageId, image.projectId);
|
||||||
|
updated = (await service.getById(imageId))!;
|
||||||
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ export class ImageService {
|
||||||
async update(
|
async update(
|
||||||
id: string,
|
id: string,
|
||||||
updates: {
|
updates: {
|
||||||
alias?: string;
|
alias?: string | null;
|
||||||
focalPoint?: { x: number; y: number };
|
focalPoint?: { x: number; y: number };
|
||||||
meta?: Record<string, unknown>;
|
meta?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -139,11 +139,21 @@ export async function uploadFile(
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|
||||||
// Read file
|
// Read file and detect MIME type from extension
|
||||||
const fs = await import('fs/promises');
|
const fs = await import('fs/promises');
|
||||||
|
const path = await import('path');
|
||||||
const fileBuffer = await fs.readFile(filepath);
|
const fileBuffer = await fs.readFile(filepath);
|
||||||
const blob = new Blob([fileBuffer]);
|
const ext = path.extname(filepath).toLowerCase();
|
||||||
formData.append('file', blob, 'test-image.png');
|
const mimeTypes: Record<string, string> = {
|
||||||
|
'.png': 'image/png',
|
||||||
|
'.jpg': 'image/jpeg',
|
||||||
|
'.jpeg': 'image/jpeg',
|
||||||
|
'.webp': 'image/webp',
|
||||||
|
};
|
||||||
|
const mimeType = mimeTypes[ext] || 'application/octet-stream';
|
||||||
|
const filename = path.basename(filepath);
|
||||||
|
const blob = new Blob([fileBuffer], { type: mimeType });
|
||||||
|
formData.append('file', blob, filename);
|
||||||
|
|
||||||
// Add other fields
|
// Add other fields
|
||||||
for (const [key, value] of Object.entries(fields)) {
|
for (const [key, value] of Object.entries(fields)) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue