229 lines
6.6 KiB
TypeScript
229 lines
6.6 KiB
TypeScript
'use server';
|
|
|
|
import {
|
|
GetPhotosOptions,
|
|
sqlDeletePhoto,
|
|
sqlInsertPhoto,
|
|
sqlDeletePhotoTagGlobally,
|
|
sqlUpdatePhoto,
|
|
sqlRenamePhotoTagGlobally,
|
|
getPhoto,
|
|
getPhotos,
|
|
} from '@/photo/db';
|
|
import {
|
|
PhotoFormData,
|
|
convertFormDataToPhotoDbInsert,
|
|
convertPhotoToFormData,
|
|
} from './form';
|
|
import { redirect } from 'next/navigation';
|
|
import {
|
|
convertUploadToPhoto,
|
|
deleteStorageUrl,
|
|
} from '@/services/storage';
|
|
import {
|
|
getPhotosCached,
|
|
getPhotosMetaCached,
|
|
revalidateAdminPaths,
|
|
revalidateAllKeysAndPaths,
|
|
revalidatePhoto,
|
|
revalidatePhotosKey,
|
|
revalidateTagsKey,
|
|
} from '@/photo/cache';
|
|
import {
|
|
PATH_ADMIN_PHOTOS,
|
|
PATH_ADMIN_TAGS,
|
|
PATH_ROOT,
|
|
pathForPhoto,
|
|
} from '@/site/paths';
|
|
import { blurImageFromUrl, extractImageDataFromBlobPath } from './server';
|
|
import { TAG_FAVS, isTagFavs } from '@/tag';
|
|
import { convertPhotoToPhotoDbInsert } from '.';
|
|
import { runAuthenticatedAdminServerAction } from '@/auth';
|
|
import { AI_IMAGE_QUERIES, AiImageQuery } from './ai';
|
|
import { streamOpenAiImageQuery } from '@/services/openai';
|
|
import { BLUR_ENABLED } from '@/site/config';
|
|
|
|
// Private actions
|
|
|
|
export const createPhotoAction = async (formData: FormData) =>
|
|
runAuthenticatedAdminServerAction(async () => {
|
|
const photo = convertFormDataToPhotoDbInsert(formData, true);
|
|
|
|
const updatedUrl = await convertUploadToPhoto(photo.url);
|
|
|
|
if (updatedUrl) {
|
|
photo.url = updatedUrl;
|
|
await sqlInsertPhoto(photo);
|
|
revalidateAllKeysAndPaths();
|
|
redirect(PATH_ADMIN_PHOTOS);
|
|
}
|
|
});
|
|
|
|
export const updatePhotoAction = async (formData: FormData) =>
|
|
runAuthenticatedAdminServerAction(async () => {
|
|
const photo = convertFormDataToPhotoDbInsert(formData);
|
|
|
|
let url: string | undefined;
|
|
if (photo.hidden && photo.url.includes(photo.id)) {
|
|
// Anonymize storage url on update if necessary by
|
|
// re-running image upload transfer logic
|
|
url = await convertUploadToPhoto(photo.url);
|
|
if (url) { photo.url = url; }
|
|
}
|
|
|
|
await sqlUpdatePhoto(photo);
|
|
|
|
revalidatePhoto(photo.id);
|
|
|
|
redirect(PATH_ADMIN_PHOTOS);
|
|
});
|
|
|
|
export const toggleFavoritePhotoAction = async (
|
|
photoId: string,
|
|
shouldRedirect?: boolean,
|
|
) =>
|
|
runAuthenticatedAdminServerAction(async () => {
|
|
const photo = await getPhoto(photoId);
|
|
if (photo) {
|
|
const { tags } = photo;
|
|
photo.tags = tags.some(tag => tag === TAG_FAVS)
|
|
? tags.filter(tag => !isTagFavs(tag))
|
|
: [...tags, TAG_FAVS];
|
|
await sqlUpdatePhoto(convertPhotoToPhotoDbInsert(photo));
|
|
revalidateAllKeysAndPaths();
|
|
if (shouldRedirect) {
|
|
redirect(pathForPhoto(photoId));
|
|
}
|
|
}
|
|
});
|
|
|
|
export const deletePhotoAction = async (
|
|
photoId: string,
|
|
photoUrl: string,
|
|
shouldRedirect?: boolean,
|
|
) =>
|
|
runAuthenticatedAdminServerAction(async () => {
|
|
await sqlDeletePhoto(photoId).then(() => deleteStorageUrl(photoUrl));
|
|
revalidateAllKeysAndPaths();
|
|
if (shouldRedirect) {
|
|
redirect(PATH_ROOT);
|
|
}
|
|
});
|
|
|
|
export const deletePhotoFormAction = async (formData: FormData) =>
|
|
runAuthenticatedAdminServerAction(() =>
|
|
deletePhotoAction(
|
|
formData.get('id') as string,
|
|
formData.get('url') as string,
|
|
)
|
|
);
|
|
|
|
export const deletePhotoTagGloballyAction = async (formData: FormData) =>
|
|
runAuthenticatedAdminServerAction(async () => {
|
|
const tag = formData.get('tag') as string;
|
|
|
|
await sqlDeletePhotoTagGlobally(tag);
|
|
|
|
revalidatePhotosKey();
|
|
revalidateAdminPaths();
|
|
});
|
|
|
|
export const renamePhotoTagGloballyAction = async (formData: FormData) =>
|
|
runAuthenticatedAdminServerAction(async () => {
|
|
const tag = formData.get('tag') as string;
|
|
const updatedTag = formData.get('updatedTag') as string;
|
|
|
|
if (tag && updatedTag && tag !== updatedTag) {
|
|
await sqlRenamePhotoTagGlobally(tag, updatedTag);
|
|
revalidatePhotosKey();
|
|
revalidateTagsKey();
|
|
redirect(PATH_ADMIN_TAGS);
|
|
}
|
|
});
|
|
|
|
export const deleteBlobPhotoAction = async (formData: FormData) =>
|
|
runAuthenticatedAdminServerAction(async () => {
|
|
await deleteStorageUrl(formData.get('url') as string);
|
|
|
|
revalidateAdminPaths();
|
|
|
|
if (formData.get('redirectToPhotos') === 'true') {
|
|
redirect(PATH_ADMIN_PHOTOS);
|
|
}
|
|
});
|
|
|
|
// Accessed from admin photo edit page
|
|
// will not update blur data
|
|
export const getExifDataAction = async (
|
|
photoFormPrevious: Partial<PhotoFormData>,
|
|
): Promise<Partial<PhotoFormData>> =>
|
|
runAuthenticatedAdminServerAction(async () => {
|
|
const { url } = photoFormPrevious;
|
|
if (url) {
|
|
const { photoFormExif } = await extractImageDataFromBlobPath(url);
|
|
if (photoFormExif) {
|
|
return photoFormExif;
|
|
}
|
|
}
|
|
return {};
|
|
});
|
|
|
|
// Accessed from admin photo table
|
|
// will update blur data
|
|
export const syncPhotoExifDataAction = async (formData: FormData) =>
|
|
runAuthenticatedAdminServerAction(async () => {
|
|
const photoId = formData.get('id') as string;
|
|
if (photoId) {
|
|
const photo = await getPhoto(photoId);
|
|
if (photo) {
|
|
const { photoFormExif } = await extractImageDataFromBlobPath(
|
|
photo.url, {
|
|
generateBlurData: BLUR_ENABLED,
|
|
});
|
|
if (photoFormExif) {
|
|
const photoFormDbInsert = convertFormDataToPhotoDbInsert({
|
|
...convertPhotoToFormData(photo),
|
|
...photoFormExif,
|
|
});
|
|
await sqlUpdatePhoto(photoFormDbInsert);
|
|
revalidatePhotosKey();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
export const syncCacheAction = async () =>
|
|
runAuthenticatedAdminServerAction(revalidateAllKeysAndPaths);
|
|
|
|
export const streamAiImageQueryAction = async (
|
|
imageBase64: string,
|
|
query: AiImageQuery,
|
|
) =>
|
|
runAuthenticatedAdminServerAction(() =>
|
|
streamOpenAiImageQuery(imageBase64, AI_IMAGE_QUERIES[query]));
|
|
|
|
export const getImageBlurAction = async (url: string) =>
|
|
runAuthenticatedAdminServerAction(() => blurImageFromUrl(url));
|
|
|
|
export const getPhotosHiddenMetaCachedAction = async () =>
|
|
runAuthenticatedAdminServerAction(() =>
|
|
getPhotosMetaCached({ hidden: 'only' }));
|
|
|
|
// Public/Private actions
|
|
|
|
export const getPhotosAction = async (options: GetPhotosOptions) =>
|
|
(options.hidden === 'include' || options.hidden === 'only')
|
|
? runAuthenticatedAdminServerAction(() => getPhotos(options))
|
|
: getPhotos(options);
|
|
|
|
export const getPhotosCachedAction = async (options: GetPhotosOptions) =>
|
|
(options.hidden === 'include' || options.hidden === 'only')
|
|
? runAuthenticatedAdminServerAction(() => getPhotosCached (options))
|
|
: getPhotosCached(options);
|
|
|
|
// Public actions
|
|
|
|
export const queryPhotosByTitleAction = async (query: string) =>
|
|
(await getPhotos({ query, limit: 10 }))
|
|
.filter(({ title }) => Boolean(title));
|