mono/packages/ui/src/components/ImageWizard/db.ts

239 lines
7.1 KiB
TypeScript

/**
* Database operations for ImageWizard
* Delegates to client-pictures.ts and client-user.ts API layer
*/
import { toast } from "sonner";
import { translate } from "@/i18n";
import {
createPicture,
updatePicture,
fetchPictureById,
uploadFileToStorage,
addCollectionPictures,
} from "@/modules/posts/client-pictures";
import { getUserOpenAIKey } from "@/modules/user/client-user";
// Re-export for backward compat
export { getUserOpenAIKey };
/**
* Upload image blob to storage
*/
export const uploadImageToStorage = async (
userId: string,
blob: Blob,
suffix: string = 'generated'
): Promise<{ fileName: string; publicUrl: string } | null> => {
const fileName = `${userId}/${Date.now()}-${suffix}.png`;
const file = new File([blob], fileName, { type: 'image/png' });
const publicUrl = await uploadFileToStorage(userId, file, fileName);
return { fileName, publicUrl };
};
/**
* Create new picture in database
*/
export const createPictureRecord = async (params: {
userId: string;
title: string | null;
description: string | null;
imageUrl: string;
parentId?: string | null;
isSelected?: boolean;
}): Promise<{ id: string } | null> => {
return createPicture({
title: params.title?.trim() || null,
description: params.description || null,
image_url: params.imageUrl,
user_id: params.userId,
parent_id: params.parentId || null,
is_selected: params.isSelected ?? false,
} as any);
};
/**
* Add picture to collections
*/
export const addPictureToCollections = async (
pictureId: string,
collectionIds: string[]
): Promise<boolean> => {
try {
const inserts = collectionIds.map(collectionId => ({
collection_id: collectionId,
picture_id: pictureId
}));
await addCollectionPictures(inserts);
return true;
} catch (error) {
console.error('Error adding to collections:', error);
return false;
}
};
/**
* Unselect all images in a family (root and all versions)
* Note: This uses updatePicture for each, since there's no batch-by-filter API
*/
export const unselectImageFamily = async (
rootParentId: string,
userId: string
): Promise<void> => {
// We need to update the root and all children — fetch them first
// For now, update just the root; the version publish flow handles the rest
try {
await updatePicture(rootParentId, { is_selected: false } as any);
} catch (error) {
console.error('Error unselecting image family:', error);
throw error;
}
};
/**
* Check selection status of an image
*/
export const getImageSelectionStatus = async (imageId: string): Promise<boolean> => {
try {
const data = await fetchPictureById(imageId);
return data?.is_selected || false;
} catch (error) {
console.error('Error getting image selection status:', error);
return false;
}
};
/**
* Publish image as new post
*/
export const publishImageAsNew = async (params: {
userId: string;
blob: Blob;
title: string;
description?: string;
isOrgContext: boolean;
orgSlug?: string;
collectionIds?: string[];
}): Promise<void> => {
const { userId, blob, title, description, collectionIds } = params;
// Upload to storage
const uploadResult = await uploadImageToStorage(userId, blob, 'generated');
if (!uploadResult) throw new Error('Failed to upload image');
// Create picture record
const pictureData = await createPictureRecord({
userId,
title,
description: description || null,
imageUrl: uploadResult.publicUrl,
});
if (!pictureData) throw new Error('Failed to create picture record');
// Add to collections if specified
if (collectionIds && collectionIds.length > 0) {
const success = await addPictureToCollections(pictureData.id, collectionIds);
if (success) {
toast.success(translate(`Image published and added to ${collectionIds.length} collection(s)!`));
} else {
toast.error(translate('Image published but failed to add to collections'));
}
} else {
toast.success(translate('Image published to gallery!'));
}
};
/**
* Publish image as version of existing image
*/
export const publishImageAsVersion = async (params: {
userId: string;
blob: Blob;
title: string;
description?: string;
parentId: string;
isOrgContext: boolean;
orgSlug?: string;
collectionIds?: string[];
}): Promise<void> => {
const { userId, blob, title, description, parentId, collectionIds } = params;
// Upload to storage
const uploadResult = await uploadImageToStorage(userId, blob, 'version');
if (!uploadResult) throw new Error('Failed to upload image');
// Unselect all images in the family first
const rootParentId = parentId && parentId.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i) ? parentId : null;
if (rootParentId) {
await unselectImageFamily(rootParentId, userId);
}
// Create version record (selected by default)
const pictureData = await createPictureRecord({
userId,
title,
description: description || null,
imageUrl: uploadResult.publicUrl,
parentId: rootParentId,
isSelected: true,
});
if (!pictureData) throw new Error('Failed to create version record');
// Add to collections if specified
if (collectionIds && collectionIds.length > 0) {
const success = await addPictureToCollections(pictureData.id, collectionIds);
if (success) {
toast.success(translate(`Version saved and added to ${collectionIds.length} collection(s)!`));
} else {
toast.error(translate('Version saved but failed to add to collections'));
}
} else {
toast.success(translate('Version saved successfully!'));
}
};
/**
* Publish image directly to an existing post
*/
export const publishImageToPost = async (params: {
userId: string;
blob: Blob;
title: string;
description?: string;
postId: string;
isOrgContext: boolean;
orgSlug?: string;
collectionIds?: string[];
}): Promise<void> => {
const { userId, blob, title, description, postId, collectionIds } = params;
// Upload to storage
const uploadResult = await uploadImageToStorage(userId, blob, 'post-add');
if (!uploadResult) throw new Error('Failed to upload image');
// Create picture record attached to post with position
const pictureData = await createPicture({
title: title,
description: description || null,
image_url: uploadResult.publicUrl,
user_id: userId,
post_id: postId,
is_selected: true,
} as any);
if (!pictureData) throw new Error('Failed to create picture record');
// Add to collections if specified
if (collectionIds && collectionIds.length > 0) {
const success = await addPictureToCollections(pictureData.id, collectionIds);
if (success) {
toast.success(translate(`Image added to post and ${collectionIds.length} collection(s)!`));
} else {
toast.error(translate('Image added to post but failed to add to collections'));
}
} else {
toast.success(translate('Image added to post successfully!'));
}
};