feat: rename schema (#19891)

This commit is contained in:
Jason Rasmussen
2025-07-14 10:13:06 -04:00
committed by GitHub
parent 33c29e4305
commit c699df002a
103 changed files with 4378 additions and 3224 deletions

View File

@@ -35,9 +35,9 @@ class ActivityAccess {
return this.db
.selectFrom('activity')
.select('activity.id')
.leftJoin('albums', (join) => join.onRef('activity.albumId', '=', 'albums.id').on('albums.deletedAt', 'is', null))
.leftJoin('album', (join) => join.onRef('activity.albumId', '=', 'album.id').on('album.deletedAt', 'is', null))
.where('activity.id', 'in', [...activityIds])
.whereRef('albums.ownerId', '=', asUuid(userId))
.whereRef('album.ownerId', '=', asUuid(userId))
.execute()
.then((activities) => new Set(activities.map((activity) => activity.id)));
}
@@ -50,14 +50,14 @@ class ActivityAccess {
}
return this.db
.selectFrom('albums')
.select('albums.id')
.leftJoin('albums_shared_users_users as albumUsers', 'albumUsers.albumsId', 'albums.id')
.leftJoin('users', (join) => join.onRef('users.id', '=', 'albumUsers.usersId').on('users.deletedAt', 'is', null))
.where('albums.id', 'in', [...albumIds])
.where('albums.isActivityEnabled', '=', true)
.where((eb) => eb.or([eb('albums.ownerId', '=', userId), eb('users.id', '=', userId)]))
.where('albums.deletedAt', 'is', null)
.selectFrom('album')
.select('album.id')
.leftJoin('album_user as albumUsers', 'albumUsers.albumsId', 'album.id')
.leftJoin('user', (join) => join.onRef('user.id', '=', 'albumUsers.usersId').on('user.deletedAt', 'is', null))
.where('album.id', 'in', [...albumIds])
.where('album.isActivityEnabled', '=', true)
.where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('user.id', '=', userId)]))
.where('album.deletedAt', 'is', null)
.execute()
.then((albums) => new Set(albums.map((album) => album.id)));
}
@@ -74,11 +74,11 @@ class AlbumAccess {
}
return this.db
.selectFrom('albums')
.select('albums.id')
.where('albums.id', 'in', [...albumIds])
.where('albums.ownerId', '=', userId)
.where('albums.deletedAt', 'is', null)
.selectFrom('album')
.select('album.id')
.where('album.id', 'in', [...albumIds])
.where('album.ownerId', '=', userId)
.where('album.deletedAt', 'is', null)
.execute()
.then((albums) => new Set(albums.map((album) => album.id)));
}
@@ -94,14 +94,14 @@ class AlbumAccess {
access === AlbumUserRole.EDITOR ? [AlbumUserRole.EDITOR] : [AlbumUserRole.EDITOR, AlbumUserRole.VIEWER];
return this.db
.selectFrom('albums')
.select('albums.id')
.leftJoin('albums_shared_users_users as albumUsers', 'albumUsers.albumsId', 'albums.id')
.leftJoin('users', (join) => join.onRef('users.id', '=', 'albumUsers.usersId').on('users.deletedAt', 'is', null))
.where('albums.id', 'in', [...albumIds])
.where('albums.deletedAt', 'is', null)
.where('users.id', '=', userId)
.where('albumUsers.role', 'in', [...accessRole])
.selectFrom('album')
.select('album.id')
.leftJoin('album_user', 'album_user.albumsId', 'album.id')
.leftJoin('user', (join) => join.onRef('user.id', '=', 'album_user.usersId').on('user.deletedAt', 'is', null))
.where('album.id', 'in', [...albumIds])
.where('album.deletedAt', 'is', null)
.where('user.id', '=', userId)
.where('album_user.role', 'in', [...accessRole])
.execute()
.then((albums) => new Set(albums.map((album) => album.id)));
}
@@ -114,10 +114,10 @@ class AlbumAccess {
}
return this.db
.selectFrom('shared_links')
.select('shared_links.albumId')
.where('shared_links.id', '=', sharedLinkId)
.where('shared_links.albumId', 'in', [...albumIds])
.selectFrom('shared_link')
.select('shared_link.albumId')
.where('shared_link.id', '=', sharedLinkId)
.where('shared_link.albumId', 'in', [...albumIds])
.execute()
.then(
(sharedLinks) => new Set(sharedLinks.flatMap((sharedLink) => (sharedLink.albumId ? [sharedLink.albumId] : []))),
@@ -136,21 +136,21 @@ class AssetAccess {
}
return this.db
.selectFrom('albums')
.innerJoin('albums_assets_assets as albumAssets', 'albums.id', 'albumAssets.albumsId')
.innerJoin('assets', (join) =>
join.onRef('assets.id', '=', 'albumAssets.assetsId').on('assets.deletedAt', 'is', null),
.selectFrom('album')
.innerJoin('album_asset as albumAssets', 'album.id', 'albumAssets.albumsId')
.innerJoin('asset', (join) =>
join.onRef('asset.id', '=', 'albumAssets.assetsId').on('asset.deletedAt', 'is', null),
)
.leftJoin('albums_shared_users_users as albumUsers', 'albumUsers.albumsId', 'albums.id')
.leftJoin('users', (join) => join.onRef('users.id', '=', 'albumUsers.usersId').on('users.deletedAt', 'is', null))
.select(['assets.id', 'assets.livePhotoVideoId'])
.leftJoin('album_user as albumUsers', 'albumUsers.albumsId', 'album.id')
.leftJoin('user', (join) => join.onRef('user.id', '=', 'albumUsers.usersId').on('user.deletedAt', 'is', null))
.select(['asset.id', 'asset.livePhotoVideoId'])
.where(
sql`array["assets"."id", "assets"."livePhotoVideoId"]`,
sql`array["asset"."id", "asset"."livePhotoVideoId"]`,
'&&',
sql`array[${sql.join([...assetIds])}]::uuid[] `,
)
.where((eb) => eb.or([eb('albums.ownerId', '=', userId), eb('users.id', '=', userId)]))
.where('albums.deletedAt', 'is', null)
.where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('user.id', '=', userId)]))
.where('album.deletedAt', 'is', null)
.execute()
.then((assets) => {
const allowedIds = new Set<string>();
@@ -174,11 +174,11 @@ class AssetAccess {
}
return this.db
.selectFrom('assets')
.select('assets.id')
.where('assets.id', 'in', [...assetIds])
.where('assets.ownerId', '=', userId)
.$if(!hasElevatedPermission, (eb) => eb.where('assets.visibility', '!=', AssetVisibility.LOCKED))
.selectFrom('asset')
.select('asset.id')
.where('asset.id', 'in', [...assetIds])
.where('asset.ownerId', '=', userId)
.$if(!hasElevatedPermission, (eb) => eb.where('asset.visibility', '!=', AssetVisibility.LOCKED))
.execute()
.then((assets) => new Set(assets.map((asset) => asset.id)));
}
@@ -191,23 +191,21 @@ class AssetAccess {
}
return this.db
.selectFrom('partners as partner')
.innerJoin('users as sharedBy', (join) =>
.selectFrom('partner')
.innerJoin('user as sharedBy', (join) =>
join.onRef('sharedBy.id', '=', 'partner.sharedById').on('sharedBy.deletedAt', 'is', null),
)
.innerJoin('assets', (join) =>
join.onRef('assets.ownerId', '=', 'sharedBy.id').on('assets.deletedAt', 'is', null),
)
.select('assets.id')
.innerJoin('asset', (join) => join.onRef('asset.ownerId', '=', 'sharedBy.id').on('asset.deletedAt', 'is', null))
.select('asset.id')
.where('partner.sharedWithId', '=', userId)
.where((eb) =>
eb.or([
eb('assets.visibility', '=', sql.lit(AssetVisibility.TIMELINE)),
eb('assets.visibility', '=', sql.lit(AssetVisibility.HIDDEN)),
eb('asset.visibility', '=', sql.lit(AssetVisibility.TIMELINE)),
eb('asset.visibility', '=', sql.lit(AssetVisibility.HIDDEN)),
]),
)
.where('assets.id', 'in', [...assetIds])
.where('asset.id', 'in', [...assetIds])
.execute()
.then((assets) => new Set(assets.map((asset) => asset.id)));
}
@@ -220,27 +218,25 @@ class AssetAccess {
}
return this.db
.selectFrom('shared_links')
.leftJoin('albums', (join) =>
join.onRef('albums.id', '=', 'shared_links.albumId').on('albums.deletedAt', 'is', null),
.selectFrom('shared_link')
.leftJoin('album', (join) => join.onRef('album.id', '=', 'shared_link.albumId').on('album.deletedAt', 'is', null))
.leftJoin('shared_link_asset', 'shared_link_asset.sharedLinksId', 'shared_link.id')
.leftJoin('asset', (join) =>
join.onRef('asset.id', '=', 'shared_link_asset.assetsId').on('asset.deletedAt', 'is', null),
)
.leftJoin('shared_link__asset', 'shared_link__asset.sharedLinksId', 'shared_links.id')
.leftJoin('assets', (join) =>
join.onRef('assets.id', '=', 'shared_link__asset.assetsId').on('assets.deletedAt', 'is', null),
)
.leftJoin('albums_assets_assets', 'albums_assets_assets.albumsId', 'albums.id')
.leftJoin('assets as albumAssets', (join) =>
join.onRef('albumAssets.id', '=', 'albums_assets_assets.assetsId').on('albumAssets.deletedAt', 'is', null),
.leftJoin('album_asset', 'album_asset.albumsId', 'album.id')
.leftJoin('asset as albumAssets', (join) =>
join.onRef('albumAssets.id', '=', 'album_asset.assetsId').on('albumAssets.deletedAt', 'is', null),
)
.select([
'assets.id as assetId',
'assets.livePhotoVideoId as assetLivePhotoVideoId',
'asset.id as assetId',
'asset.livePhotoVideoId as assetLivePhotoVideoId',
'albumAssets.id as albumAssetId',
'albumAssets.livePhotoVideoId as albumAssetLivePhotoVideoId',
])
.where('shared_links.id', '=', sharedLinkId)
.where('shared_link.id', '=', sharedLinkId)
.where(
sql`array["assets"."id", "assets"."livePhotoVideoId", "albumAssets"."id", "albumAssets"."livePhotoVideoId"]`,
sql`array["asset"."id", "asset"."livePhotoVideoId", "albumAssets"."id", "albumAssets"."livePhotoVideoId"]`,
'&&',
sql`array[${sql.join([...assetIds])}]::uuid[] `,
)
@@ -277,10 +273,10 @@ class AuthDeviceAccess {
}
return this.db
.selectFrom('sessions')
.select('sessions.id')
.where('sessions.userId', '=', userId)
.where('sessions.id', 'in', [...deviceIds])
.selectFrom('session')
.select('session.id')
.where('session.userId', '=', userId)
.where('session.id', 'in', [...deviceIds])
.execute()
.then((tokens) => new Set(tokens.map((token) => token.id)));
}
@@ -297,10 +293,10 @@ class NotificationAccess {
}
return this.db
.selectFrom('notifications')
.select('notifications.id')
.where('notifications.id', 'in', [...notificationIds])
.where('notifications.userId', '=', userId)
.selectFrom('notification')
.select('notification.id')
.where('notification.id', 'in', [...notificationIds])
.where('notification.userId', '=', userId)
.execute()
.then((stacks) => new Set(stacks.map((stack) => stack.id)));
}
@@ -317,10 +313,10 @@ class SessionAccess {
}
return this.db
.selectFrom('sessions')
.select('sessions.id')
.where('sessions.id', 'in', [...sessionIds])
.where('sessions.userId', '=', userId)
.selectFrom('session')
.select('session.id')
.where('session.id', 'in', [...sessionIds])
.where('session.userId', '=', userId)
.execute()
.then((sessions) => new Set(sessions.map((session) => session.id)));
}
@@ -336,10 +332,10 @@ class StackAccess {
}
return this.db
.selectFrom('asset_stack as stacks')
.select('stacks.id')
.where('stacks.id', 'in', [...stackIds])
.where('stacks.ownerId', '=', userId)
.selectFrom('stack')
.select('stack.id')
.where('stack.id', 'in', [...stackIds])
.where('stack.ownerId', '=', userId)
.execute()
.then((stacks) => new Set(stacks.map((stack) => stack.id)));
}
@@ -356,10 +352,10 @@ class TimelineAccess {
}
return this.db
.selectFrom('partners')
.select('partners.sharedById')
.where('partners.sharedById', 'in', [...partnerIds])
.where('partners.sharedWithId', '=', userId)
.selectFrom('partner')
.select('partner.sharedById')
.where('partner.sharedById', 'in', [...partnerIds])
.where('partner.sharedWithId', '=', userId)
.execute()
.then((partners) => new Set(partners.map((partner) => partner.sharedById)));
}
@@ -376,11 +372,11 @@ class MemoryAccess {
}
return this.db
.selectFrom('memories')
.select('memories.id')
.where('memories.id', 'in', [...memoryIds])
.where('memories.ownerId', '=', userId)
.where('memories.deletedAt', 'is', null)
.selectFrom('memory')
.select('memory.id')
.where('memory.id', 'in', [...memoryIds])
.where('memory.ownerId', '=', userId)
.where('memory.deletedAt', 'is', null)
.execute()
.then((memories) => new Set(memories.map((memory) => memory.id)));
}
@@ -413,13 +409,11 @@ class PersonAccess {
}
return this.db
.selectFrom('asset_faces')
.select('asset_faces.id')
.leftJoin('assets', (join) =>
join.onRef('assets.id', '=', 'asset_faces.assetId').on('assets.deletedAt', 'is', null),
)
.where('asset_faces.id', 'in', [...assetFaceIds])
.where('assets.ownerId', '=', userId)
.selectFrom('asset_face')
.select('asset_face.id')
.leftJoin('asset', (join) => join.onRef('asset.id', '=', 'asset_face.assetId').on('asset.deletedAt', 'is', null))
.where('asset_face.id', 'in', [...assetFaceIds])
.where('asset.ownerId', '=', userId)
.execute()
.then((faces) => new Set(faces.map((face) => face.id)));
}
@@ -436,10 +430,10 @@ class PartnerAccess {
}
return this.db
.selectFrom('partners')
.select('partners.sharedById')
.where('partners.sharedById', 'in', [...partnerIds])
.where('partners.sharedWithId', '=', userId)
.selectFrom('partner')
.select('partner.sharedById')
.where('partner.sharedById', 'in', [...partnerIds])
.where('partner.sharedWithId', '=', userId)
.execute()
.then((partners) => new Set(partners.map((partner) => partner.sharedById)));
}
@@ -456,10 +450,10 @@ class TagAccess {
}
return this.db
.selectFrom('tags')
.select('tags.id')
.where('tags.id', 'in', [...tagIds])
.where('tags.userId', '=', userId)
.selectFrom('tag')
.select('tag.id')
.where('tag.id', 'in', [...tagIds])
.where('tag.userId', '=', userId)
.execute()
.then((tags) => new Set(tags.map((tag) => tag.id)));
}

View File

@@ -27,7 +27,9 @@ export class ActivityRepository {
return this.db
.selectFrom('activity')
.selectAll('activity')
.innerJoin('users', (join) => join.onRef('users.id', '=', 'activity.userId').on('users.deletedAt', 'is', null))
.innerJoin('user as user2', (join) =>
join.onRef('user2.id', '=', 'activity.userId').on('user2.deletedAt', 'is', null),
)
.innerJoinLateral(
(eb) =>
eb
@@ -37,13 +39,13 @@ export class ActivityRepository {
(join) => join.onTrue(),
)
.select((eb) => eb.fn.toJson('user').as('user'))
.leftJoin('assets', 'assets.id', 'activity.assetId')
.leftJoin('asset', 'asset.id', 'activity.assetId')
.$if(!!userId, (qb) => qb.where('activity.userId', '=', userId!))
.$if(assetId === null, (qb) => qb.where('assetId', 'is', null))
.$if(!!assetId, (qb) => qb.where('activity.assetId', '=', assetId!))
.$if(!!albumId, (qb) => qb.where('activity.albumId', '=', albumId!))
.$if(isLiked !== undefined, (qb) => qb.where('activity.isLiked', '=', isLiked!))
.where('assets.deletedAt', 'is', null)
.where('asset.deletedAt', 'is', null)
.orderBy('activity.createdAt', 'asc')
.execute();
}
@@ -55,7 +57,7 @@ export class ActivityRepository {
.values(activity)
.returningAll()
.returning((eb) =>
jsonObjectFrom(eb.selectFrom('users').whereRef('users.id', '=', 'activity.userId').select(columns.user)).as(
jsonObjectFrom(eb.selectFrom('user').whereRef('user.id', '=', 'activity.userId').select(columns.user)).as(
'user',
),
)
@@ -82,14 +84,14 @@ export class ActivityRepository {
eb.fn.countAll<number>().filterWhere('activity.isLiked', '=', false).as('comments'),
eb.fn.countAll<number>().filterWhere('activity.isLiked', '=', true).as('likes'),
])
.innerJoin('users', (join) => join.onRef('users.id', '=', 'activity.userId').on('users.deletedAt', 'is', null))
.leftJoin('assets', 'assets.id', 'activity.assetId')
.innerJoin('user', (join) => join.onRef('user.id', '=', 'activity.userId').on('user.deletedAt', 'is', null))
.leftJoin('asset', 'asset.id', 'activity.assetId')
.$if(!!assetId, (qb) => qb.where('activity.assetId', '=', assetId!))
.where('activity.albumId', '=', albumId)
.where(({ or, and, eb }) =>
or([
and([eb('assets.deletedAt', 'is', null), eb('assets.visibility', '!=', sql.lit(AssetVisibility.LOCKED))]),
eb('assets.id', 'is', null),
and([eb('asset.deletedAt', 'is', null), eb('asset.visibility', '!=', sql.lit(AssetVisibility.LOCKED))]),
eb('asset.id', 'is', null),
]),
)
.executeTakeFirstOrThrow();

View File

@@ -18,7 +18,7 @@ export class AlbumUserRepository {
@GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }] })
create(albumUser: Insertable<AlbumUserTable>) {
return this.db
.insertInto('albums_shared_users_users')
.insertInto('album_user')
.values(albumUser)
.returning(['usersId', 'albumsId', 'role'])
.executeTakeFirstOrThrow();
@@ -27,7 +27,7 @@ export class AlbumUserRepository {
@GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }, { role: AlbumUserRole.VIEWER }] })
update({ usersId, albumsId }: AlbumPermissionId, dto: Updateable<AlbumUserTable>) {
return this.db
.updateTable('albums_shared_users_users')
.updateTable('album_user')
.set(dto)
.where('usersId', '=', usersId)
.where('albumsId', '=', albumsId)
@@ -37,10 +37,6 @@ export class AlbumUserRepository {
@GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }] })
async delete({ usersId, albumsId }: AlbumPermissionId): Promise<void> {
await this.db
.deleteFrom('albums_shared_users_users')
.where('usersId', '=', usersId)
.where('albumsId', '=', albumsId)
.execute();
await this.db.deleteFrom('album_user').where('usersId', '=', usersId).where('albumsId', '=', albumsId).execute();
}
}

View File

@@ -21,47 +21,47 @@ export interface AlbumInfoOptions {
withAssets: boolean;
}
const withOwner = (eb: ExpressionBuilder<DB, 'albums'>) => {
return jsonObjectFrom(eb.selectFrom('users').select(columns.user).whereRef('users.id', '=', 'albums.ownerId'))
const withOwner = (eb: ExpressionBuilder<DB, 'album'>) => {
return jsonObjectFrom(eb.selectFrom('user').select(columns.user).whereRef('user.id', '=', 'album.ownerId'))
.$notNull()
.as('owner');
};
const withAlbumUsers = (eb: ExpressionBuilder<DB, 'albums'>) => {
const withAlbumUsers = (eb: ExpressionBuilder<DB, 'album'>) => {
return jsonArrayFrom(
eb
.selectFrom('albums_shared_users_users as album_users')
.select('album_users.role')
.selectFrom('album_user')
.select('album_user.role')
.select((eb) =>
jsonObjectFrom(eb.selectFrom('users').select(columns.user).whereRef('users.id', '=', 'album_users.usersId'))
jsonObjectFrom(eb.selectFrom('user').select(columns.user).whereRef('user.id', '=', 'album_user.usersId'))
.$notNull()
.as('user'),
)
.whereRef('album_users.albumsId', '=', 'albums.id'),
.whereRef('album_user.albumsId', '=', 'album.id'),
)
.$notNull()
.as('albumUsers');
};
const withSharedLink = (eb: ExpressionBuilder<DB, 'albums'>) => {
return jsonArrayFrom(eb.selectFrom('shared_links').selectAll().whereRef('shared_links.albumId', '=', 'albums.id')).as(
const withSharedLink = (eb: ExpressionBuilder<DB, 'album'>) => {
return jsonArrayFrom(eb.selectFrom('shared_link').selectAll().whereRef('shared_link.albumId', '=', 'album.id')).as(
'sharedLinks',
);
};
const withAssets = (eb: ExpressionBuilder<DB, 'albums'>) => {
const withAssets = (eb: ExpressionBuilder<DB, 'album'>) => {
return eb
.selectFrom((eb) =>
eb
.selectFrom('assets')
.selectAll('assets')
.leftJoin('exif', 'assets.id', 'exif.assetId')
.select((eb) => eb.table('exif').$castTo<Exif>().as('exifInfo'))
.innerJoin('albums_assets_assets', 'albums_assets_assets.assetsId', 'assets.id')
.whereRef('albums_assets_assets.albumsId', '=', 'albums.id')
.where('assets.deletedAt', 'is', null)
.selectFrom('asset')
.selectAll('asset')
.leftJoin('asset_exif', 'asset.id', 'asset_exif.assetId')
.select((eb) => eb.table('asset_exif').$castTo<Exif>().as('exifInfo'))
.innerJoin('album_asset', 'album_asset.assetsId', 'asset.id')
.whereRef('album_asset.albumsId', '=', 'album.id')
.where('asset.deletedAt', 'is', null)
.$call(withDefaultVisibility)
.orderBy('assets.fileCreatedAt', 'desc')
.orderBy('asset.fileCreatedAt', 'desc')
.as('asset'),
)
.select((eb) => eb.fn.jsonAgg('asset').as('assets'))
@@ -75,10 +75,10 @@ export class AlbumRepository {
@GenerateSql({ params: [DummyValue.UUID, { withAssets: true }] })
async getById(id: string, options: AlbumInfoOptions) {
return this.db
.selectFrom('albums')
.selectAll('albums')
.where('albums.id', '=', id)
.where('albums.deletedAt', 'is', null)
.selectFrom('album')
.selectAll('album')
.where('album.id', '=', id)
.where('album.deletedAt', 'is', null)
.select(withOwner)
.select(withAlbumUsers)
.select(withSharedLink)
@@ -90,26 +90,26 @@ export class AlbumRepository {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
async getByAssetId(ownerId: string, assetId: string) {
return this.db
.selectFrom('albums')
.selectAll('albums')
.innerJoin('albums_assets_assets as album_assets', 'album_assets.albumsId', 'albums.id')
.selectFrom('album')
.selectAll('album')
.innerJoin('album_asset', 'album_asset.albumsId', 'album.id')
.where((eb) =>
eb.or([
eb('albums.ownerId', '=', ownerId),
eb('album.ownerId', '=', ownerId),
eb.exists(
eb
.selectFrom('albums_shared_users_users as album_users')
.whereRef('album_users.albumsId', '=', 'albums.id')
.where('album_users.usersId', '=', ownerId),
.selectFrom('album_user')
.whereRef('album_user.albumsId', '=', 'album.id')
.where('album_user.usersId', '=', ownerId),
),
]),
)
.where('album_assets.assetsId', '=', assetId)
.where('albums.deletedAt', 'is', null)
.orderBy('albums.createdAt', 'desc')
.where('album_asset.assetsId', '=', assetId)
.where('album.deletedAt', 'is', null)
.orderBy('album.createdAt', 'desc')
.select(withOwner)
.select(withAlbumUsers)
.orderBy('albums.createdAt', 'desc')
.orderBy('album.createdAt', 'desc')
.execute();
}
@@ -123,18 +123,18 @@ export class AlbumRepository {
return (
this.db
.selectFrom('assets')
.selectFrom('asset')
.$call(withDefaultVisibility)
.innerJoin('albums_assets_assets as album_assets', 'album_assets.assetsId', 'assets.id')
.select('album_assets.albumsId as albumId')
.select((eb) => eb.fn.min(sql<Date>`("assets"."localDateTime" AT TIME ZONE 'UTC'::text)::date`).as('startDate'))
.select((eb) => eb.fn.max(sql<Date>`("assets"."localDateTime" AT TIME ZONE 'UTC'::text)::date`).as('endDate'))
.innerJoin('album_asset', 'album_asset.assetsId', 'asset.id')
.select('album_asset.albumsId as albumId')
.select((eb) => eb.fn.min(sql<Date>`("asset"."localDateTime" AT TIME ZONE 'UTC'::text)::date`).as('startDate'))
.select((eb) => eb.fn.max(sql<Date>`("asset"."localDateTime" AT TIME ZONE 'UTC'::text)::date`).as('endDate'))
// lastModifiedAssetTimestamp is only used in mobile app, please remove if not need
.select((eb) => eb.fn.max('assets.updatedAt').as('lastModifiedAssetTimestamp'))
.select((eb) => sql<number>`${eb.fn.count('assets.id')}::int`.as('assetCount'))
.where('album_assets.albumsId', 'in', ids)
.where('assets.deletedAt', 'is', null)
.groupBy('album_assets.albumsId')
.select((eb) => eb.fn.max('asset.updatedAt').as('lastModifiedAssetTimestamp'))
.select((eb) => sql<number>`${eb.fn.count('asset.id')}::int`.as('assetCount'))
.where('album_asset.albumsId', 'in', ids)
.where('asset.deletedAt', 'is', null)
.groupBy('album_asset.albumsId')
.execute()
);
}
@@ -142,14 +142,14 @@ export class AlbumRepository {
@GenerateSql({ params: [DummyValue.UUID] })
async getOwned(ownerId: string) {
return this.db
.selectFrom('albums')
.selectAll('albums')
.selectFrom('album')
.selectAll('album')
.select(withOwner)
.select(withAlbumUsers)
.select(withSharedLink)
.where('albums.ownerId', '=', ownerId)
.where('albums.deletedAt', 'is', null)
.orderBy('albums.createdAt', 'desc')
.where('album.ownerId', '=', ownerId)
.where('album.deletedAt', 'is', null)
.orderBy('album.createdAt', 'desc')
.execute();
}
@@ -159,29 +159,29 @@ export class AlbumRepository {
@GenerateSql({ params: [DummyValue.UUID] })
async getShared(ownerId: string) {
return this.db
.selectFrom('albums')
.selectAll('albums')
.selectFrom('album')
.selectAll('album')
.where((eb) =>
eb.or([
eb.exists(
eb
.selectFrom('albums_shared_users_users as album_users')
.whereRef('album_users.albumsId', '=', 'albums.id')
.where((eb) => eb.or([eb('albums.ownerId', '=', ownerId), eb('album_users.usersId', '=', ownerId)])),
.selectFrom('album_user')
.whereRef('album_user.albumsId', '=', 'album.id')
.where((eb) => eb.or([eb('album.ownerId', '=', ownerId), eb('album_user.usersId', '=', ownerId)])),
),
eb.exists(
eb
.selectFrom('shared_links')
.whereRef('shared_links.albumId', '=', 'albums.id')
.where('shared_links.userId', '=', ownerId),
.selectFrom('shared_link')
.whereRef('shared_link.albumId', '=', 'album.id')
.where('shared_link.userId', '=', ownerId),
),
]),
)
.where('albums.deletedAt', 'is', null)
.where('album.deletedAt', 'is', null)
.select(withAlbumUsers)
.select(withOwner)
.select(withSharedLink)
.orderBy('albums.createdAt', 'desc')
.orderBy('album.createdAt', 'desc')
.execute();
}
@@ -191,43 +191,33 @@ export class AlbumRepository {
@GenerateSql({ params: [DummyValue.UUID] })
async getNotShared(ownerId: string) {
return this.db
.selectFrom('albums')
.selectAll('albums')
.where('albums.ownerId', '=', ownerId)
.where('albums.deletedAt', 'is', null)
.where((eb) =>
eb.not(
eb.exists(
eb
.selectFrom('albums_shared_users_users as album_users')
.whereRef('album_users.albumsId', '=', 'albums.id'),
),
),
)
.where((eb) =>
eb.not(eb.exists(eb.selectFrom('shared_links').whereRef('shared_links.albumId', '=', 'albums.id'))),
)
.selectFrom('album')
.selectAll('album')
.where('album.ownerId', '=', ownerId)
.where('album.deletedAt', 'is', null)
.where((eb) => eb.not(eb.exists(eb.selectFrom('album_user').whereRef('album_user.albumsId', '=', 'album.id'))))
.where((eb) => eb.not(eb.exists(eb.selectFrom('shared_link').whereRef('shared_link.albumId', '=', 'album.id'))))
.select(withOwner)
.orderBy('albums.createdAt', 'desc')
.orderBy('album.createdAt', 'desc')
.execute();
}
async restoreAll(userId: string): Promise<void> {
await this.db.updateTable('albums').set({ deletedAt: null }).where('ownerId', '=', userId).execute();
await this.db.updateTable('album').set({ deletedAt: null }).where('ownerId', '=', userId).execute();
}
async softDeleteAll(userId: string): Promise<void> {
await this.db.updateTable('albums').set({ deletedAt: new Date() }).where('ownerId', '=', userId).execute();
await this.db.updateTable('album').set({ deletedAt: new Date() }).where('ownerId', '=', userId).execute();
}
async deleteAll(userId: string): Promise<void> {
await this.db.deleteFrom('albums').where('ownerId', '=', userId).execute();
await this.db.deleteFrom('album').where('ownerId', '=', userId).execute();
}
@GenerateSql({ params: [[DummyValue.UUID]] })
@Chunked()
async removeAssetsFromAll(assetIds: string[]): Promise<void> {
await this.db.deleteFrom('albums_assets_assets').where('albums_assets_assets.assetsId', 'in', assetIds).execute();
await this.db.deleteFrom('album_asset').where('album_asset.assetsId', 'in', assetIds).execute();
}
@Chunked({ paramIndex: 1 })
@@ -237,9 +227,9 @@ export class AlbumRepository {
}
await this.db
.deleteFrom('albums_assets_assets')
.where('albums_assets_assets.albumsId', '=', albumId)
.where('albums_assets_assets.assetsId', 'in', assetIds)
.deleteFrom('album_asset')
.where('album_asset.albumsId', '=', albumId)
.where('album_asset.assetsId', 'in', assetIds)
.execute();
}
@@ -258,10 +248,10 @@ export class AlbumRepository {
}
return this.db
.selectFrom('albums_assets_assets')
.selectFrom('album_asset')
.selectAll()
.where('albums_assets_assets.albumsId', '=', albumId)
.where('albums_assets_assets.assetsId', 'in', assetIds)
.where('album_asset.albumsId', '=', albumId)
.where('album_asset.assetsId', 'in', assetIds)
.execute()
.then((results) => new Set(results.map(({ assetsId }) => assetsId)));
}
@@ -272,7 +262,7 @@ export class AlbumRepository {
create(album: Insertable<AlbumTable>, assetIds: string[], albumUsers: AlbumUserCreateDto[]) {
return this.db.transaction().execute(async (tx) => {
const newAlbum = await tx.insertInto('albums').values(album).returning('albums.id').executeTakeFirst();
const newAlbum = await tx.insertInto('album').values(album).returning('album.id').executeTakeFirst();
if (!newAlbum) {
throw new Error('Failed to create album');
@@ -284,7 +274,7 @@ export class AlbumRepository {
if (albumUsers.length > 0) {
await tx
.insertInto('albums_shared_users_users')
.insertInto('album_user')
.values(
albumUsers.map((albumUser) => ({ albumsId: newAlbum.id, usersId: albumUser.userId, role: albumUser.role })),
)
@@ -292,7 +282,7 @@ export class AlbumRepository {
}
return tx
.selectFrom('albums')
.selectFrom('album')
.selectAll()
.where('id', '=', newAlbum.id)
.select(withOwner)
@@ -305,10 +295,10 @@ export class AlbumRepository {
update(id: string, album: Updateable<AlbumTable>) {
return this.db
.updateTable('albums')
.updateTable('album')
.set(album)
.where('id', '=', id)
.returningAll('albums')
.returningAll('album')
.returning(withOwner)
.returning(withSharedLink)
.returning(withAlbumUsers)
@@ -316,7 +306,7 @@ export class AlbumRepository {
}
async delete(id: string): Promise<void> {
await this.db.deleteFrom('albums').where('id', '=', id).execute();
await this.db.deleteFrom('album').where('id', '=', id).execute();
}
@Chunked({ paramIndex: 2, chunkSize: 30_000 })
@@ -326,7 +316,7 @@ export class AlbumRepository {
}
await db
.insertInto('albums_assets_assets')
.insertInto('album_asset')
.values(assetIds.map((assetId) => ({ albumsId: albumId, assetsId: assetId })))
.execute();
}
@@ -343,11 +333,11 @@ export class AlbumRepository {
// Subquery for getting a new thumbnail.
const result = await this.db
.updateTable('albums')
.updateTable('album')
.set((eb) => ({
albumThumbnailAssetId: this.updateThumbnailBuilder(eb)
.select('album_assets.assetsId')
.orderBy('assets.fileCreatedAt', 'desc')
.select('album_asset.assetsId')
.orderBy('asset.fileCreatedAt', 'desc')
.limit(1),
}))
.where((eb) =>
@@ -362,7 +352,7 @@ export class AlbumRepository {
eb.exists(
this.updateThumbnailBuilder(eb)
.select(sql`1`.as('1'))
.whereRef('albums.albumThumbnailAssetId', '=', 'album_assets.assetsId'), // Has invalid assets
.whereRef('album.albumThumbnailAssetId', '=', 'album_asset.assetsId'), // Has invalid assets
),
),
]),
@@ -373,12 +363,12 @@ export class AlbumRepository {
return Number(result[0].numUpdatedRows);
}
private updateThumbnailBuilder(eb: ExpressionBuilder<DB, 'albums'>) {
private updateThumbnailBuilder(eb: ExpressionBuilder<DB, 'album'>) {
return eb
.selectFrom('albums_assets_assets as album_assets')
.innerJoin('assets', (join) =>
join.onRef('album_assets.assetsId', '=', 'assets.id').on('assets.deletedAt', 'is', null),
.selectFrom('album_asset')
.innerJoin('asset', (join) =>
join.onRef('album_asset.assetsId', '=', 'asset.id').on('asset.deletedAt', 'is', null),
)
.whereRef('album_assets.albumsId', '=', 'albums.id');
.whereRef('album_asset.albumsId', '=', 'album.id');
}
}

View File

@@ -13,45 +13,45 @@ export class ApiKeyRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {}
create(dto: Insertable<ApiKeyTable>) {
return this.db.insertInto('api_keys').values(dto).returning(columns.apiKey).executeTakeFirstOrThrow();
return this.db.insertInto('api_key').values(dto).returning(columns.apiKey).executeTakeFirstOrThrow();
}
async update(userId: string, id: string, dto: Updateable<ApiKeyTable>) {
return this.db
.updateTable('api_keys')
.updateTable('api_key')
.set(dto)
.where('api_keys.userId', '=', userId)
.where('api_key.userId', '=', userId)
.where('id', '=', asUuid(id))
.returning(columns.apiKey)
.executeTakeFirstOrThrow();
}
async delete(userId: string, id: string) {
await this.db.deleteFrom('api_keys').where('userId', '=', userId).where('id', '=', asUuid(id)).execute();
await this.db.deleteFrom('api_key').where('userId', '=', userId).where('id', '=', asUuid(id)).execute();
}
@GenerateSql({ params: [DummyValue.STRING] })
getKey(hashedToken: string) {
return this.db
.selectFrom('api_keys')
.selectFrom('api_key')
.select((eb) => [
...columns.authApiKey,
jsonObjectFrom(
eb
.selectFrom('users')
.selectFrom('user')
.select(columns.authUser)
.whereRef('users.id', '=', 'api_keys.userId')
.where('users.deletedAt', 'is', null),
.whereRef('user.id', '=', 'api_key.userId')
.where('user.deletedAt', 'is', null),
).as('user'),
])
.where('api_keys.key', '=', hashedToken)
.where('api_key.key', '=', hashedToken)
.executeTakeFirst();
}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
getById(userId: string, id: string) {
return this.db
.selectFrom('api_keys')
.selectFrom('api_key')
.select(columns.apiKey)
.where('id', '=', asUuid(id))
.where('userId', '=', userId)
@@ -61,7 +61,7 @@ export class ApiKeyRepository {
@GenerateSql({ params: [DummyValue.UUID] })
getByUserId(userId: string) {
return this.db
.selectFrom('api_keys')
.selectFrom('api_key')
.select(columns.apiKey)
.where('userId', '=', userId)
.orderBy('createdAt', 'desc')

View File

@@ -26,9 +26,9 @@ export class AssetJobRepository {
@GenerateSql({ params: [DummyValue.UUID] })
getForSearchDuplicatesJob(id: string) {
return this.db
.selectFrom('assets')
.where('assets.id', '=', asUuid(id))
.leftJoin('smart_search', 'assets.id', 'smart_search.assetId')
.selectFrom('asset')
.where('asset.id', '=', asUuid(id))
.leftJoin('smart_search', 'asset.id', 'smart_search.assetId')
.select(['id', 'type', 'ownerId', 'duplicateId', 'stackId', 'visibility', 'smart_search.embedding'])
.limit(1)
.executeTakeFirst();
@@ -37,18 +37,18 @@ export class AssetJobRepository {
@GenerateSql({ params: [DummyValue.UUID] })
getForSidecarWriteJob(id: string) {
return this.db
.selectFrom('assets')
.where('assets.id', '=', asUuid(id))
.selectFrom('asset')
.where('asset.id', '=', asUuid(id))
.select((eb) => [
'id',
'sidecarPath',
'originalPath',
jsonArrayFrom(
eb
.selectFrom('tags')
.select(['tags.value'])
.innerJoin('tag_asset', 'tags.id', 'tag_asset.tagsId')
.whereRef('assets.id', '=', 'tag_asset.assetsId'),
.selectFrom('tag')
.select(['tag.value'])
.innerJoin('tag_asset', 'tag.id', 'tag_asset.tagsId')
.whereRef('asset.id', '=', 'tag_asset.assetsId'),
).as('tags'),
])
.limit(1)
@@ -58,20 +58,20 @@ export class AssetJobRepository {
@GenerateSql({ params: [false], stream: true })
streamForThumbnailJob(force: boolean) {
return this.db
.selectFrom('assets')
.select(['assets.id', 'assets.thumbhash'])
.selectFrom('asset')
.select(['asset.id', 'asset.thumbhash'])
.select(withFiles)
.where('assets.deletedAt', 'is', null)
.where('assets.visibility', '!=', AssetVisibility.HIDDEN)
.where('asset.deletedAt', 'is', null)
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
.$if(!force, (qb) =>
qb
// If there aren't any entries, metadata extraction hasn't run yet which is required for thumbnails
.innerJoin('asset_job_status', 'asset_job_status.assetId', 'assets.id')
.innerJoin('asset_job_status', 'asset_job_status.assetId', 'asset.id')
.where((eb) =>
eb.or([
eb('asset_job_status.previewAt', 'is', null),
eb('asset_job_status.thumbnailAt', 'is', null),
eb('assets.thumbhash', 'is', null),
eb('asset.thumbhash', 'is', null),
]),
),
)
@@ -81,72 +81,72 @@ export class AssetJobRepository {
@GenerateSql({ params: [DummyValue.UUID] })
getForMigrationJob(id: string) {
return this.db
.selectFrom('assets')
.select(['assets.id', 'assets.ownerId', 'assets.encodedVideoPath'])
.selectFrom('asset')
.select(['asset.id', 'asset.ownerId', 'asset.encodedVideoPath'])
.select(withFiles)
.where('assets.id', '=', id)
.where('asset.id', '=', id)
.executeTakeFirst();
}
@GenerateSql({ params: [DummyValue.UUID] })
getForGenerateThumbnailJob(id: string) {
return this.db
.selectFrom('assets')
.selectFrom('asset')
.select([
'assets.id',
'assets.visibility',
'assets.originalFileName',
'assets.originalPath',
'assets.ownerId',
'assets.thumbhash',
'assets.type',
'asset.id',
'asset.visibility',
'asset.originalFileName',
'asset.originalPath',
'asset.ownerId',
'asset.thumbhash',
'asset.type',
])
.select(withFiles)
.$call(withExifInner)
.where('assets.id', '=', id)
.where('asset.id', '=', id)
.executeTakeFirst();
}
@GenerateSql({ params: [DummyValue.UUID] })
getForMetadataExtraction(id: string) {
return this.db
.selectFrom('assets')
.selectFrom('asset')
.select(columns.asset)
.select(withFaces)
.where('assets.id', '=', id)
.where('asset.id', '=', id)
.executeTakeFirst();
}
@GenerateSql({ params: [DummyValue.UUID, AssetFileType.THUMBNAIL] })
getAlbumThumbnailFiles(id: string, fileType?: AssetFileType) {
return this.db
.selectFrom('asset_files')
.selectFrom('asset_file')
.select(columns.assetFiles)
.where('asset_files.assetId', '=', id)
.$if(!!fileType, (qb) => qb.where('asset_files.type', '=', fileType!))
.where('asset_file.assetId', '=', id)
.$if(!!fileType, (qb) => qb.where('asset_file.type', '=', fileType!))
.execute();
}
private assetsWithPreviews() {
return this.db
.selectFrom('assets')
.where('assets.visibility', '!=', AssetVisibility.HIDDEN)
.where('assets.deletedAt', 'is', null)
.innerJoin('asset_job_status as job_status', 'assetId', 'assets.id')
.selectFrom('asset')
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
.where('asset.deletedAt', 'is', null)
.innerJoin('asset_job_status as job_status', 'assetId', 'asset.id')
.where('job_status.previewAt', 'is not', null);
}
@GenerateSql({ params: [], stream: true })
streamForSearchDuplicates(force?: boolean) {
return this.db
.selectFrom('assets')
.select(['assets.id'])
.where('assets.deletedAt', 'is', null)
.innerJoin('smart_search', 'assets.id', 'smart_search.assetId')
.selectFrom('asset')
.select(['asset.id'])
.where('asset.deletedAt', 'is', null)
.innerJoin('smart_search', 'asset.id', 'smart_search.assetId')
.$call(withDefaultVisibility)
.$if(!force, (qb) =>
qb
.innerJoin('asset_job_status as job_status', 'job_status.assetId', 'assets.id')
.innerJoin('asset_job_status as job_status', 'job_status.assetId', 'asset.id')
.where('job_status.duplicatesDetectedAt', 'is', null),
)
.stream();
@@ -155,11 +155,9 @@ export class AssetJobRepository {
@GenerateSql({ params: [], stream: true })
streamForEncodeClip(force?: boolean) {
return this.assetsWithPreviews()
.select(['assets.id'])
.select(['asset.id'])
.$if(!force, (qb) =>
qb.where((eb) =>
eb.not((eb) => eb.exists(eb.selectFrom('smart_search').whereRef('assetId', '=', 'assets.id'))),
),
qb.where((eb) => eb.not((eb) => eb.exists(eb.selectFrom('smart_search').whereRef('assetId', '=', 'asset.id')))),
)
.stream();
}
@@ -167,142 +165,142 @@ export class AssetJobRepository {
@GenerateSql({ params: [DummyValue.UUID] })
getForClipEncoding(id: string) {
return this.db
.selectFrom('assets')
.select(['assets.id', 'assets.visibility'])
.selectFrom('asset')
.select(['asset.id', 'asset.visibility'])
.select((eb) => withFiles(eb, AssetFileType.PREVIEW))
.where('assets.id', '=', id)
.where('asset.id', '=', id)
.executeTakeFirst();
}
@GenerateSql({ params: [DummyValue.UUID] })
getForDetectFacesJob(id: string) {
return this.db
.selectFrom('assets')
.select(['assets.id', 'assets.visibility'])
.selectFrom('asset')
.select(['asset.id', 'asset.visibility'])
.$call(withExifInner)
.select((eb) => withFaces(eb, true))
.select((eb) => withFiles(eb, AssetFileType.PREVIEW))
.where('assets.id', '=', id)
.where('asset.id', '=', id)
.executeTakeFirst();
}
@GenerateSql({ params: [[DummyValue.UUID]] })
getForSyncAssets(ids: string[]) {
return this.db
.selectFrom('assets')
.selectFrom('asset')
.select([
'assets.id',
'assets.isOffline',
'assets.libraryId',
'assets.originalPath',
'assets.status',
'assets.fileModifiedAt',
'asset.id',
'asset.isOffline',
'asset.libraryId',
'asset.originalPath',
'asset.status',
'asset.fileModifiedAt',
])
.where('assets.id', '=', anyUuid(ids))
.where('asset.id', '=', anyUuid(ids))
.execute();
}
@GenerateSql({ params: [DummyValue.UUID] })
getForAssetDeletion(id: string) {
return this.db
.selectFrom('assets')
.selectFrom('asset')
.select([
'assets.id',
'assets.visibility',
'assets.libraryId',
'assets.ownerId',
'assets.livePhotoVideoId',
'assets.sidecarPath',
'assets.encodedVideoPath',
'assets.originalPath',
'asset.id',
'asset.visibility',
'asset.libraryId',
'asset.ownerId',
'asset.livePhotoVideoId',
'asset.sidecarPath',
'asset.encodedVideoPath',
'asset.originalPath',
])
.$call(withExif)
.select(withFacesAndPeople)
.select(withFiles)
.leftJoin('asset_stack', 'asset_stack.id', 'assets.stackId')
.leftJoin('stack', 'stack.id', 'asset.stackId')
.leftJoinLateral(
(eb) =>
eb
.selectFrom('assets as stacked')
.select(['asset_stack.id', 'asset_stack.primaryAssetId'])
.selectFrom('asset as stacked')
.select(['stack.id', 'stack.primaryAssetId'])
.select((eb) => eb.fn<Asset[]>('array_agg', [eb.table('stacked')]).as('assets'))
.where('stacked.deletedAt', 'is not', null)
.where('stacked.visibility', '=', AssetVisibility.TIMELINE)
.whereRef('stacked.stackId', '=', 'asset_stack.id')
.groupBy('asset_stack.id')
.whereRef('stacked.stackId', '=', 'stack.id')
.groupBy('stack.id')
.as('stacked_assets'),
(join) => join.on('asset_stack.id', 'is not', null),
(join) => join.on('stack.id', 'is not', null),
)
.select((eb) => toJson(eb, 'stacked_assets').as('stack'))
.where('assets.id', '=', id)
.where('asset.id', '=', id)
.executeTakeFirst();
}
@GenerateSql({ params: [], stream: true })
streamForVideoConversion(force?: boolean) {
return this.db
.selectFrom('assets')
.select(['assets.id'])
.where('assets.type', '=', AssetType.VIDEO)
.selectFrom('asset')
.select(['asset.id'])
.where('asset.type', '=', AssetType.VIDEO)
.$if(!force, (qb) =>
qb
.where((eb) => eb.or([eb('assets.encodedVideoPath', 'is', null), eb('assets.encodedVideoPath', '=', '')]))
.where('assets.visibility', '!=', AssetVisibility.HIDDEN),
.where((eb) => eb.or([eb('asset.encodedVideoPath', 'is', null), eb('asset.encodedVideoPath', '=', '')]))
.where('asset.visibility', '!=', AssetVisibility.HIDDEN),
)
.where('assets.deletedAt', 'is', null)
.where('asset.deletedAt', 'is', null)
.stream();
}
@GenerateSql({ params: [DummyValue.UUID] })
getForVideoConversion(id: string) {
return this.db
.selectFrom('assets')
.select(['assets.id', 'assets.ownerId', 'assets.originalPath', 'assets.encodedVideoPath'])
.where('assets.id', '=', id)
.where('assets.type', '=', AssetType.VIDEO)
.selectFrom('asset')
.select(['asset.id', 'asset.ownerId', 'asset.originalPath', 'asset.encodedVideoPath'])
.where('asset.id', '=', id)
.where('asset.type', '=', AssetType.VIDEO)
.executeTakeFirst();
}
@GenerateSql({ params: [], stream: true })
streamForMetadataExtraction(force?: boolean) {
return this.db
.selectFrom('assets')
.select(['assets.id'])
.selectFrom('asset')
.select(['asset.id'])
.$if(!force, (qb) =>
qb
.leftJoin('asset_job_status', 'asset_job_status.assetId', 'assets.id')
.leftJoin('asset_job_status', 'asset_job_status.assetId', 'asset.id')
.where((eb) =>
eb.or([eb('asset_job_status.metadataExtractedAt', 'is', null), eb('asset_job_status.assetId', 'is', null)]),
),
)
.where('assets.deletedAt', 'is', null)
.where('asset.deletedAt', 'is', null)
.stream();
}
private storageTemplateAssetQuery() {
return this.db
.selectFrom('assets')
.innerJoin('exif', 'assets.id', 'exif.assetId')
.selectFrom('asset')
.innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId')
.select([
'assets.id',
'assets.ownerId',
'assets.type',
'assets.checksum',
'assets.originalPath',
'assets.isExternal',
'assets.sidecarPath',
'assets.originalFileName',
'assets.livePhotoVideoId',
'assets.fileCreatedAt',
'exif.timeZone',
'exif.fileSizeInByte',
'asset.id',
'asset.ownerId',
'asset.type',
'asset.checksum',
'asset.originalPath',
'asset.isExternal',
'asset.sidecarPath',
'asset.originalFileName',
'asset.livePhotoVideoId',
'asset.fileCreatedAt',
'asset_exif.timeZone',
'asset_exif.fileSizeInByte',
])
.where('assets.deletedAt', 'is', null);
.where('asset.deletedAt', 'is', null);
}
@GenerateSql({ params: [DummyValue.UUID] })
getForStorageTemplateJob(id: string): Promise<StorageAsset | undefined> {
return this.storageTemplateAssetQuery().where('assets.id', '=', id).executeTakeFirst() as Promise<
return this.storageTemplateAssetQuery().where('asset.id', '=', id).executeTakeFirst() as Promise<
StorageAsset | undefined
>;
}
@@ -315,21 +313,21 @@ export class AssetJobRepository {
@GenerateSql({ params: [DummyValue.DATE], stream: true })
streamForDeletedJob(trashedBefore: Date) {
return this.db
.selectFrom('assets')
.selectFrom('asset')
.select(['id', 'isOffline'])
.where('assets.deletedAt', '<=', trashedBefore)
.where('asset.deletedAt', '<=', trashedBefore)
.stream();
}
@GenerateSql({ params: [], stream: true })
streamForSidecar(force?: boolean) {
return this.db
.selectFrom('assets')
.select(['assets.id'])
.selectFrom('asset')
.select(['asset.id'])
.$if(!force, (qb) =>
qb.where((eb) => eb.or([eb('assets.sidecarPath', '=', ''), eb('assets.sidecarPath', 'is', null)])),
qb.where((eb) => eb.or([eb('asset.sidecarPath', '=', ''), eb('asset.sidecarPath', 'is', null)])),
)
.where('assets.visibility', '!=', AssetVisibility.HIDDEN)
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
.stream();
}
@@ -337,13 +335,13 @@ export class AssetJobRepository {
streamForDetectFacesJob(force?: boolean) {
return this.assetsWithPreviews()
.$if(!force, (qb) => qb.where('job_status.facesRecognizedAt', 'is', null))
.select(['assets.id'])
.orderBy('assets.createdAt', 'desc')
.select(['asset.id'])
.orderBy('asset.createdAt', 'desc')
.stream();
}
@GenerateSql({ params: [DummyValue.DATE], stream: true })
streamForMigrationJob() {
return this.db.selectFrom('assets').select(['id']).where('assets.deletedAt', 'is', null).stream();
return this.db.selectFrom('asset').select(['id']).where('asset.deletedAt', 'is', null).stream();
}
}

View File

@@ -6,10 +6,10 @@ import { Stack } from 'src/database';
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
import { AssetFileType, AssetOrder, AssetStatus, AssetType, AssetVisibility } from 'src/enum';
import { DB } from 'src/schema';
import { AssetFileTable } from 'src/schema/tables/asset-files.table';
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
import { AssetFileTable } from 'src/schema/tables/asset-file.table';
import { AssetJobStatusTable } from 'src/schema/tables/asset-job-status.table';
import { AssetTable } from 'src/schema/tables/asset.table';
import { ExifTable } from 'src/schema/tables/exif.table';
import {
anyUuid,
asUuid,
@@ -114,10 +114,10 @@ interface GetByIdsRelations {
export class AssetRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {}
async upsertExif(exif: Insertable<ExifTable>): Promise<void> {
async upsertExif(exif: Insertable<AssetExifTable>): Promise<void> {
const value = { ...exif, assetId: asUuid(exif.assetId) };
await this.db
.insertInto('exif')
.insertInto('asset_exif')
.values(value)
.onConflict((oc) =>
oc.column('assetId').doUpdateSet((eb) =>
@@ -161,12 +161,12 @@ export class AssetRepository {
@GenerateSql({ params: [[DummyValue.UUID], { model: DummyValue.STRING }] })
@Chunked()
async updateAllExif(ids: string[], options: Updateable<ExifTable>): Promise<void> {
async updateAllExif(ids: string[], options: Updateable<AssetExifTable>): Promise<void> {
if (ids.length === 0) {
return;
}
await this.db.updateTable('exif').set(options).where('assetId', 'in', ids).execute();
await this.db.updateTable('asset_exif').set(options).where('assetId', 'in', ids).execute();
}
async upsertJobStatus(...jobStatus: Insertable<AssetJobStatusTable>[]): Promise<void> {
@@ -196,11 +196,11 @@ export class AssetRepository {
}
create(asset: Insertable<AssetTable>) {
return this.db.insertInto('assets').values(asset).returningAll().executeTakeFirstOrThrow();
return this.db.insertInto('asset').values(asset).returningAll().executeTakeFirstOrThrow();
}
createAll(assets: Insertable<AssetTable>[]) {
return this.db.insertInto('assets').values(assets).returningAll().execute();
return this.db.insertInto('asset').values(assets).returningAll().execute();
}
@GenerateSql({ params: [DummyValue.UUID, { day: 1, month: 1 }] })
@@ -213,7 +213,7 @@ export class AssetRepository {
.selectFrom((eb) =>
eb
.fn('generate_series', [
sql`(select date_part('year', min(("localDateTime" at time zone 'UTC')::date))::int from assets)`,
sql`(select date_part('year', min(("localDateTime" at time zone 'UTC')::date))::int from asset)`,
sql`date_part('year', current_date)::int - 1`,
])
.as('year'),
@@ -224,30 +224,30 @@ export class AssetRepository {
.innerJoinLateral(
(qb) =>
qb
.selectFrom('assets')
.selectAll('assets')
.innerJoin('asset_job_status', 'assets.id', 'asset_job_status.assetId')
.selectFrom('asset')
.selectAll('asset')
.innerJoin('asset_job_status', 'asset.id', 'asset_job_status.assetId')
.where('asset_job_status.previewAt', 'is not', null)
.where(sql`(assets."localDateTime" at time zone 'UTC')::date`, '=', sql`today.date`)
.where('assets.ownerId', '=', anyUuid(ownerIds))
.where('assets.visibility', '=', AssetVisibility.TIMELINE)
.where(sql`(asset."localDateTime" at time zone 'UTC')::date`, '=', sql`today.date`)
.where('asset.ownerId', '=', anyUuid(ownerIds))
.where('asset.visibility', '=', AssetVisibility.TIMELINE)
.where((eb) =>
eb.exists((qb) =>
qb
.selectFrom('asset_files')
.whereRef('assetId', '=', 'assets.id')
.where('asset_files.type', '=', AssetFileType.PREVIEW),
.selectFrom('asset_file')
.whereRef('assetId', '=', 'asset.id')
.where('asset_file.type', '=', AssetFileType.PREVIEW),
),
)
.where('assets.deletedAt', 'is', null)
.orderBy(sql`(assets."localDateTime" at time zone 'UTC')::date`, 'desc')
.where('asset.deletedAt', 'is', null)
.orderBy(sql`(asset."localDateTime" at time zone 'UTC')::date`, 'desc')
.limit(20)
.as('a'),
(join) => join.onTrue(),
)
.innerJoin('exif', 'a.id', 'exif.assetId')
.innerJoin('asset_exif', 'a.id', 'asset_exif.assetId')
.selectAll('a')
.select((eb) => eb.fn.toJson(eb.table('exif')).as('exifInfo')),
.select((eb) => eb.fn.toJson(eb.table('asset_exif')).as('exifInfo')),
)
.selectFrom('res')
.select(sql<number>`date_part('year', ("localDateTime" at time zone 'UTC')::date)::int`.as('year'))
@@ -260,30 +260,30 @@ export class AssetRepository {
@GenerateSql({ params: [[DummyValue.UUID]] })
@ChunkedArray()
getByIds(ids: string[]) {
return this.db.selectFrom('assets').selectAll('assets').where('assets.id', '=', anyUuid(ids)).execute();
return this.db.selectFrom('asset').selectAll('asset').where('asset.id', '=', anyUuid(ids)).execute();
}
@GenerateSql({ params: [[DummyValue.UUID]] })
@ChunkedArray()
getByIdsWithAllRelationsButStacks(ids: string[]) {
return this.db
.selectFrom('assets')
.selectAll('assets')
.selectFrom('asset')
.selectAll('asset')
.select(withFacesAndPeople)
.select(withTags)
.$call(withExif)
.where('assets.id', '=', anyUuid(ids))
.where('asset.id', '=', anyUuid(ids))
.execute();
}
@GenerateSql({ params: [DummyValue.UUID] })
async deleteAll(ownerId: string): Promise<void> {
await this.db.deleteFrom('assets').where('ownerId', '=', ownerId).execute();
await this.db.deleteFrom('asset').where('ownerId', '=', ownerId).execute();
}
async getByDeviceIds(ownerId: string, deviceId: string, deviceAssetIds: string[]): Promise<string[]> {
const assets = await this.db
.selectFrom('assets')
.selectFrom('asset')
.select(['deviceAssetId'])
.where('deviceAssetId', 'in', deviceAssetIds)
.where('deviceId', '=', deviceId)
@@ -296,8 +296,8 @@ export class AssetRepository {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] })
getByLibraryIdAndOriginalPath(libraryId: string, originalPath: string) {
return this.db
.selectFrom('assets')
.selectAll('assets')
.selectFrom('asset')
.selectAll('asset')
.where('libraryId', '=', asUuid(libraryId))
.where('originalPath', '=', originalPath)
.limit(1)
@@ -314,7 +314,7 @@ export class AssetRepository {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] })
async getAllByDeviceId(ownerId: string, deviceId: string): Promise<string[]> {
const items = await this.db
.selectFrom('assets')
.selectFrom('asset')
.select(['deviceAssetId'])
.where('ownerId', '=', asUuid(ownerId))
.where('deviceId', '=', deviceId)
@@ -328,7 +328,7 @@ export class AssetRepository {
@GenerateSql({ params: [DummyValue.UUID] })
async getLivePhotoCount(motionId: string): Promise<number> {
const [{ count }] = await this.db
.selectFrom('assets')
.selectFrom('asset')
.select((eb) => eb.fn.countAll<number>().as('count'))
.where('livePhotoVideoId', '=', asUuid(motionId))
.execute();
@@ -338,9 +338,9 @@ export class AssetRepository {
@GenerateSql({ params: [DummyValue.UUID] })
getById(id: string, { exifInfo, faces, files, library, owner, smartSearch, stack, tags }: GetByIdsRelations = {}) {
return this.db
.selectFrom('assets')
.selectAll('assets')
.where('assets.id', '=', asUuid(id))
.selectFrom('asset')
.selectAll('asset')
.where('asset.id', '=', asUuid(id))
.$if(!!exifInfo, withExif)
.$if(!!faces, (qb) => qb.select(faces?.person ? withFacesAndPeople : withFaces).$narrowType<{ faces: NotNull }>())
.$if(!!library, (qb) => qb.select(withLibrary))
@@ -348,25 +348,25 @@ export class AssetRepository {
.$if(!!smartSearch, withSmartSearch)
.$if(!!stack, (qb) =>
qb
.leftJoin('asset_stack', 'asset_stack.id', 'assets.stackId')
.leftJoin('stack', 'stack.id', 'asset.stackId')
.$if(!stack!.assets, (qb) =>
qb.select((eb) => eb.fn.toJson(eb.table('asset_stack')).$castTo<Stack | null>().as('stack')),
qb.select((eb) => eb.fn.toJson(eb.table('stack')).$castTo<Stack | null>().as('stack')),
)
.$if(!!stack!.assets, (qb) =>
qb
.leftJoinLateral(
(eb) =>
eb
.selectFrom('assets as stacked')
.selectAll('asset_stack')
.selectFrom('asset as stacked')
.selectAll('stack')
.select((eb) => eb.fn('array_agg', [eb.table('stacked')]).as('assets'))
.whereRef('stacked.stackId', '=', 'asset_stack.id')
.whereRef('stacked.id', '!=', 'asset_stack.primaryAssetId')
.whereRef('stacked.stackId', '=', 'stack.id')
.whereRef('stacked.id', '!=', 'stack.primaryAssetId')
.where('stacked.deletedAt', 'is', null)
.where('stacked.visibility', '=', AssetVisibility.TIMELINE)
.groupBy('asset_stack.id')
.groupBy('stack.id')
.as('stacked_assets'),
(join) => join.on('asset_stack.id', 'is not', null),
(join) => join.on('stack.id', 'is not', null),
)
.select((eb) => eb.fn.toJson(eb.table('stacked_assets')).$castTo<Stack | null>().as('stack')),
),
@@ -383,11 +383,11 @@ export class AssetRepository {
if (ids.length === 0) {
return;
}
await this.db.updateTable('assets').set(options).where('id', '=', anyUuid(ids)).execute();
await this.db.updateTable('asset').set(options).where('id', '=', anyUuid(ids)).execute();
}
async updateByLibraryId(libraryId: string, options: Updateable<AssetTable>): Promise<void> {
await this.db.updateTable('assets').set(options).where('libraryId', '=', asUuid(libraryId)).execute();
await this.db.updateTable('asset').set(options).where('libraryId', '=', asUuid(libraryId)).execute();
}
async update(asset: Updateable<AssetTable> & { id: string }) {
@@ -395,9 +395,9 @@ export class AssetRepository {
delete value.id;
if (!isEmpty(value)) {
return this.db
.with('assets', (qb) => qb.updateTable('assets').set(asset).where('id', '=', asUuid(asset.id)).returningAll())
.selectFrom('assets')
.selectAll('assets')
.with('asset', (qb) => qb.updateTable('asset').set(asset).where('id', '=', asUuid(asset.id)).returningAll())
.selectFrom('asset')
.selectAll('asset')
.$call(withExif)
.$call((qb) => qb.select(withFacesAndPeople))
.executeTakeFirst();
@@ -407,14 +407,14 @@ export class AssetRepository {
}
async remove(asset: { id: string }): Promise<void> {
await this.db.deleteFrom('assets').where('id', '=', asUuid(asset.id)).execute();
await this.db.deleteFrom('asset').where('id', '=', asUuid(asset.id)).execute();
}
@GenerateSql({ params: [{ ownerId: DummyValue.UUID, libraryId: DummyValue.UUID, checksum: DummyValue.BUFFER }] })
getByChecksum({ ownerId, libraryId, checksum }: AssetGetByChecksumOptions) {
return this.db
.selectFrom('assets')
.selectAll('assets')
.selectFrom('asset')
.selectAll('asset')
.where('ownerId', '=', asUuid(ownerId))
.where('checksum', '=', checksum)
.$call((qb) => (libraryId ? qb.where('libraryId', '=', asUuid(libraryId)) : qb.where('libraryId', 'is', null)))
@@ -425,7 +425,7 @@ export class AssetRepository {
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.BUFFER]] })
getByChecksums(userId: string, checksums: Buffer[]) {
return this.db
.selectFrom('assets')
.selectFrom('asset')
.select(['id', 'checksum', 'deletedAt'])
.where('ownerId', '=', asUuid(userId))
.where('checksum', 'in', checksums)
@@ -435,7 +435,7 @@ export class AssetRepository {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.BUFFER] })
async getUploadAssetIdByChecksum(ownerId: string, checksum: Buffer): Promise<string | undefined> {
const asset = await this.db
.selectFrom('assets')
.selectFrom('asset')
.select('id')
.where('ownerId', '=', asUuid(ownerId))
.where('checksum', '=', checksum)
@@ -449,37 +449,37 @@ export class AssetRepository {
findLivePhotoMatch(options: LivePhotoSearchOptions) {
const { ownerId, otherAssetId, livePhotoCID, type } = options;
return this.db
.selectFrom('assets')
.select(['assets.id', 'assets.ownerId'])
.innerJoin('exif', 'assets.id', 'exif.assetId')
.selectFrom('asset')
.select(['asset.id', 'asset.ownerId'])
.innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId')
.where('id', '!=', asUuid(otherAssetId))
.where('ownerId', '=', asUuid(ownerId))
.where('type', '=', type)
.where('exif.livePhotoCID', '=', livePhotoCID)
.where('asset_exif.livePhotoCID', '=', livePhotoCID)
.limit(1)
.executeTakeFirst();
}
getStatistics(ownerId: string, { visibility, isFavorite, isTrashed }: AssetStatsOptions): Promise<AssetStats> {
return this.db
.selectFrom('assets')
.selectFrom('asset')
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.AUDIO).as(AssetType.AUDIO))
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.IMAGE).as(AssetType.IMAGE))
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.VIDEO).as(AssetType.VIDEO))
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.OTHER).as(AssetType.OTHER))
.where('ownerId', '=', asUuid(ownerId))
.$if(visibility === undefined, withDefaultVisibility)
.$if(!!visibility, (qb) => qb.where('assets.visibility', '=', visibility!))
.$if(!!visibility, (qb) => qb.where('asset.visibility', '=', visibility!))
.$if(isFavorite !== undefined, (qb) => qb.where('isFavorite', '=', isFavorite!))
.$if(!!isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED))
.$if(!!isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.DELETED))
.where('deletedAt', isTrashed ? 'is not' : 'is', null)
.executeTakeFirstOrThrow();
}
getRandom(userIds: string[], take: number) {
return this.db
.selectFrom('assets')
.selectAll('assets')
.selectFrom('asset')
.selectAll('asset')
.$call(withExif)
.$call(withDefaultVisibility)
.where('ownerId', '=', anyUuid(userIds))
@@ -492,38 +492,36 @@ export class AssetRepository {
@GenerateSql({ params: [{}] })
async getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]> {
return this.db
.with('assets', (qb) =>
.with('asset', (qb) =>
qb
.selectFrom('assets')
.selectFrom('asset')
.select(truncatedDate<Date>().as('timeBucket'))
.$if(!!options.isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED))
.where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null)
.$if(!!options.isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.DELETED))
.where('asset.deletedAt', options.isTrashed ? 'is not' : 'is', null)
.$if(options.visibility === undefined, withDefaultVisibility)
.$if(!!options.visibility, (qb) => qb.where('assets.visibility', '=', options.visibility!))
.$if(!!options.visibility, (qb) => qb.where('asset.visibility', '=', options.visibility!))
.$if(!!options.albumId, (qb) =>
qb
.innerJoin('albums_assets_assets', 'assets.id', 'albums_assets_assets.assetsId')
.where('albums_assets_assets.albumsId', '=', asUuid(options.albumId!)),
.innerJoin('album_asset', 'asset.id', 'album_asset.assetsId')
.where('album_asset.albumsId', '=', asUuid(options.albumId!)),
)
.$if(!!options.personId, (qb) => hasPeople(qb, [options.personId!]))
.$if(!!options.withStacked, (qb) =>
qb
.leftJoin('asset_stack', (join) =>
join
.onRef('asset_stack.id', '=', 'assets.stackId')
.onRef('asset_stack.primaryAssetId', '=', 'assets.id'),
.leftJoin('stack', (join) =>
join.onRef('stack.id', '=', 'asset.stackId').onRef('stack.primaryAssetId', '=', 'asset.id'),
)
.where((eb) => eb.or([eb('assets.stackId', 'is', null), eb(eb.table('asset_stack'), 'is not', null)])),
.where((eb) => eb.or([eb('asset.stackId', 'is', null), eb(eb.table('stack'), 'is not', null)])),
)
.$if(!!options.userIds, (qb) => qb.where('assets.ownerId', '=', anyUuid(options.userIds!)))
.$if(options.isFavorite !== undefined, (qb) => qb.where('assets.isFavorite', '=', options.isFavorite!))
.$if(!!options.assetType, (qb) => qb.where('assets.type', '=', options.assetType!))
.$if(!!options.userIds, (qb) => qb.where('asset.ownerId', '=', anyUuid(options.userIds!)))
.$if(options.isFavorite !== undefined, (qb) => qb.where('asset.isFavorite', '=', options.isFavorite!))
.$if(!!options.assetType, (qb) => qb.where('asset.type', '=', options.assetType!))
.$if(options.isDuplicate !== undefined, (qb) =>
qb.where('assets.duplicateId', options.isDuplicate ? 'is not' : 'is', null),
qb.where('asset.duplicateId', options.isDuplicate ? 'is not' : 'is', null),
)
.$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!)),
)
.selectFrom('assets')
.selectFrom('asset')
.select(sql<string>`("timeBucket" AT TIME ZONE 'UTC')::date::text`.as('timeBucket'))
.select((eb) => eb.fn.countAll<number>().as('count'))
.groupBy('timeBucket')
@@ -538,75 +536,75 @@ export class AssetRepository {
const query = this.db
.with('cte', (qb) =>
qb
.selectFrom('assets')
.innerJoin('exif', 'assets.id', 'exif.assetId')
.selectFrom('asset')
.innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId')
.select((eb) => [
'assets.duration',
'assets.id',
'assets.visibility',
'assets.isFavorite',
sql`assets.type = 'IMAGE'`.as('isImage'),
sql`assets."deletedAt" is not null`.as('isTrashed'),
'assets.livePhotoVideoId',
sql`extract(epoch from (assets."localDateTime" - assets."fileCreatedAt" at time zone 'UTC'))::real / 3600`.as(
'asset.duration',
'asset.id',
'asset.visibility',
'asset.isFavorite',
sql`asset.type = 'IMAGE'`.as('isImage'),
sql`asset."deletedAt" is not null`.as('isTrashed'),
'asset.livePhotoVideoId',
sql`extract(epoch from (asset."localDateTime" - asset."fileCreatedAt" at time zone 'UTC'))::real / 3600`.as(
'localOffsetHours',
),
'assets.ownerId',
'assets.status',
sql`assets."fileCreatedAt" at time zone 'utc'`.as('fileCreatedAt'),
eb.fn('encode', ['assets.thumbhash', sql.lit('base64')]).as('thumbhash'),
'exif.city',
'exif.country',
'exif.projectionType',
'asset.ownerId',
'asset.status',
sql`asset."fileCreatedAt" at time zone 'utc'`.as('fileCreatedAt'),
eb.fn('encode', ['asset.thumbhash', sql.lit('base64')]).as('thumbhash'),
'asset_exif.city',
'asset_exif.country',
'asset_exif.projectionType',
eb.fn
.coalesce(
eb
.case()
.when(sql`exif."exifImageHeight" = 0 or exif."exifImageWidth" = 0`)
.when(sql`asset_exif."exifImageHeight" = 0 or asset_exif."exifImageWidth" = 0`)
.then(eb.lit(1))
.when('exif.orientation', 'in', sql<string>`('5', '6', '7', '8', '-90', '90')`)
.then(sql`round(exif."exifImageHeight"::numeric / exif."exifImageWidth"::numeric, 3)`)
.else(sql`round(exif."exifImageWidth"::numeric / exif."exifImageHeight"::numeric, 3)`)
.when('asset_exif.orientation', 'in', sql<string>`('5', '6', '7', '8', '-90', '90')`)
.then(sql`round(asset_exif."exifImageHeight"::numeric / asset_exif."exifImageWidth"::numeric, 3)`)
.else(sql`round(asset_exif."exifImageWidth"::numeric / asset_exif."exifImageHeight"::numeric, 3)`)
.end(),
eb.lit(1),
)
.as('ratio'),
])
.where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null)
.where('asset.deletedAt', options.isTrashed ? 'is not' : 'is', null)
.$if(options.visibility == undefined, withDefaultVisibility)
.$if(!!options.visibility, (qb) => qb.where('assets.visibility', '=', options.visibility!))
.$if(!!options.visibility, (qb) => qb.where('asset.visibility', '=', options.visibility!))
.where(truncatedDate(), '=', timeBucket.replace(/^[+-]/, ''))
.$if(!!options.albumId, (qb) =>
qb.where((eb) =>
eb.exists(
eb
.selectFrom('albums_assets_assets')
.whereRef('albums_assets_assets.assetsId', '=', 'assets.id')
.where('albums_assets_assets.albumsId', '=', asUuid(options.albumId!)),
.selectFrom('album_asset')
.whereRef('album_asset.assetsId', '=', 'asset.id')
.where('album_asset.albumsId', '=', asUuid(options.albumId!)),
),
),
)
.$if(!!options.personId, (qb) => hasPeople(qb, [options.personId!]))
.$if(!!options.userIds, (qb) => qb.where('assets.ownerId', '=', anyUuid(options.userIds!)))
.$if(options.isFavorite !== undefined, (qb) => qb.where('assets.isFavorite', '=', options.isFavorite!))
.$if(!!options.userIds, (qb) => qb.where('asset.ownerId', '=', anyUuid(options.userIds!)))
.$if(options.isFavorite !== undefined, (qb) => qb.where('asset.isFavorite', '=', options.isFavorite!))
.$if(!!options.withStacked, (qb) =>
qb
.where((eb) =>
eb.not(
eb.exists(
eb
.selectFrom('asset_stack')
.whereRef('asset_stack.id', '=', 'assets.stackId')
.whereRef('asset_stack.primaryAssetId', '!=', 'assets.id'),
.selectFrom('stack')
.whereRef('stack.id', '=', 'asset.stackId')
.whereRef('stack.primaryAssetId', '!=', 'asset.id'),
),
),
)
.leftJoinLateral(
(eb) =>
eb
.selectFrom('assets as stacked')
.selectFrom('asset as stacked')
.select(sql`array[stacked."stackId"::text, count('stacked')::text]`.as('stack'))
.whereRef('stacked.stackId', '=', 'assets.stackId')
.whereRef('stacked.stackId', '=', 'asset.stackId')
.where('stacked.deletedAt', 'is', null)
.where('stacked.visibility', '=', AssetVisibility.TIMELINE)
.groupBy('stacked.stackId')
@@ -615,13 +613,13 @@ export class AssetRepository {
)
.select('stack'),
)
.$if(!!options.assetType, (qb) => qb.where('assets.type', '=', options.assetType!))
.$if(!!options.assetType, (qb) => qb.where('asset.type', '=', options.assetType!))
.$if(options.isDuplicate !== undefined, (qb) =>
qb.where('assets.duplicateId', options.isDuplicate ? 'is not' : 'is', null),
qb.where('asset.duplicateId', options.isDuplicate ? 'is not' : 'is', null),
)
.$if(!!options.isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED))
.$if(!!options.isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.DELETED))
.$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!))
.orderBy('assets.fileCreatedAt', options.order ?? 'desc'),
.orderBy('asset.fileCreatedAt', options.order ?? 'desc'),
)
.with('agg', (qb) =>
qb
@@ -660,17 +658,17 @@ export class AssetRepository {
const items = await this.db
.with('cities', (qb) =>
qb
.selectFrom('exif')
.selectFrom('asset_exif')
.select('city')
.where('city', 'is not', null)
.groupBy('city')
.having((eb) => eb.fn('count', [eb.ref('assetId')]), '>=', minAssetsPerField),
)
.selectFrom('assets')
.innerJoin('exif', 'assets.id', 'exif.assetId')
.innerJoin('cities', 'exif.city', 'cities.city')
.distinctOn('exif.city')
.select(['assetId as data', 'exif.city as value'])
.selectFrom('asset')
.innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId')
.innerJoin('cities', 'asset_exif.city', 'cities.city')
.distinctOn('asset_exif.city')
.select(['assetId as data', 'asset_exif.city as value'])
.$narrowType<{ value: NotNull }>()
.where('ownerId', '=', asUuid(ownerId))
.where('visibility', '=', AssetVisibility.TIMELINE)
@@ -695,27 +693,27 @@ export class AssetRepository {
getAllForUserFullSync(options: AssetFullSyncOptions) {
const { ownerId, lastId, updatedUntil, limit } = options;
return this.db
.selectFrom('assets')
.selectAll('assets')
.selectFrom('asset')
.selectAll('asset')
.$call(withExif)
.leftJoin('asset_stack', 'asset_stack.id', 'assets.stackId')
.leftJoin('stack', 'stack.id', 'asset.stackId')
.leftJoinLateral(
(eb) =>
eb
.selectFrom('assets as stacked')
.selectAll('asset_stack')
.selectFrom('asset as stacked')
.selectAll('stack')
.select((eb) => eb.fn.count(eb.table('stacked')).as('assetCount'))
.whereRef('stacked.stackId', '=', 'asset_stack.id')
.groupBy('asset_stack.id')
.whereRef('stacked.stackId', '=', 'stack.id')
.groupBy('stack.id')
.as('stacked_assets'),
(join) => join.on('asset_stack.id', 'is not', null),
(join) => join.on('stack.id', 'is not', null),
)
.select((eb) => eb.fn.toJson(eb.table('stacked_assets')).$castTo<Stack | null>().as('stack'))
.where('assets.ownerId', '=', asUuid(ownerId))
.where('assets.visibility', '!=', AssetVisibility.HIDDEN)
.where('assets.updatedAt', '<=', updatedUntil)
.$if(!!lastId, (qb) => qb.where('assets.id', '>', lastId!))
.orderBy('assets.id')
.where('asset.ownerId', '=', asUuid(ownerId))
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
.where('asset.updatedAt', '<=', updatedUntil)
.$if(!!lastId, (qb) => qb.where('asset.id', '>', lastId!))
.orderBy('asset.id')
.limit(limit)
.execute();
}
@@ -723,25 +721,25 @@ export class AssetRepository {
@GenerateSql({ params: [{ userIds: [DummyValue.UUID], updatedAfter: DummyValue.DATE, limit: 100 }] })
async getChangedDeltaSync(options: AssetDeltaSyncOptions) {
return this.db
.selectFrom('assets')
.selectAll('assets')
.selectFrom('asset')
.selectAll('asset')
.$call(withExif)
.leftJoin('asset_stack', 'asset_stack.id', 'assets.stackId')
.leftJoin('stack', 'stack.id', 'asset.stackId')
.leftJoinLateral(
(eb) =>
eb
.selectFrom('assets as stacked')
.selectAll('asset_stack')
.selectFrom('asset as stacked')
.selectAll('stack')
.select((eb) => eb.fn.count(eb.table('stacked')).as('assetCount'))
.whereRef('stacked.stackId', '=', 'asset_stack.id')
.groupBy('asset_stack.id')
.whereRef('stacked.stackId', '=', 'stack.id')
.groupBy('stack.id')
.as('stacked_assets'),
(join) => join.on('asset_stack.id', 'is not', null),
(join) => join.on('stack.id', 'is not', null),
)
.select((eb) => eb.fn.toJson(eb.table('stacked_assets').$castTo<Stack | null>()).as('stack'))
.where('assets.ownerId', '=', anyUuid(options.userIds))
.where('assets.visibility', '!=', AssetVisibility.HIDDEN)
.where('assets.updatedAt', '>', options.updatedAfter)
.where('asset.ownerId', '=', anyUuid(options.userIds))
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
.where('asset.updatedAt', '>', options.updatedAfter)
.limit(options.limit)
.execute();
}
@@ -749,7 +747,7 @@ export class AssetRepository {
async upsertFile(file: Pick<Insertable<AssetFileTable>, 'assetId' | 'path' | 'type'>): Promise<void> {
const value = { ...file, assetId: asUuid(file.assetId) };
await this.db
.insertInto('asset_files')
.insertInto('asset_file')
.values(value)
.onConflict((oc) =>
oc.columns(['assetId', 'type']).doUpdateSet((eb) => ({
@@ -766,7 +764,7 @@ export class AssetRepository {
const values = files.map((row) => ({ ...row, assetId: asUuid(row.assetId) }));
await this.db
.insertInto('asset_files')
.insertInto('asset_file')
.values(values)
.onConflict((oc) =>
oc.columns(['assetId', 'type']).doUpdateSet((eb) => ({
@@ -782,7 +780,7 @@ export class AssetRepository {
}
await this.db
.deleteFrom('asset_files')
.deleteFrom('asset_file')
.where('id', '=', anyUuid(files.map((file) => file.id)))
.execute();
}
@@ -797,7 +795,7 @@ export class AssetRepository {
const exclusions = exclusionPatterns.map((pattern) => globToSqlPattern(pattern));
return this.db
.updateTable('assets')
.updateTable('asset')
.set({
isOffline: true,
deletedAt: new Date(),
@@ -823,9 +821,9 @@ export class AssetRepository {
eb.not(
eb.exists(
this.db
.selectFrom('assets')
.selectFrom('asset')
.select('originalPath')
.whereRef('assets.originalPath', '=', eb.ref('path'))
.whereRef('asset.originalPath', '=', eb.ref('path'))
.where('libraryId', '=', asUuid(libraryId))
.where('isExternal', '=', true),
),
@@ -838,7 +836,7 @@ export class AssetRepository {
async getLibraryAssetCount(libraryId: string): Promise<number> {
const { count } = await this.db
.selectFrom('assets')
.selectFrom('asset')
.select((eb) => eb.fn.countAll<number>().as('count'))
.where('libraryId', '=', asUuid(libraryId))
.executeTakeFirstOrThrow();

View File

@@ -7,34 +7,34 @@ import { anyUuid } from 'src/utils/database';
const builder = (db: Kysely<DB>) =>
db
.selectFrom('assets')
.innerJoin('exif', 'assetId', 'id')
.select(['assets.id', 'assets.livePhotoVideoId', 'exif.fileSizeInByte as size'])
.where('assets.deletedAt', 'is', null);
.selectFrom('asset')
.innerJoin('asset_exif', 'assetId', 'id')
.select(['asset.id', 'asset.livePhotoVideoId', 'asset_exif.fileSizeInByte as size'])
.where('asset.deletedAt', 'is', null);
@Injectable()
export class DownloadRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {}
downloadAssetIds(ids: string[]) {
return builder(this.db).where('assets.id', '=', anyUuid(ids)).stream();
return builder(this.db).where('asset.id', '=', anyUuid(ids)).stream();
}
downloadMotionAssetIds(ids: string[]) {
return builder(this.db).select(['assets.originalPath']).where('assets.id', '=', anyUuid(ids)).stream();
return builder(this.db).select(['asset.originalPath']).where('asset.id', '=', anyUuid(ids)).stream();
}
downloadAlbumId(albumId: string) {
return builder(this.db)
.innerJoin('albums_assets_assets', 'assets.id', 'albums_assets_assets.assetsId')
.where('albums_assets_assets.albumsId', '=', albumId)
.innerJoin('album_asset', 'asset.id', 'album_asset.assetsId')
.where('album_asset.albumsId', '=', albumId)
.stream();
}
downloadUserId(userId: string) {
return builder(this.db)
.where('assets.ownerId', '=', userId)
.where('assets.visibility', '!=', AssetVisibility.HIDDEN)
.where('asset.ownerId', '=', userId)
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
.stream();
}
}

View File

@@ -32,28 +32,28 @@ export class DuplicateRepository {
this.db
.with('duplicates', (qb) =>
qb
.selectFrom('assets')
.selectFrom('asset')
.$call(withDefaultVisibility)
.leftJoinLateral(
(qb) =>
qb
.selectFrom('exif')
.selectAll('assets')
.select((eb) => eb.table('exif').as('exifInfo'))
.whereRef('exif.assetId', '=', 'assets.id')
.as('asset'),
.selectFrom('asset_exif')
.selectAll('asset')
.select((eb) => eb.table('asset_exif').as('exifInfo'))
.whereRef('asset_exif.assetId', '=', 'asset.id')
.as('asset2'),
(join) => join.onTrue(),
)
.select('assets.duplicateId')
.select('asset.duplicateId')
.select((eb) =>
eb.fn.jsonAgg('asset').orderBy('assets.localDateTime', 'asc').$castTo<MapAsset[]>().as('assets'),
eb.fn.jsonAgg('asset2').orderBy('asset.localDateTime', 'asc').$castTo<MapAsset[]>().as('assets'),
)
.where('assets.ownerId', '=', asUuid(userId))
.where('assets.duplicateId', 'is not', null)
.where('asset.ownerId', '=', asUuid(userId))
.where('asset.duplicateId', 'is not', null)
.$narrowType<{ duplicateId: NotNull }>()
.where('assets.deletedAt', 'is', null)
.where('assets.stackId', 'is', null)
.groupBy('assets.duplicateId'),
.where('asset.deletedAt', 'is', null)
.where('asset.stackId', 'is', null)
.groupBy('asset.duplicateId'),
)
.with('unique', (qb) =>
qb
@@ -63,10 +63,10 @@ export class DuplicateRepository {
)
.with('removed_unique', (qb) =>
qb
.updateTable('assets')
.updateTable('asset')
.set({ duplicateId: null })
.from('unique')
.whereRef('assets.duplicateId', '=', 'unique.duplicateId'),
.whereRef('asset.duplicateId', '=', 'unique.duplicateId'),
)
.selectFrom('duplicates')
.selectAll()
@@ -81,7 +81,7 @@ export class DuplicateRepository {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
async delete(userId: string, id: string): Promise<void> {
await this.db
.updateTable('assets')
.updateTable('asset')
.set({ duplicateId: null })
.where('ownerId', '=', userId)
.where('duplicateId', '=', id)
@@ -96,7 +96,7 @@ export class DuplicateRepository {
}
await this.db
.updateTable('assets')
.updateTable('asset')
.set({ duplicateId: null })
.where('ownerId', '=', userId)
.where('duplicateId', 'in', ids)
@@ -120,19 +120,19 @@ export class DuplicateRepository {
return await trx
.with('cte', (qb) =>
qb
.selectFrom('assets')
.selectFrom('asset')
.$call(withDefaultVisibility)
.select([
'assets.id as assetId',
'assets.duplicateId',
'asset.id as assetId',
'asset.duplicateId',
sql<number>`smart_search.embedding <=> ${embedding}`.as('distance'),
])
.innerJoin('smart_search', 'assets.id', 'smart_search.assetId')
.where('assets.ownerId', '=', anyUuid(userIds))
.where('assets.deletedAt', 'is', null)
.where('assets.type', '=', type)
.where('assets.id', '!=', asUuid(assetId))
.where('assets.stackId', 'is', null)
.innerJoin('smart_search', 'asset.id', 'smart_search.assetId')
.where('asset.ownerId', '=', anyUuid(userIds))
.where('asset.deletedAt', 'is', null)
.where('asset.type', '=', type)
.where('asset.id', '!=', asUuid(assetId))
.where('asset.stackId', 'is', null)
.orderBy('distance')
.limit(64),
)
@@ -148,7 +148,7 @@ export class DuplicateRepository {
})
async merge(options: DuplicateMergeOptions): Promise<void> {
await this.db
.updateTable('assets')
.updateTable('asset')
.set({ duplicateId: options.targetId })
.where((eb) =>
eb.or([eb('duplicateId', '=', anyUuid(options.sourceIds)), eb('id', '=', anyUuid(options.assetIds))]),

View File

@@ -21,50 +21,50 @@ export class LibraryRepository {
@GenerateSql({ params: [DummyValue.UUID] })
get(id: string, withDeleted = false) {
return this.db
.selectFrom('libraries')
.selectAll('libraries')
.where('libraries.id', '=', id)
.$if(!withDeleted, (qb) => qb.where('libraries.deletedAt', 'is', null))
.selectFrom('library')
.selectAll('library')
.where('library.id', '=', id)
.$if(!withDeleted, (qb) => qb.where('library.deletedAt', 'is', null))
.executeTakeFirst();
}
@GenerateSql({ params: [] })
getAll(withDeleted = false) {
return this.db
.selectFrom('libraries')
.selectAll('libraries')
.selectFrom('library')
.selectAll('library')
.orderBy('createdAt', 'asc')
.$if(!withDeleted, (qb) => qb.where('libraries.deletedAt', 'is', null))
.$if(!withDeleted, (qb) => qb.where('library.deletedAt', 'is', null))
.execute();
}
@GenerateSql()
getAllDeleted() {
return this.db
.selectFrom('libraries')
.selectAll('libraries')
.where('libraries.deletedAt', 'is not', null)
.selectFrom('library')
.selectAll('library')
.where('library.deletedAt', 'is not', null)
.orderBy('createdAt', 'asc')
.execute();
}
create(library: Insertable<LibraryTable>) {
return this.db.insertInto('libraries').values(library).returningAll().executeTakeFirstOrThrow();
return this.db.insertInto('library').values(library).returningAll().executeTakeFirstOrThrow();
}
async delete(id: string) {
await this.db.deleteFrom('libraries').where('libraries.id', '=', id).execute();
await this.db.deleteFrom('library').where('library.id', '=', id).execute();
}
async softDelete(id: string) {
await this.db.updateTable('libraries').set({ deletedAt: new Date() }).where('libraries.id', '=', id).execute();
await this.db.updateTable('library').set({ deletedAt: new Date() }).where('library.id', '=', id).execute();
}
update(id: string, library: Updateable<LibraryTable>) {
return this.db
.updateTable('libraries')
.updateTable('library')
.set(library)
.where('libraries.id', '=', id)
.where('library.id', '=', id)
.returningAll()
.executeTakeFirstOrThrow();
}
@@ -72,14 +72,14 @@ export class LibraryRepository {
@GenerateSql({ params: [DummyValue.UUID] })
async getStatistics(id: string): Promise<LibraryStatsResponseDto | undefined> {
const stats = await this.db
.selectFrom('libraries')
.innerJoin('assets', 'assets.libraryId', 'libraries.id')
.leftJoin('exif', 'exif.assetId', 'assets.id')
.selectFrom('library')
.innerJoin('asset', 'asset.libraryId', 'library.id')
.leftJoin('asset_exif', 'asset_exif.assetId', 'asset.id')
.select((eb) =>
eb.fn
.countAll<number>()
.filterWhere((eb) =>
eb.and([eb('assets.type', '=', AssetType.IMAGE), eb('assets.visibility', '!=', AssetVisibility.HIDDEN)]),
eb.and([eb('asset.type', '=', AssetType.IMAGE), eb('asset.visibility', '!=', AssetVisibility.HIDDEN)]),
)
.as('photos'),
)
@@ -87,25 +87,25 @@ export class LibraryRepository {
eb.fn
.countAll<number>()
.filterWhere((eb) =>
eb.and([eb('assets.type', '=', AssetType.VIDEO), eb('assets.visibility', '!=', AssetVisibility.HIDDEN)]),
eb.and([eb('asset.type', '=', AssetType.VIDEO), eb('asset.visibility', '!=', AssetVisibility.HIDDEN)]),
)
.as('videos'),
)
.select((eb) => eb.fn.coalesce((eb) => eb.fn.sum('exif.fileSizeInByte'), eb.val(0)).as('usage'))
.groupBy('libraries.id')
.where('libraries.id', '=', id)
.select((eb) => eb.fn.coalesce((eb) => eb.fn.sum('asset_exif.fileSizeInByte'), eb.val(0)).as('usage'))
.groupBy('library.id')
.where('library.id', '=', id)
.executeTakeFirst();
// possibly a new library with 0 assets
if (!stats) {
const zero = sql<number>`0::int`;
return this.db
.selectFrom('libraries')
.selectFrom('library')
.select(zero.as('photos'))
.select(zero.as('videos'))
.select(zero.as('usage'))
.select(zero.as('total'))
.where('libraries.id', '=', id)
.where('library.id', '=', id)
.executeTakeFirst();
}
@@ -118,6 +118,6 @@ export class LibraryRepository {
}
streamAssetIds(libraryId: string) {
return this.db.selectFrom('assets').select(['id']).where('libraryId', '=', libraryId).stream();
return this.db.selectFrom('asset').select(['id']).where('libraryId', '=', libraryId).stream();
}
}

View File

@@ -83,25 +83,32 @@ export class MapRepository {
{ isArchived, isFavorite, fileCreatedAfter, fileCreatedBefore }: MapMarkerSearchOptions = {},
) {
return this.db
.selectFrom('assets')
.innerJoin('exif', (builder) =>
.selectFrom('asset')
.innerJoin('asset_exif', (builder) =>
builder
.onRef('assets.id', '=', 'exif.assetId')
.on('exif.latitude', 'is not', null)
.on('exif.longitude', 'is not', null),
.onRef('asset.id', '=', 'asset_exif.assetId')
.on('asset_exif.latitude', 'is not', null)
.on('asset_exif.longitude', 'is not', null),
)
.select(['id', 'exif.latitude as lat', 'exif.longitude as lon', 'exif.city', 'exif.state', 'exif.country'])
.select([
'id',
'asset_exif.latitude as lat',
'asset_exif.longitude as lon',
'asset_exif.city',
'asset_exif.state',
'asset_exif.country',
])
.$narrowType<{ lat: NotNull; lon: NotNull }>()
.$if(isArchived === true, (qb) =>
qb.where((eb) =>
eb.or([
eb('assets.visibility', '=', AssetVisibility.TIMELINE),
eb('assets.visibility', '=', AssetVisibility.ARCHIVE),
eb('asset.visibility', '=', AssetVisibility.TIMELINE),
eb('asset.visibility', '=', AssetVisibility.ARCHIVE),
]),
),
)
.$if(isArchived === false || isArchived === undefined, (qb) =>
qb.where('assets.visibility', '=', AssetVisibility.TIMELINE),
qb.where('asset.visibility', '=', AssetVisibility.TIMELINE),
)
.$if(isFavorite !== undefined, (q) => q.where('isFavorite', '=', isFavorite!))
.$if(fileCreatedAfter !== undefined, (q) => q.where('fileCreatedAt', '>=', fileCreatedAfter!))
@@ -118,9 +125,9 @@ export class MapRepository {
expression.push(
eb.exists((eb) =>
eb
.selectFrom('albums_assets_assets')
.whereRef('assets.id', '=', 'albums_assets_assets.assetsId')
.where('albums_assets_assets.albumsId', 'in', albumIds),
.selectFrom('album_asset')
.whereRef('asset.id', '=', 'album_asset.assetsId')
.where('album_asset.albumsId', 'in', albumIds),
),
);
}

View File

@@ -16,14 +16,14 @@ export class MemoryRepository implements IBulkAsset {
async cleanup() {
await this.db
.deleteFrom('memories_assets_assets')
.using('assets')
.whereRef('memories_assets_assets.assetsId', '=', 'assets.id')
.where('assets.visibility', '!=', AssetVisibility.TIMELINE)
.deleteFrom('memory_asset')
.using('asset')
.whereRef('memory_asset.assetsId', '=', 'asset.id')
.where('asset.visibility', '!=', AssetVisibility.TIMELINE)
.execute();
return this.db
.deleteFrom('memories')
.deleteFrom('memory')
.where('createdAt', '<', DateTime.now().minus({ days: 30 }).toJSDate())
.where('isSaved', '=', false)
.execute();
@@ -31,7 +31,7 @@ export class MemoryRepository implements IBulkAsset {
searchBuilder(ownerId: string, dto: MemorySearchDto) {
return this.db
.selectFrom('memories')
.selectFrom('memory')
.$if(dto.isSaved !== undefined, (qb) => qb.where('isSaved', '=', dto.isSaved!))
.$if(dto.type !== undefined, (qb) => qb.where('type', '=', dto.type!))
.$if(dto.for !== undefined, (qb) =>
@@ -62,16 +62,16 @@ export class MemoryRepository implements IBulkAsset {
.select((eb) =>
jsonArrayFrom(
eb
.selectFrom('assets')
.selectAll('assets')
.innerJoin('memories_assets_assets', 'assets.id', 'memories_assets_assets.assetsId')
.whereRef('memories_assets_assets.memoriesId', '=', 'memories.id')
.orderBy('assets.fileCreatedAt', 'asc')
.where('assets.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
.where('assets.deletedAt', 'is', null),
.selectFrom('asset')
.selectAll('asset')
.innerJoin('memory_asset', 'asset.id', 'memory_asset.assetsId')
.whereRef('memory_asset.memoriesId', '=', 'memory.id')
.orderBy('asset.fileCreatedAt', 'asc')
.where('asset.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
.where('asset.deletedAt', 'is', null),
).as('assets'),
)
.selectAll('memories')
.selectAll('memory')
.orderBy('memoryAt', 'desc')
.execute();
}
@@ -83,11 +83,11 @@ export class MemoryRepository implements IBulkAsset {
async create(memory: Insertable<MemoryTable>, assetIds: Set<string>) {
const id = await this.db.transaction().execute(async (tx) => {
const { id } = await tx.insertInto('memories').values(memory).returning('id').executeTakeFirstOrThrow();
const { id } = await tx.insertInto('memory').values(memory).returning('id').executeTakeFirstOrThrow();
if (assetIds.size > 0) {
const values = [...assetIds].map((assetId) => ({ memoriesId: id, assetsId: assetId }));
await tx.insertInto('memories_assets_assets').values(values).execute();
await tx.insertInto('memory_asset').values(values).execute();
}
return id;
@@ -98,13 +98,13 @@ export class MemoryRepository implements IBulkAsset {
@GenerateSql({ params: [DummyValue.UUID, { ownerId: DummyValue.UUID, isSaved: true }] })
async update(id: string, memory: Updateable<MemoryTable>) {
await this.db.updateTable('memories').set(memory).where('id', '=', id).execute();
await this.db.updateTable('memory').set(memory).where('id', '=', id).execute();
return this.getByIdBuilder(id).executeTakeFirstOrThrow();
}
@GenerateSql({ params: [DummyValue.UUID] })
async delete(id: string) {
await this.db.deleteFrom('memories').where('id', '=', id).execute();
await this.db.deleteFrom('memory').where('id', '=', id).execute();
}
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] })
@@ -115,7 +115,7 @@ export class MemoryRepository implements IBulkAsset {
}
const results = await this.db
.selectFrom('memories_assets_assets')
.selectFrom('memory_asset')
.select(['assetsId'])
.where('memoriesId', '=', id)
.where('assetsId', 'in', assetIds)
@@ -131,7 +131,7 @@ export class MemoryRepository implements IBulkAsset {
}
await this.db
.insertInto('memories_assets_assets')
.insertInto('memory_asset')
.values(assetIds.map((assetId) => ({ memoriesId: id, assetsId: assetId })))
.execute();
}
@@ -143,27 +143,23 @@ export class MemoryRepository implements IBulkAsset {
return;
}
await this.db
.deleteFrom('memories_assets_assets')
.where('memoriesId', '=', id)
.where('assetsId', 'in', assetIds)
.execute();
await this.db.deleteFrom('memory_asset').where('memoriesId', '=', id).where('assetsId', 'in', assetIds).execute();
}
private getByIdBuilder(id: string) {
return this.db
.selectFrom('memories')
.selectAll('memories')
.selectFrom('memory')
.selectAll('memory')
.select((eb) =>
jsonArrayFrom(
eb
.selectFrom('assets')
.selectAll('assets')
.innerJoin('memories_assets_assets', 'assets.id', 'memories_assets_assets.assetsId')
.whereRef('memories_assets_assets.memoriesId', '=', 'memories.id')
.orderBy('assets.fileCreatedAt', 'asc')
.where('assets.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
.where('assets.deletedAt', 'is', null),
.selectFrom('asset')
.selectAll('asset')
.innerJoin('memory_asset', 'asset.id', 'memory_asset.assetsId')
.whereRef('memory_asset.memoriesId', '=', 'memory.id')
.orderBy('asset.fileCreatedAt', 'asc')
.where('asset.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
.where('asset.deletedAt', 'is', null),
).as('assets'),
)
.where('id', '=', id)

View File

@@ -45,7 +45,7 @@ export class MoveRepository {
eb(
'move_history.entityId',
'not in',
eb.selectFrom('assets').select('id').whereRef('assets.id', '=', 'move_history.entityId'),
eb.selectFrom('asset').select('id').whereRef('asset.id', '=', 'move_history.entityId'),
),
)
.where('move_history.pathType', '=', sql.lit(AssetPathType.ORIGINAL))

View File

@@ -12,7 +12,7 @@ export class NotificationRepository {
cleanup() {
return this.db
.deleteFrom('notifications')
.deleteFrom('notification')
.where((eb) =>
eb.or([
// remove soft-deleted notifications
@@ -38,7 +38,7 @@ export class NotificationRepository {
@GenerateSql({ params: [DummyValue.UUID, {}] }, { name: 'unread', params: [DummyValue.UUID, { unread: true }] })
search(userId: string, dto: NotificationSearchDto) {
return this.db
.selectFrom('notifications')
.selectFrom('notification')
.select(columns.notification)
.where((qb) =>
qb.and({
@@ -56,7 +56,7 @@ export class NotificationRepository {
create(notification: Insertable<NotificationTable>) {
return this.db
.insertInto('notifications')
.insertInto('notification')
.values(notification)
.returning(columns.notification)
.executeTakeFirstOrThrow();
@@ -64,7 +64,7 @@ export class NotificationRepository {
get(id: string) {
return this.db
.selectFrom('notifications')
.selectFrom('notification')
.select(columns.notification)
.where('id', '=', id)
.where('deletedAt', 'is not', null)
@@ -73,7 +73,7 @@ export class NotificationRepository {
update(id: string, notification: Updateable<NotificationTable>) {
return this.db
.updateTable('notifications')
.updateTable('notification')
.set(notification)
.where('deletedAt', 'is', null)
.where('id', '=', id)
@@ -82,12 +82,12 @@ export class NotificationRepository {
}
async updateAll(ids: string[], notification: Updateable<NotificationTable>) {
await this.db.updateTable('notifications').set(notification).where('id', 'in', ids).execute();
await this.db.updateTable('notification').set(notification).where('id', 'in', ids).execute();
}
async delete(id: string) {
await this.db
.updateTable('notifications')
.updateTable('notification')
.set({ deletedAt: DateTime.now().toJSDate() })
.where('id', '=', id)
.execute();
@@ -95,7 +95,7 @@ export class NotificationRepository {
async deleteAll(ids: string[]) {
await this.db
.updateTable('notifications')
.updateTable('notification')
.set({ deletedAt: DateTime.now().toJSDate() })
.where('id', 'in', ids)
.execute();

View File

@@ -17,15 +17,15 @@ export enum PartnerDirection {
SharedWith = 'shared-with',
}
const withSharedBy = (eb: ExpressionBuilder<DB, 'partners'>) => {
const withSharedBy = (eb: ExpressionBuilder<DB, 'partner'>) => {
return jsonObjectFrom(
eb.selectFrom('users as sharedBy').select(columns.user).whereRef('sharedBy.id', '=', 'partners.sharedById'),
eb.selectFrom('user as sharedBy').select(columns.user).whereRef('sharedBy.id', '=', 'partner.sharedById'),
).as('sharedBy');
};
const withSharedWith = (eb: ExpressionBuilder<DB, 'partners'>) => {
const withSharedWith = (eb: ExpressionBuilder<DB, 'partner'>) => {
return jsonObjectFrom(
eb.selectFrom('users as sharedWith').select(columns.user).whereRef('sharedWith.id', '=', 'partners.sharedWithId'),
eb.selectFrom('user as sharedWith').select(columns.user).whereRef('sharedWith.id', '=', 'partner.sharedWithId'),
).as('sharedWith');
};
@@ -50,7 +50,7 @@ export class PartnerRepository {
create(values: Insertable<PartnerTable>) {
return this.db
.insertInto('partners')
.insertInto('partner')
.values(values)
.returningAll()
.returning(withSharedBy)
@@ -62,7 +62,7 @@ export class PartnerRepository {
@GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }, { inTimeline: true }] })
update({ sharedWithId, sharedById }: PartnerIds, values: Updateable<PartnerTable>) {
return this.db
.updateTable('partners')
.updateTable('partner')
.set(values)
.where('sharedWithId', '=', sharedWithId)
.where('sharedById', '=', sharedById)
@@ -76,7 +76,7 @@ export class PartnerRepository {
@GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }] })
async remove({ sharedWithId, sharedById }: PartnerIds) {
await this.db
.deleteFrom('partners')
.deleteFrom('partner')
.where('sharedWithId', '=', sharedWithId)
.where('sharedById', '=', sharedById)
.execute();
@@ -84,14 +84,14 @@ export class PartnerRepository {
private builder() {
return this.db
.selectFrom('partners')
.innerJoin('users as sharedBy', (join) =>
join.onRef('partners.sharedById', '=', 'sharedBy.id').on('sharedBy.deletedAt', 'is', null),
.selectFrom('partner')
.innerJoin('user as sharedBy', (join) =>
join.onRef('partner.sharedById', '=', 'sharedBy.id').on('sharedBy.deletedAt', 'is', null),
)
.innerJoin('users as sharedWith', (join) =>
join.onRef('partners.sharedWithId', '=', 'sharedWith.id').on('sharedWith.deletedAt', 'is', null),
.innerJoin('user as sharedWith', (join) =>
join.onRef('partner.sharedWithId', '=', 'sharedWith.id').on('sharedWith.deletedAt', 'is', null),
)
.selectAll('partners')
.selectAll('partner')
.select(withSharedBy)
.select(withSharedWith);
}

View File

@@ -62,21 +62,21 @@ export type UnassignFacesOptions = DeleteFacesOptions;
export type SelectFaceOptions = (keyof Selectable<AssetFaceTable>)[];
const withPerson = (eb: ExpressionBuilder<DB, 'asset_faces'>) => {
const withPerson = (eb: ExpressionBuilder<DB, 'asset_face'>) => {
return jsonObjectFrom(
eb.selectFrom('person').selectAll('person').whereRef('person.id', '=', 'asset_faces.personId'),
eb.selectFrom('person').selectAll('person').whereRef('person.id', '=', 'asset_face.personId'),
).as('person');
};
const withAsset = (eb: ExpressionBuilder<DB, 'asset_faces'>) => {
return jsonObjectFrom(
eb.selectFrom('assets').selectAll('assets').whereRef('assets.id', '=', 'asset_faces.assetId'),
).as('asset');
const withAsset = (eb: ExpressionBuilder<DB, 'asset_face'>) => {
return jsonObjectFrom(eb.selectFrom('asset').selectAll('asset').whereRef('asset.id', '=', 'asset_face.assetId')).as(
'asset',
);
};
const withFaceSearch = (eb: ExpressionBuilder<DB, 'asset_faces'>) => {
const withFaceSearch = (eb: ExpressionBuilder<DB, 'asset_face'>) => {
return jsonObjectFrom(
eb.selectFrom('face_search').selectAll('face_search').whereRef('face_search.faceId', '=', 'asset_faces.id'),
eb.selectFrom('face_search').selectAll('face_search').whereRef('face_search.faceId', '=', 'asset_face.id'),
).as('faceSearch');
};
@@ -87,10 +87,10 @@ export class PersonRepository {
@GenerateSql({ params: [{ oldPersonId: DummyValue.UUID, newPersonId: DummyValue.UUID }] })
async reassignFaces({ oldPersonId, faceIds, newPersonId }: UpdateFacesData): Promise<number> {
const result = await this.db
.updateTable('asset_faces')
.updateTable('asset_face')
.set({ personId: newPersonId })
.$if(!!oldPersonId, (qb) => qb.where('asset_faces.personId', '=', oldPersonId!))
.$if(!!faceIds, (qb) => qb.where('asset_faces.id', 'in', faceIds!))
.$if(!!oldPersonId, (qb) => qb.where('asset_face.personId', '=', oldPersonId!))
.$if(!!faceIds, (qb) => qb.where('asset_face.id', 'in', faceIds!))
.executeTakeFirst();
return Number(result.numChangedRows ?? 0);
@@ -98,9 +98,9 @@ export class PersonRepository {
async unassignFaces({ sourceType }: UnassignFacesOptions): Promise<void> {
await this.db
.updateTable('asset_faces')
.updateTable('asset_face')
.set({ personId: null })
.where('asset_faces.sourceType', '=', sourceType)
.where('asset_face.sourceType', '=', sourceType)
.execute();
}
@@ -115,18 +115,18 @@ export class PersonRepository {
}
async deleteFaces({ sourceType }: DeleteFacesOptions): Promise<void> {
await this.db.deleteFrom('asset_faces').where('asset_faces.sourceType', '=', sourceType).execute();
await this.db.deleteFrom('asset_face').where('asset_face.sourceType', '=', sourceType).execute();
}
getAllFaces(options: GetAllFacesOptions = {}) {
return this.db
.selectFrom('asset_faces')
.selectAll('asset_faces')
.$if(options.personId === null, (qb) => qb.where('asset_faces.personId', 'is', null))
.$if(!!options.personId, (qb) => qb.where('asset_faces.personId', '=', options.personId!))
.$if(!!options.sourceType, (qb) => qb.where('asset_faces.sourceType', '=', options.sourceType!))
.$if(!!options.assetId, (qb) => qb.where('asset_faces.assetId', '=', options.assetId!))
.where('asset_faces.deletedAt', 'is', null)
.selectFrom('asset_face')
.selectAll('asset_face')
.$if(options.personId === null, (qb) => qb.where('asset_face.personId', 'is', null))
.$if(!!options.personId, (qb) => qb.where('asset_face.personId', '=', options.personId!))
.$if(!!options.sourceType, (qb) => qb.where('asset_face.sourceType', '=', options.sourceType!))
.$if(!!options.assetId, (qb) => qb.where('asset_face.assetId', '=', options.assetId!))
.where('asset_face.deletedAt', 'is', null)
.stream();
}
@@ -147,21 +147,21 @@ export class PersonRepository {
const items = await this.db
.selectFrom('person')
.selectAll('person')
.innerJoin('asset_faces', 'asset_faces.personId', 'person.id')
.innerJoin('assets', (join) =>
.innerJoin('asset_face', 'asset_face.personId', 'person.id')
.innerJoin('asset', (join) =>
join
.onRef('asset_faces.assetId', '=', 'assets.id')
.on('assets.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
.on('assets.deletedAt', 'is', null),
.onRef('asset_face.assetId', '=', 'asset.id')
.on('asset.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
.on('asset.deletedAt', 'is', null),
)
.where('person.ownerId', '=', userId)
.where('asset_faces.deletedAt', 'is', null)
.where('asset_face.deletedAt', 'is', null)
.orderBy('person.isHidden', 'asc')
.orderBy('person.isFavorite', 'desc')
.having((eb) =>
eb.or([
eb('person.name', '!=', ''),
eb((innerEb) => innerEb.fn.count('asset_faces.assetId'), '>=', options?.minimumFaceCount || 1),
eb((innerEb) => innerEb.fn.count('asset_face.assetId'), '>=', options?.minimumFaceCount || 1),
]),
)
.groupBy('person.id')
@@ -185,7 +185,7 @@ export class PersonRepository {
.$if(!options?.closestFaceAssetId, (qb) =>
qb
.orderBy(sql`NULLIF(person.name, '') is null`, 'asc')
.orderBy((eb) => eb.fn.count('asset_faces.assetId'), 'desc')
.orderBy((eb) => eb.fn.count('asset_face.assetId'), 'desc')
.orderBy(sql`NULLIF(person.name, '')`, (om) => om.asc().nullsLast())
.orderBy('person.createdAt'),
)
@@ -202,9 +202,9 @@ export class PersonRepository {
return this.db
.selectFrom('person')
.selectAll('person')
.leftJoin('asset_faces', 'asset_faces.personId', 'person.id')
.where('asset_faces.deletedAt', 'is', null)
.having((eb) => eb.fn.count('asset_faces.assetId'), '=', 0)
.leftJoin('asset_face', 'asset_face.personId', 'person.id')
.where('asset_face.deletedAt', 'is', null)
.having((eb) => eb.fn.count('asset_face.assetId'), '=', 0)
.groupBy('person.id')
.execute();
}
@@ -212,12 +212,12 @@ export class PersonRepository {
@GenerateSql({ params: [DummyValue.UUID] })
getFaces(assetId: string) {
return this.db
.selectFrom('asset_faces')
.selectAll('asset_faces')
.selectFrom('asset_face')
.selectAll('asset_face')
.select(withPerson)
.where('asset_faces.assetId', '=', assetId)
.where('asset_faces.deletedAt', 'is', null)
.orderBy('asset_faces.boundingBoxX1', 'asc')
.where('asset_face.assetId', '=', assetId)
.where('asset_face.deletedAt', 'is', null)
.orderBy('asset_face.boundingBoxX1', 'asc')
.execute();
}
@@ -225,30 +225,30 @@ export class PersonRepository {
getFaceById(id: string) {
// TODO return null instead of find or fail
return this.db
.selectFrom('asset_faces')
.selectAll('asset_faces')
.selectFrom('asset_face')
.selectAll('asset_face')
.select(withPerson)
.where('asset_faces.id', '=', id)
.where('asset_faces.deletedAt', 'is', null)
.where('asset_face.id', '=', id)
.where('asset_face.deletedAt', 'is', null)
.executeTakeFirstOrThrow();
}
@GenerateSql({ params: [DummyValue.UUID] })
getFaceForFacialRecognitionJob(id: string) {
return this.db
.selectFrom('asset_faces')
.select(['asset_faces.id', 'asset_faces.personId', 'asset_faces.sourceType'])
.selectFrom('asset_face')
.select(['asset_face.id', 'asset_face.personId', 'asset_face.sourceType'])
.select((eb) =>
jsonObjectFrom(
eb
.selectFrom('assets')
.select(['assets.ownerId', 'assets.visibility', 'assets.fileCreatedAt'])
.whereRef('assets.id', '=', 'asset_faces.assetId'),
.selectFrom('asset')
.select(['asset.ownerId', 'asset.visibility', 'asset.fileCreatedAt'])
.whereRef('asset.id', '=', 'asset_face.assetId'),
).as('asset'),
)
.select(withFaceSearch)
.where('asset_faces.id', '=', id)
.where('asset_faces.deletedAt', 'is', null)
.where('asset_face.id', '=', id)
.where('asset_face.deletedAt', 'is', null)
.executeTakeFirst();
}
@@ -256,40 +256,40 @@ export class PersonRepository {
getDataForThumbnailGenerationJob(id: string) {
return this.db
.selectFrom('person')
.innerJoin('asset_faces', 'asset_faces.id', 'person.faceAssetId')
.innerJoin('assets', 'asset_faces.assetId', 'assets.id')
.leftJoin('exif', 'exif.assetId', 'assets.id')
.innerJoin('asset_face', 'asset_face.id', 'person.faceAssetId')
.innerJoin('asset', 'asset_face.assetId', 'asset.id')
.leftJoin('asset_exif', 'asset_exif.assetId', 'asset.id')
.select([
'person.ownerId',
'asset_faces.boundingBoxX1 as x1',
'asset_faces.boundingBoxY1 as y1',
'asset_faces.boundingBoxX2 as x2',
'asset_faces.boundingBoxY2 as y2',
'asset_faces.imageWidth as oldWidth',
'asset_faces.imageHeight as oldHeight',
'assets.type',
'assets.originalPath',
'exif.orientation as exifOrientation',
'asset_face.boundingBoxX1 as x1',
'asset_face.boundingBoxY1 as y1',
'asset_face.boundingBoxX2 as x2',
'asset_face.boundingBoxY2 as y2',
'asset_face.imageWidth as oldWidth',
'asset_face.imageHeight as oldHeight',
'asset.type',
'asset.originalPath',
'asset_exif.orientation as exifOrientation',
])
.select((eb) =>
eb
.selectFrom('asset_files')
.select('asset_files.path')
.whereRef('asset_files.assetId', '=', 'assets.id')
.where('asset_files.type', '=', sql.lit(AssetFileType.PREVIEW))
.selectFrom('asset_file')
.select('asset_file.path')
.whereRef('asset_file.assetId', '=', 'asset.id')
.where('asset_file.type', '=', sql.lit(AssetFileType.PREVIEW))
.as('previewPath'),
)
.where('person.id', '=', id)
.where('asset_faces.deletedAt', 'is', null)
.where('asset_face.deletedAt', 'is', null)
.executeTakeFirst();
}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
async reassignFace(assetFaceId: string, newPersonId: string): Promise<number> {
const result = await this.db
.updateTable('asset_faces')
.updateTable('asset_face')
.set({ personId: newPersonId })
.where('asset_faces.id', '=', assetFaceId)
.where('asset_face.id', '=', assetFaceId)
.executeTakeFirst();
return Number(result.numChangedRows ?? 0);
@@ -336,16 +336,16 @@ export class PersonRepository {
@GenerateSql({ params: [DummyValue.UUID] })
async getStatistics(personId: string): Promise<PersonStatistics> {
const result = await this.db
.selectFrom('asset_faces')
.leftJoin('assets', (join) =>
.selectFrom('asset_face')
.leftJoin('asset', (join) =>
join
.onRef('assets.id', '=', 'asset_faces.assetId')
.on('asset_faces.personId', '=', personId)
.on('assets.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
.on('assets.deletedAt', 'is', null),
.onRef('asset.id', '=', 'asset_face.assetId')
.on('asset_face.personId', '=', personId)
.on('asset.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
.on('asset.deletedAt', 'is', null),
)
.select((eb) => eb.fn.count(eb.fn('distinct', ['assets.id'])).as('count'))
.where('asset_faces.deletedAt', 'is', null)
.select((eb) => eb.fn.count(eb.fn('distinct', ['asset.id'])).as('count'))
.where('asset_face.deletedAt', 'is', null)
.executeTakeFirst();
return {
@@ -361,16 +361,16 @@ export class PersonRepository {
.where((eb) =>
eb.exists((eb) =>
eb
.selectFrom('asset_faces')
.whereRef('asset_faces.personId', '=', 'person.id')
.where('asset_faces.deletedAt', 'is', null)
.selectFrom('asset_face')
.whereRef('asset_face.personId', '=', 'person.id')
.where('asset_face.deletedAt', 'is', null)
.where((eb) =>
eb.exists((eb) =>
eb
.selectFrom('assets')
.whereRef('assets.id', '=', 'asset_faces.assetId')
.where('assets.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
.where('assets.deletedAt', 'is', null),
.selectFrom('asset')
.whereRef('asset.id', '=', 'asset_face.assetId')
.where('asset.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
.where('asset.deletedAt', 'is', null),
),
),
),
@@ -402,12 +402,12 @@ export class PersonRepository {
): Promise<void> {
let query = this.db;
if (facesToAdd.length > 0) {
(query as any) = query.with('added', (db) => db.insertInto('asset_faces').values(facesToAdd));
(query as any) = query.with('added', (db) => db.insertInto('asset_face').values(facesToAdd));
}
if (faceIdsToRemove.length > 0) {
(query as any) = query.with('removed', (db) =>
db.deleteFrom('asset_faces').where('asset_faces.id', '=', (eb) => eb.fn.any(eb.val(faceIdsToRemove))),
db.deleteFrom('asset_face').where('asset_face.id', '=', (eb) => eb.fn.any(eb.val(faceIdsToRemove))),
);
}
@@ -469,23 +469,23 @@ export class PersonRepository {
}
return this.db
.selectFrom('asset_faces')
.selectAll('asset_faces')
.selectFrom('asset_face')
.selectAll('asset_face')
.select(withAsset)
.select(withPerson)
.where('asset_faces.assetId', 'in', assetIds)
.where('asset_faces.personId', 'in', personIds)
.where('asset_faces.deletedAt', 'is', null)
.where('asset_face.assetId', 'in', assetIds)
.where('asset_face.personId', 'in', personIds)
.where('asset_face.deletedAt', 'is', null)
.execute();
}
@GenerateSql({ params: [DummyValue.UUID] })
getRandomFace(personId: string) {
return this.db
.selectFrom('asset_faces')
.selectAll('asset_faces')
.where('asset_faces.personId', '=', personId)
.where('asset_faces.deletedAt', 'is', null)
.selectFrom('asset_face')
.selectAll('asset_face')
.where('asset_face.personId', '=', personId)
.where('asset_face.deletedAt', 'is', null)
.executeTakeFirst();
}
@@ -500,22 +500,22 @@ export class PersonRepository {
}
async createAssetFace(face: Insertable<AssetFaceTable>): Promise<void> {
await this.db.insertInto('asset_faces').values(face).execute();
await this.db.insertInto('asset_face').values(face).execute();
}
@GenerateSql({ params: [DummyValue.UUID] })
async deleteAssetFace(id: string): Promise<void> {
await this.db.deleteFrom('asset_faces').where('asset_faces.id', '=', id).execute();
await this.db.deleteFrom('asset_face').where('asset_face.id', '=', id).execute();
}
@GenerateSql({ params: [DummyValue.UUID] })
async softDeleteAssetFaces(id: string): Promise<void> {
await this.db.updateTable('asset_faces').set({ deletedAt: new Date() }).where('asset_faces.id', '=', id).execute();
await this.db.updateTable('asset_face').set({ deletedAt: new Date() }).where('asset_face.id', '=', id).execute();
}
async vacuum({ reindexVectors }: { reindexVectors: boolean }): Promise<void> {
await sql`VACUUM ANALYZE asset_faces, face_search, person`.execute(this.db);
await sql`REINDEX TABLE asset_faces`.execute(this.db);
await sql`VACUUM ANALYZE asset_face, face_search, person`.execute(this.db);
await sql`REINDEX TABLE asset_face`.execute(this.db);
await sql`REINDEX TABLE person`.execute(this.db);
if (reindexVectors) {
await sql`REINDEX TABLE face_search`.execute(this.db);

View File

@@ -7,7 +7,7 @@ import { MapAsset } from 'src/dtos/asset-response.dto';
import { AssetStatus, AssetType, AssetVisibility, VectorIndex } from 'src/enum';
import { probes } from 'src/repositories/database.repository';
import { DB } from 'src/schema';
import { ExifTable } from 'src/schema/tables/exif.table';
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
import { anyUuid, searchAssetBuilder } from 'src/utils/database';
import { paginationHelper } from 'src/utils/pagination';
import { isValidInteger } from 'src/validation';
@@ -183,8 +183,8 @@ export class SearchRepository {
async searchMetadata(pagination: SearchPaginationOptions, options: AssetSearchOptions) {
const orderDirection = (options.orderDirection?.toLowerCase() || 'desc') as OrderByDirection;
const items = await searchAssetBuilder(this.db, options)
.selectAll('assets')
.orderBy('assets.fileCreatedAt', orderDirection)
.selectAll('asset')
.orderBy('asset.fileCreatedAt', orderDirection)
.limit(pagination.size + 1)
.offset((pagination.page - 1) * pagination.size)
.execute();
@@ -224,13 +224,13 @@ export class SearchRepository {
const uuid = randomUUID();
const builder = searchAssetBuilder(this.db, options);
const lessThan = builder
.selectAll('assets')
.where('assets.id', '<', uuid)
.selectAll('asset')
.where('asset.id', '<', uuid)
.orderBy(sql`random()`)
.limit(size);
const greaterThan = builder
.selectAll('assets')
.where('assets.id', '>', uuid)
.selectAll('asset')
.where('asset.id', '>', uuid)
.orderBy(sql`random()`)
.limit(size);
const { rows } = await sql<MapAsset>`${lessThan} union all ${greaterThan} limit ${size}`.execute(this.db);
@@ -258,8 +258,8 @@ export class SearchRepository {
return this.db.transaction().execute(async (trx) => {
await sql`set local vchordrq.probes = ${sql.lit(probes[VectorIndex.CLIP])}`.execute(trx);
const items = await searchAssetBuilder(trx, options)
.selectAll('assets')
.innerJoin('smart_search', 'assets.id', 'smart_search.assetId')
.selectAll('asset')
.innerJoin('smart_search', 'asset.id', 'smart_search.assetId')
.orderBy(sql`smart_search.embedding <=> ${options.embedding}`)
.limit(pagination.size + 1)
.offset((pagination.page - 1) * pagination.size)
@@ -288,18 +288,18 @@ export class SearchRepository {
return await trx
.with('cte', (qb) =>
qb
.selectFrom('asset_faces')
.selectFrom('asset_face')
.select([
'asset_faces.id',
'asset_faces.personId',
'asset_face.id',
'asset_face.personId',
sql<number>`face_search.embedding <=> ${embedding}`.as('distance'),
])
.innerJoin('assets', 'assets.id', 'asset_faces.assetId')
.innerJoin('face_search', 'face_search.faceId', 'asset_faces.id')
.leftJoin('person', 'person.id', 'asset_faces.personId')
.where('assets.ownerId', '=', anyUuid(userIds))
.where('assets.deletedAt', 'is', null)
.$if(!!hasPerson, (qb) => qb.where('asset_faces.personId', 'is not', null))
.innerJoin('asset', 'asset.id', 'asset_face.assetId')
.innerJoin('face_search', 'face_search.faceId', 'asset_face.id')
.leftJoin('person', 'person.id', 'asset_face.personId')
.where('asset.ownerId', '=', anyUuid(userIds))
.where('asset.deletedAt', 'is', null)
.$if(!!hasPerson, (qb) => qb.where('asset_face.personId', 'is not', null))
.$if(!!minBirthDate, (qb) =>
qb.where((eb) =>
eb.or([eb('person.birthDate', 'is', null), eb('person.birthDate', '<=', minBirthDate!)]),
@@ -347,13 +347,13 @@ export class SearchRepository {
return this.db
.withRecursive('cte', (qb) => {
const base = qb
.selectFrom('exif')
.selectFrom('asset_exif')
.select(['city', 'assetId'])
.innerJoin('assets', 'assets.id', 'exif.assetId')
.where('assets.ownerId', '=', anyUuid(userIds))
.where('assets.visibility', '=', AssetVisibility.TIMELINE)
.where('assets.type', '=', AssetType.IMAGE)
.where('assets.deletedAt', 'is', null)
.innerJoin('asset', 'asset.id', 'asset_exif.assetId')
.where('asset.ownerId', '=', anyUuid(userIds))
.where('asset.visibility', '=', AssetVisibility.TIMELINE)
.where('asset.type', '=', AssetType.IMAGE)
.where('asset.deletedAt', 'is', null)
.orderBy('city')
.limit(1);
@@ -363,14 +363,14 @@ export class SearchRepository {
.innerJoinLateral(
(qb) =>
qb
.selectFrom('exif')
.selectFrom('asset_exif')
.select(['city', 'assetId'])
.innerJoin('assets', 'assets.id', 'exif.assetId')
.where('assets.ownerId', '=', anyUuid(userIds))
.where('assets.visibility', '=', AssetVisibility.TIMELINE)
.where('assets.type', '=', AssetType.IMAGE)
.where('assets.deletedAt', 'is', null)
.whereRef('exif.city', '>', 'cte.city')
.innerJoin('asset', 'asset.id', 'asset_exif.assetId')
.where('asset.ownerId', '=', anyUuid(userIds))
.where('asset.visibility', '=', AssetVisibility.TIMELINE)
.where('asset.type', '=', AssetType.IMAGE)
.where('asset.deletedAt', 'is', null)
.whereRef('asset_exif.city', '>', 'cte.city')
.orderBy('city')
.limit(1)
.as('l'),
@@ -379,17 +379,17 @@ export class SearchRepository {
return sql<{ city: string; assetId: string }>`(${base} union all ${recursive})`;
})
.selectFrom('assets')
.innerJoin('exif', 'assets.id', 'exif.assetId')
.innerJoin('cte', 'assets.id', 'cte.assetId')
.selectAll('assets')
.selectFrom('asset')
.innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId')
.innerJoin('cte', 'asset.id', 'cte.assetId')
.selectAll('asset')
.select((eb) =>
eb
.fn('to_jsonb', [eb.table('exif')])
.$castTo<Selectable<ExifTable>>()
.fn('to_jsonb', [eb.table('asset_exif')])
.$castTo<Selectable<AssetExifTable>>()
.as('exifInfo'),
)
.orderBy('exif.city')
.orderBy('asset_exif.city')
.execute();
}
@@ -445,10 +445,10 @@ export class SearchRepository {
private getExifField<K extends 'city' | 'state' | 'country' | 'make' | 'model'>(field: K, userIds: string[]) {
return this.db
.selectFrom('exif')
.selectFrom('asset_exif')
.select(field)
.distinctOn(field)
.innerJoin('assets', 'assets.id', 'exif.assetId')
.innerJoin('asset', 'asset.id', 'asset_exif.assetId')
.where('ownerId', '=', anyUuid(userIds))
.where('visibility', '=', AssetVisibility.TIMELINE)
.where('deletedAt', 'is', null)

View File

@@ -17,7 +17,7 @@ export class SessionRepository {
cleanup() {
return this.db
.deleteFrom('sessions')
.deleteFrom('session')
.where((eb) =>
eb.or([
eb('updatedAt', '<=', DateTime.now().minus({ days: 90 }).toJSDate()),
@@ -31,7 +31,7 @@ export class SessionRepository {
@GenerateSql({ params: [DummyValue.UUID] })
get(id: string) {
return this.db
.selectFrom('sessions')
.selectFrom('session')
.select(['id', 'expiresAt', 'pinExpiresAt'])
.where('id', '=', id)
.executeTakeFirst();
@@ -40,20 +40,20 @@ export class SessionRepository {
@GenerateSql({ params: [DummyValue.STRING] })
getByToken(token: string) {
return this.db
.selectFrom('sessions')
.selectFrom('session')
.select((eb) => [
...columns.authSession,
jsonObjectFrom(
eb
.selectFrom('users')
.selectFrom('user')
.select(columns.authUser)
.whereRef('users.id', '=', 'sessions.userId')
.where('users.deletedAt', 'is', null),
.whereRef('user.id', '=', 'session.userId')
.where('user.deletedAt', 'is', null),
).as('user'),
])
.where('sessions.token', '=', token)
.where('session.token', '=', token)
.where((eb) =>
eb.or([eb('sessions.expiresAt', 'is', null), eb('sessions.expiresAt', '>', DateTime.now().toJSDate())]),
eb.or([eb('session.expiresAt', 'is', null), eb('session.expiresAt', '>', DateTime.now().toJSDate())]),
)
.executeTakeFirst();
}
@@ -61,47 +61,47 @@ export class SessionRepository {
@GenerateSql({ params: [DummyValue.UUID] })
getByUserId(userId: string) {
return this.db
.selectFrom('sessions')
.innerJoin('users', (join) => join.onRef('users.id', '=', 'sessions.userId').on('users.deletedAt', 'is', null))
.selectAll('sessions')
.where('sessions.userId', '=', userId)
.selectFrom('session')
.innerJoin('user', (join) => join.onRef('user.id', '=', 'session.userId').on('user.deletedAt', 'is', null))
.selectAll('session')
.where('session.userId', '=', userId)
.where((eb) =>
eb.or([eb('sessions.expiresAt', 'is', null), eb('sessions.expiresAt', '>', DateTime.now().toJSDate())]),
eb.or([eb('session.expiresAt', 'is', null), eb('session.expiresAt', '>', DateTime.now().toJSDate())]),
)
.orderBy('sessions.updatedAt', 'desc')
.orderBy('sessions.createdAt', 'desc')
.orderBy('session.updatedAt', 'desc')
.orderBy('session.createdAt', 'desc')
.execute();
}
create(dto: Insertable<SessionTable>) {
return this.db.insertInto('sessions').values(dto).returningAll().executeTakeFirstOrThrow();
return this.db.insertInto('session').values(dto).returningAll().executeTakeFirstOrThrow();
}
update(id: string, dto: Updateable<SessionTable>) {
return this.db
.updateTable('sessions')
.updateTable('session')
.set(dto)
.where('sessions.id', '=', asUuid(id))
.where('session.id', '=', asUuid(id))
.returningAll()
.executeTakeFirstOrThrow();
}
@GenerateSql({ params: [DummyValue.UUID] })
async delete(id: string) {
await this.db.deleteFrom('sessions').where('id', '=', asUuid(id)).execute();
await this.db.deleteFrom('session').where('id', '=', asUuid(id)).execute();
}
@GenerateSql({ params: [DummyValue.UUID] })
async lockAll(userId: string) {
await this.db.updateTable('sessions').set({ pinExpiresAt: null }).where('userId', '=', userId).execute();
await this.db.updateTable('session').set({ pinExpiresAt: null }).where('userId', '=', userId).execute();
}
@GenerateSql({ params: [DummyValue.UUID] })
async resetSyncProgress(sessionId: string) {
await this.db.transaction().execute((tx) => {
return Promise.all([
tx.updateTable('sessions').set({ isPendingSyncReset: false }).where('id', '=', sessionId).execute(),
tx.deleteFrom('session_sync_checkpoints').where('sessionId', '=', sessionId).execute(),
tx.updateTable('session').set({ isPendingSyncReset: false }).where('id', '=', sessionId).execute(),
tx.deleteFrom('session_sync_checkpoint').where('sessionId', '=', sessionId).execute(),
]);
});
}

View File

@@ -22,61 +22,66 @@ export class SharedLinkRepository {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
get(userId: string, id: string) {
return this.db
.selectFrom('shared_links')
.selectAll('shared_links')
.selectFrom('shared_link')
.selectAll('shared_link')
.leftJoinLateral(
(eb) =>
eb
.selectFrom('shared_link__asset')
.whereRef('shared_links.id', '=', 'shared_link__asset.sharedLinksId')
.innerJoin('assets', 'assets.id', 'shared_link__asset.assetsId')
.where('assets.deletedAt', 'is', null)
.selectAll('assets')
.selectFrom('shared_link_asset')
.whereRef('shared_link.id', '=', 'shared_link_asset.sharedLinksId')
.innerJoin('asset', 'asset.id', 'shared_link_asset.assetsId')
.where('asset.deletedAt', 'is', null)
.selectAll('asset')
.innerJoinLateral(
(eb) => eb.selectFrom('exif').selectAll('exif').whereRef('exif.assetId', '=', 'assets.id').as('exifInfo'),
(eb) =>
eb
.selectFrom('asset_exif')
.selectAll('asset_exif')
.whereRef('asset_exif.assetId', '=', 'asset.id')
.as('exifInfo'),
(join) => join.onTrue(),
)
.select((eb) => eb.fn.toJson('exifInfo').as('exifInfo'))
.orderBy('assets.fileCreatedAt', 'asc')
.orderBy('asset.fileCreatedAt', 'asc')
.as('a'),
(join) => join.onTrue(),
)
.leftJoinLateral(
(eb) =>
eb
.selectFrom('albums')
.selectAll('albums')
.whereRef('albums.id', '=', 'shared_links.albumId')
.where('albums.deletedAt', 'is', null)
.leftJoin('albums_assets_assets', 'albums_assets_assets.albumsId', 'albums.id')
.selectFrom('album')
.selectAll('album')
.whereRef('album.id', '=', 'shared_link.albumId')
.where('album.deletedAt', 'is', null)
.leftJoin('album_asset', 'album_asset.albumsId', 'album.id')
.leftJoinLateral(
(eb) =>
eb
.selectFrom('assets')
.selectAll('assets')
.whereRef('albums_assets_assets.assetsId', '=', 'assets.id')
.where('assets.deletedAt', 'is', null)
.selectFrom('asset')
.selectAll('asset')
.whereRef('album_asset.assetsId', '=', 'asset.id')
.where('asset.deletedAt', 'is', null)
.innerJoinLateral(
(eb) =>
eb
.selectFrom('exif')
.selectAll('exif')
.whereRef('exif.assetId', '=', 'assets.id')
.as('assets_exifInfo'),
.selectFrom('asset_exif')
.selectAll('asset_exif')
.whereRef('asset_exif.assetId', '=', 'asset.id')
.as('exifInfo'),
(join) => join.onTrue(),
)
.select((eb) => eb.fn.toJson(eb.table('assets_exifInfo')).as('exifInfo'))
.orderBy('assets.fileCreatedAt', 'asc')
.select((eb) => eb.fn.toJson(eb.table('exifInfo')).as('exifInfo'))
.orderBy('asset.fileCreatedAt', 'asc')
.as('assets'),
(join) => join.onTrue(),
)
.innerJoinLateral(
(eb) =>
eb
.selectFrom('users')
.selectAll('users')
.whereRef('users.id', '=', 'albums.ownerId')
.where('users.deletedAt', 'is', null)
.selectFrom('user')
.selectAll('user')
.whereRef('user.id', '=', 'album.ownerId')
.where('user.deletedAt', 'is', null)
.as('owner'),
(join) => join.onTrue(),
)
@@ -84,7 +89,7 @@ export class SharedLinkRepository {
eb.fn.coalesce(eb.fn.jsonAgg('assets').filterWhere('assets.id', 'is not', null), sql`'[]'`).as('assets'),
)
.select((eb) => eb.fn.toJson('owner').as('owner'))
.groupBy(['albums.id', sql`"owner".*`])
.groupBy(['album.id', sql`"owner".*`])
.as('album'),
(join) => join.onTrue(),
)
@@ -94,29 +99,29 @@ export class SharedLinkRepository {
.$castTo<MapAsset[]>()
.as('assets'),
)
.groupBy(['shared_links.id', sql`"album".*`])
.groupBy(['shared_link.id', sql`"album".*`])
.select((eb) => eb.fn.toJson('album').$castTo<Album | null>().as('album'))
.where('shared_links.id', '=', id)
.where('shared_links.userId', '=', userId)
.where((eb) => eb.or([eb('shared_links.type', '=', SharedLinkType.INDIVIDUAL), eb('album.id', 'is not', null)]))
.orderBy('shared_links.createdAt', 'desc')
.where('shared_link.id', '=', id)
.where('shared_link.userId', '=', userId)
.where((eb) => eb.or([eb('shared_link.type', '=', SharedLinkType.INDIVIDUAL), eb('album.id', 'is not', null)]))
.orderBy('shared_link.createdAt', 'desc')
.executeTakeFirst();
}
@GenerateSql({ params: [{ userId: DummyValue.UUID, albumId: DummyValue.UUID }] })
getAll({ userId, albumId }: SharedLinkSearchOptions) {
return this.db
.selectFrom('shared_links')
.selectAll('shared_links')
.where('shared_links.userId', '=', userId)
.leftJoin('shared_link__asset', 'shared_link__asset.sharedLinksId', 'shared_links.id')
.selectFrom('shared_link')
.selectAll('shared_link')
.where('shared_link.userId', '=', userId)
.leftJoin('shared_link_asset', 'shared_link_asset.sharedLinksId', 'shared_link.id')
.leftJoinLateral(
(eb) =>
eb
.selectFrom('assets')
.select((eb) => eb.fn.jsonAgg('assets').as('assets'))
.whereRef('assets.id', '=', 'shared_link__asset.assetsId')
.where('assets.deletedAt', 'is', null)
.selectFrom('asset')
.select((eb) => eb.fn.jsonAgg('asset').as('assets'))
.whereRef('asset.id', '=', 'shared_link_asset.assetsId')
.where('asset.deletedAt', 'is', null)
.as('assets'),
(join) => join.onTrue(),
)
@@ -125,75 +130,75 @@ export class SharedLinkRepository {
.leftJoinLateral(
(eb) =>
eb
.selectFrom('albums')
.selectAll('albums')
.whereRef('albums.id', '=', 'shared_links.albumId')
.selectFrom('album')
.selectAll('album')
.whereRef('album.id', '=', 'shared_link.albumId')
.innerJoinLateral(
(eb) =>
eb
.selectFrom('users')
.selectFrom('user')
.select([
'users.id',
'users.email',
'users.createdAt',
'users.profileImagePath',
'users.isAdmin',
'users.shouldChangePassword',
'users.deletedAt',
'users.oauthId',
'users.updatedAt',
'users.storageLabel',
'users.name',
'users.quotaSizeInBytes',
'users.quotaUsageInBytes',
'users.status',
'users.profileChangedAt',
'user.id',
'user.email',
'user.createdAt',
'user.profileImagePath',
'user.isAdmin',
'user.shouldChangePassword',
'user.deletedAt',
'user.oauthId',
'user.updatedAt',
'user.storageLabel',
'user.name',
'user.quotaSizeInBytes',
'user.quotaUsageInBytes',
'user.status',
'user.profileChangedAt',
])
.whereRef('users.id', '=', 'albums.ownerId')
.where('users.deletedAt', 'is', null)
.whereRef('user.id', '=', 'album.ownerId')
.where('user.deletedAt', 'is', null)
.as('owner'),
(join) => join.onTrue(),
)
.select((eb) => eb.fn.toJson('owner').as('owner'))
.where('albums.deletedAt', 'is', null)
.where('album.deletedAt', 'is', null)
.as('album'),
(join) => join.onTrue(),
)
.select((eb) => eb.fn.toJson('album').$castTo<Album | null>().as('album'))
.where((eb) => eb.or([eb('shared_links.type', '=', SharedLinkType.INDIVIDUAL), eb('album.id', 'is not', null)]))
.$if(!!albumId, (eb) => eb.where('shared_links.albumId', '=', albumId!))
.orderBy('shared_links.createdAt', 'desc')
.distinctOn(['shared_links.createdAt'])
.where((eb) => eb.or([eb('shared_link.type', '=', SharedLinkType.INDIVIDUAL), eb('album.id', 'is not', null)]))
.$if(!!albumId, (eb) => eb.where('shared_link.albumId', '=', albumId!))
.orderBy('shared_link.createdAt', 'desc')
.distinctOn(['shared_link.createdAt'])
.execute();
}
@GenerateSql({ params: [DummyValue.BUFFER] })
async getByKey(key: Buffer) {
return this.db
.selectFrom('shared_links')
.where('shared_links.key', '=', key)
.leftJoin('albums', 'albums.id', 'shared_links.albumId')
.where('albums.deletedAt', 'is', null)
.selectFrom('shared_link')
.where('shared_link.key', '=', key)
.leftJoin('album', 'album.id', 'shared_link.albumId')
.where('album.deletedAt', 'is', null)
.select((eb) => [
...columns.authSharedLink,
jsonObjectFrom(
eb.selectFrom('users').select(columns.authUser).whereRef('users.id', '=', 'shared_links.userId'),
eb.selectFrom('user').select(columns.authUser).whereRef('user.id', '=', 'shared_link.userId'),
).as('user'),
])
.where((eb) => eb.or([eb('shared_links.type', '=', SharedLinkType.INDIVIDUAL), eb('albums.id', 'is not', null)]))
.where((eb) => eb.or([eb('shared_link.type', '=', SharedLinkType.INDIVIDUAL), eb('album.id', 'is not', null)]))
.executeTakeFirst();
}
async create(entity: Insertable<SharedLinkTable> & { assetIds?: string[] }) {
const { id } = await this.db
.insertInto('shared_links')
.insertInto('shared_link')
.values(_.omit(entity, 'assetIds'))
.returningAll()
.executeTakeFirstOrThrow();
if (entity.assetIds && entity.assetIds.length > 0) {
await this.db
.insertInto('shared_link__asset')
.insertInto('shared_link_asset')
.values(entity.assetIds!.map((assetsId) => ({ assetsId, sharedLinksId: id })))
.execute();
}
@@ -203,15 +208,15 @@ export class SharedLinkRepository {
async update(entity: Updateable<SharedLinkTable> & { id: string; assetIds?: string[] }) {
const { id } = await this.db
.updateTable('shared_links')
.updateTable('shared_link')
.set(_.omit(entity, 'assets', 'album', 'assetIds'))
.where('shared_links.id', '=', entity.id)
.where('shared_link.id', '=', entity.id)
.returningAll()
.executeTakeFirstOrThrow();
if (entity.assetIds && entity.assetIds.length > 0) {
await this.db
.insertInto('shared_link__asset')
.insertInto('shared_link_asset')
.values(entity.assetIds!.map((assetsId) => ({ assetsId, sharedLinksId: id })))
.execute();
}
@@ -220,23 +225,24 @@ export class SharedLinkRepository {
}
async remove(id: string): Promise<void> {
await this.db.deleteFrom('shared_links').where('shared_links.id', '=', id).execute();
await this.db.deleteFrom('shared_link').where('shared_link.id', '=', id).execute();
}
private getSharedLinks(id: string) {
return this.db
.selectFrom('shared_links')
.selectAll('shared_links')
.where('shared_links.id', '=', id)
.leftJoin('shared_link__asset', 'shared_link__asset.sharedLinksId', 'shared_links.id')
.selectFrom('shared_link')
.selectAll('shared_link')
.where('shared_link.id', '=', id)
.leftJoin('shared_link_asset', 'shared_link_asset.sharedLinksId', 'shared_link.id')
.leftJoinLateral(
(eb) =>
eb
.selectFrom('assets')
.whereRef('assets.id', '=', 'shared_link__asset.assetsId')
.selectAll('assets')
.selectFrom('asset')
.whereRef('asset.id', '=', 'shared_link_asset.assetsId')
.selectAll('asset')
.innerJoinLateral(
(eb) => eb.selectFrom('exif').whereRef('exif.assetId', '=', 'assets.id').selectAll().as('exif'),
(eb) =>
eb.selectFrom('asset_exif').whereRef('asset_exif.assetId', '=', 'asset.id').selectAll().as('exif'),
(join) => join.onTrue(),
)
.as('assets'),
@@ -248,7 +254,7 @@ export class SharedLinkRepository {
.$castTo<MapAsset[]>()
.as('assets'),
)
.groupBy('shared_links.id')
.groupBy('shared_link.id')
.executeTakeFirstOrThrow();
}
}

View File

@@ -13,29 +13,34 @@ export interface StackSearch {
primaryAssetId?: string;
}
const withAssets = (eb: ExpressionBuilder<DB, 'asset_stack'>, withTags = false) => {
const withAssets = (eb: ExpressionBuilder<DB, 'stack'>, withTags = false) => {
return jsonArrayFrom(
eb
.selectFrom('assets')
.selectAll('assets')
.selectFrom('asset')
.selectAll('asset')
.innerJoinLateral(
(eb) => eb.selectFrom('exif').select(columns.exif).whereRef('exif.assetId', '=', 'assets.id').as('exifInfo'),
(eb) =>
eb
.selectFrom('asset_exif')
.select(columns.exif)
.whereRef('asset_exif.assetId', '=', 'asset.id')
.as('exifInfo'),
(join) => join.onTrue(),
)
.$if(withTags, (eb) =>
eb.select((eb) =>
jsonArrayFrom(
eb
.selectFrom('tags')
.selectFrom('tag')
.select(columns.tag)
.innerJoin('tag_asset', 'tags.id', 'tag_asset.tagsId')
.whereRef('tag_asset.assetsId', '=', 'assets.id'),
.innerJoin('tag_asset', 'tag.id', 'tag_asset.tagsId')
.whereRef('tag_asset.assetsId', '=', 'asset.id'),
).as('tags'),
),
)
.select((eb) => eb.fn.toJson('exifInfo').as('exifInfo'))
.where('assets.deletedAt', 'is', null)
.whereRef('assets.stackId', '=', 'asset_stack.id')
.where('asset.deletedAt', 'is', null)
.whereRef('asset.stackId', '=', 'stack.id')
.$call(withDefaultVisibility),
).as('assets');
};
@@ -47,28 +52,28 @@ export class StackRepository {
@GenerateSql({ params: [{ ownerId: DummyValue.UUID }] })
search(query: StackSearch) {
return this.db
.selectFrom('asset_stack')
.selectAll('asset_stack')
.selectFrom('stack')
.selectAll('stack')
.select(withAssets)
.where('asset_stack.ownerId', '=', query.ownerId)
.$if(!!query.primaryAssetId, (eb) => eb.where('asset_stack.primaryAssetId', '=', query.primaryAssetId!))
.where('stack.ownerId', '=', query.ownerId)
.$if(!!query.primaryAssetId, (eb) => eb.where('stack.primaryAssetId', '=', query.primaryAssetId!))
.execute();
}
async create(entity: Omit<Insertable<StackTable>, 'primaryAssetId'>, assetIds: string[]) {
return this.db.transaction().execute(async (tx) => {
const stacks = await tx
.selectFrom('asset_stack')
.where('asset_stack.ownerId', '=', entity.ownerId)
.where('asset_stack.primaryAssetId', 'in', assetIds)
.select('asset_stack.id')
.selectFrom('stack')
.where('stack.ownerId', '=', entity.ownerId)
.where('stack.primaryAssetId', 'in', assetIds)
.select('stack.id')
.select((eb) =>
jsonArrayFrom(
eb
.selectFrom('assets')
.select('assets.id')
.whereRef('assets.stackId', '=', 'asset_stack.id')
.where('assets.deletedAt', 'is', null),
.selectFrom('asset')
.select('asset.id')
.whereRef('asset.stackId', '=', 'stack.id')
.where('asset.deletedAt', 'is', null),
).as('assets'),
)
.execute();
@@ -86,7 +91,7 @@ export class StackRepository {
if (stacks.length > 0) {
await tx
.deleteFrom('asset_stack')
.deleteFrom('stack')
.where(
'id',
'in',
@@ -96,13 +101,13 @@ export class StackRepository {
}
const newRecord = await tx
.insertInto('asset_stack')
.insertInto('stack')
.values({ ...entity, primaryAssetId: assetIds[0] })
.returning('id')
.executeTakeFirstOrThrow();
await tx
.updateTable('assets')
.updateTable('asset')
.set({
stackId: newRecord.id,
updatedAt: new Date(),
@@ -111,8 +116,8 @@ export class StackRepository {
.execute();
return tx
.selectFrom('asset_stack')
.selectAll('asset_stack')
.selectFrom('stack')
.selectAll('stack')
.select(withAssets)
.where('id', '=', newRecord.id)
.executeTakeFirstOrThrow();
@@ -121,19 +126,19 @@ export class StackRepository {
@GenerateSql({ params: [DummyValue.UUID] })
async delete(id: string): Promise<void> {
await this.db.deleteFrom('asset_stack').where('id', '=', asUuid(id)).execute();
await this.db.deleteFrom('stack').where('id', '=', asUuid(id)).execute();
}
async deleteAll(ids: string[]): Promise<void> {
await this.db.deleteFrom('asset_stack').where('id', 'in', ids).execute();
await this.db.deleteFrom('stack').where('id', 'in', ids).execute();
}
update(id: string, entity: Updateable<StackTable>) {
return this.db
.updateTable('asset_stack')
.updateTable('stack')
.set(entity)
.where('id', '=', asUuid(id))
.returningAll('asset_stack')
.returningAll('stack')
.returning((eb) => withAssets(eb, true))
.executeTakeFirstOrThrow();
}
@@ -141,7 +146,7 @@ export class StackRepository {
@GenerateSql({ params: [DummyValue.UUID] })
getById(id: string) {
return this.db
.selectFrom('asset_stack')
.selectFrom('stack')
.selectAll()
.select((eb) => withAssets(eb, true))
.where('id', '=', asUuid(id))

View File

@@ -13,7 +13,7 @@ export class SyncCheckpointRepository {
@GenerateSql({ params: [DummyValue.UUID] })
getAll(sessionId: string) {
return this.db
.selectFrom('session_sync_checkpoints')
.selectFrom('session_sync_checkpoint')
.select(['type', 'ack'])
.where('sessionId', '=', sessionId)
.execute();
@@ -21,7 +21,7 @@ export class SyncCheckpointRepository {
upsertAll(items: Insertable<SessionSyncCheckpointTable>[]) {
return this.db
.insertInto('session_sync_checkpoints')
.insertInto('session_sync_checkpoint')
.values(items)
.onConflict((oc) =>
oc.columns(['sessionId', 'type']).doUpdateSet((eb) => ({
@@ -34,7 +34,7 @@ export class SyncCheckpointRepository {
@GenerateSql({ params: [DummyValue.UUID] })
deleteAll(sessionId: string, types?: SyncEntityType[]) {
return this.db
.deleteFrom('session_sync_checkpoints')
.deleteFrom('session_sync_checkpoint')
.where('sessionId', '=', sessionId)
.$if(!!types, (qb) => qb.where('type', 'in', types!))
.execute();

View File

@@ -7,27 +7,27 @@ import { DB } from 'src/schema';
import { SyncAck } from 'src/types';
type AuditTables =
| 'users_audit'
| 'partners_audit'
| 'assets_audit'
| 'albums_audit'
| 'album_users_audit'
| 'album_assets_audit'
| 'memories_audit'
| 'memory_assets_audit'
| 'stacks_audit'
| 'user_audit'
| 'partner_audit'
| 'asset_audit'
| 'album_audit'
| 'album_user_audit'
| 'album_asset_audit'
| 'memory_audit'
| 'memory_asset_audit'
| 'stack_audit'
| 'person_audit'
| 'user_metadata_audit';
type UpsertTables =
| 'users'
| 'partners'
| 'assets'
| 'exif'
| 'albums'
| 'albums_shared_users_users'
| 'memories'
| 'memories_assets_assets'
| 'asset_stack'
| 'user'
| 'partner'
| 'asset'
| 'asset_exif'
| 'album'
| 'album_user'
| 'memory'
| 'memory_asset'
| 'stack'
| 'person'
| 'user_metadata';
@@ -100,7 +100,7 @@ class AlbumSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
getCreatedAfter(userId: string, afterCreateId?: string) {
return this.db
.selectFrom('albums_shared_users_users')
.selectFrom('album_user')
.select(['albumsId as id', 'createId'])
.where('usersId', '=', userId)
.$if(!!afterCreateId, (qb) => qb.where('createId', '>=', afterCreateId!))
@@ -112,7 +112,7 @@ class AlbumSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getDeletes(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('albums_audit')
.selectFrom('album_audit')
.select(['id', 'albumId'])
.where('userId', '=', userId)
.$call(this.auditTableFilters(ack))
@@ -122,24 +122,24 @@ class AlbumSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getUpserts(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('albums')
.distinctOn(['albums.id', 'albums.updateId'])
.where('albums.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.$if(!!ack, (qb) => qb.where('albums.updateId', '>', ack!.updateId))
.orderBy('albums.updateId', 'asc')
.leftJoin('albums_shared_users_users as album_users', 'albums.id', 'album_users.albumsId')
.where((eb) => eb.or([eb('albums.ownerId', '=', userId), eb('album_users.usersId', '=', userId)]))
.selectFrom('album')
.distinctOn(['album.id', 'album.updateId'])
.where('album.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.$if(!!ack, (qb) => qb.where('album.updateId', '>', ack!.updateId))
.orderBy('album.updateId', 'asc')
.leftJoin('album_user as album_users', 'album.id', 'album_users.albumsId')
.where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_users.usersId', '=', userId)]))
.select([
'albums.id',
'albums.ownerId',
'albums.albumName as name',
'albums.description',
'albums.createdAt',
'albums.updatedAt',
'albums.albumThumbnailAssetId as thumbnailAssetId',
'albums.isActivityEnabled',
'albums.order',
'albums.updateId',
'album.id',
'album.ownerId',
'album.albumName as name',
'album.description',
'album.createdAt',
'album.updatedAt',
'album.albumThumbnailAssetId as thumbnailAssetId',
'album.isActivityEnabled',
'album.order',
'album.updateId',
])
.stream();
}
@@ -149,31 +149,31 @@ class AlbumAssetSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID, DummyValue.UUID], stream: true })
getBackfill(albumId: string, afterUpdateId: string | undefined, beforeUpdateId: string) {
return this.db
.selectFrom('assets')
.innerJoin('albums_assets_assets as album_assets', 'album_assets.assetsId', 'assets.id')
.selectFrom('asset')
.innerJoin('album_asset', 'album_asset.assetsId', 'asset.id')
.select(columns.syncAsset)
.select('assets.updateId')
.where('album_assets.albumsId', '=', albumId)
.where('assets.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.where('assets.updateId', '<=', beforeUpdateId)
.$if(!!afterUpdateId, (eb) => eb.where('assets.updateId', '>=', afterUpdateId!))
.orderBy('assets.updateId', 'asc')
.select('asset.updateId')
.where('album_asset.albumsId', '=', albumId)
.where('asset.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.where('asset.updateId', '<=', beforeUpdateId)
.$if(!!afterUpdateId, (eb) => eb.where('asset.updateId', '>=', afterUpdateId!))
.orderBy('asset.updateId', 'asc')
.stream();
}
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getUpserts(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('assets')
.innerJoin('albums_assets_assets as album_assets', 'album_assets.assetsId', 'assets.id')
.selectFrom('asset')
.innerJoin('album_asset', 'album_asset.assetsId', 'asset.id')
.select(columns.syncAsset)
.select('assets.updateId')
.where('assets.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.$if(!!ack, (qb) => qb.where('assets.updateId', '>', ack!.updateId))
.orderBy('assets.updateId', 'asc')
.innerJoin('albums', 'albums.id', 'album_assets.albumsId')
.leftJoin('albums_shared_users_users as album_users', 'album_users.albumsId', 'album_assets.albumsId')
.where((eb) => eb.or([eb('albums.ownerId', '=', userId), eb('album_users.usersId', '=', userId)]))
.select('asset.updateId')
.where('asset.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.$if(!!ack, (qb) => qb.where('asset.updateId', '>', ack!.updateId))
.orderBy('asset.updateId', 'asc')
.innerJoin('album', 'album.id', 'album_asset.albumsId')
.leftJoin('album_user', 'album_user.albumsId', 'album_asset.albumsId')
.where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.usersId', '=', userId)]))
.stream();
}
}
@@ -182,31 +182,31 @@ class AlbumAssetExifSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID, DummyValue.UUID], stream: true })
getBackfill(albumId: string, afterUpdateId: string | undefined, beforeUpdateId: string) {
return this.db
.selectFrom('exif')
.innerJoin('albums_assets_assets as album_assets', 'album_assets.assetsId', 'exif.assetId')
.selectFrom('asset_exif')
.innerJoin('album_asset', 'album_asset.assetsId', 'asset_exif.assetId')
.select(columns.syncAssetExif)
.select('exif.updateId')
.where('album_assets.albumsId', '=', albumId)
.where('exif.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.where('exif.updateId', '<=', beforeUpdateId)
.$if(!!afterUpdateId, (eb) => eb.where('exif.updateId', '>=', afterUpdateId!))
.orderBy('exif.updateId', 'asc')
.select('asset_exif.updateId')
.where('album_asset.albumsId', '=', albumId)
.where('asset_exif.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.where('asset_exif.updateId', '<=', beforeUpdateId)
.$if(!!afterUpdateId, (eb) => eb.where('asset_exif.updateId', '>=', afterUpdateId!))
.orderBy('asset_exif.updateId', 'asc')
.stream();
}
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getUpserts(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('exif')
.innerJoin('albums_assets_assets as album_assets', 'album_assets.assetsId', 'exif.assetId')
.selectFrom('asset_exif')
.innerJoin('album_asset', 'album_asset.assetsId', 'asset_exif.assetId')
.select(columns.syncAssetExif)
.select('exif.updateId')
.where('exif.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.$if(!!ack, (qb) => qb.where('exif.updateId', '>', ack!.updateId))
.orderBy('exif.updateId', 'asc')
.innerJoin('albums', 'albums.id', 'album_assets.albumsId')
.leftJoin('albums_shared_users_users as album_users', 'album_users.albumsId', 'album_assets.albumsId')
.where((eb) => eb.or([eb('albums.ownerId', '=', userId), eb('album_users.usersId', '=', userId)]))
.select('asset_exif.updateId')
.where('asset_exif.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.$if(!!ack, (qb) => qb.where('asset_exif.updateId', '>', ack!.updateId))
.orderBy('asset_exif.updateId', 'asc')
.innerJoin('album', 'album.id', 'album_asset.albumsId')
.leftJoin('album_user', 'album_user.albumsId', 'album_asset.albumsId')
.where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.usersId', '=', userId)]))
.stream();
}
}
@@ -215,7 +215,7 @@ class AlbumToAssetSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID, DummyValue.UUID], stream: true })
getBackfill(albumId: string, afterUpdateId: string | undefined, beforeUpdateId: string) {
return this.db
.selectFrom('albums_assets_assets as album_assets')
.selectFrom('album_asset as album_assets')
.select(['album_assets.assetsId as assetId', 'album_assets.albumsId as albumId', 'album_assets.updateId'])
.where('album_assets.albumsId', '=', albumId)
.where('album_assets.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
@@ -228,22 +228,22 @@ class AlbumToAssetSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getDeletes(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('album_assets_audit')
.selectFrom('album_asset_audit')
.select(['id', 'assetId', 'albumId'])
.where((eb) =>
eb(
'albumId',
'in',
eb
.selectFrom('albums')
.selectFrom('album')
.select(['id'])
.where('ownerId', '=', userId)
.union((eb) =>
eb.parens(
eb
.selectFrom('albums_shared_users_users as albumUsers')
.select(['albumUsers.albumsId as id'])
.where('albumUsers.usersId', '=', userId),
.selectFrom('album_user')
.select(['album_user.albumsId as id'])
.where('album_user.usersId', '=', userId),
),
),
),
@@ -255,14 +255,14 @@ class AlbumToAssetSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getUpserts(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('albums_assets_assets as album_assets')
.select(['album_assets.assetsId as assetId', 'album_assets.albumsId as albumId', 'album_assets.updateId'])
.where('album_assets.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.$if(!!ack, (qb) => qb.where('album_assets.updateId', '>', ack!.updateId))
.orderBy('album_assets.updateId', 'asc')
.innerJoin('albums', 'albums.id', 'album_assets.albumsId')
.leftJoin('albums_shared_users_users as album_users', 'album_users.albumsId', 'album_assets.albumsId')
.where((eb) => eb.or([eb('albums.ownerId', '=', userId), eb('album_users.usersId', '=', userId)]))
.selectFrom('album_asset')
.select(['album_asset.assetsId as assetId', 'album_asset.albumsId as albumId', 'album_asset.updateId'])
.where('album_asset.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.$if(!!ack, (qb) => qb.where('album_asset.updateId', '>', ack!.updateId))
.orderBy('album_asset.updateId', 'asc')
.innerJoin('album', 'album.id', 'album_asset.albumsId')
.leftJoin('album_user', 'album_user.albumsId', 'album_asset.albumsId')
.where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.usersId', '=', userId)]))
.stream();
}
}
@@ -271,9 +271,9 @@ class AlbumUserSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID, DummyValue.UUID, DummyValue.UUID], stream: true })
getBackfill(albumId: string, afterUpdateId: string | undefined, beforeUpdateId: string) {
return this.db
.selectFrom('albums_shared_users_users as album_users')
.selectFrom('album_user')
.select(columns.syncAlbumUser)
.select('album_users.updateId')
.select('album_user.updateId')
.where('albumsId', '=', albumId)
.where('updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.where('updateId', '<=', beforeUpdateId)
@@ -285,22 +285,22 @@ class AlbumUserSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getDeletes(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('album_users_audit')
.selectFrom('album_user_audit')
.select(['id', 'userId', 'albumId'])
.where((eb) =>
eb(
'albumId',
'in',
eb
.selectFrom('albums')
.selectFrom('album')
.select(['id'])
.where('ownerId', '=', userId)
.union((eb) =>
eb.parens(
eb
.selectFrom('albums_shared_users_users as albumUsers')
.select(['albumUsers.albumsId as id'])
.where('albumUsers.usersId', '=', userId),
.selectFrom('album_user')
.select(['album_user.albumsId as id'])
.where('album_user.usersId', '=', userId),
),
),
),
@@ -312,24 +312,24 @@ class AlbumUserSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getUpserts(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('albums_shared_users_users as album_users')
.selectFrom('album_user')
.select(columns.syncAlbumUser)
.select('album_users.updateId')
.where('album_users.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.$if(!!ack, (qb) => qb.where('album_users.updateId', '>', ack!.updateId))
.orderBy('album_users.updateId', 'asc')
.select('album_user.updateId')
.where('album_user.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.$if(!!ack, (qb) => qb.where('album_user.updateId', '>', ack!.updateId))
.orderBy('album_user.updateId', 'asc')
.where((eb) =>
eb(
'album_users.albumsId',
'album_user.albumsId',
'in',
eb
.selectFrom('albums')
.selectFrom('album')
.select(['id'])
.where('ownerId', '=', userId)
.union((eb) =>
eb.parens(
eb
.selectFrom('albums_shared_users_users as albumUsers')
.selectFrom('album_user as albumUsers')
.select(['albumUsers.albumsId as id'])
.where('albumUsers.usersId', '=', userId),
),
@@ -344,7 +344,7 @@ class AssetSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getDeletes(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('assets_audit')
.selectFrom('asset_audit')
.select(['id', 'assetId'])
.where('ownerId', '=', userId)
.$call(this.auditTableFilters(ack))
@@ -354,9 +354,9 @@ class AssetSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getUpserts(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('assets')
.selectFrom('asset')
.select(columns.syncAsset)
.select('assets.updateId')
.select('asset.updateId')
.where('ownerId', '=', userId)
.$call(this.upsertTableFilters(ack))
.stream();
@@ -402,10 +402,10 @@ class AssetExifSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getUpserts(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('exif')
.selectFrom('asset_exif')
.select(columns.syncAssetExif)
.select('exif.updateId')
.where('assetId', 'in', (eb) => eb.selectFrom('assets').select('id').where('ownerId', '=', userId))
.select('asset_exif.updateId')
.where('assetId', 'in', (eb) => eb.selectFrom('asset').select('id').where('ownerId', '=', userId))
.$call(this.upsertTableFilters(ack))
.stream();
}
@@ -415,7 +415,7 @@ class MemorySync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getDeletes(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('memories_audit')
.selectFrom('memory_audit')
.select(['id', 'memoryId'])
.where('userId', '=', userId)
.$call(this.auditTableFilters(ack))
@@ -425,7 +425,7 @@ class MemorySync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getUpserts(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('memories')
.selectFrom('memory')
.select([
'id',
'createdAt',
@@ -451,9 +451,9 @@ class MemoryToAssetSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getDeletes(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('memory_assets_audit')
.selectFrom('memory_asset_audit')
.select(['id', 'memoryId', 'assetId'])
.where('memoryId', 'in', (eb) => eb.selectFrom('memories').select('id').where('ownerId', '=', userId))
.where('memoryId', 'in', (eb) => eb.selectFrom('memory').select('id').where('ownerId', '=', userId))
.$call(this.auditTableFilters(ack))
.stream();
}
@@ -461,10 +461,10 @@ class MemoryToAssetSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getUpserts(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('memories_assets_assets')
.selectFrom('memory_asset')
.select(['memoriesId as memoryId', 'assetsId as assetId'])
.select('updateId')
.where('memoriesId', 'in', (eb) => eb.selectFrom('memories').select('id').where('ownerId', '=', userId))
.where('memoriesId', 'in', (eb) => eb.selectFrom('memory').select('id').where('ownerId', '=', userId))
.$call(this.upsertTableFilters(ack))
.stream();
}
@@ -474,19 +474,19 @@ class PartnerSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
getCreatedAfter(userId: string, afterCreateId?: string) {
return this.db
.selectFrom('partners')
.selectFrom('partner')
.select(['sharedById', 'createId'])
.where('sharedWithId', '=', userId)
.$if(!!afterCreateId, (qb) => qb.where('createId', '>=', afterCreateId!))
.where('createdAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.orderBy('partners.createId', 'asc')
.orderBy('partner.createId', 'asc')
.execute();
}
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getDeletes(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('partners_audit')
.selectFrom('partner_audit')
.select(['id', 'sharedById', 'sharedWithId'])
.where((eb) => eb.or([eb('sharedById', '=', userId), eb('sharedWithId', '=', userId)]))
.$call(this.auditTableFilters(ack))
@@ -496,7 +496,7 @@ class PartnerSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getUpserts(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('partners')
.selectFrom('partner')
.select(['sharedById', 'sharedWithId', 'inTimeline', 'updateId'])
.where((eb) => eb.or([eb('sharedById', '=', userId), eb('sharedWithId', '=', userId)]))
.$call(this.upsertTableFilters(ack))
@@ -508,9 +508,9 @@ class PartnerAssetsSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID, DummyValue.UUID], stream: true })
getBackfill(partnerId: string, afterUpdateId: string | undefined, beforeUpdateId: string) {
return this.db
.selectFrom('assets')
.selectFrom('asset')
.select(columns.syncAsset)
.select('assets.updateId')
.select('asset.updateId')
.where('ownerId', '=', partnerId)
.where('updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.where('updateId', '<=', beforeUpdateId)
@@ -522,10 +522,10 @@ class PartnerAssetsSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getDeletes(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('assets_audit')
.selectFrom('asset_audit')
.select(['id', 'assetId'])
.where('ownerId', 'in', (eb) =>
eb.selectFrom('partners').select(['sharedById']).where('sharedWithId', '=', userId),
eb.selectFrom('partner').select(['sharedById']).where('sharedWithId', '=', userId),
)
.$call(this.auditTableFilters(ack))
.stream();
@@ -534,11 +534,11 @@ class PartnerAssetsSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getUpserts(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('assets')
.selectFrom('asset')
.select(columns.syncAsset)
.select('assets.updateId')
.select('asset.updateId')
.where('ownerId', 'in', (eb) =>
eb.selectFrom('partners').select(['sharedById']).where('sharedWithId', '=', userId),
eb.selectFrom('partner').select(['sharedById']).where('sharedWithId', '=', userId),
)
.$call(this.upsertTableFilters(ack))
.stream();
@@ -549,30 +549,30 @@ class PartnerAssetExifsSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID, DummyValue.UUID], stream: true })
getBackfill(partnerId: string, afterUpdateId: string | undefined, beforeUpdateId: string) {
return this.db
.selectFrom('exif')
.selectFrom('asset_exif')
.select(columns.syncAssetExif)
.select('exif.updateId')
.innerJoin('assets', 'assets.id', 'exif.assetId')
.where('assets.ownerId', '=', partnerId)
.where('exif.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.where('exif.updateId', '<=', beforeUpdateId)
.$if(!!afterUpdateId, (eb) => eb.where('exif.updateId', '>=', afterUpdateId!))
.orderBy('exif.updateId', 'asc')
.select('asset_exif.updateId')
.innerJoin('asset', 'asset.id', 'asset_exif.assetId')
.where('asset.ownerId', '=', partnerId)
.where('asset_exif.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
.where('asset_exif.updateId', '<=', beforeUpdateId)
.$if(!!afterUpdateId, (eb) => eb.where('asset_exif.updateId', '>=', afterUpdateId!))
.orderBy('asset_exif.updateId', 'asc')
.stream();
}
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getUpserts(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('exif')
.selectFrom('asset_exif')
.select(columns.syncAssetExif)
.select('exif.updateId')
.select('asset_exif.updateId')
.where('assetId', 'in', (eb) =>
eb
.selectFrom('assets')
.selectFrom('asset')
.select('id')
.where('ownerId', 'in', (eb) =>
eb.selectFrom('partners').select(['sharedById']).where('sharedWithId', '=', userId),
eb.selectFrom('partner').select(['sharedById']).where('sharedWithId', '=', userId),
),
)
.$call(this.upsertTableFilters(ack))
@@ -584,7 +584,7 @@ class StackSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getDeletes(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('stacks_audit')
.selectFrom('stack_audit')
.select(['id', 'stackId'])
.where('userId', '=', userId)
.$call(this.auditTableFilters(ack))
@@ -594,7 +594,7 @@ class StackSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getUpserts(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('asset_stack')
.selectFrom('stack')
.select(columns.syncStack)
.select('updateId')
.where('ownerId', '=', userId)
@@ -607,11 +607,9 @@ class PartnerStackSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getDeletes(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('stacks_audit')
.selectFrom('stack_audit')
.select(['id', 'stackId'])
.where('userId', 'in', (eb) =>
eb.selectFrom('partners').select(['sharedById']).where('sharedWithId', '=', userId),
)
.where('userId', 'in', (eb) => eb.selectFrom('partner').select(['sharedById']).where('sharedWithId', '=', userId))
.$call(this.auditTableFilters(ack))
.stream();
}
@@ -619,7 +617,7 @@ class PartnerStackSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID, DummyValue.UUID], stream: true })
getBackfill(partnerId: string, afterUpdateId: string | undefined, beforeUpdateId: string) {
return this.db
.selectFrom('asset_stack')
.selectFrom('stack')
.select(columns.syncStack)
.select('updateId')
.where('ownerId', '=', partnerId)
@@ -633,11 +631,11 @@ class PartnerStackSync extends BaseSync {
@GenerateSql({ params: [DummyValue.UUID], stream: true })
getUpserts(userId: string, ack?: SyncAck) {
return this.db
.selectFrom('asset_stack')
.selectFrom('stack')
.select(columns.syncStack)
.select('updateId')
.where('ownerId', 'in', (eb) =>
eb.selectFrom('partners').select(['sharedById']).where('sharedWithId', '=', userId),
eb.selectFrom('partner').select(['sharedById']).where('sharedWithId', '=', userId),
)
.$call(this.upsertTableFilters(ack))
.stream();
@@ -647,13 +645,13 @@ class PartnerStackSync extends BaseSync {
class UserSync extends BaseSync {
@GenerateSql({ params: [], stream: true })
getDeletes(ack?: SyncAck) {
return this.db.selectFrom('users_audit').select(['id', 'userId']).$call(this.auditTableFilters(ack)).stream();
return this.db.selectFrom('user_audit').select(['id', 'userId']).$call(this.auditTableFilters(ack)).stream();
}
@GenerateSql({ params: [], stream: true })
getUpserts(ack?: SyncAck) {
return this.db
.selectFrom('users')
.selectFrom('user')
.select(['id', 'name', 'email', 'deletedAt', 'updateId'])
.$call(this.upsertTableFilters(ack))
.stream();

View File

@@ -19,13 +19,13 @@ export class TagRepository {
@GenerateSql({ params: [DummyValue.UUID] })
get(id: string) {
return this.db.selectFrom('tags').select(columns.tag).where('id', '=', id).executeTakeFirst();
return this.db.selectFrom('tag').select(columns.tag).where('id', '=', id).executeTakeFirst();
}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] })
getByValue(userId: string, value: string) {
return this.db
.selectFrom('tags')
.selectFrom('tag')
.select(columns.tag)
.where('userId', '=', userId)
.where('value', '=', value)
@@ -37,7 +37,7 @@ export class TagRepository {
const parentId = _parentId ?? null;
return this.db.transaction().execute(async (tx) => {
const tag = await this.db
.insertInto('tags')
.insertInto('tag')
.values({ userId, value, parentId })
.onConflict((oc) => oc.columns(['userId', 'value']).doUpdateSet({ parentId }))
.returning(columns.tag)
@@ -45,18 +45,18 @@ export class TagRepository {
// update closure table
await tx
.insertInto('tags_closure')
.insertInto('tag_closure')
.values({ id_ancestor: tag.id, id_descendant: tag.id })
.onConflict((oc) => oc.doNothing())
.execute();
if (parentId) {
await tx
.insertInto('tags_closure')
.insertInto('tag_closure')
.columns(['id_ancestor', 'id_descendant'])
.expression(
this.db
.selectFrom('tags_closure')
.selectFrom('tag_closure')
.select(['id_ancestor', sql.raw<string>(`'${tag.id}'`).as('id_descendant')])
.where('id_descendant', '=', parentId),
)
@@ -70,22 +70,22 @@ export class TagRepository {
@GenerateSql({ params: [DummyValue.UUID] })
getAll(userId: string) {
return this.db.selectFrom('tags').select(columns.tag).where('userId', '=', userId).orderBy('value').execute();
return this.db.selectFrom('tag').select(columns.tag).where('userId', '=', userId).orderBy('value').execute();
}
@GenerateSql({ params: [{ userId: DummyValue.UUID, color: DummyValue.STRING, value: DummyValue.STRING }] })
create(tag: Insertable<TagTable>) {
return this.db.insertInto('tags').values(tag).returningAll().executeTakeFirstOrThrow();
return this.db.insertInto('tag').values(tag).returningAll().executeTakeFirstOrThrow();
}
@GenerateSql({ params: [DummyValue.UUID, { color: DummyValue.STRING }] })
update(id: string, dto: Updateable<TagTable>) {
return this.db.updateTable('tags').set(dto).where('id', '=', id).returningAll().executeTakeFirstOrThrow();
return this.db.updateTable('tag').set(dto).where('id', '=', id).returningAll().executeTakeFirstOrThrow();
}
@GenerateSql({ params: [DummyValue.UUID] })
async delete(id: string) {
await this.db.deleteFrom('tags').where('id', '=', id).execute();
await this.db.deleteFrom('tag').where('id', '=', id).execute();
}
@ChunkedSet({ paramIndex: 1 })
@@ -166,17 +166,17 @@ export class TagRepository {
// TODO rewrite as a single statement
await this.db.transaction().execute(async (tx) => {
const result = await tx
.selectFrom('assets')
.innerJoin('tag_asset', 'tag_asset.assetsId', 'assets.id')
.innerJoin('tags_closure', 'tags_closure.id_descendant', 'tag_asset.tagsId')
.innerJoin('tags', 'tags.id', 'tags_closure.id_descendant')
.select((eb) => ['tags.id', eb.fn.count<number>('assets.id').as('count')])
.groupBy('tags.id')
.selectFrom('asset')
.innerJoin('tag_asset', 'tag_asset.assetsId', 'asset.id')
.innerJoin('tag_closure', 'tag_closure.id_descendant', 'tag_asset.tagsId')
.innerJoin('tag', 'tag.id', 'tag_closure.id_descendant')
.select((eb) => ['tag.id', eb.fn.count<number>('asset.id').as('count')])
.groupBy('tag.id')
.execute();
const ids = result.filter(({ count }) => count === 0).map(({ id }) => id);
if (ids.length > 0) {
await this.db.deleteFrom('tags').where('id', 'in', ids).execute();
await this.db.deleteFrom('tag').where('id', 'in', ids).execute();
this.logger.log(`Deleted ${ids.length} empty tags`);
}
});

View File

@@ -8,13 +8,13 @@ export class TrashRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {}
getDeletedIds(): AsyncIterableIterator<{ id: string }> {
return this.db.selectFrom('assets').select(['id']).where('status', '=', AssetStatus.DELETED).stream();
return this.db.selectFrom('asset').select(['id']).where('status', '=', AssetStatus.DELETED).stream();
}
@GenerateSql({ params: [DummyValue.UUID] })
async restore(userId: string): Promise<number> {
const { numUpdatedRows } = await this.db
.updateTable('assets')
.updateTable('asset')
.where('ownerId', '=', userId)
.where('status', '=', AssetStatus.TRASHED)
.set({ status: AssetStatus.ACTIVE, deletedAt: null })
@@ -26,7 +26,7 @@ export class TrashRepository {
@GenerateSql({ params: [DummyValue.UUID] })
async empty(userId: string): Promise<number> {
const { numUpdatedRows } = await this.db
.updateTable('assets')
.updateTable('asset')
.where('ownerId', '=', userId)
.where('status', '=', AssetStatus.TRASHED)
.set({ status: AssetStatus.DELETED })
@@ -42,7 +42,7 @@ export class TrashRepository {
}
const { numUpdatedRows } = await this.db
.updateTable('assets')
.updateTable('asset')
.where('status', '=', AssetStatus.TRASHED)
.where('id', 'in', ids)
.set({ status: AssetStatus.ACTIVE, deletedAt: null })

View File

@@ -34,12 +34,12 @@ export interface UserFindOptions {
withDeleted?: boolean;
}
const withMetadata = (eb: ExpressionBuilder<DB, 'users'>) => {
const withMetadata = (eb: ExpressionBuilder<DB, 'user'>) => {
return jsonArrayFrom(
eb
.selectFrom('user_metadata')
.select(['user_metadata.key', 'user_metadata.value'])
.whereRef('users.id', '=', 'user_metadata.userId'),
.whereRef('user.id', '=', 'user_metadata.userId'),
).as('metadata');
};
@@ -52,11 +52,11 @@ export class UserRepository {
options = options || {};
return this.db
.selectFrom('users')
.selectFrom('user')
.select(columns.userAdmin)
.select(withMetadata)
.where('users.id', '=', userId)
.$if(!options.withDeleted, (eb) => eb.where('users.deletedAt', 'is', null))
.where('user.id', '=', userId)
.$if(!options.withDeleted, (eb) => eb.where('user.deletedAt', 'is', null))
.executeTakeFirst();
}
@@ -71,21 +71,21 @@ export class UserRepository {
@GenerateSql()
getAdmin() {
return this.db
.selectFrom('users')
.selectFrom('user')
.select(columns.userAdmin)
.select(withMetadata)
.where('users.isAdmin', '=', true)
.where('users.deletedAt', 'is', null)
.where('user.isAdmin', '=', true)
.where('user.deletedAt', 'is', null)
.executeTakeFirst();
}
@GenerateSql()
async hasAdmin(): Promise<boolean> {
const admin = await this.db
.selectFrom('users')
.select('users.id')
.where('users.isAdmin', '=', true)
.where('users.deletedAt', 'is', null)
.selectFrom('user')
.select('user.id')
.where('user.isAdmin', '=', true)
.where('user.deletedAt', 'is', null)
.executeTakeFirst();
return !!admin;
@@ -94,59 +94,59 @@ export class UserRepository {
@GenerateSql({ params: [DummyValue.UUID] })
getForPinCode(id: string) {
return this.db
.selectFrom('users')
.select(['users.pinCode', 'users.password'])
.where('users.id', '=', id)
.where('users.deletedAt', 'is', null)
.selectFrom('user')
.select(['user.pinCode', 'user.password'])
.where('user.id', '=', id)
.where('user.deletedAt', 'is', null)
.executeTakeFirstOrThrow();
}
@GenerateSql({ params: [DummyValue.UUID] })
getForChangePassword(id: string) {
return this.db
.selectFrom('users')
.select(['users.id', 'users.password'])
.where('users.id', '=', id)
.where('users.deletedAt', 'is', null)
.selectFrom('user')
.select(['user.id', 'user.password'])
.where('user.id', '=', id)
.where('user.deletedAt', 'is', null)
.executeTakeFirstOrThrow();
}
@GenerateSql({ params: [DummyValue.EMAIL] })
getByEmail(email: string, options?: { withPassword?: boolean }) {
return this.db
.selectFrom('users')
.selectFrom('user')
.select(columns.userAdmin)
.select(withMetadata)
.$if(!!options?.withPassword, (eb) => eb.select('password'))
.where('email', '=', email)
.where('users.deletedAt', 'is', null)
.where('user.deletedAt', 'is', null)
.executeTakeFirst();
}
@GenerateSql({ params: [DummyValue.STRING] })
getByStorageLabel(storageLabel: string) {
return this.db
.selectFrom('users')
.selectFrom('user')
.select(columns.userAdmin)
.where('users.storageLabel', '=', storageLabel)
.where('users.deletedAt', 'is', null)
.where('user.storageLabel', '=', storageLabel)
.where('user.deletedAt', 'is', null)
.executeTakeFirst();
}
@GenerateSql({ params: [DummyValue.STRING] })
getByOAuthId(oauthId: string) {
return this.db
.selectFrom('users')
.selectFrom('user')
.select(columns.userAdmin)
.select(withMetadata)
.where('users.oauthId', '=', oauthId)
.where('users.deletedAt', 'is', null)
.where('user.oauthId', '=', oauthId)
.where('user.deletedAt', 'is', null)
.executeTakeFirst();
}
@GenerateSql({ params: [DateTime.now().minus({ years: 1 })] })
getDeletedAfter(target: DateTime) {
return this.db.selectFrom('users').select(['id']).where('users.deletedAt', '<', target.toJSDate()).execute();
return this.db.selectFrom('user').select(['id']).where('user.deletedAt', '<', target.toJSDate()).execute();
}
@GenerateSql(
@@ -155,18 +155,18 @@ export class UserRepository {
)
getList({ id, withDeleted }: UserListFilter = {}) {
return this.db
.selectFrom('users')
.selectFrom('user')
.select(columns.userAdmin)
.select(withMetadata)
.$if(!withDeleted, (eb) => eb.where('users.deletedAt', 'is', null))
.$if(!!id, (eb) => eb.where('users.id', '=', id!))
.$if(!withDeleted, (eb) => eb.where('user.deletedAt', 'is', null))
.$if(!!id, (eb) => eb.where('user.id', '=', id!))
.orderBy('createdAt', 'desc')
.execute();
}
async create(dto: Insertable<UserTable>) {
return this.db
.insertInto('users')
.insertInto('user')
.values(dto)
.returning(columns.userAdmin)
.returning(withMetadata)
@@ -175,10 +175,10 @@ export class UserRepository {
update(id: string, dto: Updateable<UserTable>) {
return this.db
.updateTable('users')
.updateTable('user')
.set(dto)
.where('users.id', '=', asUuid(id))
.where('users.deletedAt', 'is', null)
.where('user.id', '=', asUuid(id))
.where('user.deletedAt', 'is', null)
.returning(columns.userAdmin)
.returning(withMetadata)
.executeTakeFirstOrThrow();
@@ -186,9 +186,9 @@ export class UserRepository {
restore(id: string) {
return this.db
.updateTable('users')
.updateTable('user')
.set({ status: UserStatus.ACTIVE, deletedAt: null })
.where('users.id', '=', asUuid(id))
.where('user.id', '=', asUuid(id))
.returning(columns.userAdmin)
.returning(withMetadata)
.executeTakeFirstOrThrow();
@@ -213,24 +213,24 @@ export class UserRepository {
delete(user: { id: string }, hard?: boolean) {
return hard
? this.db.deleteFrom('users').where('id', '=', user.id).execute()
: this.db.updateTable('users').set({ deletedAt: new Date() }).where('id', '=', user.id).execute();
? this.db.deleteFrom('user').where('id', '=', user.id).execute()
: this.db.updateTable('user').set({ deletedAt: new Date() }).where('id', '=', user.id).execute();
}
@GenerateSql()
getUserStats() {
return this.db
.selectFrom('users')
.leftJoin('assets', (join) => join.onRef('assets.ownerId', '=', 'users.id').on('assets.deletedAt', 'is', null))
.leftJoin('exif', 'exif.assetId', 'assets.id')
.select(['users.id as userId', 'users.name as userName', 'users.quotaSizeInBytes'])
.selectFrom('user')
.leftJoin('asset', (join) => join.onRef('asset.ownerId', '=', 'user.id').on('asset.deletedAt', 'is', null))
.leftJoin('asset_exif', 'asset_exif.assetId', 'asset.id')
.select(['user.id as userId', 'user.name as userName', 'user.quotaSizeInBytes'])
.select((eb) => [
eb.fn
.countAll<number>()
.filterWhere((eb) =>
eb.and([
eb('assets.type', '=', sql.lit(AssetType.IMAGE)),
eb('assets.visibility', '!=', sql.lit(AssetVisibility.HIDDEN)),
eb('asset.type', '=', sql.lit(AssetType.IMAGE)),
eb('asset.visibility', '!=', sql.lit(AssetVisibility.HIDDEN)),
]),
)
.as('photos'),
@@ -238,20 +238,23 @@ export class UserRepository {
.countAll<number>()
.filterWhere((eb) =>
eb.and([
eb('assets.type', '=', sql.lit(AssetType.VIDEO)),
eb('assets.visibility', '!=', sql.lit(AssetVisibility.HIDDEN)),
eb('asset.type', '=', sql.lit(AssetType.VIDEO)),
eb('asset.visibility', '!=', sql.lit(AssetVisibility.HIDDEN)),
]),
)
.as('videos'),
eb.fn
.coalesce(eb.fn.sum<number>('exif.fileSizeInByte').filterWhere('assets.libraryId', 'is', null), eb.lit(0))
.coalesce(
eb.fn.sum<number>('asset_exif.fileSizeInByte').filterWhere('asset.libraryId', 'is', null),
eb.lit(0),
)
.as('usage'),
eb.fn
.coalesce(
eb.fn
.sum<number>('exif.fileSizeInByte')
.sum<number>('asset_exif.fileSizeInByte')
.filterWhere((eb) =>
eb.and([eb('assets.libraryId', 'is', null), eb('assets.type', '=', sql.lit(AssetType.IMAGE))]),
eb.and([eb('asset.libraryId', 'is', null), eb('asset.type', '=', sql.lit(AssetType.IMAGE))]),
),
eb.lit(0),
)
@@ -259,45 +262,45 @@ export class UserRepository {
eb.fn
.coalesce(
eb.fn
.sum<number>('exif.fileSizeInByte')
.sum<number>('asset_exif.fileSizeInByte')
.filterWhere((eb) =>
eb.and([eb('assets.libraryId', 'is', null), eb('assets.type', '=', sql.lit(AssetType.VIDEO))]),
eb.and([eb('asset.libraryId', 'is', null), eb('asset.type', '=', sql.lit(AssetType.VIDEO))]),
),
eb.lit(0),
)
.as('usageVideos'),
])
.groupBy('users.id')
.orderBy('users.createdAt', 'asc')
.groupBy('user.id')
.orderBy('user.createdAt', 'asc')
.execute();
}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.NUMBER] })
async updateUsage(id: string, delta: number): Promise<void> {
await this.db
.updateTable('users')
.updateTable('user')
.set({ quotaUsageInBytes: sql`"quotaUsageInBytes" + ${delta}`, updatedAt: new Date() })
.where('id', '=', asUuid(id))
.where('users.deletedAt', 'is', null)
.where('user.deletedAt', 'is', null)
.execute();
}
@GenerateSql({ params: [DummyValue.UUID] })
async syncUsage(id?: string) {
const query = this.db
.updateTable('users')
.updateTable('user')
.set({
quotaUsageInBytes: (eb) =>
eb
.selectFrom('assets')
.leftJoin('exif', 'exif.assetId', 'assets.id')
.select((eb) => eb.fn.coalesce(eb.fn.sum<number>('exif.fileSizeInByte'), eb.lit(0)).as('usage'))
.where('assets.libraryId', 'is', null)
.where('assets.ownerId', '=', eb.ref('users.id')),
.selectFrom('asset')
.leftJoin('asset_exif', 'asset_exif.assetId', 'asset.id')
.select((eb) => eb.fn.coalesce(eb.fn.sum<number>('asset_exif.fileSizeInByte'), eb.lit(0)).as('usage'))
.where('asset.libraryId', 'is', null)
.where('asset.ownerId', '=', eb.ref('user.id')),
updatedAt: new Date(),
})
.where('users.deletedAt', 'is', null)
.$if(id != undefined, (eb) => eb.where('users.id', '=', asUuid(id!)));
.where('user.deletedAt', 'is', null)
.$if(id != undefined, (eb) => eb.where('user.id', '=', asUuid(id!)));
await query.execute();
}

View File

@@ -11,8 +11,8 @@ export class ViewRepository {
@GenerateSql({ params: [DummyValue.UUID] })
async getUniqueOriginalPaths(userId: string) {
const results = await this.db
.selectFrom('assets')
.select((eb) => eb.fn<string>('substring', ['assets.originalPath', eb.val('^(.*/)[^/]*$')]).as('directoryPath'))
.selectFrom('asset')
.select((eb) => eb.fn<string>('substring', ['asset.originalPath', eb.val('^(.*/)[^/]*$')]).as('directoryPath'))
.distinct()
.where('ownerId', '=', asUuid(userId))
.where('visibility', '=', AssetVisibility.TIMELINE)
@@ -30,8 +30,8 @@ export class ViewRepository {
const normalizedPath = partialPath.replaceAll(/\/$/g, '');
return this.db
.selectFrom('assets')
.selectAll('assets')
.selectFrom('asset')
.selectAll('asset')
.$call(withExif)
.where('ownerId', '=', asUuid(userId))
.where('visibility', '=', AssetVisibility.TIMELINE)
@@ -42,7 +42,7 @@ export class ViewRepository {
.where('originalPath', 'like', `%${normalizedPath}/%`)
.where('originalPath', 'not like', `%${normalizedPath}/%/%`)
.orderBy(
(eb) => eb.fn('regexp_replace', ['assets.originalPath', eb.val('.*/(.+)'), eb.val(String.raw`\1`)]),
(eb) => eb.fn('regexp_replace', ['asset.originalPath', eb.val('.*/(.+)'), eb.val(String.raw`\1`)]),
'asc',
)
.execute();