Merge branch 'main' into ai-content

This commit is contained in:
Sam Becker 2024-03-19 12:46:39 -05:00
commit 6abb2e611d
4 changed files with 111 additions and 79 deletions

View File

@ -41,7 +41,7 @@
"jest-environment-jsdom": "^29.7.0",
"nanoid": "^5.0.6",
"next": "14.1.3",
"next-auth": "5.0.0-beta.13",
"next-auth": "5.0.0-beta.15",
"next-themes": "^0.3.0",
"openai": "^4.29.2",
"postcss": "8.4.35",

14
pnpm-lock.yaml generated
View File

@ -102,8 +102,8 @@ dependencies:
specifier: 14.1.3
version: 14.1.3(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)
next-auth:
specifier: 5.0.0-beta.13
version: 5.0.0-beta.13(next@14.1.3)(react@18.2.0)
specifier: 5.0.0-beta.15
version: 5.0.0-beta.15(next@14.1.3)(react@18.2.0)
next-themes:
specifier: ^0.3.0
version: 0.3.0(react-dom@18.2.0)(react@18.2.0)
@ -162,8 +162,8 @@ packages:
'@jridgewell/trace-mapping': 0.3.22
dev: false
/@auth/core@0.27.0:
resolution: {integrity: sha512-3bydnRJIM/Al6mkYmb53MsC+6G8ojw3lLPzwgVnX4dCo6N2lrib6Wq6r0vxZIhuHGjLObqqtUfpeaEj5aeTHFg==}
/@auth/core@0.28.0:
resolution: {integrity: sha512-/fh/tb/L4NMSYcyPoo4Imn8vN6MskcVfgESF8/ndgtI4fhD/7u7i5fTVzWgNRZ4ebIEGHNDbWFRxaTu1NtQgvA==}
peerDependencies:
'@simplewebauthn/browser': ^9.0.1
'@simplewebauthn/server': ^9.0.2
@ -6344,8 +6344,8 @@ packages:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
dev: false
/next-auth@5.0.0-beta.13(next@14.1.3)(react@18.2.0):
resolution: {integrity: sha512-2m2Gq69WQ0YXcHCCpHn2y5z1bxSlqD/XOuAgrdtz49/VIAdTFFeYZz97RYqf6xMF8VGmoG32VUnJ6LzaHk6Fwg==}
/next-auth@5.0.0-beta.15(next@14.1.3)(react@18.2.0):
resolution: {integrity: sha512-UQggNq8CDu3/w8CYkihKLLnRPNXel98K0j7mtjj9a6XTNYo4Hni8xg/2h1YhElW6vXE8mgtvmH11rU8NKw86jQ==}
peerDependencies:
'@simplewebauthn/browser': ^9.0.1
'@simplewebauthn/server': ^9.0.2
@ -6360,7 +6360,7 @@ packages:
nodemailer:
optional: true
dependencies:
'@auth/core': 0.27.0
'@auth/core': 0.28.0
next: 14.1.3(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
dev: false

View File

@ -44,6 +44,17 @@ export const {
},
});
export const safelyRunServerAdminAction = async <T>(
callback: () => T,
): Promise<T> => {
const session = await auth();
if (session?.user) {
return callback();
} else {
throw new Error('Unauthorized server action request');
}
};
export const generateAuthSecret = () => fetch(
'https://generate-secret.vercel.app/32',
{ cache: 'no-cache' },

View File

@ -33,47 +33,54 @@ import {
import { extractExifDataFromBlobPath } from './server';
import { TAG_FAVS, isTagFavs } from '@/tag';
import { convertPhotoToPhotoDbInsert } from '.';
import { safelyRunServerAdminAction } from '@/auth';
export async function createPhotoAction(formData: FormData) {
const photo = convertFormDataToPhotoDbInsert(formData, true);
return safelyRunServerAdminAction(async () => {
const photo = convertFormDataToPhotoDbInsert(formData, true);
const updatedUrl = await convertUploadToPhoto(photo.url, photo.id);
if (updatedUrl) { photo.url = updatedUrl; }
await sqlInsertPhoto(photo);
revalidateAllKeysAndPaths();
redirect(PATH_ADMIN_PHOTOS);
const updatedUrl = await convertUploadToPhoto(photo.url, photo.id);
if (updatedUrl) { photo.url = updatedUrl; }
await sqlInsertPhoto(photo);
revalidateAllKeysAndPaths();
redirect(PATH_ADMIN_PHOTOS);
});
}
export async function updatePhotoAction(formData: FormData) {
const photo = convertFormDataToPhotoDbInsert(formData);
return safelyRunServerAdminAction(async () => {
const photo = convertFormDataToPhotoDbInsert(formData);
await sqlUpdatePhoto(photo);
await sqlUpdatePhoto(photo);
revalidateAllKeysAndPaths();
revalidateAllKeysAndPaths();
redirect(PATH_ADMIN_PHOTOS);
redirect(PATH_ADMIN_PHOTOS);
});
}
export async function toggleFavoritePhotoAction(
photoId: string,
shouldRedirect?: boolean,
) {
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));
return safelyRunServerAdminAction(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 async function deletePhotoAction(
@ -81,82 +88,96 @@ export async function deletePhotoAction(
photoUrl: string,
shouldRedirect?: boolean,
) {
await sqlDeletePhoto(photoId).then(() => deleteStorageUrl(photoUrl));
revalidateAllKeysAndPaths();
if (shouldRedirect) {
redirect(PATH_ROOT);
}
return safelyRunServerAdminAction(async () => {
await sqlDeletePhoto(photoId).then(() => deleteStorageUrl(photoUrl));
revalidateAllKeysAndPaths();
if (shouldRedirect) {
redirect(PATH_ROOT);
}
});
};
export async function deletePhotoFormAction(formData: FormData) {
return deletePhotoAction(
formData.get('id') as string,
formData.get('url') as string,
return safelyRunServerAdminAction(async () =>
deletePhotoAction(
formData.get('id') as string,
formData.get('url') as string,
)
);
};
export async function deletePhotoTagGloballyAction(formData: FormData) {
const tag = formData.get('tag') as string;
return safelyRunServerAdminAction(async () => {
const tag = formData.get('tag') as string;
await sqlDeletePhotoTagGlobally(tag);
await sqlDeletePhotoTagGlobally(tag);
revalidatePhotosKey();
revalidateAdminPaths();
revalidatePhotosKey();
revalidateAdminPaths();
});
}
export async function renamePhotoTagGloballyAction(formData: FormData) {
const tag = formData.get('tag') as string;
const updatedTag = formData.get('updatedTag') as string;
return safelyRunServerAdminAction(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);
}
if (tag && updatedTag && tag !== updatedTag) {
await sqlRenamePhotoTagGlobally(tag, updatedTag);
revalidatePhotosKey();
revalidateTagsKey();
redirect(PATH_ADMIN_TAGS);
}
});
}
export async function deleteBlobPhotoAction(formData: FormData) {
await deleteStorageUrl(formData.get('url') as string);
return safelyRunServerAdminAction(async () => {
await deleteStorageUrl(formData.get('url') as string);
revalidateAdminPaths();
revalidateAdminPaths();
if (formData.get('redirectToPhotos') === 'true') {
redirect(PATH_ADMIN_PHOTOS);
}
if (formData.get('redirectToPhotos') === 'true') {
redirect(PATH_ADMIN_PHOTOS);
}
});
}
export async function getExifDataAction(
photoFormPrevious: Partial<PhotoFormData>,
): Promise<Partial<PhotoFormData>> {
const { url } = photoFormPrevious;
if (url) {
const { photoFormExif } = await extractExifDataFromBlobPath(url);
if (photoFormExif) {
return photoFormExif;
return safelyRunServerAdminAction(async () => {
const { url } = photoFormPrevious;
if (url) {
const { photoFormExif } = await extractExifDataFromBlobPath(url);
if (photoFormExif) {
return photoFormExif;
}
}
}
return {};
return {};
});
}
export async function syncPhotoExifDataAction(formData: FormData) {
const photoId = formData.get('id') as string;
if (photoId) {
const photo = await getPhoto(photoId);
if (photo) {
const { photoFormExif } = await extractExifDataFromBlobPath(photo.url);
if (photoFormExif) {
const photoFormDbInsert = convertFormDataToPhotoDbInsert({
...convertPhotoToFormData(photo),
...photoFormExif,
});
await sqlUpdatePhoto(photoFormDbInsert);
revalidatePhotosKey();
return safelyRunServerAdminAction(async () => {
const photoId = formData.get('id') as string;
if (photoId) {
const photo = await getPhoto(photoId);
if (photo) {
const { photoFormExif } = await extractExifDataFromBlobPath(photo.url);
if (photoFormExif) {
const photoFormDbInsert = convertFormDataToPhotoDbInsert({
...convertPhotoToFormData(photo),
...photoFormExif,
});
await sqlUpdatePhoto(photoFormDbInsert);
revalidatePhotosKey();
}
}
}
}
});
}
export async function syncCacheAction() {
revalidateAllKeysAndPaths();
return safelyRunServerAdminAction(revalidateAllKeysAndPaths);
}