Add recipe to db model, refactor migrations
This commit is contained in:
parent
64a49c85a3
commit
faad28e6f7
40
src/photo/db/migration.ts
Normal file
40
src/photo/db/migration.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { sql } from '@/platforms/postgres';
|
||||
|
||||
interface Migration {
|
||||
label: string
|
||||
fields: string[]
|
||||
run: () => ReturnType<typeof sql>
|
||||
}
|
||||
|
||||
export const MIGRATIONS: Migration[] = [{
|
||||
label: '01: AI Text Generation',
|
||||
fields: ['caption', 'semantic_description'],
|
||||
run: () => sql`
|
||||
ALTER TABLE photos
|
||||
ADD COLUMN IF NOT EXISTS caption TEXT,
|
||||
ADD COLUMN IF NOT EXISTS semantic_description TEXT
|
||||
`,
|
||||
}, {
|
||||
label: '02: Lens Metadata',
|
||||
fields: ['lens_make', 'lens_model'],
|
||||
run: () => sql`
|
||||
ALTER TABLE photos
|
||||
ADD COLUMN IF NOT EXISTS lens_make VARCHAR(255),
|
||||
ADD COLUMN IF NOT EXISTS lens_model VARCHAR(255)
|
||||
`,
|
||||
}, {
|
||||
label: '03: Fujifilm Recipe',
|
||||
fields: ['fujifilm_recipe'],
|
||||
run: () => sql`
|
||||
ALTER TABLE photos
|
||||
ADD COLUMN IF NOT EXISTS fujifilm_recipe JSONB
|
||||
`,
|
||||
}];
|
||||
|
||||
export const migrationForError = (e: any) =>
|
||||
MIGRATIONS.find(migration =>
|
||||
migration.fields.some(field =>
|
||||
new RegExp(`column "${field}" of relation "photos" does not exist`, 'i')
|
||||
.test(e.message),
|
||||
),
|
||||
);
|
||||
@ -23,6 +23,7 @@ import {
|
||||
import { getWheresFromOptions } from '.';
|
||||
import { FocalLengths } from '@/focal';
|
||||
import { Lenses, createLensKey } from '@/lens';
|
||||
import { migrationForError } from './migration';
|
||||
|
||||
const createPhotosTable = () =>
|
||||
sql`
|
||||
@ -50,6 +51,7 @@ const createPhotosTable = () =>
|
||||
latitude DOUBLE PRECISION,
|
||||
longitude DOUBLE PRECISION,
|
||||
film_simulation VARCHAR(255),
|
||||
fujifilm_recipe JSONB,
|
||||
priority_order REAL,
|
||||
taken_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
taken_at_naive VARCHAR(255) NOT NULL,
|
||||
@ -59,24 +61,6 @@ const createPhotosTable = () =>
|
||||
)
|
||||
`;
|
||||
|
||||
// Migration 01
|
||||
const MIGRATION_FIELDS_01 = ['caption', 'semantic_description'];
|
||||
const runMigration01 = () =>
|
||||
sql`
|
||||
ALTER TABLE photos
|
||||
ADD COLUMN IF NOT EXISTS caption TEXT,
|
||||
ADD COLUMN IF NOT EXISTS semantic_description TEXT
|
||||
`;
|
||||
|
||||
// Migration 02
|
||||
const MIGRATION_FIELDS_02 = ['lens_make', 'lens_model'];
|
||||
const runMigration02 = () =>
|
||||
sql`
|
||||
ALTER TABLE photos
|
||||
ADD COLUMN IF NOT EXISTS lens_make VARCHAR(255),
|
||||
ADD COLUMN IF NOT EXISTS lens_model VARCHAR(255)
|
||||
`;
|
||||
|
||||
// Wrapper for most queries for JIT table creation/migration running
|
||||
const safelyQueryPhotos = async <T>(
|
||||
callback: () => Promise<T>,
|
||||
@ -89,19 +73,10 @@ const safelyQueryPhotos = async <T>(
|
||||
try {
|
||||
result = await callback();
|
||||
} catch (e: any) {
|
||||
if (MIGRATION_FIELDS_01.some(field => new RegExp(
|
||||
`column "${field}" of relation "photos" does not exist`,
|
||||
'i',
|
||||
).test(e.message))) {
|
||||
console.log('Running migration 01 ...');
|
||||
await runMigration01();
|
||||
result = await callback();
|
||||
} else if (MIGRATION_FIELDS_02.some(field => new RegExp(
|
||||
`column "${field}" of relation "photos" does not exist`,
|
||||
'i',
|
||||
).test(e.message))) {
|
||||
console.log('Running migration 02 ...');
|
||||
await runMigration02();
|
||||
const migration = migrationForError(e);
|
||||
if (migration) {
|
||||
console.log(`Running Migration ${migration.label} ...`);
|
||||
await migration.run();
|
||||
result = await callback();
|
||||
} else if (/relation "photos" does not exist/i.test(e.message)) {
|
||||
// If the table does not exist, create it
|
||||
@ -163,6 +138,7 @@ export const insertPhoto = (photo: PhotoDbInsert) =>
|
||||
latitude,
|
||||
longitude,
|
||||
film_simulation,
|
||||
fujifilm_recipe,
|
||||
priority_order,
|
||||
hidden,
|
||||
taken_at,
|
||||
@ -192,6 +168,7 @@ export const insertPhoto = (photo: PhotoDbInsert) =>
|
||||
${photo.latitude},
|
||||
${photo.longitude},
|
||||
${photo.filmSimulation},
|
||||
${JSON.stringify(photo.fujifilmRecipe)},
|
||||
${photo.priorityOrder},
|
||||
${photo.hidden},
|
||||
${photo.takenAt},
|
||||
@ -224,6 +201,7 @@ export const updatePhoto = (photo: PhotoDbInsert) =>
|
||||
latitude=${photo.latitude},
|
||||
longitude=${photo.longitude},
|
||||
film_simulation=${photo.filmSimulation},
|
||||
fujifilm_recipe=${JSON.stringify(photo.fujifilmRecipe)},
|
||||
priority_order=${photo.priorityOrder || null},
|
||||
hidden=${photo.hidden},
|
||||
taken_at=${photo.takenAt},
|
||||
|
||||
@ -23,6 +23,7 @@ import { FilmSimulation } from '@/simulation';
|
||||
import { GEO_PRIVACY_ENABLED } from '@/app/config';
|
||||
import { TAG_FAVS, getValidationMessageForTags } from '@/tag';
|
||||
import { MAKE_FUJIFILM } from '@/platforms/fujifilm';
|
||||
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
|
||||
|
||||
type VirtualFields = 'favorite';
|
||||
|
||||
@ -107,6 +108,11 @@ const FORM_METADATA = (
|
||||
selectOptionsDefaultLabel: 'Unknown',
|
||||
shouldHide: ({ make }) => make !== MAKE_FUJIFILM,
|
||||
},
|
||||
fujifilmRecipe: {
|
||||
type: 'textarea',
|
||||
label: 'fujifilm recipe',
|
||||
shouldHide: ({ make }) => make !== MAKE_FUJIFILM,
|
||||
},
|
||||
focalLength: { label: 'focal length' },
|
||||
focalLengthIn35MmFormat: { label: 'focal length 35mm-equivalent' },
|
||||
lensMake: { label: 'lens make' },
|
||||
@ -200,6 +206,7 @@ export const convertPhotoToFormData = (
|
||||
export const convertExifToFormData = (
|
||||
data: ExifData,
|
||||
filmSimulation?: FilmSimulation,
|
||||
fujifilmRecipe?: Partial<FujifilmRecipe>,
|
||||
): Omit<
|
||||
Record<keyof PhotoExif, string | undefined>,
|
||||
'takenAt' | 'takenAtNaive'
|
||||
@ -223,6 +230,7 @@ export const convertExifToFormData = (
|
||||
longitude:
|
||||
!GEO_PRIVACY_ENABLED ? data.tags?.GPSLongitude?.toString() : undefined,
|
||||
filmSimulation,
|
||||
fujifilmRecipe: JSON.stringify(fujifilmRecipe),
|
||||
...data.tags?.DateTimeOriginal && {
|
||||
takenAt: convertTimestampWithOffsetToPostgresString(
|
||||
data.tags.DateTimeOriginal,
|
||||
@ -267,7 +275,10 @@ export const convertFormDataToPhotoDbInsert = (
|
||||
});
|
||||
|
||||
return {
|
||||
...(photoForm as PhotoFormData & { filmSimulation?: FilmSimulation }),
|
||||
...(photoForm as PhotoFormData & {
|
||||
filmSimulation?: FilmSimulation
|
||||
fujifilmRecipe?: FujifilmRecipe
|
||||
}),
|
||||
...!photoForm.id && { id: generateNanoid() },
|
||||
// Delete array field when empty
|
||||
tags: tags.length > 0 ? tags : undefined,
|
||||
|
||||
@ -20,6 +20,7 @@ import { parameterize } from '@/utility/string';
|
||||
import camelcaseKeys from 'camelcase-keys';
|
||||
import { isBefore } from 'date-fns';
|
||||
import type { Metadata } from 'next';
|
||||
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
|
||||
|
||||
export const OUTDATED_THRESHOLD = new Date('2024-06-16');
|
||||
|
||||
@ -66,6 +67,7 @@ export interface PhotoExif {
|
||||
latitude?: number
|
||||
longitude?: number
|
||||
filmSimulation?: FilmSimulation
|
||||
fujifilmRecipe?: string
|
||||
takenAt?: string
|
||||
takenAtNaive?: string
|
||||
}
|
||||
@ -88,11 +90,13 @@ export interface PhotoDbInsert extends PhotoExif {
|
||||
}
|
||||
|
||||
// Raw db response
|
||||
export interface PhotoDb extends Omit<PhotoDbInsert, 'takenAt' | 'tags'> {
|
||||
export interface PhotoDb extends
|
||||
Omit<PhotoDbInsert, 'takenAt' | 'tags' | 'fujifilmRecipe'> {
|
||||
updatedAt: Date
|
||||
createdAt: Date
|
||||
takenAt: Date
|
||||
tags: string[]
|
||||
fujifilmRecipe?: Partial<FujifilmRecipe>
|
||||
}
|
||||
|
||||
// Parsed db response
|
||||
@ -159,6 +163,7 @@ export const convertPhotoToPhotoDbInsert = (
|
||||
): PhotoDbInsert => ({
|
||||
...photo,
|
||||
takenAt: photo.takenAt.toISOString(),
|
||||
fujifilmRecipe: JSON.stringify(photo.fujifilmRecipe),
|
||||
});
|
||||
|
||||
export const photoStatsAsString = (photo: Photo) => [
|
||||
|
||||
@ -83,7 +83,6 @@ export const extractImageDataFromBlobPath = async (
|
||||
if (Buffer.isBuffer(makerNote)) {
|
||||
filmSimulation = getFujifilmSimulationFromMakerNote(makerNote);
|
||||
recipe = getFujifilmRecipeFromMakerNote(makerNote);
|
||||
console.log(recipe);
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,7 +116,7 @@ export const extractImageDataFromBlobPath = async (
|
||||
url,
|
||||
},
|
||||
...generateBlurData && { blurData },
|
||||
...convertExifToFormData(exifData, filmSimulation),
|
||||
...convertExifToFormData(exifData, filmSimulation, recipe),
|
||||
},
|
||||
},
|
||||
imageResizedBase64,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user