Make postgres requests safer

This commit is contained in:
Sam Becker 2023-09-22 09:07:35 -05:00
parent 849744845c
commit 09ed9683dd
2 changed files with 60 additions and 54 deletions

View File

@ -3,8 +3,8 @@
import { revalidatePath } from 'next/cache';
import {
sqlDeletePhoto,
sqlInsertPhotoIntoDb,
sqlUpdatePhotoInDb,
sqlInsertPhoto,
sqlUpdatePhoto,
} from '@/services/postgres';
import { convertFormDataToPhoto } from './form';
import { redirect } from 'next/navigation';
@ -24,7 +24,7 @@ export async function createPhotoAction(formData: FormData) {
if (updatedUrl) { photo.url = updatedUrl; }
await sqlInsertPhotoIntoDb(photo);
await sqlInsertPhoto(photo);
revalidatePhotosTag();
@ -34,7 +34,7 @@ export async function createPhotoAction(formData: FormData) {
export async function updatePhotoAction(formData: FormData) {
const photo = convertFormDataToPhoto(formData);
await sqlUpdatePhotoInDb(photo);
await sqlUpdatePhoto(photo);
revalidatePhotosTag();

View File

@ -44,7 +44,7 @@ const sqlCreatePhotosTable = () =>
`;
// Must provide id as 8-character nanoid
export const sqlInsertPhotoIntoDb = (photo: PhotoDbInsert) => {
export const sqlInsertPhoto = (photo: PhotoDbInsert) => {
return sql`
INSERT INTO photos (
id,
@ -97,7 +97,7 @@ export const sqlInsertPhotoIntoDb = (photo: PhotoDbInsert) => {
`;
};
export const sqlUpdatePhotoInDb = (photo: PhotoDbInsert) =>
export const sqlUpdatePhoto = (photo: PhotoDbInsert) =>
sql`
UPDATE photos SET
url=${photo.url},
@ -128,7 +128,7 @@ export const sqlUpdatePhotoInDb = (photo: PhotoDbInsert) =>
export const sqlDeletePhoto = (id: string) =>
sql`DELETE FROM photos WHERE id=${id}`;
const sqlGetPhotosFromDb = (
const sqlGetPhotos = (
limit = PHOTO_DEFAULT_LIMIT,
offset = 0,
) =>
@ -138,7 +138,7 @@ const sqlGetPhotosFromDb = (
LIMIT ${limit} OFFSET ${offset}
`;
const sqlGetPhotosFromDbSortedByCreatedAt = (
const sqlGetPhotosSortedByCreatedAt = (
limit = PHOTO_DEFAULT_LIMIT,
offset = 0,
) =>
@ -148,7 +148,7 @@ const sqlGetPhotosFromDbSortedByCreatedAt = (
LIMIT ${limit} OFFSET ${offset}
`;
const sqlGetPhotosFromDbSortedByPriority = (
const sqlGetPhotosSortedByPriority = (
limit = PHOTO_DEFAULT_LIMIT,
offset = 0,
) =>
@ -158,7 +158,7 @@ const sqlGetPhotosFromDbSortedByPriority = (
LIMIT ${limit} OFFSET ${offset}
`;
const sqlGetPhotosFromDbByTag = (
const sqlGetPhotosByTag = (
limit = PHOTO_DEFAULT_LIMIT,
offset = 0,
tag: string,
@ -191,9 +191,17 @@ const sqlGetPhotosTakenBeforeDate = (
LIMIT ${limit}
`;
const sqlGetPhotoFromDb = (id: string) =>
const sqlGetPhoto = (id: string) =>
sql<PhotoDb>`SELECT * FROM photos WHERE id=${id} LIMIT 1`;
const sqlGetPhotosCount = async () => sql`
SELECT COUNT(*) FROM photos
`.then(({ rows }) => parseInt(rows[0].count, 10));
const sqlGetUniqueTags = async () => sql`
SELECT DISTINCT unnest(tags) FROM photos
`.then(({ rows }) => rows.map(row => row.unnest as string));
export type GetPhotosOptions = {
sortBy?: 'createdAt' | 'takenAt' | 'priority'
limit?: number
@ -203,6 +211,36 @@ export type GetPhotosOptions = {
takenAfterInclusive?: Date
}
const safelyQueryPhotos = async <T>(callback: () => Promise<T>): Promise<T> => {
let result: T;
try {
result = await callback();
} catch (e: any) {
if (/relation "photos" does not exist/i.test(e.message)) {
console.log(
'Creating table "photos" because it did not exist',
);
await sqlCreatePhotosTable();
result = await callback();
} else if (/endpoint is in transition/i.test(e.message)) {
// Wait 5 seconds and try again
await new Promise(resolve => setTimeout(resolve, 5000));
try {
result = await callback();
} catch (e: any) {
console.log(`sql get error on retry (after 5000ms): ${e.message} `);
throw e;
}
} else {
console.log(`sql get error: ${e.message} `);
throw e;
}
}
return result;
};
export const getPhotos = async (options: GetPhotosOptions = {}) => {
const {
sortBy = 'takenAt',
@ -213,63 +251,31 @@ export const getPhotos = async (options: GetPhotosOptions = {}) => {
takenAfterInclusive,
} = options;
let photos;
const getPhotosRequest = takenBefore
? () => sqlGetPhotosTakenBeforeDate(takenBefore, limit)
: takenAfterInclusive
? () => sqlGetPhotosTakenAfterDateInclusive(takenAfterInclusive, limit)
: tag
? () => sqlGetPhotosFromDbByTag(limit, offset, tag)
? () => sqlGetPhotosByTag(limit, offset, tag)
: sortBy === 'createdAt'
? () => sqlGetPhotosFromDbSortedByCreatedAt(limit, offset)
? () => sqlGetPhotosSortedByCreatedAt(limit, offset)
: sortBy === 'priority'
? () => sqlGetPhotosFromDbSortedByPriority(limit, offset)
: () => sqlGetPhotosFromDb(limit, offset);
? () => sqlGetPhotosSortedByPriority(limit, offset)
: () => sqlGetPhotos(limit, offset);
const getPhotosRequestAndParse = () =>
getPhotosRequest().then(({ rows }) => rows.map(parsePhotoFromDb));
try {
photos = await getPhotosRequestAndParse();
} catch (e: any) {
if (/relation "photos" does not exist/i.test(e.message)) {
console.log(
'Creating table "photos" because it did not exist',
);
await sqlCreatePhotosTable();
photos = await getPhotosRequestAndParse();
} else if (/endpoint is in transition/i.test(e.message)) {
// Wait 5 seconds and try again
await new Promise(resolve => setTimeout(resolve, 5000));
try {
photos = await getPhotosRequestAndParse();
} catch (e: any) {
console.log(`sql get error on retry (after 5000ms): ${e.message} `);
throw e;
}
} else {
console.log(`sql get error: ${e.message} `);
throw e;
}
}
return photos;
return safelyQueryPhotos(() => getPhotosRequest())
.then(({ rows }) => rows.map(parsePhotoFromDb));
};
export const getPhotosCount = async () => sql`
SELECT COUNT(*) FROM photos
`.then(({ rows }) => parseInt(rows[0].count, 10));
export const getUniqueTags = async () => sql`
SELECT DISTINCT unnest(tags) FROM photos
`.then(({ rows }) => rows.map(row => row.unnest as string));
export const getPhoto = async (id: string): Promise<Photo | undefined> => {
// Check for photo id forwarding
// and convert short ids to uuids
const photoId = translatePhotoId(id);
return sqlGetPhotoFromDb(photoId)
return safelyQueryPhotos(() => sqlGetPhoto(photoId))
.then(({ rows }) => rows.map(parsePhotoFromDb))
.then(photos => photos.length > 0 ? photos[0] : undefined);
};
export const getPhotosCount = () => safelyQueryPhotos(sqlGetPhotosCount);
export const getUniqueTags = () => safelyQueryPhotos(sqlGetUniqueTags);