fix: update database schema for Immich v1.99+ compatibility

- Rename album_asset columns: albumsId -> albumId, assetsId -> assetId
- Remove sidecarPath field (no longer exists in Immich v2.x schema)
- Update all API files referencing these columns

Fixes #214, #212

Tested with Immich v2.4.1
This commit is contained in:
Jed Esposito committed 2026-01-14 14:20:38 +13:00
1 parent 5af9e85f16
commit 4334d03464
20 files changed
+42 -43

No files matched your search

+3 -3
View File
@@ -36,7 +36,7 @@ export default async function handler(
duration: assets.duration,
encodedVideoPath: assets.encodedVideoPath,
originalFileName: assets.originalFileName,
sidecarPath: assets.sidecarPath,
deletedAt: assets.deletedAt,
localDateTime: assets.localDateTime,
exifImageWidth: exif.exifImageWidth,
@@ -46,12 +46,12 @@ export default async function handler(
orientation: exif.orientation,
})
.from(albumsAssetsAssets)
.leftJoin(assets, eq(albumsAssetsAssets.assetsId, assets.id))
.leftJoin(assets, eq(albumsAssetsAssets.assetId, assets.id))
.leftJoin(exif, eq(assets.id, exif.assetId))
.leftJoin(assetFaces, eq(assets.id, assetFaces.assetId))
.leftJoin(person, eq(assetFaces.personId, person.id))
.where(and(
eq(albumsAssetsAssets.albumsId, id),
eq(albumsAssetsAssets.albumId, id),
eq(assets.visibility, "timeline"),
eq(assets.status, "active"),
faceId ? eq(assetFaces.personId, faceId) : undefined,
+2 -2
View File
@@ -31,8 +31,8 @@ export default async function handler(
faceCount: count(sql<string>`DISTINCT ${person.id}`), // Ensure unique personId
})
.from(albums)
.leftJoin(albumsAssetsAssets, eq(albums.id, albumsAssetsAssets.albumsId))
.leftJoin(assets, eq(albumsAssetsAssets.assetsId, assets.id))
.leftJoin(albumsAssetsAssets, eq(albums.id, albumsAssetsAssets.albumId))
.leftJoin(assets, eq(albumsAssetsAssets.assetId, assets.id))
.leftJoin(exif, and(eq(assets.id, exif.assetId), eq(assets.visibility, "timeline")))
.leftJoin(assetFaces, eq(assets.id, assetFaces.assetId))
.leftJoin(person, and(eq(assetFaces.personId, person.id), eq(person.isHidden, false)))
+2 -2
View File
@@ -26,8 +26,8 @@ export default async function handler(
numberOfPhotos: count(assets.id),
})
.from(albums)
.leftJoin(albumsAssetsAssets, eq(albums.id, albumsAssetsAssets.albumsId))
.leftJoin(assets, eq(albumsAssetsAssets.assetsId, assets.id))
.leftJoin(albumsAssetsAssets, eq(albums.id, albumsAssetsAssets.albumId))
.leftJoin(assets, eq(albumsAssetsAssets.assetId, assets.id))
.leftJoin(assetFaces, eq(assets.id, assetFaces.assetId))
.leftJoin(person, and(eq(assetFaces.personId, person.id), eq(person.isHidden, false)))
.where(and(eq(albums.ownerId, currentUser.id), eq(albums.id, id), isNotNull(person.id)))
+2 -2
View File
@@ -27,8 +27,8 @@ export default async function handler(
faceCount: count(sql<string>`DISTINCT ${person.id}`), // Ensure unique personId
})
.from(albums)
.leftJoin(albumsAssetsAssets, eq(albums.id, albumsAssetsAssets.albumsId))
.leftJoin(assets, eq(albumsAssetsAssets.assetsId, assets.id))
.leftJoin(albumsAssetsAssets, eq(albums.id, albumsAssetsAssets.albumId))
.leftJoin(assets, eq(albumsAssetsAssets.assetId, assets.id))
.leftJoin(exif, and(eq(assets.id, exif.assetId), eq(assets.visibility, "timeline")))
.leftJoin(assetFaces, eq(assets.id, assetFaces.assetId))
.leftJoin(person, and(eq(assetFaces.personId, person.id), eq(person.isHidden, false)))
+2 -2
View File
@@ -69,9 +69,9 @@ export default async function handler(
faceCount: count(sql<string>`DISTINCT ${assetFaces.personId}`), // Ensure unique personId
})
.from(albums)
.leftJoin(albumsAssetsAssets, eq(albums.id, albumsAssetsAssets.albumsId))
.leftJoin(albumsAssetsAssets, eq(albums.id, albumsAssetsAssets.albumId))
.leftJoin(assets, and(
eq(albumsAssetsAssets.assetsId, assets.id),
eq(albumsAssetsAssets.assetId, assets.id),
eq(assets.visibility, "timeline"),
eq(assets.status, "active"),
))
+2 -2
View File
@@ -61,13 +61,13 @@ export default async function handler(
const secondaryAlbumAssets = await db
.select()
.from(albumsAssetsAssets)
.where(inArray(albumsAssetsAssets.albumsId, secondaryAlbumIds));
.where(inArray(albumsAssetsAssets.albumId, secondaryAlbumIds));
if (secondaryAlbumAssets.length === 0) {
return res.status(404).json({ error: "Secondary album assets not found" });
}
const secondaryAlbumAssetsIds = secondaryAlbumAssets.map((albumAsset) => albumAsset.assetsId);
const secondaryAlbumAssetsIds = secondaryAlbumAssets.map((albumAsset) => albumAsset.assetId);
const addAssetsToPrimaryAlbumURL = `${ENV.IMMICH_URL}/api/albums/${primaryAlbumId}/assets`;
const addAssetsToPrimaryAlbum = await fetch(addAssetsToPrimaryAlbumURL, {
@@ -16,7 +16,7 @@ const SELECT_ORPHAN_PHOTOS = (date: string, ownerId: string) =>
a."duration",
a."encodedVideoPath",
a."originalFileName",
a."sidecarPath",
a."thumbhash",
a."deletedAt",
e."exifImageWidth",
@@ -27,12 +27,12 @@ const SELECT_ORPHAN_PHOTOS = (date: string, ownerId: string) =>
asset a
LEFT JOIN
album_asset aaa
ON a.id = aaa."assetsId"
ON a.id = aaa."assetId"
LEFT JOIN
asset_exif e
ON a.id = e."assetId"
WHERE
aaa."albumsId" IS NULL
aaa."albumId" IS NULL
AND a."ownerId" = '${ownerId}'
AND e."dateTimeOriginal"::date = '${date}'
AND a."visibility" = 'timeline'
@@ -21,12 +21,12 @@ export default async function handler(
date: sql`DATE(${exif.dateTimeOriginal})`,
asset_count: desc(count(assets.id)),
}).from(assets)
.leftJoin(albumsAssetsAssets, eq(assets.id, albumsAssetsAssets.assetsId))
.leftJoin(albumsAssetsAssets, eq(assets.id, albumsAssetsAssets.assetId))
.leftJoin(exif, eq(assets.id, exif.assetId))
.where(and (
eq(assets.ownerId, currentUser.id),
eq(assets.visibility, "timeline"),
isNull(albumsAssetsAssets.albumsId),
isNull(albumsAssetsAssets.albumId),
isNotNull(exif.dateTimeOriginal),
))
@@ -17,7 +17,7 @@ export default async function handler(
value: count(sql`DISTINCT ${assets.id}`),
})
.from(assets)
.innerJoin(albumsAssetsAssets, eq(assets.id, albumsAssetsAssets.assetsId))
.innerJoin(albumsAssetsAssets, eq(assets.id, albumsAssetsAssets.assetId))
.where(eq(assets.ownerId, currentUser.id));
// Count total photos
+1 -1
View File
@@ -58,7 +58,7 @@ const dbAssets = await db
duration: assets.duration,
encodedVideoPath: assets.encodedVideoPath,
originalFileName: assets.originalFileName,
sidecarPath: assets.sidecarPath,
deletedAt: assets.deletedAt,
localDateTime: assets.localDateTime,
exifImageWidth: exif.exifImageWidth,
+3 -3
View File
@@ -21,15 +21,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
longitude: exif.longitude,
}).from(assets)
.innerJoin(exif, eq(assets.id, exif.assetId))
.innerJoin(albumsAssetsAssets, eq(assets.id, albumsAssetsAssets.assetsId))
.innerJoin(assetFaces, eq(albumsAssetsAssets.assetsId, assetFaces.assetId))
.innerJoin(albumsAssetsAssets, eq(assets.id, albumsAssetsAssets.assetId))
.innerJoin(assetFaces, eq(albumsAssetsAssets.assetId, assetFaces.assetId))
.innerJoin(person, eq(assetFaces.personId, person.id))
.where(
and(
eq(assets.ownerId, currentUser.id),
isNotNull(exif.latitude),
isNotNull(exif.longitude),
albumIds?.length > 0 ? inArray(albumsAssetsAssets.albumsId, [albumIds]) : undefined,
albumIds?.length > 0 ? inArray(albumsAssetsAssets.albumId, [albumIds]) : undefined,
peopleIds?.length > 0 ? inArray(person.id, [peopleIds]) : undefined
)
);
@@ -27,8 +27,8 @@ export default async function handler(
})
.from(assets)
.leftJoin(exif, eq(exif.assetId, assets.id))
.leftJoin(albumsAssetsAssets, eq(albumsAssetsAssets.assetsId, assets.id))
.leftJoin(albums, eq(albums.id, albumsAssetsAssets.albumsId))
.leftJoin(albumsAssetsAssets, eq(albumsAssetsAssets.assetId, assets.id))
.leftJoin(albums, eq(albums.id, albumsAssetsAssets.albumId))
.where(
and(
isNull(exif.latitude),
@@ -22,7 +22,7 @@ const getRowsByDates = async (startDateDate: Date, endDateDate: Date, currentUse
duration: assets.duration,
encodedVideoPath: assets.encodedVideoPath,
originalFileName: assets.originalFileName,
sidecarPath: assets.sidecarPath,
deletedAt: assets.deletedAt,
localDateTime: assets.localDateTime,
exifImageWidth: exif.exifImageWidth,
@@ -55,7 +55,7 @@ const getRowsByAlbums = async (currentUser: IUser, albumId: string) => {
duration: assets.duration,
encodedVideoPath: assets.encodedVideoPath,
originalFileName: assets.originalFileName,
sidecarPath: assets.sidecarPath,
deletedAt: assets.deletedAt,
localDateTime: assets.localDateTime,
exifImageWidth: exif.exifImageWidth,
@@ -66,8 +66,8 @@ const getRowsByAlbums = async (currentUser: IUser, albumId: string) => {
})
.from(assets)
.leftJoin(exif, eq(exif.assetId, assets.id))
.leftJoin(albumsAssetsAssets, eq(albumsAssetsAssets.assetsId, assets.id))
.leftJoin(albums, eq(albums.id, albumsAssetsAssets.albumsId))
.leftJoin(albumsAssetsAssets, eq(albumsAssetsAssets.assetId, assets.id))
.leftJoin(albums, eq(albums.id, albumsAssetsAssets.albumId))
.where(and(
isNull(exif.latitude),
isNotNull(assets.createdAt),
+2 -2
View File
@@ -36,8 +36,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
.leftJoin(assetFaces, eq(assetFaces.personId, personRecord.id))
.leftJoin(assets, eq(assets.id, assetFaces.assetId))
.leftJoin(exif, eq(exif.assetId, assets.id))
.leftJoin(albumsAssetsAssets, eq(albumsAssetsAssets.assetsId, assets.id))
.where(eq(albumsAssetsAssets.albumsId, albums.id))
.leftJoin(albumsAssetsAssets, eq(albumsAssetsAssets.assetId, assets.id))
.where(eq(albumsAssetsAssets.albumId, albums.id))
.groupBy(albums.id);
+2 -2
View File
@@ -66,8 +66,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
cover: albums.albumThumbnailAssetId
})
.from(albumsAssetsAssets)
.leftJoin(albums, eq(albums.id, albumsAssetsAssets.albumsId))
.leftJoin(assets, eq(assets.id, albumsAssetsAssets.assetsId))
.leftJoin(albums, eq(albums.id, albumsAssetsAssets.albumId))
.leftJoin(assets, eq(assets.id, albumsAssetsAssets.assetId))
.leftJoin(exif, eq(exif.assetId, assets.id))
.where(and(
eq(sql`EXTRACT(YEAR FROM ${exif.dateTimeOriginal})`, year),
+3 -3
View File
@@ -63,7 +63,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
duration: assets.duration,
encodedVideoPath: assets.encodedVideoPath,
originalFileName: assets.originalFileName,
sidecarPath: assets.sidecarPath,
deletedAt: assets.deletedAt,
localDateTime: assets.localDateTime,
exifImageWidth: exif.exifImageWidth,
@@ -76,8 +76,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
orientation: exif.orientation,
}).from(assets)
.innerJoin(assetFaces, eq(assets.id, assetFaces.assetId))
.leftJoin(albumsAssetsAssets, eq(assets.id, albumsAssetsAssets.assetsId))
.leftJoin(albums, eq(albumsAssetsAssets.albumsId, albums.id))
.leftJoin(albumsAssetsAssets, eq(assets.id, albumsAssetsAssets.assetId))
.leftJoin(albums, eq(albumsAssetsAssets.albumId, albums.id))
.innerJoin(exif, eq(exif.assetId, assets.id))
.where(and(
filteredPersonIds?.length > 0 ? inArray(assetFaces.personId, filteredPersonIds) : undefined,
+2 -2
View File
@@ -44,9 +44,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
assetCount: count(assets.id),
}).from(assets)
.innerJoin(assetFaces, eq(assets.id, assetFaces.assetId))
.innerJoin(albumsAssetsAssets, eq(assets.id, albumsAssetsAssets.assetsId))
.innerJoin(albumsAssetsAssets, eq(assets.id, albumsAssetsAssets.assetId))
.innerJoin(person, eq(assetFaces.personId, person.id))
.innerJoin(albums, eq(albumsAssetsAssets.albumsId, albums.id))
.innerJoin(albums, eq(albumsAssetsAssets.albumId, albums.id))
.innerJoin(exif, eq(exif.assetId, assets.id))
.where(and(
personIds?.length > 0 ? inArray(assetFaces.personId, personIds) : undefined,
+2 -2
View File
@@ -1,7 +1,7 @@
import { pgTable, uuid, timestamp } from 'drizzle-orm/pg-core';
export const albumsAssetsAssets = pgTable('album_asset', {
albumsId: uuid('albumsId').notNull(),
assetsId: uuid('assetsId').notNull(),
albumId: uuid("albumId").notNull(),
assetId: uuid("assetId").notNull(),
createdAt: timestamp('createdAt', { withTimezone: true }).defaultNow().notNull(),
});
-1
View File
@@ -19,7 +19,6 @@ export const assets = pgTable('asset', {
createdAt: timestamp('createdAt', { withTimezone: true }).defaultNow().notNull(),
status: varchar('status').default('active').notNull(),
originalFileName: varchar('originalFileName').notNull(),
sidecarPath: varchar('sidecarPath'),
// thumbhash: bytea('thumbhash'),
isOffline: boolean('isOffline').default(false).notNull(),
// libraryId: uuid('libraryId').references(() => libraries.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
+2 -2
View File
@@ -44,11 +44,11 @@ export const faceSearchRelations = relations(faceSearch, ({ one }) => ({
// Assuming there are relations to be defined with `albums` and `assets`
export const albumsAssetsAssetsRelations = relations(albumsAssetsAssets, ({ one }) => ({
album: one(albums, {
fields: [albumsAssetsAssets.albumsId],
fields: [albumsAssetsAssets.albumId],
references: [albums.id],
}),
asset: one(assets, {
fields: [albumsAssetsAssets.assetsId],
fields: [albumsAssetsAssets.assetId],
references: [assets.id],
}),
}));