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