mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
chore!: migrate album owner to album_user (#27467)
Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com> Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
@@ -154,23 +154,31 @@ describe('/albums', () => {
|
|||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: user1SharedLink,
|
albumName: user1SharedLink,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
|
||||||
|
]),
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: user1SharedEditorUser,
|
albumName: user1SharedEditorUser,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
|
||||||
|
]),
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: user1SharedViewerUser,
|
albumName: user1SharedViewerUser,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
|
||||||
|
]),
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user2.userId,
|
|
||||||
albumName: user2SharedUser,
|
albumName: user2SharedUser,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user2.userId }) },
|
||||||
|
]),
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
@@ -184,23 +192,31 @@ describe('/albums', () => {
|
|||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: user1SharedEditorUser,
|
albumName: user1SharedEditorUser,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
|
||||||
|
]),
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: user1SharedViewerUser,
|
albumName: user1SharedViewerUser,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
|
||||||
|
]),
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: user1SharedLink,
|
albumName: user1SharedLink,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
|
||||||
|
]),
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: user1NotShared,
|
albumName: user1NotShared,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
|
||||||
|
]),
|
||||||
shared: false,
|
shared: false,
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
@@ -216,23 +232,31 @@ describe('/albums', () => {
|
|||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: user1SharedEditorUser,
|
albumName: user1SharedEditorUser,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
|
||||||
|
]),
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: user1SharedViewerUser,
|
albumName: user1SharedViewerUser,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
|
||||||
|
]),
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: user1SharedLink,
|
albumName: user1SharedLink,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
|
||||||
|
]),
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user2.userId,
|
|
||||||
albumName: user2SharedUser,
|
albumName: user2SharedUser,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user2.userId }) },
|
||||||
|
]),
|
||||||
shared: true,
|
shared: true,
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
@@ -248,8 +272,10 @@ describe('/albums', () => {
|
|||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: user1NotShared,
|
albumName: user1NotShared,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) },
|
||||||
|
]),
|
||||||
shared: false,
|
shared: false,
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
@@ -286,13 +312,17 @@ describe('/albums', () => {
|
|||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user4.userId,
|
|
||||||
albumName: user4DeletedAsset,
|
albumName: user4DeletedAsset,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user4.userId }) },
|
||||||
|
]),
|
||||||
shared: false,
|
shared: false,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ownerId: user4.userId,
|
|
||||||
albumName: user4Empty,
|
albumName: user4Empty,
|
||||||
|
albumUsers: expect.arrayContaining([
|
||||||
|
{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user4.userId }) },
|
||||||
|
]),
|
||||||
shared: false,
|
shared: false,
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
@@ -362,8 +392,8 @@ describe('/albums', () => {
|
|||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual({
|
expect(body).toEqual(
|
||||||
...user2Albums[0],
|
expect.objectContaining({
|
||||||
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
||||||
assetCount: 1,
|
assetCount: 1,
|
||||||
lastModifiedAssetTimestamp: expect.any(String),
|
lastModifiedAssetTimestamp: expect.any(String),
|
||||||
@@ -371,7 +401,8 @@ describe('/albums', () => {
|
|||||||
startDate: expect.any(String),
|
startDate: expect.any(String),
|
||||||
albumUsers: expect.any(Array),
|
albumUsers: expect.any(Array),
|
||||||
shared: true,
|
shared: true,
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -397,15 +428,13 @@ describe('/albums', () => {
|
|||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
createdAt: expect.any(String),
|
createdAt: expect.any(String),
|
||||||
updatedAt: expect.any(String),
|
updatedAt: expect.any(String),
|
||||||
ownerId: user1.userId,
|
|
||||||
albumName: 'New album',
|
albumName: 'New album',
|
||||||
description: '',
|
description: '',
|
||||||
albumThumbnailAssetId: null,
|
albumThumbnailAssetId: null,
|
||||||
shared: false,
|
shared: false,
|
||||||
albumUsers: [],
|
albumUsers: [{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }],
|
||||||
hasSharedLink: false,
|
hasSharedLink: false,
|
||||||
assetCount: 0,
|
assetCount: 0,
|
||||||
owner: expect.objectContaining({ email: user1.userEmail }),
|
|
||||||
isActivityEnabled: true,
|
isActivityEnabled: true,
|
||||||
order: AssetOrder.Desc,
|
order: AssetOrder.Desc,
|
||||||
});
|
});
|
||||||
@@ -621,11 +650,11 @@ describe('/albums', () => {
|
|||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
albumUsers: [
|
albumUsers: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
user: expect.objectContaining({ id: user2.userId }),
|
user: expect.objectContaining({ id: user2.userId }),
|
||||||
}),
|
}),
|
||||||
],
|
]),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -637,7 +666,7 @@ describe('/albums', () => {
|
|||||||
.send({ albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }] });
|
.send({ albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }] });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest('Cannot be shared with owner'));
|
expect(body).toEqual(errorDto.badRequest('User already added'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not be able to add existing user to shared album', async () => {
|
it('should not be able to add existing user to shared album', async () => {
|
||||||
@@ -663,7 +692,7 @@ describe('/albums', () => {
|
|||||||
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }],
|
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }],
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(album.albumUsers[0].role).toEqual(AlbumUserRole.Viewer);
|
expect(album.albumUsers[1].role).toEqual(AlbumUserRole.Viewer);
|
||||||
|
|
||||||
const { status } = await request(app)
|
const { status } = await request(app)
|
||||||
.put(`/albums/${album.id}/user/${user2.userId}`)
|
.put(`/albums/${album.id}/user/${user2.userId}`)
|
||||||
@@ -678,7 +707,10 @@ describe('/albums', () => {
|
|||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
albumUsers: [expect.objectContaining({ role: AlbumUserRole.Editor })],
|
albumUsers: [
|
||||||
|
expect.objectContaining({ role: AlbumUserRole.Owner }),
|
||||||
|
expect.objectContaining({ role: AlbumUserRole.Editor }),
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -689,7 +721,7 @@ describe('/albums', () => {
|
|||||||
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }],
|
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }],
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(album.albumUsers[0].role).toEqual(AlbumUserRole.Viewer);
|
expect(album.albumUsers[1].role).toEqual(AlbumUserRole.Viewer);
|
||||||
|
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.put(`/albums/${album.id}/user/${user2.userId}`)
|
.put(`/albums/${album.id}/user/${user2.userId}`)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
AlbumUserRole,
|
||||||
AssetTypeEnum,
|
AssetTypeEnum,
|
||||||
AssetVisibility,
|
AssetVisibility,
|
||||||
UserAvatarColor,
|
UserAvatarColor,
|
||||||
@@ -420,9 +421,7 @@ export function getAlbum(
|
|||||||
albumThumbnailAssetId: album.thumbnailAssetId,
|
albumThumbnailAssetId: album.thumbnailAssetId,
|
||||||
createdAt: album.createdAt,
|
createdAt: album.createdAt,
|
||||||
updatedAt: album.updatedAt,
|
updatedAt: album.updatedAt,
|
||||||
ownerId: albumOwner.id,
|
albumUsers: [{ user: albumOwner, role: AlbumUserRole.Owner }],
|
||||||
owner: albumOwner,
|
|
||||||
albumUsers: [], // Empty array for non-shared album
|
|
||||||
shared: false,
|
shared: false,
|
||||||
hasSharedLink: false,
|
hasSharedLink: false,
|
||||||
isActivityEnabled: true,
|
isActivityEnabled: true,
|
||||||
|
|||||||
+3301
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@ enum AlbumUserRole {
|
|||||||
// do not change this order!
|
// do not change this order!
|
||||||
editor,
|
editor,
|
||||||
viewer,
|
viewer,
|
||||||
|
owner,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Model for an album stored in the server
|
// Model for an album stored in the server
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
|||||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
|
||||||
import 'package:immich_mobile/models/albums/album_search.model.dart';
|
import 'package:immich_mobile/models/albums/album_search.model.dart';
|
||||||
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
|
|
||||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||||
|
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
|
||||||
|
|
||||||
class RemoteAlbumService {
|
class RemoteAlbumService {
|
||||||
final DriftRemoteAlbumRepository _repository;
|
final DriftRemoteAlbumRepository _repository;
|
||||||
@@ -28,10 +28,6 @@ class RemoteAlbumService {
|
|||||||
return _repository.get(albumId);
|
return _repository.get(albumId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<RemoteAlbum?> getByName(String albumName, String ownerId) {
|
|
||||||
return _repository.getByName(albumName, ownerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<RemoteAlbum>> sortAlbums(
|
Future<List<RemoteAlbum>> sortAlbums(
|
||||||
List<RemoteAlbum> albums,
|
List<RemoteAlbum> albums,
|
||||||
AlbumSortMode sortMode, {
|
AlbumSortMode sortMode, {
|
||||||
@@ -86,8 +82,18 @@ class RemoteAlbumService {
|
|||||||
return filtered;
|
return filtered;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<RemoteAlbum> createAlbum({required String title, required List<String> assetIds, String? description}) async {
|
Future<RemoteAlbum> createAlbum({
|
||||||
final album = await _albumApiRepository.createDriftAlbum(title, description: description, assetIds: assetIds);
|
required String title,
|
||||||
|
required UserDto owner,
|
||||||
|
required List<String> assetIds,
|
||||||
|
String? description,
|
||||||
|
}) async {
|
||||||
|
final album = await _albumApiRepository.createDriftAlbum(
|
||||||
|
title,
|
||||||
|
owner,
|
||||||
|
description: description,
|
||||||
|
assetIds: assetIds,
|
||||||
|
);
|
||||||
await _repository.create(album, assetIds);
|
await _repository.create(album, assetIds);
|
||||||
|
|
||||||
return album;
|
return album;
|
||||||
@@ -101,8 +107,10 @@ class RemoteAlbumService {
|
|||||||
bool? isActivityEnabled,
|
bool? isActivityEnabled,
|
||||||
AlbumAssetOrder? order,
|
AlbumAssetOrder? order,
|
||||||
}) async {
|
}) async {
|
||||||
|
final owner = await _repository.getOwner(albumId);
|
||||||
final updatedAlbum = await _albumApiRepository.updateAlbum(
|
final updatedAlbum = await _albumApiRepository.updateAlbum(
|
||||||
albumId,
|
albumId,
|
||||||
|
owner,
|
||||||
name: name,
|
name: name,
|
||||||
description: description,
|
description: description,
|
||||||
thumbnailAssetId: thumbnailAssetId,
|
thumbnailAssetId: thumbnailAssetId,
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/store.service.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/store.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
|
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
|
||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
@@ -12,6 +15,7 @@ final syncLinkedAlbumServiceProvider = Provider(
|
|||||||
ref.watch(localAlbumRepository),
|
ref.watch(localAlbumRepository),
|
||||||
ref.watch(remoteAlbumRepository),
|
ref.watch(remoteAlbumRepository),
|
||||||
ref.watch(driftAlbumApiRepositoryProvider),
|
ref.watch(driftAlbumApiRepositoryProvider),
|
||||||
|
ref.watch(storeServiceProvider),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -19,8 +23,14 @@ class SyncLinkedAlbumService {
|
|||||||
final DriftLocalAlbumRepository _localAlbumRepository;
|
final DriftLocalAlbumRepository _localAlbumRepository;
|
||||||
final DriftRemoteAlbumRepository _remoteAlbumRepository;
|
final DriftRemoteAlbumRepository _remoteAlbumRepository;
|
||||||
final DriftAlbumApiRepository _albumApiRepository;
|
final DriftAlbumApiRepository _albumApiRepository;
|
||||||
|
final StoreService _storeService;
|
||||||
|
|
||||||
SyncLinkedAlbumService(this._localAlbumRepository, this._remoteAlbumRepository, this._albumApiRepository);
|
SyncLinkedAlbumService(
|
||||||
|
this._localAlbumRepository,
|
||||||
|
this._remoteAlbumRepository,
|
||||||
|
this._albumApiRepository,
|
||||||
|
this._storeService,
|
||||||
|
);
|
||||||
|
|
||||||
final _log = Logger("SyncLinkedAlbumService");
|
final _log = Logger("SyncLinkedAlbumService");
|
||||||
|
|
||||||
@@ -103,7 +113,11 @@ class SyncLinkedAlbumService {
|
|||||||
/// Creates a new remote album and links it to the local album
|
/// Creates a new remote album and links it to the local album
|
||||||
Future<void> _createAndLinkNewRemoteAlbum(LocalAlbum localAlbum) async {
|
Future<void> _createAndLinkNewRemoteAlbum(LocalAlbum localAlbum) async {
|
||||||
dPrint(() => "Creating new remote album for local album: ${localAlbum.name}");
|
dPrint(() => "Creating new remote album for local album: ${localAlbum.name}");
|
||||||
final newRemoteAlbum = await _albumApiRepository.createDriftAlbum(localAlbum.name, assetIds: []);
|
final newRemoteAlbum = await _albumApiRepository.createDriftAlbum(
|
||||||
|
localAlbum.name,
|
||||||
|
_storeService.get(StoreKey.currentUser),
|
||||||
|
assetIds: [],
|
||||||
|
);
|
||||||
await _remoteAlbumRepository.create(newRemoteAlbum, []);
|
await _remoteAlbumRepository.create(newRemoteAlbum, []);
|
||||||
return _localAlbumRepository.linkRemoteAlbum(localAlbum.id, newRemoteAlbum.id);
|
return _localAlbumRepository.linkRemoteAlbum(localAlbum.id, newRemoteAlbum.id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -225,6 +225,8 @@ class SyncStreamService {
|
|||||||
return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'partner backfill');
|
return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'partner backfill');
|
||||||
case SyncEntityType.albumV1:
|
case SyncEntityType.albumV1:
|
||||||
return _syncStreamRepository.updateAlbumsV1(data.cast());
|
return _syncStreamRepository.updateAlbumsV1(data.cast());
|
||||||
|
case SyncEntityType.albumV2:
|
||||||
|
return _syncStreamRepository.updateAlbumsV2(data.cast());
|
||||||
case SyncEntityType.albumDeleteV1:
|
case SyncEntityType.albumDeleteV1:
|
||||||
return _syncStreamRepository.deleteAlbumsV1(data.cast());
|
return _syncStreamRepository.deleteAlbumsV1(data.cast());
|
||||||
case SyncEntityType.albumUserV1:
|
case SyncEntityType.albumUserV1:
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||||
|
|
||||||
@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_remote_album_owner_id ON remote_album_entity (owner_id)')
|
|
||||||
class RemoteAlbumEntity extends Table with DriftDefaultsMixin {
|
class RemoteAlbumEntity extends Table with DriftDefaultsMixin {
|
||||||
const RemoteAlbumEntity();
|
const RemoteAlbumEntity();
|
||||||
|
|
||||||
@@ -18,8 +16,6 @@ class RemoteAlbumEntity extends Table with DriftDefaultsMixin {
|
|||||||
|
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
|
||||||
TextColumn get ownerId => text().references(UserEntity, #id, onDelete: KeyAction.cascade)();
|
|
||||||
|
|
||||||
TextColumn get thumbnailAssetId =>
|
TextColumn get thumbnailAssetId =>
|
||||||
text().references(RemoteAssetEntity, #id, onDelete: KeyAction.setNull).nullable()();
|
text().references(RemoteAssetEntity, #id, onDelete: KeyAction.setNull).nullable()();
|
||||||
|
|
||||||
|
|||||||
+25
-215
@@ -7,11 +7,9 @@ import 'package:immich_mobile/domain/models/album/album.model.dart' as i2;
|
|||||||
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart'
|
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart'
|
||||||
as i3;
|
as i3;
|
||||||
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4;
|
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4;
|
||||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'
|
||||||
as i5;
|
as i5;
|
||||||
import 'package:drift/internal/modular.dart' as i6;
|
import 'package:drift/internal/modular.dart' as i6;
|
||||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'
|
|
||||||
as i7;
|
|
||||||
|
|
||||||
typedef $$RemoteAlbumEntityTableCreateCompanionBuilder =
|
typedef $$RemoteAlbumEntityTableCreateCompanionBuilder =
|
||||||
i1.RemoteAlbumEntityCompanion Function({
|
i1.RemoteAlbumEntityCompanion Function({
|
||||||
@@ -20,7 +18,6 @@ typedef $$RemoteAlbumEntityTableCreateCompanionBuilder =
|
|||||||
i0.Value<String> description,
|
i0.Value<String> description,
|
||||||
i0.Value<DateTime> createdAt,
|
i0.Value<DateTime> createdAt,
|
||||||
i0.Value<DateTime> updatedAt,
|
i0.Value<DateTime> updatedAt,
|
||||||
required String ownerId,
|
|
||||||
i0.Value<String?> thumbnailAssetId,
|
i0.Value<String?> thumbnailAssetId,
|
||||||
i0.Value<bool> isActivityEnabled,
|
i0.Value<bool> isActivityEnabled,
|
||||||
required i2.AlbumAssetOrder order,
|
required i2.AlbumAssetOrder order,
|
||||||
@@ -32,7 +29,6 @@ typedef $$RemoteAlbumEntityTableUpdateCompanionBuilder =
|
|||||||
i0.Value<String> description,
|
i0.Value<String> description,
|
||||||
i0.Value<DateTime> createdAt,
|
i0.Value<DateTime> createdAt,
|
||||||
i0.Value<DateTime> updatedAt,
|
i0.Value<DateTime> updatedAt,
|
||||||
i0.Value<String> ownerId,
|
|
||||||
i0.Value<String?> thumbnailAssetId,
|
i0.Value<String?> thumbnailAssetId,
|
||||||
i0.Value<bool> isActivityEnabled,
|
i0.Value<bool> isActivityEnabled,
|
||||||
i0.Value<i2.AlbumAssetOrder> order,
|
i0.Value<i2.AlbumAssetOrder> order,
|
||||||
@@ -51,42 +47,10 @@ final class $$RemoteAlbumEntityTableReferences
|
|||||||
super.$_typedResult,
|
super.$_typedResult,
|
||||||
);
|
);
|
||||||
|
|
||||||
static i5.$UserEntityTable _ownerIdTable(i0.GeneratedDatabase db) =>
|
static i5.$RemoteAssetEntityTable _thumbnailAssetIdTable(
|
||||||
i6.ReadDatabaseContainer(db)
|
|
||||||
.resultSet<i5.$UserEntityTable>('user_entity')
|
|
||||||
.createAlias(
|
|
||||||
i0.$_aliasNameGenerator(
|
|
||||||
i6.ReadDatabaseContainer(db)
|
|
||||||
.resultSet<i1.$RemoteAlbumEntityTable>('remote_album_entity')
|
|
||||||
.ownerId,
|
|
||||||
i6.ReadDatabaseContainer(
|
|
||||||
db,
|
|
||||||
).resultSet<i5.$UserEntityTable>('user_entity').id,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
i5.$$UserEntityTableProcessedTableManager get ownerId {
|
|
||||||
final $_column = $_itemColumn<String>('owner_id')!;
|
|
||||||
|
|
||||||
final manager = i5
|
|
||||||
.$$UserEntityTableTableManager(
|
|
||||||
$_db,
|
|
||||||
i6.ReadDatabaseContainer(
|
|
||||||
$_db,
|
|
||||||
).resultSet<i5.$UserEntityTable>('user_entity'),
|
|
||||||
)
|
|
||||||
.filter((f) => f.id.sqlEquals($_column));
|
|
||||||
final item = $_typedResult.readTableOrNull(_ownerIdTable($_db));
|
|
||||||
if (item == null) return manager;
|
|
||||||
return i0.ProcessedTableManager(
|
|
||||||
manager.$state.copyWith(prefetchedData: [item]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static i7.$RemoteAssetEntityTable _thumbnailAssetIdTable(
|
|
||||||
i0.GeneratedDatabase db,
|
i0.GeneratedDatabase db,
|
||||||
) => i6.ReadDatabaseContainer(db)
|
) => i6.ReadDatabaseContainer(db)
|
||||||
.resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity')
|
.resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity')
|
||||||
.createAlias(
|
.createAlias(
|
||||||
i0.$_aliasNameGenerator(
|
i0.$_aliasNameGenerator(
|
||||||
i6.ReadDatabaseContainer(db)
|
i6.ReadDatabaseContainer(db)
|
||||||
@@ -94,19 +58,19 @@ final class $$RemoteAlbumEntityTableReferences
|
|||||||
.thumbnailAssetId,
|
.thumbnailAssetId,
|
||||||
i6.ReadDatabaseContainer(
|
i6.ReadDatabaseContainer(
|
||||||
db,
|
db,
|
||||||
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity').id,
|
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity').id,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
i7.$$RemoteAssetEntityTableProcessedTableManager? get thumbnailAssetId {
|
i5.$$RemoteAssetEntityTableProcessedTableManager? get thumbnailAssetId {
|
||||||
final $_column = $_itemColumn<String>('thumbnail_asset_id');
|
final $_column = $_itemColumn<String>('thumbnail_asset_id');
|
||||||
if ($_column == null) return null;
|
if ($_column == null) return null;
|
||||||
final manager = i7
|
final manager = i5
|
||||||
.$$RemoteAssetEntityTableTableManager(
|
.$$RemoteAssetEntityTableTableManager(
|
||||||
$_db,
|
$_db,
|
||||||
i6.ReadDatabaseContainer(
|
i6.ReadDatabaseContainer(
|
||||||
$_db,
|
$_db,
|
||||||
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
|
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||||
)
|
)
|
||||||
.filter((f) => f.id.sqlEquals($_column));
|
.filter((f) => f.id.sqlEquals($_column));
|
||||||
final item = $_typedResult.readTableOrNull(_thumbnailAssetIdTable($_db));
|
final item = $_typedResult.readTableOrNull(_thumbnailAssetIdTable($_db));
|
||||||
@@ -162,51 +126,24 @@ class $$RemoteAlbumEntityTableFilterComposer
|
|||||||
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
|
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
i5.$$UserEntityTableFilterComposer get ownerId {
|
i5.$$RemoteAssetEntityTableFilterComposer get thumbnailAssetId {
|
||||||
final i5.$$UserEntityTableFilterComposer composer = $composerBuilder(
|
final i5.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder(
|
||||||
composer: this,
|
|
||||||
getCurrentColumn: (t) => t.ownerId,
|
|
||||||
referencedTable: i6.ReadDatabaseContainer(
|
|
||||||
$db,
|
|
||||||
).resultSet<i5.$UserEntityTable>('user_entity'),
|
|
||||||
getReferencedColumn: (t) => t.id,
|
|
||||||
builder:
|
|
||||||
(
|
|
||||||
joinBuilder, {
|
|
||||||
$addJoinBuilderToRootComposer,
|
|
||||||
$removeJoinBuilderFromRootComposer,
|
|
||||||
}) => i5.$$UserEntityTableFilterComposer(
|
|
||||||
$db: $db,
|
|
||||||
$table: i6.ReadDatabaseContainer(
|
|
||||||
$db,
|
|
||||||
).resultSet<i5.$UserEntityTable>('user_entity'),
|
|
||||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
|
||||||
joinBuilder: joinBuilder,
|
|
||||||
$removeJoinBuilderFromRootComposer:
|
|
||||||
$removeJoinBuilderFromRootComposer,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return composer;
|
|
||||||
}
|
|
||||||
|
|
||||||
i7.$$RemoteAssetEntityTableFilterComposer get thumbnailAssetId {
|
|
||||||
final i7.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder(
|
|
||||||
composer: this,
|
composer: this,
|
||||||
getCurrentColumn: (t) => t.thumbnailAssetId,
|
getCurrentColumn: (t) => t.thumbnailAssetId,
|
||||||
referencedTable: i6.ReadDatabaseContainer(
|
referencedTable: i6.ReadDatabaseContainer(
|
||||||
$db,
|
$db,
|
||||||
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
|
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||||
getReferencedColumn: (t) => t.id,
|
getReferencedColumn: (t) => t.id,
|
||||||
builder:
|
builder:
|
||||||
(
|
(
|
||||||
joinBuilder, {
|
joinBuilder, {
|
||||||
$addJoinBuilderToRootComposer,
|
$addJoinBuilderToRootComposer,
|
||||||
$removeJoinBuilderFromRootComposer,
|
$removeJoinBuilderFromRootComposer,
|
||||||
}) => i7.$$RemoteAssetEntityTableFilterComposer(
|
}) => i5.$$RemoteAssetEntityTableFilterComposer(
|
||||||
$db: $db,
|
$db: $db,
|
||||||
$table: i6.ReadDatabaseContainer(
|
$table: i6.ReadDatabaseContainer(
|
||||||
$db,
|
$db,
|
||||||
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
|
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||||
joinBuilder: joinBuilder,
|
joinBuilder: joinBuilder,
|
||||||
$removeJoinBuilderFromRootComposer:
|
$removeJoinBuilderFromRootComposer:
|
||||||
@@ -261,52 +198,25 @@ class $$RemoteAlbumEntityTableOrderingComposer
|
|||||||
builder: (column) => i0.ColumnOrderings(column),
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
i5.$$UserEntityTableOrderingComposer get ownerId {
|
i5.$$RemoteAssetEntityTableOrderingComposer get thumbnailAssetId {
|
||||||
final i5.$$UserEntityTableOrderingComposer composer = $composerBuilder(
|
final i5.$$RemoteAssetEntityTableOrderingComposer composer =
|
||||||
composer: this,
|
|
||||||
getCurrentColumn: (t) => t.ownerId,
|
|
||||||
referencedTable: i6.ReadDatabaseContainer(
|
|
||||||
$db,
|
|
||||||
).resultSet<i5.$UserEntityTable>('user_entity'),
|
|
||||||
getReferencedColumn: (t) => t.id,
|
|
||||||
builder:
|
|
||||||
(
|
|
||||||
joinBuilder, {
|
|
||||||
$addJoinBuilderToRootComposer,
|
|
||||||
$removeJoinBuilderFromRootComposer,
|
|
||||||
}) => i5.$$UserEntityTableOrderingComposer(
|
|
||||||
$db: $db,
|
|
||||||
$table: i6.ReadDatabaseContainer(
|
|
||||||
$db,
|
|
||||||
).resultSet<i5.$UserEntityTable>('user_entity'),
|
|
||||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
|
||||||
joinBuilder: joinBuilder,
|
|
||||||
$removeJoinBuilderFromRootComposer:
|
|
||||||
$removeJoinBuilderFromRootComposer,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return composer;
|
|
||||||
}
|
|
||||||
|
|
||||||
i7.$$RemoteAssetEntityTableOrderingComposer get thumbnailAssetId {
|
|
||||||
final i7.$$RemoteAssetEntityTableOrderingComposer composer =
|
|
||||||
$composerBuilder(
|
$composerBuilder(
|
||||||
composer: this,
|
composer: this,
|
||||||
getCurrentColumn: (t) => t.thumbnailAssetId,
|
getCurrentColumn: (t) => t.thumbnailAssetId,
|
||||||
referencedTable: i6.ReadDatabaseContainer(
|
referencedTable: i6.ReadDatabaseContainer(
|
||||||
$db,
|
$db,
|
||||||
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
|
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||||
getReferencedColumn: (t) => t.id,
|
getReferencedColumn: (t) => t.id,
|
||||||
builder:
|
builder:
|
||||||
(
|
(
|
||||||
joinBuilder, {
|
joinBuilder, {
|
||||||
$addJoinBuilderToRootComposer,
|
$addJoinBuilderToRootComposer,
|
||||||
$removeJoinBuilderFromRootComposer,
|
$removeJoinBuilderFromRootComposer,
|
||||||
}) => i7.$$RemoteAssetEntityTableOrderingComposer(
|
}) => i5.$$RemoteAssetEntityTableOrderingComposer(
|
||||||
$db: $db,
|
$db: $db,
|
||||||
$table: i6.ReadDatabaseContainer(
|
$table: i6.ReadDatabaseContainer(
|
||||||
$db,
|
$db,
|
||||||
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
|
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||||
joinBuilder: joinBuilder,
|
joinBuilder: joinBuilder,
|
||||||
$removeJoinBuilderFromRootComposer:
|
$removeJoinBuilderFromRootComposer:
|
||||||
@@ -351,52 +261,25 @@ class $$RemoteAlbumEntityTableAnnotationComposer
|
|||||||
i0.GeneratedColumnWithTypeConverter<i2.AlbumAssetOrder, int> get order =>
|
i0.GeneratedColumnWithTypeConverter<i2.AlbumAssetOrder, int> get order =>
|
||||||
$composableBuilder(column: $table.order, builder: (column) => column);
|
$composableBuilder(column: $table.order, builder: (column) => column);
|
||||||
|
|
||||||
i5.$$UserEntityTableAnnotationComposer get ownerId {
|
i5.$$RemoteAssetEntityTableAnnotationComposer get thumbnailAssetId {
|
||||||
final i5.$$UserEntityTableAnnotationComposer composer = $composerBuilder(
|
final i5.$$RemoteAssetEntityTableAnnotationComposer composer =
|
||||||
composer: this,
|
|
||||||
getCurrentColumn: (t) => t.ownerId,
|
|
||||||
referencedTable: i6.ReadDatabaseContainer(
|
|
||||||
$db,
|
|
||||||
).resultSet<i5.$UserEntityTable>('user_entity'),
|
|
||||||
getReferencedColumn: (t) => t.id,
|
|
||||||
builder:
|
|
||||||
(
|
|
||||||
joinBuilder, {
|
|
||||||
$addJoinBuilderToRootComposer,
|
|
||||||
$removeJoinBuilderFromRootComposer,
|
|
||||||
}) => i5.$$UserEntityTableAnnotationComposer(
|
|
||||||
$db: $db,
|
|
||||||
$table: i6.ReadDatabaseContainer(
|
|
||||||
$db,
|
|
||||||
).resultSet<i5.$UserEntityTable>('user_entity'),
|
|
||||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
|
||||||
joinBuilder: joinBuilder,
|
|
||||||
$removeJoinBuilderFromRootComposer:
|
|
||||||
$removeJoinBuilderFromRootComposer,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return composer;
|
|
||||||
}
|
|
||||||
|
|
||||||
i7.$$RemoteAssetEntityTableAnnotationComposer get thumbnailAssetId {
|
|
||||||
final i7.$$RemoteAssetEntityTableAnnotationComposer composer =
|
|
||||||
$composerBuilder(
|
$composerBuilder(
|
||||||
composer: this,
|
composer: this,
|
||||||
getCurrentColumn: (t) => t.thumbnailAssetId,
|
getCurrentColumn: (t) => t.thumbnailAssetId,
|
||||||
referencedTable: i6.ReadDatabaseContainer(
|
referencedTable: i6.ReadDatabaseContainer(
|
||||||
$db,
|
$db,
|
||||||
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
|
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||||
getReferencedColumn: (t) => t.id,
|
getReferencedColumn: (t) => t.id,
|
||||||
builder:
|
builder:
|
||||||
(
|
(
|
||||||
joinBuilder, {
|
joinBuilder, {
|
||||||
$addJoinBuilderToRootComposer,
|
$addJoinBuilderToRootComposer,
|
||||||
$removeJoinBuilderFromRootComposer,
|
$removeJoinBuilderFromRootComposer,
|
||||||
}) => i7.$$RemoteAssetEntityTableAnnotationComposer(
|
}) => i5.$$RemoteAssetEntityTableAnnotationComposer(
|
||||||
$db: $db,
|
$db: $db,
|
||||||
$table: i6.ReadDatabaseContainer(
|
$table: i6.ReadDatabaseContainer(
|
||||||
$db,
|
$db,
|
||||||
).resultSet<i7.$RemoteAssetEntityTable>('remote_asset_entity'),
|
).resultSet<i5.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||||
joinBuilder: joinBuilder,
|
joinBuilder: joinBuilder,
|
||||||
$removeJoinBuilderFromRootComposer:
|
$removeJoinBuilderFromRootComposer:
|
||||||
@@ -420,7 +303,7 @@ class $$RemoteAlbumEntityTableTableManager
|
|||||||
$$RemoteAlbumEntityTableUpdateCompanionBuilder,
|
$$RemoteAlbumEntityTableUpdateCompanionBuilder,
|
||||||
(i1.RemoteAlbumEntityData, i1.$$RemoteAlbumEntityTableReferences),
|
(i1.RemoteAlbumEntityData, i1.$$RemoteAlbumEntityTableReferences),
|
||||||
i1.RemoteAlbumEntityData,
|
i1.RemoteAlbumEntityData,
|
||||||
i0.PrefetchHooks Function({bool ownerId, bool thumbnailAssetId})
|
i0.PrefetchHooks Function({bool thumbnailAssetId})
|
||||||
> {
|
> {
|
||||||
$$RemoteAlbumEntityTableTableManager(
|
$$RemoteAlbumEntityTableTableManager(
|
||||||
i0.GeneratedDatabase db,
|
i0.GeneratedDatabase db,
|
||||||
@@ -445,7 +328,6 @@ class $$RemoteAlbumEntityTableTableManager
|
|||||||
i0.Value<String> description = const i0.Value.absent(),
|
i0.Value<String> description = const i0.Value.absent(),
|
||||||
i0.Value<DateTime> createdAt = const i0.Value.absent(),
|
i0.Value<DateTime> createdAt = const i0.Value.absent(),
|
||||||
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||||
i0.Value<String> ownerId = const i0.Value.absent(),
|
|
||||||
i0.Value<String?> thumbnailAssetId = const i0.Value.absent(),
|
i0.Value<String?> thumbnailAssetId = const i0.Value.absent(),
|
||||||
i0.Value<bool> isActivityEnabled = const i0.Value.absent(),
|
i0.Value<bool> isActivityEnabled = const i0.Value.absent(),
|
||||||
i0.Value<i2.AlbumAssetOrder> order = const i0.Value.absent(),
|
i0.Value<i2.AlbumAssetOrder> order = const i0.Value.absent(),
|
||||||
@@ -455,7 +337,6 @@ class $$RemoteAlbumEntityTableTableManager
|
|||||||
description: description,
|
description: description,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
ownerId: ownerId,
|
|
||||||
thumbnailAssetId: thumbnailAssetId,
|
thumbnailAssetId: thumbnailAssetId,
|
||||||
isActivityEnabled: isActivityEnabled,
|
isActivityEnabled: isActivityEnabled,
|
||||||
order: order,
|
order: order,
|
||||||
@@ -467,7 +348,6 @@ class $$RemoteAlbumEntityTableTableManager
|
|||||||
i0.Value<String> description = const i0.Value.absent(),
|
i0.Value<String> description = const i0.Value.absent(),
|
||||||
i0.Value<DateTime> createdAt = const i0.Value.absent(),
|
i0.Value<DateTime> createdAt = const i0.Value.absent(),
|
||||||
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
||||||
required String ownerId,
|
|
||||||
i0.Value<String?> thumbnailAssetId = const i0.Value.absent(),
|
i0.Value<String?> thumbnailAssetId = const i0.Value.absent(),
|
||||||
i0.Value<bool> isActivityEnabled = const i0.Value.absent(),
|
i0.Value<bool> isActivityEnabled = const i0.Value.absent(),
|
||||||
required i2.AlbumAssetOrder order,
|
required i2.AlbumAssetOrder order,
|
||||||
@@ -477,7 +357,6 @@ class $$RemoteAlbumEntityTableTableManager
|
|||||||
description: description,
|
description: description,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
ownerId: ownerId,
|
|
||||||
thumbnailAssetId: thumbnailAssetId,
|
thumbnailAssetId: thumbnailAssetId,
|
||||||
isActivityEnabled: isActivityEnabled,
|
isActivityEnabled: isActivityEnabled,
|
||||||
order: order,
|
order: order,
|
||||||
@@ -490,7 +369,7 @@ class $$RemoteAlbumEntityTableTableManager
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
prefetchHooksCallback: ({ownerId = false, thumbnailAssetId = false}) {
|
prefetchHooksCallback: ({thumbnailAssetId = false}) {
|
||||||
return i0.PrefetchHooks(
|
return i0.PrefetchHooks(
|
||||||
db: db,
|
db: db,
|
||||||
explicitlyWatchedTables: [],
|
explicitlyWatchedTables: [],
|
||||||
@@ -510,21 +389,6 @@ class $$RemoteAlbumEntityTableTableManager
|
|||||||
dynamic
|
dynamic
|
||||||
>
|
>
|
||||||
>(state) {
|
>(state) {
|
||||||
if (ownerId) {
|
|
||||||
state =
|
|
||||||
state.withJoin(
|
|
||||||
currentTable: table,
|
|
||||||
currentColumn: table.ownerId,
|
|
||||||
referencedTable: i1
|
|
||||||
.$$RemoteAlbumEntityTableReferences
|
|
||||||
._ownerIdTable(db),
|
|
||||||
referencedColumn: i1
|
|
||||||
.$$RemoteAlbumEntityTableReferences
|
|
||||||
._ownerIdTable(db)
|
|
||||||
.id,
|
|
||||||
)
|
|
||||||
as T;
|
|
||||||
}
|
|
||||||
if (thumbnailAssetId) {
|
if (thumbnailAssetId) {
|
||||||
state =
|
state =
|
||||||
state.withJoin(
|
state.withJoin(
|
||||||
@@ -564,12 +428,8 @@ typedef $$RemoteAlbumEntityTableProcessedTableManager =
|
|||||||
$$RemoteAlbumEntityTableUpdateCompanionBuilder,
|
$$RemoteAlbumEntityTableUpdateCompanionBuilder,
|
||||||
(i1.RemoteAlbumEntityData, i1.$$RemoteAlbumEntityTableReferences),
|
(i1.RemoteAlbumEntityData, i1.$$RemoteAlbumEntityTableReferences),
|
||||||
i1.RemoteAlbumEntityData,
|
i1.RemoteAlbumEntityData,
|
||||||
i0.PrefetchHooks Function({bool ownerId, bool thumbnailAssetId})
|
i0.PrefetchHooks Function({bool thumbnailAssetId})
|
||||||
>;
|
>;
|
||||||
i0.Index get idxRemoteAlbumOwnerId => i0.Index(
|
|
||||||
'idx_remote_album_owner_id',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_remote_album_owner_id ON remote_album_entity (owner_id)',
|
|
||||||
);
|
|
||||||
|
|
||||||
class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity
|
class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity
|
||||||
with i0.TableInfo<$RemoteAlbumEntityTable, i1.RemoteAlbumEntityData> {
|
with i0.TableInfo<$RemoteAlbumEntityTable, i1.RemoteAlbumEntityData> {
|
||||||
@@ -636,20 +496,6 @@ class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity
|
|||||||
requiredDuringInsert: false,
|
requiredDuringInsert: false,
|
||||||
defaultValue: i4.currentDateAndTime,
|
defaultValue: i4.currentDateAndTime,
|
||||||
);
|
);
|
||||||
static const i0.VerificationMeta _ownerIdMeta = const i0.VerificationMeta(
|
|
||||||
'ownerId',
|
|
||||||
);
|
|
||||||
@override
|
|
||||||
late final i0.GeneratedColumn<String> ownerId = i0.GeneratedColumn<String>(
|
|
||||||
'owner_id',
|
|
||||||
aliasedName,
|
|
||||||
false,
|
|
||||||
type: i0.DriftSqlType.string,
|
|
||||||
requiredDuringInsert: true,
|
|
||||||
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
|
||||||
'REFERENCES user_entity (id) ON DELETE CASCADE',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
static const i0.VerificationMeta _thumbnailAssetIdMeta =
|
static const i0.VerificationMeta _thumbnailAssetIdMeta =
|
||||||
const i0.VerificationMeta('thumbnailAssetId');
|
const i0.VerificationMeta('thumbnailAssetId');
|
||||||
@override
|
@override
|
||||||
@@ -698,7 +544,6 @@ class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity
|
|||||||
description,
|
description,
|
||||||
createdAt,
|
createdAt,
|
||||||
updatedAt,
|
updatedAt,
|
||||||
ownerId,
|
|
||||||
thumbnailAssetId,
|
thumbnailAssetId,
|
||||||
isActivityEnabled,
|
isActivityEnabled,
|
||||||
order,
|
order,
|
||||||
@@ -749,14 +594,6 @@ class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity
|
|||||||
updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta),
|
updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (data.containsKey('owner_id')) {
|
|
||||||
context.handle(
|
|
||||||
_ownerIdMeta,
|
|
||||||
ownerId.isAcceptableOrUnknown(data['owner_id']!, _ownerIdMeta),
|
|
||||||
);
|
|
||||||
} else if (isInserting) {
|
|
||||||
context.missing(_ownerIdMeta);
|
|
||||||
}
|
|
||||||
if (data.containsKey('thumbnail_asset_id')) {
|
if (data.containsKey('thumbnail_asset_id')) {
|
||||||
context.handle(
|
context.handle(
|
||||||
_thumbnailAssetIdMeta,
|
_thumbnailAssetIdMeta,
|
||||||
@@ -807,10 +644,6 @@ class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity
|
|||||||
i0.DriftSqlType.dateTime,
|
i0.DriftSqlType.dateTime,
|
||||||
data['${effectivePrefix}updated_at'],
|
data['${effectivePrefix}updated_at'],
|
||||||
)!,
|
)!,
|
||||||
ownerId: attachedDatabase.typeMapping.read(
|
|
||||||
i0.DriftSqlType.string,
|
|
||||||
data['${effectivePrefix}owner_id'],
|
|
||||||
)!,
|
|
||||||
thumbnailAssetId: attachedDatabase.typeMapping.read(
|
thumbnailAssetId: attachedDatabase.typeMapping.read(
|
||||||
i0.DriftSqlType.string,
|
i0.DriftSqlType.string,
|
||||||
data['${effectivePrefix}thumbnail_asset_id'],
|
data['${effectivePrefix}thumbnail_asset_id'],
|
||||||
@@ -850,7 +683,6 @@ class RemoteAlbumEntityData extends i0.DataClass
|
|||||||
final String description;
|
final String description;
|
||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
final DateTime updatedAt;
|
final DateTime updatedAt;
|
||||||
final String ownerId;
|
|
||||||
final String? thumbnailAssetId;
|
final String? thumbnailAssetId;
|
||||||
final bool isActivityEnabled;
|
final bool isActivityEnabled;
|
||||||
final i2.AlbumAssetOrder order;
|
final i2.AlbumAssetOrder order;
|
||||||
@@ -860,7 +692,6 @@ class RemoteAlbumEntityData extends i0.DataClass
|
|||||||
required this.description,
|
required this.description,
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
required this.ownerId,
|
|
||||||
this.thumbnailAssetId,
|
this.thumbnailAssetId,
|
||||||
required this.isActivityEnabled,
|
required this.isActivityEnabled,
|
||||||
required this.order,
|
required this.order,
|
||||||
@@ -873,7 +704,6 @@ class RemoteAlbumEntityData extends i0.DataClass
|
|||||||
map['description'] = i0.Variable<String>(description);
|
map['description'] = i0.Variable<String>(description);
|
||||||
map['created_at'] = i0.Variable<DateTime>(createdAt);
|
map['created_at'] = i0.Variable<DateTime>(createdAt);
|
||||||
map['updated_at'] = i0.Variable<DateTime>(updatedAt);
|
map['updated_at'] = i0.Variable<DateTime>(updatedAt);
|
||||||
map['owner_id'] = i0.Variable<String>(ownerId);
|
|
||||||
if (!nullToAbsent || thumbnailAssetId != null) {
|
if (!nullToAbsent || thumbnailAssetId != null) {
|
||||||
map['thumbnail_asset_id'] = i0.Variable<String>(thumbnailAssetId);
|
map['thumbnail_asset_id'] = i0.Variable<String>(thumbnailAssetId);
|
||||||
}
|
}
|
||||||
@@ -897,7 +727,6 @@ class RemoteAlbumEntityData extends i0.DataClass
|
|||||||
description: serializer.fromJson<String>(json['description']),
|
description: serializer.fromJson<String>(json['description']),
|
||||||
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
||||||
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
|
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
|
||||||
ownerId: serializer.fromJson<String>(json['ownerId']),
|
|
||||||
thumbnailAssetId: serializer.fromJson<String?>(json['thumbnailAssetId']),
|
thumbnailAssetId: serializer.fromJson<String?>(json['thumbnailAssetId']),
|
||||||
isActivityEnabled: serializer.fromJson<bool>(json['isActivityEnabled']),
|
isActivityEnabled: serializer.fromJson<bool>(json['isActivityEnabled']),
|
||||||
order: i1.$RemoteAlbumEntityTable.$converterorder.fromJson(
|
order: i1.$RemoteAlbumEntityTable.$converterorder.fromJson(
|
||||||
@@ -914,7 +743,6 @@ class RemoteAlbumEntityData extends i0.DataClass
|
|||||||
'description': serializer.toJson<String>(description),
|
'description': serializer.toJson<String>(description),
|
||||||
'createdAt': serializer.toJson<DateTime>(createdAt),
|
'createdAt': serializer.toJson<DateTime>(createdAt),
|
||||||
'updatedAt': serializer.toJson<DateTime>(updatedAt),
|
'updatedAt': serializer.toJson<DateTime>(updatedAt),
|
||||||
'ownerId': serializer.toJson<String>(ownerId),
|
|
||||||
'thumbnailAssetId': serializer.toJson<String?>(thumbnailAssetId),
|
'thumbnailAssetId': serializer.toJson<String?>(thumbnailAssetId),
|
||||||
'isActivityEnabled': serializer.toJson<bool>(isActivityEnabled),
|
'isActivityEnabled': serializer.toJson<bool>(isActivityEnabled),
|
||||||
'order': serializer.toJson<int>(
|
'order': serializer.toJson<int>(
|
||||||
@@ -929,7 +757,6 @@ class RemoteAlbumEntityData extends i0.DataClass
|
|||||||
String? description,
|
String? description,
|
||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
DateTime? updatedAt,
|
DateTime? updatedAt,
|
||||||
String? ownerId,
|
|
||||||
i0.Value<String?> thumbnailAssetId = const i0.Value.absent(),
|
i0.Value<String?> thumbnailAssetId = const i0.Value.absent(),
|
||||||
bool? isActivityEnabled,
|
bool? isActivityEnabled,
|
||||||
i2.AlbumAssetOrder? order,
|
i2.AlbumAssetOrder? order,
|
||||||
@@ -939,7 +766,6 @@ class RemoteAlbumEntityData extends i0.DataClass
|
|||||||
description: description ?? this.description,
|
description: description ?? this.description,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
updatedAt: updatedAt ?? this.updatedAt,
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
ownerId: ownerId ?? this.ownerId,
|
|
||||||
thumbnailAssetId: thumbnailAssetId.present
|
thumbnailAssetId: thumbnailAssetId.present
|
||||||
? thumbnailAssetId.value
|
? thumbnailAssetId.value
|
||||||
: this.thumbnailAssetId,
|
: this.thumbnailAssetId,
|
||||||
@@ -955,7 +781,6 @@ class RemoteAlbumEntityData extends i0.DataClass
|
|||||||
: this.description,
|
: this.description,
|
||||||
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
||||||
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
|
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
|
||||||
ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId,
|
|
||||||
thumbnailAssetId: data.thumbnailAssetId.present
|
thumbnailAssetId: data.thumbnailAssetId.present
|
||||||
? data.thumbnailAssetId.value
|
? data.thumbnailAssetId.value
|
||||||
: this.thumbnailAssetId,
|
: this.thumbnailAssetId,
|
||||||
@@ -974,7 +799,6 @@ class RemoteAlbumEntityData extends i0.DataClass
|
|||||||
..write('description: $description, ')
|
..write('description: $description, ')
|
||||||
..write('createdAt: $createdAt, ')
|
..write('createdAt: $createdAt, ')
|
||||||
..write('updatedAt: $updatedAt, ')
|
..write('updatedAt: $updatedAt, ')
|
||||||
..write('ownerId: $ownerId, ')
|
|
||||||
..write('thumbnailAssetId: $thumbnailAssetId, ')
|
..write('thumbnailAssetId: $thumbnailAssetId, ')
|
||||||
..write('isActivityEnabled: $isActivityEnabled, ')
|
..write('isActivityEnabled: $isActivityEnabled, ')
|
||||||
..write('order: $order')
|
..write('order: $order')
|
||||||
@@ -989,7 +813,6 @@ class RemoteAlbumEntityData extends i0.DataClass
|
|||||||
description,
|
description,
|
||||||
createdAt,
|
createdAt,
|
||||||
updatedAt,
|
updatedAt,
|
||||||
ownerId,
|
|
||||||
thumbnailAssetId,
|
thumbnailAssetId,
|
||||||
isActivityEnabled,
|
isActivityEnabled,
|
||||||
order,
|
order,
|
||||||
@@ -1003,7 +826,6 @@ class RemoteAlbumEntityData extends i0.DataClass
|
|||||||
other.description == this.description &&
|
other.description == this.description &&
|
||||||
other.createdAt == this.createdAt &&
|
other.createdAt == this.createdAt &&
|
||||||
other.updatedAt == this.updatedAt &&
|
other.updatedAt == this.updatedAt &&
|
||||||
other.ownerId == this.ownerId &&
|
|
||||||
other.thumbnailAssetId == this.thumbnailAssetId &&
|
other.thumbnailAssetId == this.thumbnailAssetId &&
|
||||||
other.isActivityEnabled == this.isActivityEnabled &&
|
other.isActivityEnabled == this.isActivityEnabled &&
|
||||||
other.order == this.order);
|
other.order == this.order);
|
||||||
@@ -1016,7 +838,6 @@ class RemoteAlbumEntityCompanion
|
|||||||
final i0.Value<String> description;
|
final i0.Value<String> description;
|
||||||
final i0.Value<DateTime> createdAt;
|
final i0.Value<DateTime> createdAt;
|
||||||
final i0.Value<DateTime> updatedAt;
|
final i0.Value<DateTime> updatedAt;
|
||||||
final i0.Value<String> ownerId;
|
|
||||||
final i0.Value<String?> thumbnailAssetId;
|
final i0.Value<String?> thumbnailAssetId;
|
||||||
final i0.Value<bool> isActivityEnabled;
|
final i0.Value<bool> isActivityEnabled;
|
||||||
final i0.Value<i2.AlbumAssetOrder> order;
|
final i0.Value<i2.AlbumAssetOrder> order;
|
||||||
@@ -1026,7 +847,6 @@ class RemoteAlbumEntityCompanion
|
|||||||
this.description = const i0.Value.absent(),
|
this.description = const i0.Value.absent(),
|
||||||
this.createdAt = const i0.Value.absent(),
|
this.createdAt = const i0.Value.absent(),
|
||||||
this.updatedAt = const i0.Value.absent(),
|
this.updatedAt = const i0.Value.absent(),
|
||||||
this.ownerId = const i0.Value.absent(),
|
|
||||||
this.thumbnailAssetId = const i0.Value.absent(),
|
this.thumbnailAssetId = const i0.Value.absent(),
|
||||||
this.isActivityEnabled = const i0.Value.absent(),
|
this.isActivityEnabled = const i0.Value.absent(),
|
||||||
this.order = const i0.Value.absent(),
|
this.order = const i0.Value.absent(),
|
||||||
@@ -1037,13 +857,11 @@ class RemoteAlbumEntityCompanion
|
|||||||
this.description = const i0.Value.absent(),
|
this.description = const i0.Value.absent(),
|
||||||
this.createdAt = const i0.Value.absent(),
|
this.createdAt = const i0.Value.absent(),
|
||||||
this.updatedAt = const i0.Value.absent(),
|
this.updatedAt = const i0.Value.absent(),
|
||||||
required String ownerId,
|
|
||||||
this.thumbnailAssetId = const i0.Value.absent(),
|
this.thumbnailAssetId = const i0.Value.absent(),
|
||||||
this.isActivityEnabled = const i0.Value.absent(),
|
this.isActivityEnabled = const i0.Value.absent(),
|
||||||
required i2.AlbumAssetOrder order,
|
required i2.AlbumAssetOrder order,
|
||||||
}) : id = i0.Value(id),
|
}) : id = i0.Value(id),
|
||||||
name = i0.Value(name),
|
name = i0.Value(name),
|
||||||
ownerId = i0.Value(ownerId),
|
|
||||||
order = i0.Value(order);
|
order = i0.Value(order);
|
||||||
static i0.Insertable<i1.RemoteAlbumEntityData> custom({
|
static i0.Insertable<i1.RemoteAlbumEntityData> custom({
|
||||||
i0.Expression<String>? id,
|
i0.Expression<String>? id,
|
||||||
@@ -1051,7 +869,6 @@ class RemoteAlbumEntityCompanion
|
|||||||
i0.Expression<String>? description,
|
i0.Expression<String>? description,
|
||||||
i0.Expression<DateTime>? createdAt,
|
i0.Expression<DateTime>? createdAt,
|
||||||
i0.Expression<DateTime>? updatedAt,
|
i0.Expression<DateTime>? updatedAt,
|
||||||
i0.Expression<String>? ownerId,
|
|
||||||
i0.Expression<String>? thumbnailAssetId,
|
i0.Expression<String>? thumbnailAssetId,
|
||||||
i0.Expression<bool>? isActivityEnabled,
|
i0.Expression<bool>? isActivityEnabled,
|
||||||
i0.Expression<int>? order,
|
i0.Expression<int>? order,
|
||||||
@@ -1062,7 +879,6 @@ class RemoteAlbumEntityCompanion
|
|||||||
if (description != null) 'description': description,
|
if (description != null) 'description': description,
|
||||||
if (createdAt != null) 'created_at': createdAt,
|
if (createdAt != null) 'created_at': createdAt,
|
||||||
if (updatedAt != null) 'updated_at': updatedAt,
|
if (updatedAt != null) 'updated_at': updatedAt,
|
||||||
if (ownerId != null) 'owner_id': ownerId,
|
|
||||||
if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId,
|
if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId,
|
||||||
if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled,
|
if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled,
|
||||||
if (order != null) 'order': order,
|
if (order != null) 'order': order,
|
||||||
@@ -1075,7 +891,6 @@ class RemoteAlbumEntityCompanion
|
|||||||
i0.Value<String>? description,
|
i0.Value<String>? description,
|
||||||
i0.Value<DateTime>? createdAt,
|
i0.Value<DateTime>? createdAt,
|
||||||
i0.Value<DateTime>? updatedAt,
|
i0.Value<DateTime>? updatedAt,
|
||||||
i0.Value<String>? ownerId,
|
|
||||||
i0.Value<String?>? thumbnailAssetId,
|
i0.Value<String?>? thumbnailAssetId,
|
||||||
i0.Value<bool>? isActivityEnabled,
|
i0.Value<bool>? isActivityEnabled,
|
||||||
i0.Value<i2.AlbumAssetOrder>? order,
|
i0.Value<i2.AlbumAssetOrder>? order,
|
||||||
@@ -1086,7 +901,6 @@ class RemoteAlbumEntityCompanion
|
|||||||
description: description ?? this.description,
|
description: description ?? this.description,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
updatedAt: updatedAt ?? this.updatedAt,
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
ownerId: ownerId ?? this.ownerId,
|
|
||||||
thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId,
|
thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId,
|
||||||
isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled,
|
isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled,
|
||||||
order: order ?? this.order,
|
order: order ?? this.order,
|
||||||
@@ -1111,9 +925,6 @@ class RemoteAlbumEntityCompanion
|
|||||||
if (updatedAt.present) {
|
if (updatedAt.present) {
|
||||||
map['updated_at'] = i0.Variable<DateTime>(updatedAt.value);
|
map['updated_at'] = i0.Variable<DateTime>(updatedAt.value);
|
||||||
}
|
}
|
||||||
if (ownerId.present) {
|
|
||||||
map['owner_id'] = i0.Variable<String>(ownerId.value);
|
|
||||||
}
|
|
||||||
if (thumbnailAssetId.present) {
|
if (thumbnailAssetId.present) {
|
||||||
map['thumbnail_asset_id'] = i0.Variable<String>(thumbnailAssetId.value);
|
map['thumbnail_asset_id'] = i0.Variable<String>(thumbnailAssetId.value);
|
||||||
}
|
}
|
||||||
@@ -1136,7 +947,6 @@ class RemoteAlbumEntityCompanion
|
|||||||
..write('description: $description, ')
|
..write('description: $description, ')
|
||||||
..write('createdAt: $createdAt, ')
|
..write('createdAt: $createdAt, ')
|
||||||
..write('updatedAt: $updatedAt, ')
|
..write('updatedAt: $updatedAt, ')
|
||||||
..write('ownerId: $ownerId, ')
|
|
||||||
..write('thumbnailAssetId: $thumbnailAssetId, ')
|
..write('thumbnailAssetId: $thumbnailAssetId, ')
|
||||||
..write('isActivityEnabled: $isActivityEnabled, ')
|
..write('isActivityEnabled: $isActivityEnabled, ')
|
||||||
..write('order: $order')
|
..write('order: $order')
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class Drift extends $Drift {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 23;
|
int get schemaVersion => 24;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
@@ -246,6 +246,10 @@ class Drift extends $Drift {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
from23To24: (m, v24) async {
|
||||||
|
await customStatement('DROP INDEX IF EXISTS idx_remote_album_owner_id');
|
||||||
|
await m.alterTable(TableMigration(v24.remoteAlbumEntity));
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
localAlbumEntity,
|
localAlbumEntity,
|
||||||
localAlbumAssetEntity,
|
localAlbumAssetEntity,
|
||||||
i7.idxLocalAlbumAssetAlbumAsset,
|
i7.idxLocalAlbumAssetAlbumAsset,
|
||||||
i5.idxRemoteAlbumOwnerId,
|
|
||||||
i4.idxLocalAssetChecksum,
|
i4.idxLocalAssetChecksum,
|
||||||
i4.idxLocalAssetCloudId,
|
i4.idxLocalAssetCloudId,
|
||||||
i3.idxStackPrimaryAssetId,
|
i3.idxStackPrimaryAssetId,
|
||||||
@@ -160,15 +159,6 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
),
|
),
|
||||||
result: [i0.TableUpdate('stack_entity', kind: i0.UpdateKind.delete)],
|
result: [i0.TableUpdate('stack_entity', kind: i0.UpdateKind.delete)],
|
||||||
),
|
),
|
||||||
i0.WritePropagation(
|
|
||||||
on: i0.TableUpdateQuery.onTableName(
|
|
||||||
'user_entity',
|
|
||||||
limitUpdateKind: i0.UpdateKind.delete,
|
|
||||||
),
|
|
||||||
result: [
|
|
||||||
i0.TableUpdate('remote_album_entity', kind: i0.UpdateKind.delete),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
i0.WritePropagation(
|
i0.WritePropagation(
|
||||||
on: i0.TableUpdateQuery.onTableName(
|
on: i0.TableUpdateQuery.onTableName(
|
||||||
'remote_asset_entity',
|
'remote_asset_entity',
|
||||||
|
|||||||
@@ -11824,6 +11824,557 @@ i1.GeneratedColumn<int> _column_209(String aliasedName) =>
|
|||||||
type: i1.DriftSqlType.int,
|
type: i1.DriftSqlType.int,
|
||||||
$customConstraints: 'NOT NULL',
|
$customConstraints: 'NOT NULL',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final class Schema24 extends i0.VersionedSchema {
|
||||||
|
Schema24({required super.database}) : super(version: 24);
|
||||||
|
@override
|
||||||
|
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||||
|
userEntity,
|
||||||
|
remoteAssetEntity,
|
||||||
|
stackEntity,
|
||||||
|
localAssetEntity,
|
||||||
|
remoteAlbumEntity,
|
||||||
|
localAlbumEntity,
|
||||||
|
localAlbumAssetEntity,
|
||||||
|
idxLocalAlbumAssetAlbumAsset,
|
||||||
|
idxLocalAssetChecksum,
|
||||||
|
idxLocalAssetCloudId,
|
||||||
|
idxStackPrimaryAssetId,
|
||||||
|
idxRemoteAssetOwnerChecksum,
|
||||||
|
uQRemoteAssetsOwnerChecksum,
|
||||||
|
uQRemoteAssetsOwnerLibraryChecksum,
|
||||||
|
idxRemoteAssetChecksum,
|
||||||
|
idxRemoteAssetStackId,
|
||||||
|
idxRemoteAssetLocalDateTimeDay,
|
||||||
|
idxRemoteAssetLocalDateTimeMonth,
|
||||||
|
authUserEntity,
|
||||||
|
userMetadataEntity,
|
||||||
|
partnerEntity,
|
||||||
|
remoteExifEntity,
|
||||||
|
remoteAlbumAssetEntity,
|
||||||
|
remoteAlbumUserEntity,
|
||||||
|
remoteAssetCloudIdEntity,
|
||||||
|
memoryEntity,
|
||||||
|
memoryAssetEntity,
|
||||||
|
personEntity,
|
||||||
|
assetFaceEntity,
|
||||||
|
storeEntity,
|
||||||
|
trashedLocalAssetEntity,
|
||||||
|
assetEditEntity,
|
||||||
|
idxPartnerSharedWithId,
|
||||||
|
idxLatLng,
|
||||||
|
idxRemoteAlbumAssetAlbumAsset,
|
||||||
|
idxRemoteAssetCloudId,
|
||||||
|
idxPersonOwnerId,
|
||||||
|
idxAssetFacePersonId,
|
||||||
|
idxAssetFaceAssetId,
|
||||||
|
idxTrashedLocalAssetChecksum,
|
||||||
|
idxTrashedLocalAssetAlbum,
|
||||||
|
idxAssetEditAssetId,
|
||||||
|
];
|
||||||
|
late final Shape33 userEntity = Shape33(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_108,
|
||||||
|
_column_109,
|
||||||
|
_column_110,
|
||||||
|
_column_111,
|
||||||
|
_column_112,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape34 remoteAssetEntity = Shape34(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_108,
|
||||||
|
_column_113,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_116,
|
||||||
|
_column_117,
|
||||||
|
_column_118,
|
||||||
|
_column_107,
|
||||||
|
_column_119,
|
||||||
|
_column_120,
|
||||||
|
_column_121,
|
||||||
|
_column_122,
|
||||||
|
_column_123,
|
||||||
|
_column_124,
|
||||||
|
_column_125,
|
||||||
|
_column_126,
|
||||||
|
_column_127,
|
||||||
|
_column_128,
|
||||||
|
_column_129,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape35 stackEntity = Shape35(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'stack_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_121,
|
||||||
|
_column_130,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape36 localAssetEntity = Shape36(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'local_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_108,
|
||||||
|
_column_113,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_116,
|
||||||
|
_column_117,
|
||||||
|
_column_118,
|
||||||
|
_column_107,
|
||||||
|
_column_131,
|
||||||
|
_column_120,
|
||||||
|
_column_132,
|
||||||
|
_column_133,
|
||||||
|
_column_134,
|
||||||
|
_column_135,
|
||||||
|
_column_136,
|
||||||
|
_column_137,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape48 remoteAlbumEntity = Shape48(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_album_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_108,
|
||||||
|
_column_138,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_139,
|
||||||
|
_column_140,
|
||||||
|
_column_141,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape38 localAlbumEntity = Shape38(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'local_album_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_108,
|
||||||
|
_column_115,
|
||||||
|
_column_142,
|
||||||
|
_column_143,
|
||||||
|
_column_144,
|
||||||
|
_column_145,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape39 localAlbumAssetEntity = Shape39(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'local_album_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
||||||
|
columns: [_column_146, _column_147, _column_145],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
final i1.Index idxLocalAlbumAssetAlbumAsset = i1.Index(
|
||||||
|
'idx_local_album_asset_album_asset',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxLocalAssetChecksum = i1.Index(
|
||||||
|
'idx_local_asset_checksum',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)',
|
||||||
|
);
|
||||||
|
final i1.Index idxLocalAssetCloudId = i1.Index(
|
||||||
|
'idx_local_asset_cloud_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxStackPrimaryAssetId = i1.Index(
|
||||||
|
'idx_stack_primary_asset_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAssetOwnerChecksum = i1.Index(
|
||||||
|
'idx_remote_asset_owner_checksum',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)',
|
||||||
|
);
|
||||||
|
final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index(
|
||||||
|
'UQ_remote_assets_owner_checksum',
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)',
|
||||||
|
);
|
||||||
|
final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index(
|
||||||
|
'UQ_remote_assets_owner_library_checksum',
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAssetChecksum = i1.Index(
|
||||||
|
'idx_remote_asset_checksum',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAssetStackId = i1.Index(
|
||||||
|
'idx_remote_asset_stack_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAssetLocalDateTimeDay = i1.Index(
|
||||||
|
'idx_remote_asset_local_date_time_day',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME(\'%Y-%m-%d\', local_date_time))',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAssetLocalDateTimeMonth = i1.Index(
|
||||||
|
'idx_remote_asset_local_date_time_month',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME(\'%Y-%m\', local_date_time))',
|
||||||
|
);
|
||||||
|
late final Shape40 authUserEntity = Shape40(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'auth_user_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_108,
|
||||||
|
_column_109,
|
||||||
|
_column_148,
|
||||||
|
_column_110,
|
||||||
|
_column_111,
|
||||||
|
_column_149,
|
||||||
|
_column_150,
|
||||||
|
_column_151,
|
||||||
|
_column_152,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape4 userMetadataEntity = Shape4(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_metadata_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(user_id, "key")'],
|
||||||
|
columns: [_column_153, _column_154, _column_155],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape41 partnerEntity = Shape41(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'partner_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'],
|
||||||
|
columns: [_column_156, _column_157, _column_158],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape42 remoteExifEntity = Shape42(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_exif_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_159,
|
||||||
|
_column_160,
|
||||||
|
_column_161,
|
||||||
|
_column_162,
|
||||||
|
_column_163,
|
||||||
|
_column_164,
|
||||||
|
_column_117,
|
||||||
|
_column_116,
|
||||||
|
_column_165,
|
||||||
|
_column_166,
|
||||||
|
_column_167,
|
||||||
|
_column_168,
|
||||||
|
_column_135,
|
||||||
|
_column_136,
|
||||||
|
_column_169,
|
||||||
|
_column_170,
|
||||||
|
_column_171,
|
||||||
|
_column_172,
|
||||||
|
_column_173,
|
||||||
|
_column_174,
|
||||||
|
_column_175,
|
||||||
|
_column_176,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape7 remoteAlbumAssetEntity = Shape7(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_album_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
||||||
|
columns: [_column_159, _column_177],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape10 remoteAlbumUserEntity = Shape10(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_album_user_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(album_id, user_id)'],
|
||||||
|
columns: [_column_177, _column_153, _column_178],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape43 remoteAssetCloudIdEntity = Shape43(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_asset_cloud_id_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_159,
|
||||||
|
_column_179,
|
||||||
|
_column_180,
|
||||||
|
_column_134,
|
||||||
|
_column_135,
|
||||||
|
_column_136,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape44 memoryEntity = Shape44(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'memory_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_124,
|
||||||
|
_column_121,
|
||||||
|
_column_113,
|
||||||
|
_column_181,
|
||||||
|
_column_182,
|
||||||
|
_column_183,
|
||||||
|
_column_184,
|
||||||
|
_column_185,
|
||||||
|
_column_186,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape12 memoryAssetEntity = Shape12(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'memory_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'],
|
||||||
|
columns: [_column_159, _column_187],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape45 personEntity = Shape45(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'person_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_121,
|
||||||
|
_column_108,
|
||||||
|
_column_188,
|
||||||
|
_column_189,
|
||||||
|
_column_190,
|
||||||
|
_column_191,
|
||||||
|
_column_192,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape46 assetFaceEntity = Shape46(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'asset_face_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_159,
|
||||||
|
_column_193,
|
||||||
|
_column_194,
|
||||||
|
_column_195,
|
||||||
|
_column_196,
|
||||||
|
_column_197,
|
||||||
|
_column_198,
|
||||||
|
_column_199,
|
||||||
|
_column_200,
|
||||||
|
_column_201,
|
||||||
|
_column_124,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape18 storeEntity = Shape18(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'store_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [_column_202, _column_203, _column_204],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape47 trashedLocalAssetEntity = Shape47(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'trashed_local_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id, album_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_108,
|
||||||
|
_column_113,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_116,
|
||||||
|
_column_117,
|
||||||
|
_column_118,
|
||||||
|
_column_107,
|
||||||
|
_column_205,
|
||||||
|
_column_131,
|
||||||
|
_column_120,
|
||||||
|
_column_132,
|
||||||
|
_column_206,
|
||||||
|
_column_137,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape32 assetEditEntity = Shape32(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'asset_edit_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_159,
|
||||||
|
_column_207,
|
||||||
|
_column_208,
|
||||||
|
_column_209,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
final i1.Index idxPartnerSharedWithId = i1.Index(
|
||||||
|
'idx_partner_shared_with_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxLatLng = i1.Index(
|
||||||
|
'idx_lat_lng',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAlbumAssetAlbumAsset = i1.Index(
|
||||||
|
'idx_remote_album_asset_album_asset',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAssetCloudId = i1.Index(
|
||||||
|
'idx_remote_asset_cloud_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxPersonOwnerId = i1.Index(
|
||||||
|
'idx_person_owner_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxAssetFacePersonId = i1.Index(
|
||||||
|
'idx_asset_face_person_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxAssetFaceAssetId = i1.Index(
|
||||||
|
'idx_asset_face_asset_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxTrashedLocalAssetChecksum = i1.Index(
|
||||||
|
'idx_trashed_local_asset_checksum',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)',
|
||||||
|
);
|
||||||
|
final i1.Index idxTrashedLocalAssetAlbum = i1.Index(
|
||||||
|
'idx_trashed_local_asset_album',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxAssetEditAssetId = i1.Index(
|
||||||
|
'idx_asset_edit_asset_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Shape48 extends i0.VersionedTable {
|
||||||
|
Shape48({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<String> get id =>
|
||||||
|
columnsByName['id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get name =>
|
||||||
|
columnsByName['name']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get description =>
|
||||||
|
columnsByName['description']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get createdAt =>
|
||||||
|
columnsByName['created_at']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get updatedAt =>
|
||||||
|
columnsByName['updated_at']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get thumbnailAssetId =>
|
||||||
|
columnsByName['thumbnail_asset_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<int> get isActivityEnabled =>
|
||||||
|
columnsByName['is_activity_enabled']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get order =>
|
||||||
|
columnsByName['order']! as i1.GeneratedColumn<int>;
|
||||||
|
}
|
||||||
|
|
||||||
i0.MigrationStepWithVersion migrationSteps({
|
i0.MigrationStepWithVersion migrationSteps({
|
||||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||||
@@ -11847,6 +12398,7 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
required Future<void> Function(i1.Migrator m, Schema21 schema) from20To21,
|
required Future<void> Function(i1.Migrator m, Schema21 schema) from20To21,
|
||||||
required Future<void> Function(i1.Migrator m, Schema22 schema) from21To22,
|
required Future<void> Function(i1.Migrator m, Schema22 schema) from21To22,
|
||||||
required Future<void> Function(i1.Migrator m, Schema23 schema) from22To23,
|
required Future<void> Function(i1.Migrator m, Schema23 schema) from22To23,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema24 schema) from23To24,
|
||||||
}) {
|
}) {
|
||||||
return (currentVersion, database) async {
|
return (currentVersion, database) async {
|
||||||
switch (currentVersion) {
|
switch (currentVersion) {
|
||||||
@@ -11960,6 +12512,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
final migrator = i1.Migrator(database, schema);
|
final migrator = i1.Migrator(database, schema);
|
||||||
await from22To23(migrator, schema);
|
await from22To23(migrator, schema);
|
||||||
return 23;
|
return 23;
|
||||||
|
case 23:
|
||||||
|
final schema = Schema24(database: database);
|
||||||
|
final migrator = i1.Migrator(database, schema);
|
||||||
|
await from23To24(migrator, schema);
|
||||||
|
return 24;
|
||||||
default:
|
default:
|
||||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||||
}
|
}
|
||||||
@@ -11989,6 +12546,7 @@ i1.OnUpgrade stepByStep({
|
|||||||
required Future<void> Function(i1.Migrator m, Schema21 schema) from20To21,
|
required Future<void> Function(i1.Migrator m, Schema21 schema) from20To21,
|
||||||
required Future<void> Function(i1.Migrator m, Schema22 schema) from21To22,
|
required Future<void> Function(i1.Migrator m, Schema22 schema) from21To22,
|
||||||
required Future<void> Function(i1.Migrator m, Schema23 schema) from22To23,
|
required Future<void> Function(i1.Migrator m, Schema23 schema) from22To23,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema24 schema) from23To24,
|
||||||
}) => i0.VersionedSchema.stepByStepHelper(
|
}) => i0.VersionedSchema.stepByStepHelper(
|
||||||
step: migrationSteps(
|
step: migrationSteps(
|
||||||
from1To2: from1To2,
|
from1To2: from1To2,
|
||||||
@@ -12013,5 +12571,6 @@ i1.OnUpgrade stepByStep({
|
|||||||
from20To21: from20To21,
|
from20To21: from20To21,
|
||||||
from21To22: from21To22,
|
from21To22: from21To22,
|
||||||
from22To23: from22To23,
|
from22To23: from22To23,
|
||||||
|
from23To24: from23To24,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -32,17 +32,23 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
|
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
|
||||||
useColumns: false,
|
useColumns: false,
|
||||||
),
|
),
|
||||||
leftOuterJoin(_db.userEntity, _db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId), useColumns: false),
|
|
||||||
leftOuterJoin(
|
leftOuterJoin(
|
||||||
_db.remoteAlbumUserEntity,
|
_db.remoteAlbumUserEntity,
|
||||||
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
|
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
|
||||||
useColumns: false,
|
useColumns: false,
|
||||||
),
|
),
|
||||||
|
innerJoin(
|
||||||
|
_db.userEntity,
|
||||||
|
_db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId) &
|
||||||
|
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) &
|
||||||
|
_db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
query
|
query
|
||||||
..where(_db.remoteAssetEntity.deletedAt.isNull())
|
..where(_db.remoteAssetEntity.deletedAt.isNull())
|
||||||
..addColumns([assetCount])
|
..addColumns([assetCount])
|
||||||
..addColumns([_db.userEntity.name])
|
..addColumns([_db.userEntity.name, _db.userEntity.id])
|
||||||
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
|
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
|
||||||
..groupBy([_db.remoteAlbumEntity.id]);
|
..groupBy([_db.remoteAlbumEntity.id]);
|
||||||
|
|
||||||
@@ -63,6 +69,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
.readTable(_db.remoteAlbumEntity)
|
.readTable(_db.remoteAlbumEntity)
|
||||||
.toDto(
|
.toDto(
|
||||||
assetCount: row.read(assetCount) ?? 0,
|
assetCount: row.read(assetCount) ?? 0,
|
||||||
|
ownerId: row.read(_db.userEntity.id)!,
|
||||||
ownerName: row.read(_db.userEntity.name)!,
|
ownerName: row.read(_db.userEntity.name)!,
|
||||||
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
|
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
|
||||||
),
|
),
|
||||||
@@ -85,20 +92,22 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
|
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
|
||||||
useColumns: false,
|
useColumns: false,
|
||||||
),
|
),
|
||||||
leftOuterJoin(
|
|
||||||
_db.userEntity,
|
|
||||||
_db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId),
|
|
||||||
useColumns: false,
|
|
||||||
),
|
|
||||||
leftOuterJoin(
|
leftOuterJoin(
|
||||||
_db.remoteAlbumUserEntity,
|
_db.remoteAlbumUserEntity,
|
||||||
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
|
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
|
||||||
useColumns: false,
|
useColumns: false,
|
||||||
),
|
),
|
||||||
|
innerJoin(
|
||||||
|
_db.userEntity,
|
||||||
|
_db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId) &
|
||||||
|
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) &
|
||||||
|
_db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
])
|
])
|
||||||
..where(_db.remoteAlbumEntity.id.equals(albumId) & _db.remoteAssetEntity.deletedAt.isNull())
|
..where(_db.remoteAlbumEntity.id.equals(albumId) & _db.remoteAssetEntity.deletedAt.isNull())
|
||||||
..addColumns([assetCount])
|
..addColumns([assetCount])
|
||||||
..addColumns([_db.userEntity.name])
|
..addColumns([_db.userEntity.name, _db.userEntity.id])
|
||||||
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
|
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
|
||||||
..groupBy([_db.remoteAlbumEntity.id]);
|
..groupBy([_db.remoteAlbumEntity.id]);
|
||||||
|
|
||||||
@@ -108,6 +117,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
.readTable(_db.remoteAlbumEntity)
|
.readTable(_db.remoteAlbumEntity)
|
||||||
.toDto(
|
.toDto(
|
||||||
assetCount: row.read(assetCount) ?? 0,
|
assetCount: row.read(assetCount) ?? 0,
|
||||||
|
ownerId: row.read(_db.userEntity.id)!,
|
||||||
ownerName: row.read(_db.userEntity.name)!,
|
ownerName: row.read(_db.userEntity.name)!,
|
||||||
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
|
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
|
||||||
),
|
),
|
||||||
@@ -116,12 +126,29 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<RemoteAlbum?> getByName(String albumName, String ownerId) {
|
Future<RemoteAlbum?> getByName(String albumName, String ownerId) {
|
||||||
final query = _db.remoteAlbumEntity.select()
|
final query = _db.remoteAlbumEntity.select().join([
|
||||||
..where((row) => row.name.equals(albumName) & row.ownerId.equals(ownerId))
|
innerJoin(
|
||||||
..orderBy([(row) => OrderingTerm.desc(row.createdAt)])
|
_db.remoteAlbumUserEntity,
|
||||||
|
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) &
|
||||||
|
_db.remoteAlbumUserEntity.userId.equals(ownerId) &
|
||||||
|
_db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
query
|
||||||
|
..addColumns([_db.remoteAlbumUserEntity.userId])
|
||||||
|
..where(_db.remoteAlbumEntity.name.equals(albumName))
|
||||||
|
..orderBy([OrderingTerm.desc(_db.remoteAlbumEntity.createdAt)])
|
||||||
..limit(1);
|
..limit(1);
|
||||||
|
|
||||||
return query.map((row) => row.toDto(ownerName: '', isShared: false)).getSingleOrNull();
|
return query
|
||||||
|
.map(
|
||||||
|
(row) => row
|
||||||
|
.readTable(_db.remoteAlbumEntity)
|
||||||
|
.toDto(ownerId: row.read(_db.remoteAlbumUserEntity.userId)!, ownerName: '', isShared: false),
|
||||||
|
)
|
||||||
|
.getSingleOrNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> create(RemoteAlbum album, List<String> assetIds) async {
|
Future<void> create(RemoteAlbum album, List<String> assetIds) async {
|
||||||
@@ -129,7 +156,6 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
final entity = RemoteAlbumEntityCompanion(
|
final entity = RemoteAlbumEntityCompanion(
|
||||||
id: Value(album.id),
|
id: Value(album.id),
|
||||||
name: Value(album.name),
|
name: Value(album.name),
|
||||||
ownerId: Value(album.ownerId),
|
|
||||||
createdAt: Value(album.createdAt),
|
createdAt: Value(album.createdAt),
|
||||||
updatedAt: Value(album.updatedAt),
|
updatedAt: Value(album.updatedAt),
|
||||||
description: Value(album.description),
|
description: Value(album.description),
|
||||||
@@ -140,6 +166,14 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
|
|
||||||
await _db.remoteAlbumEntity.insertOne(entity);
|
await _db.remoteAlbumEntity.insertOne(entity);
|
||||||
|
|
||||||
|
await _db.remoteAlbumUserEntity.insertOne(
|
||||||
|
RemoteAlbumUserEntityCompanion(
|
||||||
|
albumId: Value(album.id),
|
||||||
|
userId: Value(album.ownerId),
|
||||||
|
role: const Value(AlbumUserRole.owner),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (assetIds.isNotEmpty) {
|
if (assetIds.isNotEmpty) {
|
||||||
final albumAssets = assetIds.map(
|
final albumAssets = assetIds.map(
|
||||||
(assetId) => RemoteAlbumAssetEntityCompanion(albumId: Value(album.id), assetId: Value(assetId)),
|
(assetId) => RemoteAlbumAssetEntityCompanion(albumId: Value(album.id), assetId: Value(assetId)),
|
||||||
@@ -157,7 +191,6 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
RemoteAlbumEntityCompanion(
|
RemoteAlbumEntityCompanion(
|
||||||
id: Value(album.id),
|
id: Value(album.id),
|
||||||
name: Value(album.name),
|
name: Value(album.name),
|
||||||
ownerId: Value(album.ownerId),
|
|
||||||
createdAt: Value(album.createdAt),
|
createdAt: Value(album.createdAt),
|
||||||
updatedAt: Value(album.updatedAt),
|
updatedAt: Value(album.updatedAt),
|
||||||
description: Value(album.description),
|
description: Value(album.description),
|
||||||
@@ -197,7 +230,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
Future<List<UserDto>> getSharedUsers(String albumId) async {
|
Future<List<UserDto>> getSharedUsers(String albumId) async {
|
||||||
final albumUserRows = await (_db.select(
|
final albumUserRows = await (_db.select(
|
||||||
_db.remoteAlbumUserEntity,
|
_db.remoteAlbumUserEntity,
|
||||||
)..where((row) => row.albumId.equals(albumId))).get();
|
)..where((row) => row.albumId.equals(albumId) & row.role.isNotValue(AlbumUserRole.owner.index))).get();
|
||||||
|
|
||||||
if (albumUserRows.isEmpty) {
|
if (albumUserRows.isEmpty) {
|
||||||
return [];
|
return [];
|
||||||
@@ -295,19 +328,21 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
|
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
|
||||||
useColumns: false,
|
useColumns: false,
|
||||||
),
|
),
|
||||||
leftOuterJoin(
|
|
||||||
_db.userEntity,
|
|
||||||
_db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId),
|
|
||||||
useColumns: false,
|
|
||||||
),
|
|
||||||
leftOuterJoin(
|
leftOuterJoin(
|
||||||
_db.remoteAlbumUserEntity,
|
_db.remoteAlbumUserEntity,
|
||||||
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
|
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
|
||||||
useColumns: false,
|
useColumns: false,
|
||||||
),
|
),
|
||||||
|
innerJoin(
|
||||||
|
_db.userEntity,
|
||||||
|
_db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId) &
|
||||||
|
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) &
|
||||||
|
_db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
])
|
])
|
||||||
..where(_db.remoteAlbumEntity.id.equals(albumId))
|
..where(_db.remoteAlbumEntity.id.equals(albumId))
|
||||||
..addColumns([_db.userEntity.name])
|
..addColumns([_db.userEntity.name, _db.userEntity.id])
|
||||||
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
|
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
|
||||||
..groupBy([_db.remoteAlbumEntity.id]);
|
..groupBy([_db.remoteAlbumEntity.id]);
|
||||||
|
|
||||||
@@ -315,6 +350,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
final album = row
|
final album = row
|
||||||
.readTable(_db.remoteAlbumEntity)
|
.readTable(_db.remoteAlbumEntity)
|
||||||
.toDto(
|
.toDto(
|
||||||
|
ownerId: row.read(_db.userEntity.id)!,
|
||||||
ownerName: row.read(_db.userEntity.name)!,
|
ownerName: row.read(_db.userEntity.name)!,
|
||||||
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
|
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
|
||||||
);
|
);
|
||||||
@@ -355,6 +391,37 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
return _db.managers.remoteAlbumEntity.count();
|
return _db.managers.remoteAlbumEntity.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<UserDto> getOwner(String albumId) {
|
||||||
|
final query =
|
||||||
|
_db.userEntity.select().join([
|
||||||
|
innerJoin(
|
||||||
|
_db.remoteAlbumUserEntity,
|
||||||
|
_db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
|
])..where(
|
||||||
|
_db.remoteAlbumUserEntity.albumId.equals(albumId) &
|
||||||
|
_db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner),
|
||||||
|
);
|
||||||
|
|
||||||
|
return query
|
||||||
|
.map(
|
||||||
|
(row) => UserDto(
|
||||||
|
id: row.read(_db.userEntity.id)!,
|
||||||
|
email: row.read(_db.userEntity.email)!,
|
||||||
|
name: row.read(_db.userEntity.name)!,
|
||||||
|
memoryEnabled: true,
|
||||||
|
inTimeline: false,
|
||||||
|
isPartnerSharedBy: false,
|
||||||
|
isPartnerSharedWith: false,
|
||||||
|
profileChangedAt: row.read(_db.userEntity.profileChangedAt)!,
|
||||||
|
hasProfileImage: row.read(_db.userEntity.hasProfileImage)!,
|
||||||
|
avatarColor: AvatarColor.values[row.read(_db.userEntity.avatarColor)!],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.getSingle();
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<String>> getLinkedAssetIds(String userId, String localAlbumId, String remoteAlbumId) async {
|
Future<List<String>> getLinkedAssetIds(String userId, String localAlbumId, String remoteAlbumId) async {
|
||||||
// Find remote asset ids that:
|
// Find remote asset ids that:
|
||||||
// 1. Belong to the provided local album (via local_album_asset_entity)
|
// 1. Belong to the provided local album (via local_album_asset_entity)
|
||||||
@@ -416,21 +483,23 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
|
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
|
||||||
useColumns: false,
|
useColumns: false,
|
||||||
),
|
),
|
||||||
leftOuterJoin(
|
|
||||||
_db.userEntity,
|
|
||||||
_db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId),
|
|
||||||
useColumns: false,
|
|
||||||
),
|
|
||||||
leftOuterJoin(
|
leftOuterJoin(
|
||||||
_db.remoteAlbumUserEntity,
|
_db.remoteAlbumUserEntity,
|
||||||
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
|
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
|
||||||
useColumns: false,
|
useColumns: false,
|
||||||
),
|
),
|
||||||
|
innerJoin(
|
||||||
|
_db.userEntity,
|
||||||
|
_db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId) &
|
||||||
|
_db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) &
|
||||||
|
_db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
])
|
])
|
||||||
..where(_db.remoteAlbumEntity.id.isIn(albumIds) & _db.remoteAssetEntity.deletedAt.isNull())
|
..where(_db.remoteAlbumEntity.id.isIn(albumIds) & _db.remoteAssetEntity.deletedAt.isNull())
|
||||||
..addColumns([assetCount])
|
..addColumns([assetCount])
|
||||||
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
|
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
|
||||||
..addColumns([_db.userEntity.name])
|
..addColumns([_db.userEntity.name, _db.userEntity.id])
|
||||||
..groupBy([_db.remoteAlbumEntity.id]);
|
..groupBy([_db.remoteAlbumEntity.id]);
|
||||||
|
|
||||||
return query
|
return query
|
||||||
@@ -438,6 +507,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
(row) => row
|
(row) => row
|
||||||
.readTable(_db.remoteAlbumEntity)
|
.readTable(_db.remoteAlbumEntity)
|
||||||
.toDto(
|
.toDto(
|
||||||
|
ownerId: row.read(_db.userEntity.id)!,
|
||||||
ownerName: row.read(_db.userEntity.name) ?? '',
|
ownerName: row.read(_db.userEntity.name) ?? '',
|
||||||
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
|
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
|
||||||
assetCount: row.read(assetCount) ?? 0,
|
assetCount: row.read(assetCount) ?? 0,
|
||||||
@@ -448,7 +518,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension on RemoteAlbumEntityData {
|
extension on RemoteAlbumEntityData {
|
||||||
RemoteAlbum toDto({int assetCount = 0, required String ownerName, required bool isShared}) {
|
RemoteAlbum toDto({int assetCount = 0, required String ownerName, required String ownerId, required bool isShared}) {
|
||||||
return RemoteAlbum(
|
return RemoteAlbum(
|
||||||
id: id,
|
id: id,
|
||||||
name: name,
|
name: name,
|
||||||
|
|||||||
@@ -53,7 +53,10 @@ class SyncApiRepository {
|
|||||||
SyncRequestType.partnersV1,
|
SyncRequestType.partnersV1,
|
||||||
SyncRequestType.partnerAssetsV1,
|
SyncRequestType.partnerAssetsV1,
|
||||||
SyncRequestType.partnerAssetExifsV1,
|
SyncRequestType.partnerAssetExifsV1,
|
||||||
SyncRequestType.albumsV1,
|
if (serverVersion < const SemVer(major: 3, minor: 0, patch: 0))
|
||||||
|
SyncRequestType.albumsV1
|
||||||
|
else
|
||||||
|
SyncRequestType.albumsV2,
|
||||||
SyncRequestType.albumUsersV1,
|
SyncRequestType.albumUsersV1,
|
||||||
SyncRequestType.albumAssetsV1,
|
SyncRequestType.albumAssetsV1,
|
||||||
SyncRequestType.albumAssetExifsV1,
|
SyncRequestType.albumAssetExifsV1,
|
||||||
@@ -162,6 +165,7 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
|
|||||||
SyncEntityType.partnerAssetExifV1: SyncAssetExifV1.fromJson,
|
SyncEntityType.partnerAssetExifV1: SyncAssetExifV1.fromJson,
|
||||||
SyncEntityType.partnerAssetExifBackfillV1: SyncAssetExifV1.fromJson,
|
SyncEntityType.partnerAssetExifBackfillV1: SyncAssetExifV1.fromJson,
|
||||||
SyncEntityType.albumV1: SyncAlbumV1.fromJson,
|
SyncEntityType.albumV1: SyncAlbumV1.fromJson,
|
||||||
|
SyncEntityType.albumV2: SyncAlbumV2.fromJson,
|
||||||
SyncEntityType.albumDeleteV1: SyncAlbumDeleteV1.fromJson,
|
SyncEntityType.albumDeleteV1: SyncAlbumDeleteV1.fromJson,
|
||||||
SyncEntityType.albumUserV1: SyncAlbumUserV1.fromJson,
|
SyncEntityType.albumUserV1: SyncAlbumUserV1.fromJson,
|
||||||
SyncEntityType.albumUserBackfillV1: SyncAlbumUserV1.fromJson,
|
SyncEntityType.albumUserBackfillV1: SyncAlbumUserV1.fromJson,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
|
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:openapi/api.dart' as api show AssetVisibility, AlbumUserRole, UserMetadataKey, AssetEditAction;
|
import 'package:openapi/api.dart' as api show AssetVisibility, AlbumUserRole, UserMetadataKey, AssetEditAction;
|
||||||
import 'package:openapi/api.dart' hide AlbumUserRole, UserMetadataKey, AssetEditAction, AssetVisibility;
|
import 'package:openapi/api.dart' hide UserMetadataKey, AssetEditAction, AssetVisibility, AlbumUserRole;
|
||||||
|
|
||||||
class SyncStreamRepository extends DriftDatabaseRepository {
|
class SyncStreamRepository extends DriftDatabaseRepository {
|
||||||
final Logger _logger = Logger('DriftSyncStreamRepository');
|
final Logger _logger = Logger('DriftSyncStreamRepository');
|
||||||
@@ -397,6 +397,47 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateAlbumsV1(Iterable<SyncAlbumV1> data) async {
|
Future<void> updateAlbumsV1(Iterable<SyncAlbumV1> data) async {
|
||||||
|
try {
|
||||||
|
await _db.transaction(() async {
|
||||||
|
await _db.batch((batch) {
|
||||||
|
for (final album in data) {
|
||||||
|
final companion = RemoteAlbumEntityCompanion(
|
||||||
|
name: Value(album.name),
|
||||||
|
description: Value(album.description),
|
||||||
|
isActivityEnabled: Value(album.isActivityEnabled),
|
||||||
|
order: Value(album.order.toAlbumAssetOrder()),
|
||||||
|
thumbnailAssetId: Value(album.thumbnailAssetId),
|
||||||
|
createdAt: Value(album.createdAt),
|
||||||
|
updatedAt: Value(album.updatedAt),
|
||||||
|
);
|
||||||
|
|
||||||
|
batch.insert(
|
||||||
|
_db.remoteAlbumEntity,
|
||||||
|
companion.copyWith(id: Value(album.id)),
|
||||||
|
onConflict: DoUpdate((_) => companion),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await _db.batch((batch) {
|
||||||
|
for (final album in data) {
|
||||||
|
final companion = RemoteAlbumUserEntityCompanion(
|
||||||
|
albumId: Value(album.id),
|
||||||
|
userId: Value(album.ownerId),
|
||||||
|
role: const Value(AlbumUserRole.owner),
|
||||||
|
);
|
||||||
|
|
||||||
|
batch.insert(_db.remoteAlbumUserEntity, companion, onConflict: DoUpdate((_) => companion));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (error, stack) {
|
||||||
|
_logger.severe('Error: updateAlbumsV1', error, stack);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateAlbumsV2(Iterable<SyncAlbumV2> data) async {
|
||||||
try {
|
try {
|
||||||
await _db.batch((batch) {
|
await _db.batch((batch) {
|
||||||
for (final album in data) {
|
for (final album in data) {
|
||||||
@@ -406,7 +447,6 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
|||||||
isActivityEnabled: Value(album.isActivityEnabled),
|
isActivityEnabled: Value(album.isActivityEnabled),
|
||||||
order: Value(album.order.toAlbumAssetOrder()),
|
order: Value(album.order.toAlbumAssetOrder()),
|
||||||
thumbnailAssetId: Value(album.thumbnailAssetId),
|
thumbnailAssetId: Value(album.thumbnailAssetId),
|
||||||
ownerId: Value(album.ownerId),
|
|
||||||
createdAt: Value(album.createdAt),
|
createdAt: Value(album.createdAt),
|
||||||
updatedAt: Value(album.updatedAt),
|
updatedAt: Value(album.updatedAt),
|
||||||
);
|
);
|
||||||
@@ -419,7 +459,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error, stack) {
|
} catch (error, stack) {
|
||||||
_logger.severe('Error: updateAlbumsV1', error, stack);
|
_logger.severe('Error: updateAlbumsV2', error, stack);
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -821,6 +861,7 @@ extension on api.AlbumUserRole {
|
|||||||
AlbumUserRole toAlbumUserRole() => switch (this) {
|
AlbumUserRole toAlbumUserRole() => switch (this) {
|
||||||
api.AlbumUserRole.editor => AlbumUserRole.editor,
|
api.AlbumUserRole.editor => AlbumUserRole.editor,
|
||||||
api.AlbumUserRole.viewer => AlbumUserRole.viewer,
|
api.AlbumUserRole.viewer => AlbumUserRole.viewer,
|
||||||
|
api.AlbumUserRole.owner => AlbumUserRole.owner,
|
||||||
_ => throw Exception('Unknown AlbumUserRole value: $this'),
|
_ => throw Exception('Unknown AlbumUserRole value: $this'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:immich_mobile/domain/services/remote_album.service.dart';
|
|||||||
import 'package:immich_mobile/models/albums/album_search.model.dart';
|
import 'package:immich_mobile/models/albums/album_search.model.dart';
|
||||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
class RemoteAlbumState {
|
class RemoteAlbumState {
|
||||||
@@ -81,7 +82,17 @@ class RemoteAlbumNotifier extends Notifier<RemoteAlbumState> {
|
|||||||
List<String> assetIds = const [],
|
List<String> assetIds = const [],
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final album = await _remoteAlbumService.createAlbum(title: title, description: description, assetIds: assetIds);
|
final currentUser = ref.read(currentUserProvider);
|
||||||
|
if (currentUser == null) {
|
||||||
|
throw Exception('User not logged in');
|
||||||
|
}
|
||||||
|
|
||||||
|
final album = await _remoteAlbumService.createAlbum(
|
||||||
|
title: title,
|
||||||
|
owner: currentUser,
|
||||||
|
description: description,
|
||||||
|
assetIds: assetIds,
|
||||||
|
);
|
||||||
|
|
||||||
state = state.copyWith(albums: [...state.albums, album]);
|
state = state.copyWith(albums: [...state.albums, album]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/api.repository.dart';
|
import 'package:immich_mobile/repositories/api.repository.dart';
|
||||||
// ignore: import_rule_openapi
|
// ignore: import_rule_openapi
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart' hide AlbumUserRole;
|
||||||
|
|
||||||
final driftAlbumApiRepositoryProvider = Provider(
|
final driftAlbumApiRepositoryProvider = Provider(
|
||||||
(ref) => DriftAlbumApiRepository(ref.watch(apiServiceProvider).albumsApi),
|
(ref) => DriftAlbumApiRepository(ref.watch(apiServiceProvider).albumsApi),
|
||||||
@@ -14,12 +15,17 @@ class DriftAlbumApiRepository extends ApiRepository {
|
|||||||
|
|
||||||
DriftAlbumApiRepository(this._api);
|
DriftAlbumApiRepository(this._api);
|
||||||
|
|
||||||
Future<RemoteAlbum> createDriftAlbum(String name, {required Iterable<String> assetIds, String? description}) async {
|
Future<RemoteAlbum> createDriftAlbum(
|
||||||
|
String name,
|
||||||
|
UserDto owner, {
|
||||||
|
required Iterable<String> assetIds,
|
||||||
|
String? description,
|
||||||
|
}) async {
|
||||||
final responseDto = await checkNull(
|
final responseDto = await checkNull(
|
||||||
_api.createAlbum(CreateAlbumDto(albumName: name, description: description, assetIds: assetIds.toList())),
|
_api.createAlbum(CreateAlbumDto(albumName: name, description: description, assetIds: assetIds.toList())),
|
||||||
);
|
);
|
||||||
|
|
||||||
return responseDto.toRemoteAlbum();
|
return responseDto.toRemoteAlbum(owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<({List<String> removed, List<String> failed})> removeAssets(String albumId, Iterable<String> assetIds) async {
|
Future<({List<String> removed, List<String> failed})> removeAssets(String albumId, Iterable<String> assetIds) async {
|
||||||
@@ -50,7 +56,8 @@ class DriftAlbumApiRepository extends ApiRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<RemoteAlbum> updateAlbum(
|
Future<RemoteAlbum> updateAlbum(
|
||||||
String albumId, {
|
String albumId,
|
||||||
|
UserDto owner, {
|
||||||
String? name,
|
String? name,
|
||||||
String? description,
|
String? description,
|
||||||
String? thumbnailAssetId,
|
String? thumbnailAssetId,
|
||||||
@@ -75,17 +82,16 @@ class DriftAlbumApiRepository extends ApiRepository {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return responseDto.toRemoteAlbum();
|
return responseDto.toRemoteAlbum(owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteAlbum(String albumId) {
|
Future<void> deleteAlbum(String albumId) {
|
||||||
return _api.deleteAlbum(albumId);
|
return _api.deleteAlbum(albumId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<RemoteAlbum> addUsers(String albumId, Iterable<String> userIds) async {
|
Future<void> addUsers(String albumId, Iterable<String> userIds) async {
|
||||||
final albumUsers = userIds.map((userId) => AlbumUserAddDto(userId: userId)).toList();
|
final albumUsers = userIds.map((userId) => AlbumUserAddDto(userId: userId)).toList();
|
||||||
final response = await checkNull(_api.addUsersToAlbum(albumId, AddUsersDto(albumUsers: albumUsers)));
|
await checkNull(_api.addUsersToAlbum(albumId, AddUsersDto(albumUsers: albumUsers)));
|
||||||
return response.toRemoteAlbum();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> removeUser(String albumId, {required String userId}) async {
|
Future<void> removeUser(String albumId, {required String userId}) async {
|
||||||
@@ -99,11 +105,12 @@ class DriftAlbumApiRepository extends ApiRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension on AlbumResponseDto {
|
extension on AlbumResponseDto {
|
||||||
RemoteAlbum toRemoteAlbum() {
|
RemoteAlbum toRemoteAlbum(final UserDto user) {
|
||||||
return RemoteAlbum(
|
return RemoteAlbum(
|
||||||
id: id,
|
id: id,
|
||||||
name: albumName,
|
name: albumName,
|
||||||
ownerId: owner.id,
|
ownerId: user.id,
|
||||||
|
ownerName: user.name,
|
||||||
description: description,
|
description: description,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
@@ -111,7 +118,6 @@ extension on AlbumResponseDto {
|
|||||||
isActivityEnabled: isActivityEnabled,
|
isActivityEnabled: isActivityEnabled,
|
||||||
order: order == AssetOrder.asc ? AlbumAssetOrder.asc : AlbumAssetOrder.desc,
|
order: order == AssetOrder.asc ? AlbumAssetOrder.asc : AlbumAssetOrder.desc,
|
||||||
assetCount: assetCount,
|
assetCount: assetCount,
|
||||||
ownerName: owner.name,
|
|
||||||
isShared: albumUsers.length > 2,
|
isShared: albumUsers.length > 2,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,7 +241,8 @@ class ActionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> setAlbumCover(String albumId, String assetId) async {
|
Future<bool> setAlbumCover(String albumId, String assetId) async {
|
||||||
final updatedAlbum = await _albumApiRepository.updateAlbum(albumId, thumbnailAssetId: assetId);
|
final owner = await _remoteAlbumRepository.getOwner(albumId);
|
||||||
|
final updatedAlbum = await _albumApiRepository.updateAlbum(albumId, owner, thumbnailAssetId: assetId);
|
||||||
await _remoteAlbumRepository.update(updatedAlbum);
|
await _remoteAlbumRepository.update(updatedAlbum);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+1
@@ -568,6 +568,7 @@ Class | Method | HTTP request | Description
|
|||||||
- [SyncAlbumUserDeleteV1](doc//SyncAlbumUserDeleteV1.md)
|
- [SyncAlbumUserDeleteV1](doc//SyncAlbumUserDeleteV1.md)
|
||||||
- [SyncAlbumUserV1](doc//SyncAlbumUserV1.md)
|
- [SyncAlbumUserV1](doc//SyncAlbumUserV1.md)
|
||||||
- [SyncAlbumV1](doc//SyncAlbumV1.md)
|
- [SyncAlbumV1](doc//SyncAlbumV1.md)
|
||||||
|
- [SyncAlbumV2](doc//SyncAlbumV2.md)
|
||||||
- [SyncAssetDeleteV1](doc//SyncAssetDeleteV1.md)
|
- [SyncAssetDeleteV1](doc//SyncAssetDeleteV1.md)
|
||||||
- [SyncAssetEditDeleteV1](doc//SyncAssetEditDeleteV1.md)
|
- [SyncAssetEditDeleteV1](doc//SyncAssetEditDeleteV1.md)
|
||||||
- [SyncAssetEditV1](doc//SyncAssetEditV1.md)
|
- [SyncAssetEditV1](doc//SyncAssetEditV1.md)
|
||||||
|
|||||||
Generated
+1
@@ -316,6 +316,7 @@ part 'model/sync_album_to_asset_v1.dart';
|
|||||||
part 'model/sync_album_user_delete_v1.dart';
|
part 'model/sync_album_user_delete_v1.dart';
|
||||||
part 'model/sync_album_user_v1.dart';
|
part 'model/sync_album_user_v1.dart';
|
||||||
part 'model/sync_album_v1.dart';
|
part 'model/sync_album_v1.dart';
|
||||||
|
part 'model/sync_album_v2.dart';
|
||||||
part 'model/sync_asset_delete_v1.dart';
|
part 'model/sync_asset_delete_v1.dart';
|
||||||
part 'model/sync_asset_edit_delete_v1.dart';
|
part 'model/sync_asset_edit_delete_v1.dart';
|
||||||
part 'model/sync_asset_edit_v1.dart';
|
part 'model/sync_asset_edit_v1.dart';
|
||||||
|
|||||||
Generated
+2
@@ -678,6 +678,8 @@ class ApiClient {
|
|||||||
return SyncAlbumUserV1.fromJson(value);
|
return SyncAlbumUserV1.fromJson(value);
|
||||||
case 'SyncAlbumV1':
|
case 'SyncAlbumV1':
|
||||||
return SyncAlbumV1.fromJson(value);
|
return SyncAlbumV1.fromJson(value);
|
||||||
|
case 'SyncAlbumV2':
|
||||||
|
return SyncAlbumV2.fromJson(value);
|
||||||
case 'SyncAssetDeleteV1':
|
case 'SyncAssetDeleteV1':
|
||||||
return SyncAssetDeleteV1.fromJson(value);
|
return SyncAssetDeleteV1.fromJson(value);
|
||||||
case 'SyncAssetEditDeleteV1':
|
case 'SyncAssetEditDeleteV1':
|
||||||
|
|||||||
+2
-18
@@ -26,8 +26,6 @@ class AlbumResponseDto {
|
|||||||
required this.isActivityEnabled,
|
required this.isActivityEnabled,
|
||||||
this.lastModifiedAssetTimestamp,
|
this.lastModifiedAssetTimestamp,
|
||||||
this.order,
|
this.order,
|
||||||
required this.owner,
|
|
||||||
required this.ownerId,
|
|
||||||
required this.shared,
|
required this.shared,
|
||||||
this.startDate,
|
this.startDate,
|
||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
@@ -39,6 +37,7 @@ class AlbumResponseDto {
|
|||||||
/// Thumbnail asset ID
|
/// Thumbnail asset ID
|
||||||
String? albumThumbnailAssetId;
|
String? albumThumbnailAssetId;
|
||||||
|
|
||||||
|
/// First entry is always the album owner. Second entry is the auth user, if it differs from the owner. The rest are ordered alphabetically.
|
||||||
List<AlbumUserResponseDto> albumUsers;
|
List<AlbumUserResponseDto> albumUsers;
|
||||||
|
|
||||||
/// Number of assets
|
/// Number of assets
|
||||||
@@ -90,11 +89,6 @@ class AlbumResponseDto {
|
|||||||
///
|
///
|
||||||
AssetOrder? order;
|
AssetOrder? order;
|
||||||
|
|
||||||
UserResponseDto owner;
|
|
||||||
|
|
||||||
/// Owner user ID
|
|
||||||
String ownerId;
|
|
||||||
|
|
||||||
/// Is shared album
|
/// Is shared album
|
||||||
bool shared;
|
bool shared;
|
||||||
|
|
||||||
@@ -125,8 +119,6 @@ class AlbumResponseDto {
|
|||||||
other.isActivityEnabled == isActivityEnabled &&
|
other.isActivityEnabled == isActivityEnabled &&
|
||||||
other.lastModifiedAssetTimestamp == lastModifiedAssetTimestamp &&
|
other.lastModifiedAssetTimestamp == lastModifiedAssetTimestamp &&
|
||||||
other.order == order &&
|
other.order == order &&
|
||||||
other.owner == owner &&
|
|
||||||
other.ownerId == ownerId &&
|
|
||||||
other.shared == shared &&
|
other.shared == shared &&
|
||||||
other.startDate == startDate &&
|
other.startDate == startDate &&
|
||||||
other.updatedAt == updatedAt;
|
other.updatedAt == updatedAt;
|
||||||
@@ -147,14 +139,12 @@ class AlbumResponseDto {
|
|||||||
(isActivityEnabled.hashCode) +
|
(isActivityEnabled.hashCode) +
|
||||||
(lastModifiedAssetTimestamp == null ? 0 : lastModifiedAssetTimestamp!.hashCode) +
|
(lastModifiedAssetTimestamp == null ? 0 : lastModifiedAssetTimestamp!.hashCode) +
|
||||||
(order == null ? 0 : order!.hashCode) +
|
(order == null ? 0 : order!.hashCode) +
|
||||||
(owner.hashCode) +
|
|
||||||
(ownerId.hashCode) +
|
|
||||||
(shared.hashCode) +
|
(shared.hashCode) +
|
||||||
(startDate == null ? 0 : startDate!.hashCode) +
|
(startDate == null ? 0 : startDate!.hashCode) +
|
||||||
(updatedAt.hashCode);
|
(updatedAt.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'AlbumResponseDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, albumUsers=$albumUsers, assetCount=$assetCount, contributorCounts=$contributorCounts, createdAt=$createdAt, description=$description, endDate=$endDate, hasSharedLink=$hasSharedLink, id=$id, isActivityEnabled=$isActivityEnabled, lastModifiedAssetTimestamp=$lastModifiedAssetTimestamp, order=$order, owner=$owner, ownerId=$ownerId, shared=$shared, startDate=$startDate, updatedAt=$updatedAt]';
|
String toString() => 'AlbumResponseDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, albumUsers=$albumUsers, assetCount=$assetCount, contributorCounts=$contributorCounts, createdAt=$createdAt, description=$description, endDate=$endDate, hasSharedLink=$hasSharedLink, id=$id, isActivityEnabled=$isActivityEnabled, lastModifiedAssetTimestamp=$lastModifiedAssetTimestamp, order=$order, shared=$shared, startDate=$startDate, updatedAt=$updatedAt]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@@ -187,8 +177,6 @@ class AlbumResponseDto {
|
|||||||
} else {
|
} else {
|
||||||
// json[r'order'] = null;
|
// json[r'order'] = null;
|
||||||
}
|
}
|
||||||
json[r'owner'] = this.owner;
|
|
||||||
json[r'ownerId'] = this.ownerId;
|
|
||||||
json[r'shared'] = this.shared;
|
json[r'shared'] = this.shared;
|
||||||
if (this.startDate != null) {
|
if (this.startDate != null) {
|
||||||
json[r'startDate'] = this.startDate!.toUtc().toIso8601String();
|
json[r'startDate'] = this.startDate!.toUtc().toIso8601String();
|
||||||
@@ -221,8 +209,6 @@ class AlbumResponseDto {
|
|||||||
isActivityEnabled: mapValueOfType<bool>(json, r'isActivityEnabled')!,
|
isActivityEnabled: mapValueOfType<bool>(json, r'isActivityEnabled')!,
|
||||||
lastModifiedAssetTimestamp: mapDateTime(json, r'lastModifiedAssetTimestamp', r''),
|
lastModifiedAssetTimestamp: mapDateTime(json, r'lastModifiedAssetTimestamp', r''),
|
||||||
order: AssetOrder.fromJson(json[r'order']),
|
order: AssetOrder.fromJson(json[r'order']),
|
||||||
owner: UserResponseDto.fromJson(json[r'owner'])!,
|
|
||||||
ownerId: mapValueOfType<String>(json, r'ownerId')!,
|
|
||||||
shared: mapValueOfType<bool>(json, r'shared')!,
|
shared: mapValueOfType<bool>(json, r'shared')!,
|
||||||
startDate: mapDateTime(json, r'startDate', r''),
|
startDate: mapDateTime(json, r'startDate', r''),
|
||||||
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
|
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
|
||||||
@@ -282,8 +268,6 @@ class AlbumResponseDto {
|
|||||||
'hasSharedLink',
|
'hasSharedLink',
|
||||||
'id',
|
'id',
|
||||||
'isActivityEnabled',
|
'isActivityEnabled',
|
||||||
'owner',
|
|
||||||
'ownerId',
|
|
||||||
'shared',
|
'shared',
|
||||||
'updatedAt',
|
'updatedAt',
|
||||||
};
|
};
|
||||||
|
|||||||
+3
@@ -24,11 +24,13 @@ class AlbumUserRole {
|
|||||||
String toJson() => value;
|
String toJson() => value;
|
||||||
|
|
||||||
static const editor = AlbumUserRole._(r'editor');
|
static const editor = AlbumUserRole._(r'editor');
|
||||||
|
static const owner = AlbumUserRole._(r'owner');
|
||||||
static const viewer = AlbumUserRole._(r'viewer');
|
static const viewer = AlbumUserRole._(r'viewer');
|
||||||
|
|
||||||
/// List of all possible values in this [enum][AlbumUserRole].
|
/// List of all possible values in this [enum][AlbumUserRole].
|
||||||
static const values = <AlbumUserRole>[
|
static const values = <AlbumUserRole>[
|
||||||
editor,
|
editor,
|
||||||
|
owner,
|
||||||
viewer,
|
viewer,
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -69,6 +71,7 @@ class AlbumUserRoleTypeTransformer {
|
|||||||
if (data != null) {
|
if (data != null) {
|
||||||
switch (data) {
|
switch (data) {
|
||||||
case r'editor': return AlbumUserRole.editor;
|
case r'editor': return AlbumUserRole.editor;
|
||||||
|
case r'owner': return AlbumUserRole.owner;
|
||||||
case r'viewer': return AlbumUserRole.viewer;
|
case r'viewer': return AlbumUserRole.viewer;
|
||||||
default:
|
default:
|
||||||
if (!allowNull) {
|
if (!allowNull) {
|
||||||
|
|||||||
+170
@@ -0,0 +1,170 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
class SyncAlbumV2 {
|
||||||
|
/// Returns a new [SyncAlbumV2] instance.
|
||||||
|
SyncAlbumV2({
|
||||||
|
required this.createdAt,
|
||||||
|
required this.description,
|
||||||
|
required this.id,
|
||||||
|
required this.isActivityEnabled,
|
||||||
|
required this.name,
|
||||||
|
required this.order,
|
||||||
|
required this.thumbnailAssetId,
|
||||||
|
required this.updatedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Created at
|
||||||
|
DateTime createdAt;
|
||||||
|
|
||||||
|
/// Album description
|
||||||
|
String description;
|
||||||
|
|
||||||
|
/// Album ID
|
||||||
|
String id;
|
||||||
|
|
||||||
|
/// Is activity enabled
|
||||||
|
bool isActivityEnabled;
|
||||||
|
|
||||||
|
/// Album name
|
||||||
|
String name;
|
||||||
|
|
||||||
|
AssetOrder order;
|
||||||
|
|
||||||
|
/// Thumbnail asset ID
|
||||||
|
String? thumbnailAssetId;
|
||||||
|
|
||||||
|
/// Updated at
|
||||||
|
DateTime updatedAt;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is SyncAlbumV2 &&
|
||||||
|
other.createdAt == createdAt &&
|
||||||
|
other.description == description &&
|
||||||
|
other.id == id &&
|
||||||
|
other.isActivityEnabled == isActivityEnabled &&
|
||||||
|
other.name == name &&
|
||||||
|
other.order == order &&
|
||||||
|
other.thumbnailAssetId == thumbnailAssetId &&
|
||||||
|
other.updatedAt == updatedAt;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(createdAt.hashCode) +
|
||||||
|
(description.hashCode) +
|
||||||
|
(id.hashCode) +
|
||||||
|
(isActivityEnabled.hashCode) +
|
||||||
|
(name.hashCode) +
|
||||||
|
(order.hashCode) +
|
||||||
|
(thumbnailAssetId == null ? 0 : thumbnailAssetId!.hashCode) +
|
||||||
|
(updatedAt.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'SyncAlbumV2[createdAt=$createdAt, description=$description, id=$id, isActivityEnabled=$isActivityEnabled, name=$name, order=$order, thumbnailAssetId=$thumbnailAssetId, updatedAt=$updatedAt]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
|
||||||
|
? this.createdAt.millisecondsSinceEpoch
|
||||||
|
: this.createdAt.toUtc().toIso8601String();
|
||||||
|
json[r'description'] = this.description;
|
||||||
|
json[r'id'] = this.id;
|
||||||
|
json[r'isActivityEnabled'] = this.isActivityEnabled;
|
||||||
|
json[r'name'] = this.name;
|
||||||
|
json[r'order'] = this.order;
|
||||||
|
if (this.thumbnailAssetId != null) {
|
||||||
|
json[r'thumbnailAssetId'] = this.thumbnailAssetId;
|
||||||
|
} else {
|
||||||
|
// json[r'thumbnailAssetId'] = null;
|
||||||
|
}
|
||||||
|
json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
|
||||||
|
? this.updatedAt.millisecondsSinceEpoch
|
||||||
|
: this.updatedAt.toUtc().toIso8601String();
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [SyncAlbumV2] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static SyncAlbumV2? fromJson(dynamic value) {
|
||||||
|
upgradeDto(value, "SyncAlbumV2");
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return SyncAlbumV2(
|
||||||
|
createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!,
|
||||||
|
description: mapValueOfType<String>(json, r'description')!,
|
||||||
|
id: mapValueOfType<String>(json, r'id')!,
|
||||||
|
isActivityEnabled: mapValueOfType<bool>(json, r'isActivityEnabled')!,
|
||||||
|
name: mapValueOfType<String>(json, r'name')!,
|
||||||
|
order: AssetOrder.fromJson(json[r'order'])!,
|
||||||
|
thumbnailAssetId: mapValueOfType<String>(json, r'thumbnailAssetId'),
|
||||||
|
updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<SyncAlbumV2> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <SyncAlbumV2>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = SyncAlbumV2.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, SyncAlbumV2> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, SyncAlbumV2>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = SyncAlbumV2.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of SyncAlbumV2-objects as value to a dart map
|
||||||
|
static Map<String, List<SyncAlbumV2>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<SyncAlbumV2>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = SyncAlbumV2.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'createdAt',
|
||||||
|
'description',
|
||||||
|
'id',
|
||||||
|
'isActivityEnabled',
|
||||||
|
'name',
|
||||||
|
'order',
|
||||||
|
'thumbnailAssetId',
|
||||||
|
'updatedAt',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
+3
@@ -44,6 +44,7 @@ class SyncEntityType {
|
|||||||
static const partnerStackDeleteV1 = SyncEntityType._(r'PartnerStackDeleteV1');
|
static const partnerStackDeleteV1 = SyncEntityType._(r'PartnerStackDeleteV1');
|
||||||
static const partnerStackV1 = SyncEntityType._(r'PartnerStackV1');
|
static const partnerStackV1 = SyncEntityType._(r'PartnerStackV1');
|
||||||
static const albumV1 = SyncEntityType._(r'AlbumV1');
|
static const albumV1 = SyncEntityType._(r'AlbumV1');
|
||||||
|
static const albumV2 = SyncEntityType._(r'AlbumV2');
|
||||||
static const albumDeleteV1 = SyncEntityType._(r'AlbumDeleteV1');
|
static const albumDeleteV1 = SyncEntityType._(r'AlbumDeleteV1');
|
||||||
static const albumUserV1 = SyncEntityType._(r'AlbumUserV1');
|
static const albumUserV1 = SyncEntityType._(r'AlbumUserV1');
|
||||||
static const albumUserBackfillV1 = SyncEntityType._(r'AlbumUserBackfillV1');
|
static const albumUserBackfillV1 = SyncEntityType._(r'AlbumUserBackfillV1');
|
||||||
@@ -97,6 +98,7 @@ class SyncEntityType {
|
|||||||
partnerStackDeleteV1,
|
partnerStackDeleteV1,
|
||||||
partnerStackV1,
|
partnerStackV1,
|
||||||
albumV1,
|
albumV1,
|
||||||
|
albumV2,
|
||||||
albumDeleteV1,
|
albumDeleteV1,
|
||||||
albumUserV1,
|
albumUserV1,
|
||||||
albumUserBackfillV1,
|
albumUserBackfillV1,
|
||||||
@@ -185,6 +187,7 @@ class SyncEntityTypeTypeTransformer {
|
|||||||
case r'PartnerStackDeleteV1': return SyncEntityType.partnerStackDeleteV1;
|
case r'PartnerStackDeleteV1': return SyncEntityType.partnerStackDeleteV1;
|
||||||
case r'PartnerStackV1': return SyncEntityType.partnerStackV1;
|
case r'PartnerStackV1': return SyncEntityType.partnerStackV1;
|
||||||
case r'AlbumV1': return SyncEntityType.albumV1;
|
case r'AlbumV1': return SyncEntityType.albumV1;
|
||||||
|
case r'AlbumV2': return SyncEntityType.albumV2;
|
||||||
case r'AlbumDeleteV1': return SyncEntityType.albumDeleteV1;
|
case r'AlbumDeleteV1': return SyncEntityType.albumDeleteV1;
|
||||||
case r'AlbumUserV1': return SyncEntityType.albumUserV1;
|
case r'AlbumUserV1': return SyncEntityType.albumUserV1;
|
||||||
case r'AlbumUserBackfillV1': return SyncEntityType.albumUserBackfillV1;
|
case r'AlbumUserBackfillV1': return SyncEntityType.albumUserBackfillV1;
|
||||||
|
|||||||
+3
@@ -24,6 +24,7 @@ class SyncRequestType {
|
|||||||
String toJson() => value;
|
String toJson() => value;
|
||||||
|
|
||||||
static const albumsV1 = SyncRequestType._(r'AlbumsV1');
|
static const albumsV1 = SyncRequestType._(r'AlbumsV1');
|
||||||
|
static const albumsV2 = SyncRequestType._(r'AlbumsV2');
|
||||||
static const albumUsersV1 = SyncRequestType._(r'AlbumUsersV1');
|
static const albumUsersV1 = SyncRequestType._(r'AlbumUsersV1');
|
||||||
static const albumToAssetsV1 = SyncRequestType._(r'AlbumToAssetsV1');
|
static const albumToAssetsV1 = SyncRequestType._(r'AlbumToAssetsV1');
|
||||||
static const albumAssetsV1 = SyncRequestType._(r'AlbumAssetsV1');
|
static const albumAssetsV1 = SyncRequestType._(r'AlbumAssetsV1');
|
||||||
@@ -49,6 +50,7 @@ class SyncRequestType {
|
|||||||
/// List of all possible values in this [enum][SyncRequestType].
|
/// List of all possible values in this [enum][SyncRequestType].
|
||||||
static const values = <SyncRequestType>[
|
static const values = <SyncRequestType>[
|
||||||
albumsV1,
|
albumsV1,
|
||||||
|
albumsV2,
|
||||||
albumUsersV1,
|
albumUsersV1,
|
||||||
albumToAssetsV1,
|
albumToAssetsV1,
|
||||||
albumAssetsV1,
|
albumAssetsV1,
|
||||||
@@ -109,6 +111,7 @@ class SyncRequestTypeTypeTransformer {
|
|||||||
if (data != null) {
|
if (data != null) {
|
||||||
switch (data) {
|
switch (data) {
|
||||||
case r'AlbumsV1': return SyncRequestType.albumsV1;
|
case r'AlbumsV1': return SyncRequestType.albumsV1;
|
||||||
|
case r'AlbumsV2': return SyncRequestType.albumsV2;
|
||||||
case r'AlbumUsersV1': return SyncRequestType.albumUsersV1;
|
case r'AlbumUsersV1': return SyncRequestType.albumUsersV1;
|
||||||
case r'AlbumToAssetsV1': return SyncRequestType.albumToAssetsV1;
|
case r'AlbumToAssetsV1': return SyncRequestType.albumToAssetsV1;
|
||||||
case r'AlbumAssetsV1': return SyncRequestType.albumAssetsV1;
|
case r'AlbumAssetsV1': return SyncRequestType.albumAssetsV1;
|
||||||
|
|||||||
+4
@@ -27,6 +27,7 @@ import 'schema_v20.dart' as v20;
|
|||||||
import 'schema_v21.dart' as v21;
|
import 'schema_v21.dart' as v21;
|
||||||
import 'schema_v22.dart' as v22;
|
import 'schema_v22.dart' as v22;
|
||||||
import 'schema_v23.dart' as v23;
|
import 'schema_v23.dart' as v23;
|
||||||
|
import 'schema_v24.dart' as v24;
|
||||||
|
|
||||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
@override
|
@override
|
||||||
@@ -78,6 +79,8 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||||||
return v22.DatabaseAtV22(db);
|
return v22.DatabaseAtV22(db);
|
||||||
case 23:
|
case 23:
|
||||||
return v23.DatabaseAtV23(db);
|
return v23.DatabaseAtV23(db);
|
||||||
|
case 24:
|
||||||
|
return v24.DatabaseAtV24(db);
|
||||||
default:
|
default:
|
||||||
throw MissingSchemaException(version, versions);
|
throw MissingSchemaException(version, versions);
|
||||||
}
|
}
|
||||||
@@ -107,5 +110,6 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||||||
21,
|
21,
|
||||||
22,
|
22,
|
||||||
23,
|
23,
|
||||||
|
24,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
+9131
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@ import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.d
|
|||||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/remote_asset_cloud_id.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/remote_asset_cloud_id.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart';
|
||||||
@@ -151,13 +152,13 @@ class MediumRepositoryContext {
|
|||||||
String? thumbnailAssetId,
|
String? thumbnailAssetId,
|
||||||
}) async {
|
}) async {
|
||||||
id = TestUtils.uuid(id);
|
id = TestUtils.uuid(id);
|
||||||
return db
|
|
||||||
|
final album = await db
|
||||||
.into(db.remoteAlbumEntity)
|
.into(db.remoteAlbumEntity)
|
||||||
.insertReturning(
|
.insertReturning(
|
||||||
RemoteAlbumEntityCompanion(
|
RemoteAlbumEntityCompanion(
|
||||||
id: Value(id),
|
id: Value(id),
|
||||||
name: Value(name ?? 'remote_album_$id'),
|
name: Value(name ?? 'remote_album_$id'),
|
||||||
ownerId: Value(TestUtils.uuid(ownerId)),
|
|
||||||
createdAt: Value(TestUtils.date(createdAt)),
|
createdAt: Value(TestUtils.date(createdAt)),
|
||||||
updatedAt: Value(TestUtils.date(updatedAt)),
|
updatedAt: Value(TestUtils.date(updatedAt)),
|
||||||
description: Value(description ?? 'Description for album $id'),
|
description: Value(description ?? 'Description for album $id'),
|
||||||
@@ -166,6 +167,18 @@ class MediumRepositoryContext {
|
|||||||
thumbnailAssetId: Value(thumbnailAssetId),
|
thumbnailAssetId: Value(thumbnailAssetId),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await db
|
||||||
|
.into(db.remoteAlbumUserEntity)
|
||||||
|
.insert(
|
||||||
|
RemoteAlbumUserEntityCompanion.insert(
|
||||||
|
albumId: id,
|
||||||
|
userId: ownerId ?? const Uuid().v4(),
|
||||||
|
role: AlbumUserRole.owner,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return album;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> insertRemoteAlbumAsset({required String albumId, required String assetId}) {
|
Future<void> insertRemoteAlbumAsset({required String albumId, required String assetId}) {
|
||||||
|
|||||||
@@ -15279,9 +15279,11 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"albumUsers": {
|
"albumUsers": {
|
||||||
|
"description": "First entry is always the album owner. Second entry is the auth user, if it differs from the owner. The rest are ordered alphabetically.",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/components/schemas/AlbumUserResponseDto"
|
"$ref": "#/components/schemas/AlbumUserResponseDto"
|
||||||
},
|
},
|
||||||
|
"minItems": 1,
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"assetCount": {
|
"assetCount": {
|
||||||
@@ -15330,13 +15332,6 @@
|
|||||||
"order": {
|
"order": {
|
||||||
"$ref": "#/components/schemas/AssetOrder"
|
"$ref": "#/components/schemas/AssetOrder"
|
||||||
},
|
},
|
||||||
"owner": {
|
|
||||||
"$ref": "#/components/schemas/UserResponseDto"
|
|
||||||
},
|
|
||||||
"ownerId": {
|
|
||||||
"description": "Owner user ID",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"shared": {
|
"shared": {
|
||||||
"description": "Is shared album",
|
"description": "Is shared album",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
@@ -15362,8 +15357,6 @@
|
|||||||
"hasSharedLink",
|
"hasSharedLink",
|
||||||
"id",
|
"id",
|
||||||
"isActivityEnabled",
|
"isActivityEnabled",
|
||||||
"owner",
|
|
||||||
"ownerId",
|
|
||||||
"shared",
|
"shared",
|
||||||
"updatedAt"
|
"updatedAt"
|
||||||
],
|
],
|
||||||
@@ -15453,6 +15446,7 @@
|
|||||||
"description": "Album user role",
|
"description": "Album user role",
|
||||||
"enum": [
|
"enum": [
|
||||||
"editor",
|
"editor",
|
||||||
|
"owner",
|
||||||
"viewer"
|
"viewer"
|
||||||
],
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -22531,6 +22525,59 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"SyncAlbumV2": {
|
||||||
|
"properties": {
|
||||||
|
"createdAt": {
|
||||||
|
"description": "Created at",
|
||||||
|
"example": "2024-01-01T00:00:00.000Z",
|
||||||
|
"format": "date-time",
|
||||||
|
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"description": "Album description",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"description": "Album ID",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"isActivityEnabled": {
|
||||||
|
"description": "Is activity enabled",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"description": "Album name",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"order": {
|
||||||
|
"$ref": "#/components/schemas/AssetOrder"
|
||||||
|
},
|
||||||
|
"thumbnailAssetId": {
|
||||||
|
"description": "Thumbnail asset ID",
|
||||||
|
"nullable": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"description": "Updated at",
|
||||||
|
"example": "2024-01-01T00:00:00.000Z",
|
||||||
|
"format": "date-time",
|
||||||
|
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"createdAt",
|
||||||
|
"description",
|
||||||
|
"id",
|
||||||
|
"isActivityEnabled",
|
||||||
|
"name",
|
||||||
|
"order",
|
||||||
|
"thumbnailAssetId",
|
||||||
|
"updatedAt"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"SyncAssetDeleteV1": {
|
"SyncAssetDeleteV1": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"assetId": {
|
"assetId": {
|
||||||
@@ -23216,6 +23263,7 @@
|
|||||||
"PartnerStackDeleteV1",
|
"PartnerStackDeleteV1",
|
||||||
"PartnerStackV1",
|
"PartnerStackV1",
|
||||||
"AlbumV1",
|
"AlbumV1",
|
||||||
|
"AlbumV2",
|
||||||
"AlbumDeleteV1",
|
"AlbumDeleteV1",
|
||||||
"AlbumUserV1",
|
"AlbumUserV1",
|
||||||
"AlbumUserBackfillV1",
|
"AlbumUserBackfillV1",
|
||||||
@@ -23510,6 +23558,7 @@
|
|||||||
"description": "Sync request type",
|
"description": "Sync request type",
|
||||||
"enum": [
|
"enum": [
|
||||||
"AlbumsV1",
|
"AlbumsV1",
|
||||||
|
"AlbumsV2",
|
||||||
"AlbumUsersV1",
|
"AlbumUsersV1",
|
||||||
"AlbumToAssetsV1",
|
"AlbumToAssetsV1",
|
||||||
"AlbumAssetsV1",
|
"AlbumAssetsV1",
|
||||||
|
|||||||
@@ -457,6 +457,7 @@ export type AlbumResponseDto = {
|
|||||||
albumName: string;
|
albumName: string;
|
||||||
/** Thumbnail asset ID */
|
/** Thumbnail asset ID */
|
||||||
albumThumbnailAssetId: string | null;
|
albumThumbnailAssetId: string | null;
|
||||||
|
/** First entry is always the album owner. Second entry is the auth user, if it differs from the owner. The rest are ordered alphabetically. */
|
||||||
albumUsers: AlbumUserResponseDto[];
|
albumUsers: AlbumUserResponseDto[];
|
||||||
/** Number of assets */
|
/** Number of assets */
|
||||||
assetCount: number;
|
assetCount: number;
|
||||||
@@ -476,9 +477,6 @@ export type AlbumResponseDto = {
|
|||||||
/** Last modified asset timestamp */
|
/** Last modified asset timestamp */
|
||||||
lastModifiedAssetTimestamp?: string;
|
lastModifiedAssetTimestamp?: string;
|
||||||
order?: AssetOrder;
|
order?: AssetOrder;
|
||||||
owner: UserResponseDto;
|
|
||||||
/** Owner user ID */
|
|
||||||
ownerId: string;
|
|
||||||
/** Is shared album */
|
/** Is shared album */
|
||||||
shared: boolean;
|
shared: boolean;
|
||||||
/** Start date (earliest asset) */
|
/** Start date (earliest asset) */
|
||||||
@@ -2881,6 +2879,23 @@ export type SyncAlbumV1 = {
|
|||||||
/** Updated at */
|
/** Updated at */
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
};
|
};
|
||||||
|
export type SyncAlbumV2 = {
|
||||||
|
/** Created at */
|
||||||
|
createdAt: string;
|
||||||
|
/** Album description */
|
||||||
|
description: string;
|
||||||
|
/** Album ID */
|
||||||
|
id: string;
|
||||||
|
/** Is activity enabled */
|
||||||
|
isActivityEnabled: boolean;
|
||||||
|
/** Album name */
|
||||||
|
name: string;
|
||||||
|
order: AssetOrder;
|
||||||
|
/** Thumbnail asset ID */
|
||||||
|
thumbnailAssetId: string | null;
|
||||||
|
/** Updated at */
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
export type SyncAssetDeleteV1 = {
|
export type SyncAssetDeleteV1 = {
|
||||||
/** Asset ID */
|
/** Asset ID */
|
||||||
assetId: string;
|
assetId: string;
|
||||||
@@ -6731,6 +6746,7 @@ export enum AssetVisibility {
|
|||||||
}
|
}
|
||||||
export enum AlbumUserRole {
|
export enum AlbumUserRole {
|
||||||
Editor = "editor",
|
Editor = "editor",
|
||||||
|
Owner = "owner",
|
||||||
Viewer = "viewer"
|
Viewer = "viewer"
|
||||||
}
|
}
|
||||||
export enum BulkIdErrorReason {
|
export enum BulkIdErrorReason {
|
||||||
@@ -7110,6 +7126,7 @@ export enum SyncEntityType {
|
|||||||
PartnerStackDeleteV1 = "PartnerStackDeleteV1",
|
PartnerStackDeleteV1 = "PartnerStackDeleteV1",
|
||||||
PartnerStackV1 = "PartnerStackV1",
|
PartnerStackV1 = "PartnerStackV1",
|
||||||
AlbumV1 = "AlbumV1",
|
AlbumV1 = "AlbumV1",
|
||||||
|
AlbumV2 = "AlbumV2",
|
||||||
AlbumDeleteV1 = "AlbumDeleteV1",
|
AlbumDeleteV1 = "AlbumDeleteV1",
|
||||||
AlbumUserV1 = "AlbumUserV1",
|
AlbumUserV1 = "AlbumUserV1",
|
||||||
AlbumUserBackfillV1 = "AlbumUserBackfillV1",
|
AlbumUserBackfillV1 = "AlbumUserBackfillV1",
|
||||||
@@ -7142,6 +7159,7 @@ export enum SyncEntityType {
|
|||||||
}
|
}
|
||||||
export enum SyncRequestType {
|
export enum SyncRequestType {
|
||||||
AlbumsV1 = "AlbumsV1",
|
AlbumsV1 = "AlbumsV1",
|
||||||
|
AlbumsV2 = "AlbumsV2",
|
||||||
AlbumUsersV1 = "AlbumUsersV1",
|
AlbumUsersV1 = "AlbumUsersV1",
|
||||||
AlbumToAssetsV1 = "AlbumToAssetsV1",
|
AlbumToAssetsV1 = "AlbumToAssetsV1",
|
||||||
AlbumAssetsV1 = "AlbumAssetsV1",
|
AlbumAssetsV1 = "AlbumAssetsV1",
|
||||||
|
|||||||
@@ -195,7 +195,6 @@ export type SharedLink = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type Album = Selectable<AlbumTable> & {
|
export type Album = Selectable<AlbumTable> & {
|
||||||
owner: ShallowDehydrateObject<User>;
|
|
||||||
assets: ShallowDehydrateObject<Selectable<AssetTable>>[];
|
assets: ShallowDehydrateObject<Selectable<AssetTable>>[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { ShallowDehydrateObject } from 'kysely';
|
import { ShallowDehydrateObject } from 'kysely';
|
||||||
import _ from 'lodash';
|
|
||||||
import { createZodDto } from 'nestjs-zod';
|
import { createZodDto } from 'nestjs-zod';
|
||||||
import { AlbumUser, AuthSharedLink, User } from 'src/database';
|
import { AlbumUser, AuthSharedLink } from 'src/database';
|
||||||
import { BulkIdErrorReasonSchema } from 'src/dtos/asset-ids.response.dto';
|
import { BulkIdErrorReasonSchema } from 'src/dtos/asset-ids.response.dto';
|
||||||
import { MapAsset } from 'src/dtos/asset-response.dto';
|
import { MapAsset } from 'src/dtos/asset-response.dto';
|
||||||
import { UserResponseSchema, mapUser } from 'src/dtos/user.dto';
|
import { UserResponseSchema, mapUser } from 'src/dtos/user.dto';
|
||||||
@@ -104,7 +103,6 @@ const ContributorCountResponseSchema = z
|
|||||||
export const AlbumResponseSchema = z
|
export const AlbumResponseSchema = z
|
||||||
.object({
|
.object({
|
||||||
id: z.string().describe('Album ID'),
|
id: z.string().describe('Album ID'),
|
||||||
ownerId: z.string().describe('Owner user ID'),
|
|
||||||
albumName: z.string().describe('Album name'),
|
albumName: z.string().describe('Album name'),
|
||||||
description: z.string().describe('Album description'),
|
description: z.string().describe('Album description'),
|
||||||
// TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers.
|
// TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers.
|
||||||
@@ -113,9 +111,13 @@ export const AlbumResponseSchema = z
|
|||||||
updatedAt: z.string().meta({ format: 'date-time' }).describe('Last update date'),
|
updatedAt: z.string().meta({ format: 'date-time' }).describe('Last update date'),
|
||||||
albumThumbnailAssetId: z.string().nullable().describe('Thumbnail asset ID'),
|
albumThumbnailAssetId: z.string().nullable().describe('Thumbnail asset ID'),
|
||||||
shared: z.boolean().describe('Is shared album'),
|
shared: z.boolean().describe('Is shared album'),
|
||||||
albumUsers: z.array(AlbumUserResponseSchema),
|
albumUsers: z
|
||||||
|
.array(AlbumUserResponseSchema)
|
||||||
|
.min(1)
|
||||||
|
.describe(
|
||||||
|
'First entry is always the album owner. Second entry is the auth user, if it differs from the owner. The rest are ordered alphabetically.',
|
||||||
|
),
|
||||||
hasSharedLink: z.boolean().describe('Has shared link'),
|
hasSharedLink: z.boolean().describe('Has shared link'),
|
||||||
owner: UserResponseSchema,
|
|
||||||
assetCount: z.int().min(0).describe('Number of assets'),
|
assetCount: z.int().min(0).describe('Number of assets'),
|
||||||
// TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers.
|
// TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers.
|
||||||
lastModifiedAssetTimestamp: z
|
lastModifiedAssetTimestamp: z
|
||||||
@@ -155,8 +157,6 @@ export type MapAlbumDto = {
|
|||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
id: string;
|
id: string;
|
||||||
ownerId: string;
|
|
||||||
owner: ShallowDehydrateObject<User>;
|
|
||||||
isActivityEnabled: boolean;
|
isActivityEnabled: boolean;
|
||||||
order: AssetOrder;
|
order: AssetOrder;
|
||||||
};
|
};
|
||||||
@@ -174,12 +174,10 @@ export const mapAlbum = (entity: MaybeDehydrated<MapAlbumDto>): AlbumResponseDto
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const albumUsersSorted = _.orderBy(albumUsers, ['role', 'user.name']);
|
|
||||||
|
|
||||||
const assets = entity.assets || [];
|
const assets = entity.assets || [];
|
||||||
|
|
||||||
const hasSharedLink = !!entity.sharedLinks && entity.sharedLinks.length > 0;
|
const hasSharedLink = !!entity.sharedLinks && entity.sharedLinks.length > 0;
|
||||||
const hasSharedUser = albumUsers.length > 0;
|
const hasSharedUser = albumUsers.length > 1;
|
||||||
|
|
||||||
let startDate = assets.at(0)?.localDateTime;
|
let startDate = assets.at(0)?.localDateTime;
|
||||||
let endDate = assets.at(-1)?.localDateTime;
|
let endDate = assets.at(-1)?.localDateTime;
|
||||||
@@ -195,9 +193,7 @@ export const mapAlbum = (entity: MaybeDehydrated<MapAlbumDto>): AlbumResponseDto
|
|||||||
createdAt: asDateString(entity.createdAt),
|
createdAt: asDateString(entity.createdAt),
|
||||||
updatedAt: asDateString(entity.updatedAt),
|
updatedAt: asDateString(entity.updatedAt),
|
||||||
id: entity.id,
|
id: entity.id,
|
||||||
ownerId: entity.ownerId,
|
albumUsers,
|
||||||
owner: mapUser(entity.owner),
|
|
||||||
albumUsers: albumUsersSorted,
|
|
||||||
shared: hasSharedUser || hasSharedLink,
|
shared: hasSharedUser || hasSharedLink,
|
||||||
hasSharedLink,
|
hasSharedLink,
|
||||||
startDate: asDateString(startDate),
|
startDate: asDateString(startDate),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { createZodDto } from 'nestjs-zod';
|
import { createZodDto } from 'nestjs-zod';
|
||||||
import { AssetEditActionSchema } from 'src/dtos/editing.dto';
|
import { AssetEditActionSchema } from 'src/dtos/editing.dto';
|
||||||
import {
|
import {
|
||||||
|
AlbumUserRole,
|
||||||
AlbumUserRoleSchema,
|
AlbumUserRoleSchema,
|
||||||
AssetOrderSchema,
|
AssetOrderSchema,
|
||||||
AssetTypeSchema,
|
AssetTypeSchema,
|
||||||
@@ -211,6 +212,19 @@ const SyncAlbumV1Schema = z
|
|||||||
})
|
})
|
||||||
.meta({ id: 'SyncAlbumV1' });
|
.meta({ id: 'SyncAlbumV1' });
|
||||||
|
|
||||||
|
const SyncAlbumV2Schema = z
|
||||||
|
.object({
|
||||||
|
id: z.string().describe('Album ID'),
|
||||||
|
name: z.string().describe('Album name'),
|
||||||
|
description: z.string().describe('Album description'),
|
||||||
|
createdAt: isoDatetimeToDate.describe('Created at'),
|
||||||
|
updatedAt: isoDatetimeToDate.describe('Updated at'),
|
||||||
|
thumbnailAssetId: z.string().nullable().describe('Thumbnail asset ID'),
|
||||||
|
isActivityEnabled: z.boolean().describe('Is activity enabled'),
|
||||||
|
order: AssetOrderSchema,
|
||||||
|
})
|
||||||
|
.meta({ id: 'SyncAlbumV2' });
|
||||||
|
|
||||||
const SyncAlbumToAssetV1Schema = z
|
const SyncAlbumToAssetV1Schema = z
|
||||||
.object({
|
.object({
|
||||||
albumId: z.string().describe('Album ID'),
|
albumId: z.string().describe('Album ID'),
|
||||||
@@ -234,10 +248,21 @@ class SyncAlbumUserV1 extends createZodDto(SyncAlbumUserV1Schema) {}
|
|||||||
@ExtraModel()
|
@ExtraModel()
|
||||||
class SyncAlbumV1 extends createZodDto(SyncAlbumV1Schema) {}
|
class SyncAlbumV1 extends createZodDto(SyncAlbumV1Schema) {}
|
||||||
@ExtraModel()
|
@ExtraModel()
|
||||||
|
class SyncAlbumV2 extends createZodDto(SyncAlbumV2Schema) {}
|
||||||
|
@ExtraModel()
|
||||||
class SyncAlbumToAssetV1 extends createZodDto(SyncAlbumToAssetV1Schema) {}
|
class SyncAlbumToAssetV1 extends createZodDto(SyncAlbumToAssetV1Schema) {}
|
||||||
@ExtraModel()
|
@ExtraModel()
|
||||||
class SyncAlbumToAssetDeleteV1 extends createZodDto(SyncAlbumToAssetDeleteV1Schema) {}
|
class SyncAlbumToAssetDeleteV1 extends createZodDto(SyncAlbumToAssetDeleteV1Schema) {}
|
||||||
|
|
||||||
|
export function syncAlbumV2ToV1(
|
||||||
|
albumV2: SyncAlbumV2,
|
||||||
|
albumUsers: { userId: string; role: AlbumUserRole }[],
|
||||||
|
): SyncAlbumV1 {
|
||||||
|
const owner = albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
|
|
||||||
|
return { ...albumV2, ownerId: owner.userId };
|
||||||
|
}
|
||||||
|
|
||||||
const SyncMemoryV1Schema = z
|
const SyncMemoryV1Schema = z
|
||||||
.object({
|
.object({
|
||||||
id: z.string().describe('Memory ID'),
|
id: z.string().describe('Memory ID'),
|
||||||
@@ -407,6 +432,7 @@ export type SyncItem = {
|
|||||||
[SyncEntityType.PartnerAssetExifV1]: SyncAssetExifV1;
|
[SyncEntityType.PartnerAssetExifV1]: SyncAssetExifV1;
|
||||||
[SyncEntityType.PartnerAssetExifBackfillV1]: SyncAssetExifV1;
|
[SyncEntityType.PartnerAssetExifBackfillV1]: SyncAssetExifV1;
|
||||||
[SyncEntityType.AlbumV1]: SyncAlbumV1;
|
[SyncEntityType.AlbumV1]: SyncAlbumV1;
|
||||||
|
[SyncEntityType.AlbumV2]: SyncAlbumV2;
|
||||||
[SyncEntityType.AlbumDeleteV1]: SyncAlbumDeleteV1;
|
[SyncEntityType.AlbumDeleteV1]: SyncAlbumDeleteV1;
|
||||||
[SyncEntityType.AlbumUserV1]: SyncAlbumUserV1;
|
[SyncEntityType.AlbumUserV1]: SyncAlbumUserV1;
|
||||||
[SyncEntityType.AlbumUserBackfillV1]: SyncAlbumUserV1;
|
[SyncEntityType.AlbumUserBackfillV1]: SyncAlbumUserV1;
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export enum AssetFileType {
|
|||||||
|
|
||||||
export enum AlbumUserRole {
|
export enum AlbumUserRole {
|
||||||
Editor = 'editor',
|
Editor = 'editor',
|
||||||
|
Owner = 'owner',
|
||||||
Viewer = 'viewer',
|
Viewer = 'viewer',
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -797,6 +798,7 @@ export enum ExitCode {
|
|||||||
|
|
||||||
export enum SyncRequestType {
|
export enum SyncRequestType {
|
||||||
AlbumsV1 = 'AlbumsV1',
|
AlbumsV1 = 'AlbumsV1',
|
||||||
|
AlbumsV2 = 'AlbumsV2',
|
||||||
AlbumUsersV1 = 'AlbumUsersV1',
|
AlbumUsersV1 = 'AlbumUsersV1',
|
||||||
AlbumToAssetsV1 = 'AlbumToAssetsV1',
|
AlbumToAssetsV1 = 'AlbumToAssetsV1',
|
||||||
AlbumAssetsV1 = 'AlbumAssetsV1',
|
AlbumAssetsV1 = 'AlbumAssetsV1',
|
||||||
@@ -852,6 +854,7 @@ export enum SyncEntityType {
|
|||||||
PartnerStackV1 = 'PartnerStackV1',
|
PartnerStackV1 = 'PartnerStackV1',
|
||||||
|
|
||||||
AlbumV1 = 'AlbumV1',
|
AlbumV1 = 'AlbumV1',
|
||||||
|
AlbumV2 = 'AlbumV2',
|
||||||
AlbumDeleteV1 = 'AlbumDeleteV1',
|
AlbumDeleteV1 = 'AlbumDeleteV1',
|
||||||
|
|
||||||
AlbumUserV1 = 'AlbumUserV1',
|
AlbumUserV1 = 'AlbumUserV1',
|
||||||
|
|||||||
@@ -14,27 +14,26 @@ select
|
|||||||
"activity"."id"
|
"activity"."id"
|
||||||
from
|
from
|
||||||
"activity"
|
"activity"
|
||||||
left join "album" on "activity"."albumId" = "album"."id"
|
inner join "album" on "activity"."albumId" = "album"."id"
|
||||||
and "album"."deletedAt" is null
|
and "album"."deletedAt" is null
|
||||||
|
inner join "album_user" on "album"."id" = "album_user"."albumId"
|
||||||
|
and "album_user"."role" = 'owner'
|
||||||
|
and "album_user"."userId" = $1::uuid
|
||||||
where
|
where
|
||||||
"activity"."id" in ($1)
|
"activity"."id" in ($2)
|
||||||
and "album"."ownerId" = $2::uuid
|
|
||||||
|
|
||||||
-- AccessRepository.activity.checkCreateAccess
|
-- AccessRepository.activity.checkCreateAccess
|
||||||
select
|
select
|
||||||
"album"."id"
|
"album"."id"
|
||||||
from
|
from
|
||||||
"album"
|
"album"
|
||||||
left join "album_user" as "albumUsers" on "albumUsers"."albumId" = "album"."id"
|
inner join "album_user" as "albumUsers" on "albumUsers"."albumId" = "album"."id"
|
||||||
left join "user" on "user"."id" = "albumUsers"."userId"
|
inner join "user" on "user"."id" = "albumUsers"."userId"
|
||||||
and "user"."deletedAt" is null
|
and "user"."deletedAt" is null
|
||||||
where
|
where
|
||||||
"album"."id" in ($1)
|
"album"."id" in ($1)
|
||||||
and "album"."isActivityEnabled" = $2
|
and "album"."isActivityEnabled" = $2
|
||||||
and (
|
and "user"."id" = $3
|
||||||
"album"."ownerId" = $3
|
|
||||||
or "user"."id" = $4
|
|
||||||
)
|
|
||||||
and "album"."deletedAt" is null
|
and "album"."deletedAt" is null
|
||||||
|
|
||||||
-- AccessRepository.album.checkOwnerAccess
|
-- AccessRepository.album.checkOwnerAccess
|
||||||
@@ -42,9 +41,11 @@ select
|
|||||||
"album"."id"
|
"album"."id"
|
||||||
from
|
from
|
||||||
"album"
|
"album"
|
||||||
|
inner join "album_user" on "album"."id" = "album_user"."albumId"
|
||||||
|
and "album_user"."role" = 'owner'
|
||||||
|
and "album_user"."userId" = $1
|
||||||
where
|
where
|
||||||
"album"."id" in ($1)
|
"album"."id" in ($2)
|
||||||
and "album"."ownerId" = $2
|
|
||||||
and "album"."deletedAt" is null
|
and "album"."deletedAt" is null
|
||||||
|
|
||||||
-- AccessRepository.album.checkSharedAlbumAccess
|
-- AccessRepository.album.checkSharedAlbumAccess
|
||||||
@@ -52,8 +53,8 @@ select
|
|||||||
"album"."id"
|
"album"."id"
|
||||||
from
|
from
|
||||||
"album"
|
"album"
|
||||||
left join "album_user" on "album_user"."albumId" = "album"."id"
|
inner join "album_user" on "album_user"."albumId" = "album"."id"
|
||||||
left join "user" on "user"."id" = "album_user"."userId"
|
inner join "user" on "user"."id" = "album_user"."userId"
|
||||||
and "user"."deletedAt" is null
|
and "user"."deletedAt" is null
|
||||||
where
|
where
|
||||||
"album"."id" in ($1)
|
"album"."id" in ($1)
|
||||||
@@ -93,10 +94,7 @@ where
|
|||||||
"asset"."id" = any (target.ids)
|
"asset"."id" = any (target.ids)
|
||||||
or "asset"."livePhotoVideoId" = any (target.ids)
|
or "asset"."livePhotoVideoId" = any (target.ids)
|
||||||
)
|
)
|
||||||
and (
|
and "user"."id" = $2
|
||||||
"album"."ownerId" = $2
|
|
||||||
or "user"."id" = $3
|
|
||||||
)
|
|
||||||
and "album"."deletedAt" is null
|
and "album"."deletedAt" is null
|
||||||
|
|
||||||
-- AccessRepository.asset.checkOwnerAccess
|
-- AccessRepository.asset.checkOwnerAccess
|
||||||
|
|||||||
@@ -1,26 +1,17 @@
|
|||||||
-- NOTE: This file is auto generated by ./sql-generator
|
-- NOTE: This file is auto generated by ./sql-generator
|
||||||
|
|
||||||
-- AlbumRepository.getById
|
-- AlbumRepository.getById
|
||||||
|
with
|
||||||
|
"album_user" as (
|
||||||
|
select
|
||||||
|
*
|
||||||
|
from
|
||||||
|
"album_user"
|
||||||
|
where
|
||||||
|
"album_user"."albumId" = $1
|
||||||
|
)
|
||||||
select
|
select
|
||||||
"album".*,
|
"album".*,
|
||||||
(
|
|
||||||
select
|
|
||||||
to_json(obj)
|
|
||||||
from
|
|
||||||
(
|
|
||||||
select
|
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"email",
|
|
||||||
"avatarColor",
|
|
||||||
"profileImagePath",
|
|
||||||
"profileChangedAt"
|
|
||||||
from
|
|
||||||
"user"
|
|
||||||
where
|
|
||||||
"user"."id" = "album"."ownerId"
|
|
||||||
) as obj
|
|
||||||
) as "owner",
|
|
||||||
(
|
(
|
||||||
select
|
select
|
||||||
coalesce(json_agg(agg), '[]')
|
coalesce(json_agg(agg), '[]')
|
||||||
@@ -41,15 +32,21 @@ select
|
|||||||
"profileImagePath",
|
"profileImagePath",
|
||||||
"profileChangedAt"
|
"profileChangedAt"
|
||||||
from
|
from
|
||||||
"user"
|
(
|
||||||
where
|
select
|
||||||
"user"."id" = "album_user"."userId"
|
1
|
||||||
|
) as "dummy"
|
||||||
) as obj
|
) as obj
|
||||||
) as "user"
|
) as "user"
|
||||||
from
|
from
|
||||||
"album_user"
|
"album_user"
|
||||||
|
inner join "user" on "user"."id" = "album_user"."userId"
|
||||||
where
|
where
|
||||||
"album_user"."albumId" = "album"."id"
|
"album_user"."albumId" = "album"."id"
|
||||||
|
order by
|
||||||
|
"album_user"."role",
|
||||||
|
"album_user"."userId" = $2 desc,
|
||||||
|
"user"."name" asc
|
||||||
) as agg
|
) as agg
|
||||||
) as "albumUsers",
|
) as "albumUsers",
|
||||||
(
|
(
|
||||||
@@ -88,30 +85,12 @@ select
|
|||||||
from
|
from
|
||||||
"album"
|
"album"
|
||||||
where
|
where
|
||||||
"album"."id" = $1
|
"album"."id" = $3
|
||||||
and "album"."deletedAt" is null
|
and "album"."deletedAt" is null
|
||||||
|
|
||||||
-- AlbumRepository.getByAssetId
|
-- AlbumRepository.getByAssetId
|
||||||
select
|
select
|
||||||
"album".*,
|
"album".*,
|
||||||
(
|
|
||||||
select
|
|
||||||
to_json(obj)
|
|
||||||
from
|
|
||||||
(
|
|
||||||
select
|
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"email",
|
|
||||||
"avatarColor",
|
|
||||||
"profileImagePath",
|
|
||||||
"profileChangedAt"
|
|
||||||
from
|
|
||||||
"user"
|
|
||||||
where
|
|
||||||
"user"."id" = "album"."ownerId"
|
|
||||||
) as obj
|
|
||||||
) as "owner",
|
|
||||||
(
|
(
|
||||||
select
|
select
|
||||||
coalesce(json_agg(agg), '[]')
|
coalesce(json_agg(agg), '[]')
|
||||||
@@ -132,24 +111,28 @@ select
|
|||||||
"profileImagePath",
|
"profileImagePath",
|
||||||
"profileChangedAt"
|
"profileChangedAt"
|
||||||
from
|
from
|
||||||
"user"
|
(
|
||||||
where
|
select
|
||||||
"user"."id" = "album_user"."userId"
|
1
|
||||||
|
) as "dummy"
|
||||||
) as obj
|
) as obj
|
||||||
) as "user"
|
) as "user"
|
||||||
from
|
from
|
||||||
"album_user"
|
"album_user"
|
||||||
|
inner join "user" on "user"."id" = "album_user"."userId"
|
||||||
where
|
where
|
||||||
"album_user"."albumId" = "album"."id"
|
"album_user"."albumId" = "album"."id"
|
||||||
|
order by
|
||||||
|
"album_user"."role",
|
||||||
|
"album_user"."userId" = $1 desc,
|
||||||
|
"user"."name" asc
|
||||||
) as agg
|
) as agg
|
||||||
) as "albumUsers"
|
) as "albumUsers"
|
||||||
from
|
from
|
||||||
"album"
|
"album"
|
||||||
inner join "album_asset" on "album_asset"."albumId" = "album"."id"
|
inner join "album_asset" on "album_asset"."albumId" = "album"."id"
|
||||||
where
|
where
|
||||||
(
|
exists (
|
||||||
"album"."ownerId" = $1
|
|
||||||
or exists (
|
|
||||||
select
|
select
|
||||||
from
|
from
|
||||||
"album_user"
|
"album_user"
|
||||||
@@ -157,11 +140,9 @@ where
|
|||||||
"album_user"."albumId" = "album"."id"
|
"album_user"."albumId" = "album"."id"
|
||||||
and "album_user"."userId" = $2
|
and "album_user"."userId" = $2
|
||||||
)
|
)
|
||||||
)
|
|
||||||
and "album_asset"."assetId" = $3
|
and "album_asset"."assetId" = $3
|
||||||
and "album"."deletedAt" is null
|
and "album"."deletedAt" is null
|
||||||
order by
|
order by
|
||||||
"album"."createdAt" desc,
|
|
||||||
"album"."createdAt" desc
|
"album"."createdAt" desc
|
||||||
|
|
||||||
-- AlbumRepository.getByAssetIds
|
-- AlbumRepository.getByAssetIds
|
||||||
@@ -172,18 +153,15 @@ from
|
|||||||
"album"
|
"album"
|
||||||
inner join "album_asset" on "album_asset"."albumId" = "album"."id"
|
inner join "album_asset" on "album_asset"."albumId" = "album"."id"
|
||||||
where
|
where
|
||||||
(
|
exists (
|
||||||
"album"."ownerId" = $1
|
|
||||||
or exists (
|
|
||||||
select
|
select
|
||||||
from
|
from
|
||||||
"album_user"
|
"album_user"
|
||||||
where
|
where
|
||||||
"album_user"."albumId" = "album"."id"
|
"album_user"."albumId" = "album"."id"
|
||||||
and "album_user"."userId" = $2
|
and "album_user"."userId" = $1
|
||||||
)
|
)
|
||||||
)
|
and "album_asset"."assetId" in ($2)
|
||||||
and "album_asset"."assetId" in ($3)
|
|
||||||
and "album"."deletedAt" is null
|
and "album"."deletedAt" is null
|
||||||
|
|
||||||
-- AlbumRepository.getMetadataForIds
|
-- AlbumRepository.getMetadataForIds
|
||||||
@@ -210,24 +188,6 @@ group by
|
|||||||
-- AlbumRepository.getOwned
|
-- AlbumRepository.getOwned
|
||||||
select
|
select
|
||||||
"album".*,
|
"album".*,
|
||||||
(
|
|
||||||
select
|
|
||||||
to_json(obj)
|
|
||||||
from
|
|
||||||
(
|
|
||||||
select
|
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"email",
|
|
||||||
"avatarColor",
|
|
||||||
"profileImagePath",
|
|
||||||
"profileChangedAt"
|
|
||||||
from
|
|
||||||
"user"
|
|
||||||
where
|
|
||||||
"user"."id" = "album"."ownerId"
|
|
||||||
) as obj
|
|
||||||
) as "owner",
|
|
||||||
(
|
(
|
||||||
select
|
select
|
||||||
coalesce(json_agg(agg), '[]')
|
coalesce(json_agg(agg), '[]')
|
||||||
@@ -248,15 +208,21 @@ select
|
|||||||
"profileImagePath",
|
"profileImagePath",
|
||||||
"profileChangedAt"
|
"profileChangedAt"
|
||||||
from
|
from
|
||||||
"user"
|
(
|
||||||
where
|
select
|
||||||
"user"."id" = "album_user"."userId"
|
1
|
||||||
|
) as "dummy"
|
||||||
) as obj
|
) as obj
|
||||||
) as "user"
|
) as "user"
|
||||||
from
|
from
|
||||||
"album_user"
|
"album_user"
|
||||||
|
inner join "user" on "user"."id" = "album_user"."userId"
|
||||||
where
|
where
|
||||||
"album_user"."albumId" = "album"."id"
|
"album_user"."albumId" = "album"."id"
|
||||||
|
order by
|
||||||
|
"album_user"."role",
|
||||||
|
"album_user"."userId" = $1 desc,
|
||||||
|
"user"."name" asc
|
||||||
) as agg
|
) as agg
|
||||||
) as "albumUsers",
|
) as "albumUsers",
|
||||||
(
|
(
|
||||||
@@ -274,9 +240,11 @@ select
|
|||||||
) as "sharedLinks"
|
) as "sharedLinks"
|
||||||
from
|
from
|
||||||
"album"
|
"album"
|
||||||
|
inner join "album_user" on "album_user"."albumId" = "album"."id"
|
||||||
|
and "album_user"."userId" = $2
|
||||||
|
and "album_user"."role" = 'owner'
|
||||||
where
|
where
|
||||||
"album"."ownerId" = $1
|
"album"."deletedAt" is null
|
||||||
and "album"."deletedAt" is null
|
|
||||||
order by
|
order by
|
||||||
"album"."createdAt" desc
|
"album"."createdAt" desc
|
||||||
|
|
||||||
@@ -303,35 +271,23 @@ select
|
|||||||
"profileImagePath",
|
"profileImagePath",
|
||||||
"profileChangedAt"
|
"profileChangedAt"
|
||||||
from
|
from
|
||||||
"user"
|
(
|
||||||
where
|
select
|
||||||
"user"."id" = "album_user"."userId"
|
1
|
||||||
|
) as "dummy"
|
||||||
) as obj
|
) as obj
|
||||||
) as "user"
|
) as "user"
|
||||||
from
|
from
|
||||||
"album_user"
|
"album_user"
|
||||||
|
inner join "user" on "user"."id" = "album_user"."userId"
|
||||||
where
|
where
|
||||||
"album_user"."albumId" = "album"."id"
|
"album_user"."albumId" = "album"."id"
|
||||||
|
order by
|
||||||
|
"album_user"."role",
|
||||||
|
"album_user"."userId" = $1 desc,
|
||||||
|
"user"."name" asc
|
||||||
) as agg
|
) as agg
|
||||||
) as "albumUsers",
|
) as "albumUsers",
|
||||||
(
|
|
||||||
select
|
|
||||||
to_json(obj)
|
|
||||||
from
|
|
||||||
(
|
|
||||||
select
|
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"email",
|
|
||||||
"avatarColor",
|
|
||||||
"profileImagePath",
|
|
||||||
"profileChangedAt"
|
|
||||||
from
|
|
||||||
"user"
|
|
||||||
where
|
|
||||||
"user"."id" = "album"."ownerId"
|
|
||||||
) as obj
|
|
||||||
) as "owner",
|
|
||||||
(
|
(
|
||||||
select
|
select
|
||||||
coalesce(json_agg(agg), '[]')
|
coalesce(json_agg(agg), '[]')
|
||||||
@@ -347,35 +303,60 @@ select
|
|||||||
) as "sharedLinks"
|
) as "sharedLinks"
|
||||||
from
|
from
|
||||||
"album"
|
"album"
|
||||||
where
|
inner join (
|
||||||
(
|
|
||||||
exists (
|
|
||||||
select
|
select
|
||||||
|
"album_user"."albumId" as "id"
|
||||||
from
|
from
|
||||||
"album_user"
|
"album_user"
|
||||||
where
|
where
|
||||||
"album_user"."albumId" = "album"."id"
|
"album_user"."userId" = $2
|
||||||
and (
|
and "album_user"."albumId" in (
|
||||||
"album"."ownerId" = $1
|
|
||||||
or "album_user"."userId" = $2
|
|
||||||
)
|
|
||||||
)
|
|
||||||
or exists (
|
|
||||||
select
|
select
|
||||||
|
"album_user"."albumId"
|
||||||
|
from
|
||||||
|
"album_user"
|
||||||
|
where
|
||||||
|
"album_user"."role" != 'owner'
|
||||||
|
)
|
||||||
|
union
|
||||||
|
select
|
||||||
|
"shared_link"."albumId" as "id"
|
||||||
from
|
from
|
||||||
"shared_link"
|
"shared_link"
|
||||||
where
|
where
|
||||||
"shared_link"."albumId" = "album"."id"
|
"shared_link"."userId" = $3
|
||||||
and "shared_link"."userId" = $3
|
and "shared_link"."albumId" is not null
|
||||||
)
|
) as "matching" on "matching"."id" = "album"."id"
|
||||||
)
|
inner join "album_user" on "album_user"."albumId" = "album"."id"
|
||||||
and "album"."deletedAt" is null
|
and "album_user"."role" = 'owner'
|
||||||
|
where
|
||||||
|
"album"."deletedAt" is null
|
||||||
order by
|
order by
|
||||||
"album"."createdAt" desc
|
"album"."createdAt" desc
|
||||||
|
|
||||||
-- AlbumRepository.getNotShared
|
-- AlbumRepository.getNotShared
|
||||||
select
|
select
|
||||||
"album".*,
|
"album".*,
|
||||||
|
(
|
||||||
|
select
|
||||||
|
coalesce(json_agg(agg), '[]')
|
||||||
|
from
|
||||||
|
(
|
||||||
|
select
|
||||||
|
"shared_link".*
|
||||||
|
from
|
||||||
|
"shared_link"
|
||||||
|
where
|
||||||
|
"shared_link"."albumId" = "album"."id"
|
||||||
|
) as agg
|
||||||
|
) as "sharedLinks",
|
||||||
|
(
|
||||||
|
select
|
||||||
|
coalesce(json_agg(agg), '[]')
|
||||||
|
from
|
||||||
|
(
|
||||||
|
select
|
||||||
|
"album_user"."role",
|
||||||
(
|
(
|
||||||
select
|
select
|
||||||
to_json(obj)
|
to_json(obj)
|
||||||
@@ -389,22 +370,37 @@ select
|
|||||||
"profileImagePath",
|
"profileImagePath",
|
||||||
"profileChangedAt"
|
"profileChangedAt"
|
||||||
from
|
from
|
||||||
"user"
|
(
|
||||||
where
|
select
|
||||||
"user"."id" = "album"."ownerId"
|
1
|
||||||
|
) as "dummy"
|
||||||
) as obj
|
) as obj
|
||||||
) as "owner"
|
) as "user"
|
||||||
|
from
|
||||||
|
"album_user"
|
||||||
|
inner join "user" on "user"."id" = "album_user"."userId"
|
||||||
|
where
|
||||||
|
"album_user"."albumId" = "album"."id"
|
||||||
|
order by
|
||||||
|
"album_user"."role",
|
||||||
|
"album_user"."userId" = $1 desc,
|
||||||
|
"user"."name" asc
|
||||||
|
) as agg
|
||||||
|
) as "albumUsers"
|
||||||
from
|
from
|
||||||
"album"
|
"album"
|
||||||
|
inner join "album_user" on "album_user"."albumId" = "album"."id"
|
||||||
|
and "album_user"."userId" = $2
|
||||||
|
and "album_user"."role" = 'owner'
|
||||||
where
|
where
|
||||||
"album"."ownerId" = $1
|
"album"."deletedAt" is null
|
||||||
and "album"."deletedAt" is null
|
|
||||||
and not exists (
|
and not exists (
|
||||||
select
|
select
|
||||||
from
|
from
|
||||||
"album_user"
|
"album_user" as "au"
|
||||||
where
|
where
|
||||||
"album_user"."albumId" = "album"."id"
|
"au"."albumId" = "album"."id"
|
||||||
|
and "au"."role" != 'owner'
|
||||||
)
|
)
|
||||||
and not exists (
|
and not exists (
|
||||||
select
|
select
|
||||||
@@ -430,6 +426,117 @@ where
|
|||||||
"album_asset"."albumId" = $1
|
"album_asset"."albumId" = $1
|
||||||
and "album_asset"."assetId" in ($2)
|
and "album_asset"."assetId" in ($2)
|
||||||
|
|
||||||
|
-- AlbumRepository.addAssetIds
|
||||||
|
insert into
|
||||||
|
"album_asset"
|
||||||
|
select
|
||||||
|
$1::uuid as "albumId",
|
||||||
|
unnest($2::uuid[]) as "assetId"
|
||||||
|
from
|
||||||
|
(
|
||||||
|
select
|
||||||
|
1
|
||||||
|
) as "dummy"
|
||||||
|
on conflict do nothing
|
||||||
|
|
||||||
|
-- AlbumRepository.create
|
||||||
|
with
|
||||||
|
"album" as (
|
||||||
|
insert into
|
||||||
|
"album" ("albumName")
|
||||||
|
values
|
||||||
|
($1)
|
||||||
|
returning
|
||||||
|
*
|
||||||
|
),
|
||||||
|
"album_user" as (
|
||||||
|
insert into
|
||||||
|
"album_user"
|
||||||
|
select
|
||||||
|
"album"."id" as "albumId",
|
||||||
|
unnest($2::uuid[]) as "userId",
|
||||||
|
unnest($3::album_user_role_enum[]) as "role"
|
||||||
|
from
|
||||||
|
"album"
|
||||||
|
returning
|
||||||
|
"album_user"."albumId",
|
||||||
|
"album_user"."userId",
|
||||||
|
"album_user"."role"
|
||||||
|
),
|
||||||
|
"album_asset" as (
|
||||||
|
insert into
|
||||||
|
"album_asset"
|
||||||
|
select
|
||||||
|
"album"."id" as "albumId",
|
||||||
|
unnest($4::uuid[]) as "assetId"
|
||||||
|
from
|
||||||
|
"album"
|
||||||
|
on conflict do nothing
|
||||||
|
returning
|
||||||
|
"album_asset"."albumId",
|
||||||
|
"album_asset"."assetId"
|
||||||
|
)
|
||||||
|
select
|
||||||
|
"album".*,
|
||||||
|
(
|
||||||
|
select
|
||||||
|
coalesce(json_agg(agg), '[]')
|
||||||
|
from
|
||||||
|
(
|
||||||
|
select
|
||||||
|
"album_user"."role",
|
||||||
|
(
|
||||||
|
select
|
||||||
|
to_json(obj)
|
||||||
|
from
|
||||||
|
(
|
||||||
|
select
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"email",
|
||||||
|
"avatarColor",
|
||||||
|
"profileImagePath",
|
||||||
|
"profileChangedAt"
|
||||||
|
from
|
||||||
|
(
|
||||||
|
select
|
||||||
|
1
|
||||||
|
) as "dummy"
|
||||||
|
) as obj
|
||||||
|
) as "user"
|
||||||
|
from
|
||||||
|
"album_user"
|
||||||
|
inner join "user" on "user"."id" = "album_user"."userId"
|
||||||
|
where
|
||||||
|
"album_user"."albumId" = "album"."id"
|
||||||
|
order by
|
||||||
|
"album_user"."role",
|
||||||
|
"user"."name" asc
|
||||||
|
) as agg
|
||||||
|
) as "albumUsers",
|
||||||
|
(
|
||||||
|
select
|
||||||
|
json_agg("asset") as "assets"
|
||||||
|
from
|
||||||
|
(
|
||||||
|
select
|
||||||
|
"asset".*,
|
||||||
|
"asset_exif" as "exifInfo"
|
||||||
|
from
|
||||||
|
"asset"
|
||||||
|
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
|
||||||
|
inner join "album_asset" on "album_asset"."assetId" = "asset"."id"
|
||||||
|
where
|
||||||
|
"album_asset"."albumId" = "album"."id"
|
||||||
|
and "asset"."deletedAt" is null
|
||||||
|
and "asset"."visibility" in ('archive', 'timeline')
|
||||||
|
order by
|
||||||
|
"asset"."fileCreatedAt" desc
|
||||||
|
) as "asset"
|
||||||
|
) as "assets"
|
||||||
|
from
|
||||||
|
"album"
|
||||||
|
|
||||||
-- AlbumRepository.getContributorCounts
|
-- AlbumRepository.getContributorCounts
|
||||||
select
|
select
|
||||||
"asset"."ownerId" as "userId",
|
"asset"."ownerId" as "userId",
|
||||||
|
|||||||
@@ -139,7 +139,15 @@ from
|
|||||||
from
|
from
|
||||||
"user"
|
"user"
|
||||||
where
|
where
|
||||||
"user"."id" = "album"."ownerId"
|
exists (
|
||||||
|
select
|
||||||
|
from
|
||||||
|
"album_user"
|
||||||
|
where
|
||||||
|
"album_user"."role" = 'owner'
|
||||||
|
and "album_user"."albumId" = "album"."id"
|
||||||
|
and "album_user"."userId" = "user"."id"
|
||||||
|
)
|
||||||
and "user"."deletedAt" is null
|
and "user"."deletedAt" is null
|
||||||
) as "owner" on true
|
) as "owner" on true
|
||||||
where
|
where
|
||||||
@@ -201,7 +209,15 @@ from
|
|||||||
from
|
from
|
||||||
"user"
|
"user"
|
||||||
where
|
where
|
||||||
"user"."id" = "album"."ownerId"
|
exists (
|
||||||
|
select
|
||||||
|
from
|
||||||
|
"album_user"
|
||||||
|
where
|
||||||
|
"album_user"."role" = 'owner'
|
||||||
|
and "album_user"."albumId" = "album"."id"
|
||||||
|
and "album_user"."userId" = "user"."id"
|
||||||
|
)
|
||||||
and "user"."deletedAt" is null
|
and "user"."deletedAt" is null
|
||||||
) as "owner" on true
|
) as "owner" on true
|
||||||
where
|
where
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ order by
|
|||||||
-- SyncRepository.album.getUpserts
|
-- SyncRepository.album.getUpserts
|
||||||
select distinct
|
select distinct
|
||||||
on ("album"."id", "album"."updateId") "album"."id",
|
on ("album"."id", "album"."updateId") "album"."id",
|
||||||
"album"."ownerId",
|
|
||||||
"album"."albumName" as "name",
|
"album"."albumName" as "name",
|
||||||
"album"."description",
|
"album"."description",
|
||||||
"album"."createdAt",
|
"album"."createdAt",
|
||||||
@@ -44,13 +43,19 @@ from
|
|||||||
where
|
where
|
||||||
"album"."updateId" < $1
|
"album"."updateId" < $1
|
||||||
and "album"."updateId" > $2
|
and "album"."updateId" > $2
|
||||||
and (
|
and "album_users"."userId" = $3
|
||||||
"album"."ownerId" = $3
|
|
||||||
or "album_users"."userId" = $4
|
|
||||||
)
|
|
||||||
order by
|
order by
|
||||||
"album"."updateId" asc
|
"album"."updateId" asc
|
||||||
|
|
||||||
|
-- SyncRepository.album.getAlbumUsers
|
||||||
|
select
|
||||||
|
"userId",
|
||||||
|
"role"
|
||||||
|
from
|
||||||
|
"album_user"
|
||||||
|
where
|
||||||
|
"albumId" = $1
|
||||||
|
|
||||||
-- SyncRepository.albumAsset.getBackfill
|
-- SyncRepository.albumAsset.getBackfill
|
||||||
select
|
select
|
||||||
"asset"."id",
|
"asset"."id",
|
||||||
@@ -109,16 +114,12 @@ select
|
|||||||
from
|
from
|
||||||
"asset" as "asset"
|
"asset" as "asset"
|
||||||
inner join "album_asset" on "album_asset"."assetId" = "asset"."id"
|
inner join "album_asset" on "album_asset"."assetId" = "asset"."id"
|
||||||
inner join "album" on "album"."id" = "album_asset"."albumId"
|
inner join "album_user" on "album_user"."albumId" = "album_asset"."albumId"
|
||||||
left join "album_user" on "album_user"."albumId" = "album_asset"."albumId"
|
|
||||||
where
|
where
|
||||||
"asset"."updateId" < $1
|
"asset"."updateId" < $1
|
||||||
and "asset"."updateId" > $2
|
and "asset"."updateId" > $2
|
||||||
and "album_asset"."updateId" <= $3
|
and "album_asset"."updateId" <= $3
|
||||||
and (
|
and "album_user"."userId" = $4
|
||||||
"album"."ownerId" = $4
|
|
||||||
or "album_user"."userId" = $5
|
|
||||||
)
|
|
||||||
order by
|
order by
|
||||||
"asset"."updateId" asc
|
"asset"."updateId" asc
|
||||||
|
|
||||||
@@ -147,15 +148,11 @@ select
|
|||||||
from
|
from
|
||||||
"album_asset" as "album_asset"
|
"album_asset" as "album_asset"
|
||||||
inner join "asset" on "asset"."id" = "album_asset"."assetId"
|
inner join "asset" on "asset"."id" = "album_asset"."assetId"
|
||||||
inner join "album" on "album"."id" = "album_asset"."albumId"
|
inner join "album_user" on "album_user"."albumId" = "album_asset"."albumId"
|
||||||
left join "album_user" on "album_user"."albumId" = "album_asset"."albumId"
|
|
||||||
where
|
where
|
||||||
"album_asset"."updateId" < $1
|
"album_asset"."updateId" < $1
|
||||||
and "album_asset"."updateId" > $2
|
and "album_asset"."updateId" > $2
|
||||||
and (
|
and "album_user"."userId" = $3
|
||||||
"album"."ownerId" = $3
|
|
||||||
or "album_user"."userId" = $4
|
|
||||||
)
|
|
||||||
order by
|
order by
|
||||||
"album_asset"."updateId" asc
|
"album_asset"."updateId" asc
|
||||||
|
|
||||||
@@ -229,16 +226,12 @@ select
|
|||||||
from
|
from
|
||||||
"asset_exif" as "asset_exif"
|
"asset_exif" as "asset_exif"
|
||||||
inner join "album_asset" on "album_asset"."assetId" = "asset_exif"."assetId"
|
inner join "album_asset" on "album_asset"."assetId" = "asset_exif"."assetId"
|
||||||
inner join "album" on "album"."id" = "album_asset"."albumId"
|
inner join "album_user" on "album_user"."albumId" = "album_asset"."albumId"
|
||||||
left join "album_user" on "album_user"."albumId" = "album_asset"."albumId"
|
|
||||||
where
|
where
|
||||||
"asset_exif"."updateId" < $1
|
"asset_exif"."updateId" < $1
|
||||||
and "asset_exif"."updateId" > $2
|
and "asset_exif"."updateId" > $2
|
||||||
and "album_asset"."updateId" <= $3
|
and "album_asset"."updateId" <= $3
|
||||||
and (
|
and "album_user"."userId" = $4
|
||||||
"album"."ownerId" = $4
|
|
||||||
or "album_user"."userId" = $5
|
|
||||||
)
|
|
||||||
order by
|
order by
|
||||||
"asset_exif"."updateId" asc
|
"asset_exif"."updateId" asc
|
||||||
|
|
||||||
@@ -278,10 +271,7 @@ from
|
|||||||
where
|
where
|
||||||
"album_asset"."updateId" < $1
|
"album_asset"."updateId" < $1
|
||||||
and "album_asset"."updateId" > $2
|
and "album_asset"."updateId" > $2
|
||||||
and (
|
and "album_user"."userId" = $3
|
||||||
"album"."ownerId" = $3
|
|
||||||
or "album_user"."userId" = $4
|
|
||||||
)
|
|
||||||
order by
|
order by
|
||||||
"album_asset"."updateId" asc
|
"album_asset"."updateId" asc
|
||||||
|
|
||||||
@@ -311,21 +301,12 @@ where
|
|||||||
"album_asset_audit"."id" < $1
|
"album_asset_audit"."id" < $1
|
||||||
and "album_asset_audit"."id" > $2
|
and "album_asset_audit"."id" > $2
|
||||||
and "albumId" in (
|
and "albumId" in (
|
||||||
select
|
|
||||||
"id"
|
|
||||||
from
|
|
||||||
"album"
|
|
||||||
where
|
|
||||||
"ownerId" = $3
|
|
||||||
union
|
|
||||||
(
|
|
||||||
select
|
select
|
||||||
"album_user"."albumId" as "id"
|
"album_user"."albumId" as "id"
|
||||||
from
|
from
|
||||||
"album_user"
|
"album_user"
|
||||||
where
|
where
|
||||||
"album_user"."userId" = $4
|
"album_user"."userId" = $3
|
||||||
)
|
|
||||||
)
|
)
|
||||||
order by
|
order by
|
||||||
"album_asset_audit"."id" asc
|
"album_asset_audit"."id" asc
|
||||||
@@ -337,15 +318,11 @@ select
|
|||||||
"album_asset"."updateId"
|
"album_asset"."updateId"
|
||||||
from
|
from
|
||||||
"album_asset" as "album_asset"
|
"album_asset" as "album_asset"
|
||||||
inner join "album" on "album"."id" = "album_asset"."albumId"
|
inner join "album_user" on "album_user"."albumId" = "album_asset"."albumId"
|
||||||
left join "album_user" on "album_user"."albumId" = "album_asset"."albumId"
|
|
||||||
where
|
where
|
||||||
"album_asset"."updateId" < $1
|
"album_asset"."updateId" < $1
|
||||||
and "album_asset"."updateId" > $2
|
and "album_asset"."updateId" > $2
|
||||||
and (
|
and "album_user"."userId" = $3
|
||||||
"album"."ownerId" = $3
|
|
||||||
or "album_user"."userId" = $4
|
|
||||||
)
|
|
||||||
order by
|
order by
|
||||||
"album_asset"."updateId" asc
|
"album_asset"."updateId" asc
|
||||||
|
|
||||||
@@ -376,21 +353,12 @@ where
|
|||||||
"album_user_audit"."id" < $1
|
"album_user_audit"."id" < $1
|
||||||
and "album_user_audit"."id" > $2
|
and "album_user_audit"."id" > $2
|
||||||
and "albumId" in (
|
and "albumId" in (
|
||||||
select
|
|
||||||
"id"
|
|
||||||
from
|
|
||||||
"album"
|
|
||||||
where
|
|
||||||
"ownerId" = $3
|
|
||||||
union
|
|
||||||
(
|
|
||||||
select
|
select
|
||||||
"album_user"."albumId" as "id"
|
"album_user"."albumId" as "id"
|
||||||
from
|
from
|
||||||
"album_user"
|
"album_user"
|
||||||
where
|
where
|
||||||
"album_user"."userId" = $4
|
"album_user"."userId" = $3
|
||||||
)
|
|
||||||
)
|
)
|
||||||
order by
|
order by
|
||||||
"album_user_audit"."id" asc
|
"album_user_audit"."id" asc
|
||||||
@@ -407,21 +375,12 @@ where
|
|||||||
"album_user"."updateId" < $1
|
"album_user"."updateId" < $1
|
||||||
and "album_user"."updateId" > $2
|
and "album_user"."updateId" > $2
|
||||||
and "album_user"."albumId" in (
|
and "album_user"."albumId" in (
|
||||||
select
|
|
||||||
"id"
|
|
||||||
from
|
|
||||||
"album"
|
|
||||||
where
|
|
||||||
"ownerId" = $3
|
|
||||||
union
|
|
||||||
(
|
|
||||||
select
|
select
|
||||||
"albumUsers"."albumId" as "id"
|
"albumUsers"."albumId" as "id"
|
||||||
from
|
from
|
||||||
"album_user" as "albumUsers"
|
"album_user" as "albumUsers"
|
||||||
where
|
where
|
||||||
"albumUsers"."userId" = $4
|
"albumUsers"."userId" = $3
|
||||||
)
|
|
||||||
)
|
)
|
||||||
order by
|
order by
|
||||||
"album_user"."updateId" asc
|
"album_user"."updateId" asc
|
||||||
|
|||||||
@@ -35,9 +35,14 @@ class ActivityAccess {
|
|||||||
return this.db
|
return this.db
|
||||||
.selectFrom('activity')
|
.selectFrom('activity')
|
||||||
.select('activity.id')
|
.select('activity.id')
|
||||||
.leftJoin('album', (join) => join.onRef('activity.albumId', '=', 'album.id').on('album.deletedAt', 'is', null))
|
.innerJoin('album', (join) => join.onRef('activity.albumId', '=', 'album.id').on('album.deletedAt', 'is', null))
|
||||||
|
.innerJoin('album_user', (join) =>
|
||||||
|
join
|
||||||
|
.onRef('album.id', '=', 'album_user.albumId')
|
||||||
|
.on('album_user.role', '=', sql.lit(AlbumUserRole.Owner))
|
||||||
|
.on('album_user.userId', '=', asUuid(userId)),
|
||||||
|
)
|
||||||
.where('activity.id', 'in', [...activityIds])
|
.where('activity.id', 'in', [...activityIds])
|
||||||
.whereRef('album.ownerId', '=', asUuid(userId))
|
|
||||||
.execute()
|
.execute()
|
||||||
.then((activities) => new Set(activities.map((activity) => activity.id)));
|
.then((activities) => new Set(activities.map((activity) => activity.id)));
|
||||||
}
|
}
|
||||||
@@ -52,11 +57,11 @@ class ActivityAccess {
|
|||||||
return this.db
|
return this.db
|
||||||
.selectFrom('album')
|
.selectFrom('album')
|
||||||
.select('album.id')
|
.select('album.id')
|
||||||
.leftJoin('album_user as albumUsers', 'albumUsers.albumId', 'album.id')
|
.innerJoin('album_user as albumUsers', 'albumUsers.albumId', 'album.id')
|
||||||
.leftJoin('user', (join) => join.onRef('user.id', '=', 'albumUsers.userId').on('user.deletedAt', 'is', null))
|
.innerJoin('user', (join) => join.onRef('user.id', '=', 'albumUsers.userId').on('user.deletedAt', 'is', null))
|
||||||
.where('album.id', 'in', [...albumIds])
|
.where('album.id', 'in', [...albumIds])
|
||||||
.where('album.isActivityEnabled', '=', true)
|
.where('album.isActivityEnabled', '=', true)
|
||||||
.where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('user.id', '=', userId)]))
|
.where((eb) => eb('user.id', '=', userId))
|
||||||
.where('album.deletedAt', 'is', null)
|
.where('album.deletedAt', 'is', null)
|
||||||
.execute()
|
.execute()
|
||||||
.then((albums) => new Set(albums.map((album) => album.id)));
|
.then((albums) => new Set(albums.map((album) => album.id)));
|
||||||
@@ -77,7 +82,12 @@ class AlbumAccess {
|
|||||||
.selectFrom('album')
|
.selectFrom('album')
|
||||||
.select('album.id')
|
.select('album.id')
|
||||||
.where('album.id', 'in', [...albumIds])
|
.where('album.id', 'in', [...albumIds])
|
||||||
.where('album.ownerId', '=', userId)
|
.innerJoin('album_user', (join) =>
|
||||||
|
join
|
||||||
|
.onRef('album.id', '=', 'album_user.albumId')
|
||||||
|
.on('album_user.role', '=', sql.lit(AlbumUserRole.Owner))
|
||||||
|
.on('album_user.userId', '=', userId),
|
||||||
|
)
|
||||||
.where('album.deletedAt', 'is', null)
|
.where('album.deletedAt', 'is', null)
|
||||||
.execute()
|
.execute()
|
||||||
.then((albums) => new Set(albums.map((album) => album.id)));
|
.then((albums) => new Set(albums.map((album) => album.id)));
|
||||||
@@ -96,8 +106,8 @@ class AlbumAccess {
|
|||||||
return this.db
|
return this.db
|
||||||
.selectFrom('album')
|
.selectFrom('album')
|
||||||
.select('album.id')
|
.select('album.id')
|
||||||
.leftJoin('album_user', 'album_user.albumId', 'album.id')
|
.innerJoin('album_user', 'album_user.albumId', 'album.id')
|
||||||
.leftJoin('user', (join) => join.onRef('user.id', '=', 'album_user.userId').on('user.deletedAt', 'is', null))
|
.innerJoin('user', (join) => join.onRef('user.id', '=', 'album_user.userId').on('user.deletedAt', 'is', null))
|
||||||
.where('album.id', 'in', [...albumIds])
|
.where('album.id', 'in', [...albumIds])
|
||||||
.where('album.deletedAt', 'is', null)
|
.where('album.deletedAt', 'is', null)
|
||||||
.where('user.id', '=', userId)
|
.where('user.id', '=', userId)
|
||||||
@@ -152,7 +162,7 @@ class AssetAccess {
|
|||||||
eb('asset.livePhotoVideoId', '=', sql<string>`any(target.ids)`),
|
eb('asset.livePhotoVideoId', '=', sql<string>`any(target.ids)`),
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
.where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('user.id', '=', userId)]))
|
.where('user.id', '=', userId)
|
||||||
.where('album.deletedAt', 'is', null)
|
.where('album.deletedAt', 'is', null)
|
||||||
.execute()
|
.execute()
|
||||||
.then((assets) => {
|
.then((assets) => {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { DummyValue, GenerateSql } from 'src/decorators';
|
|||||||
import { AssetVisibility } from 'src/enum';
|
import { AssetVisibility } from 'src/enum';
|
||||||
import { DB } from 'src/schema';
|
import { DB } from 'src/schema';
|
||||||
import { ActivityTable } from 'src/schema/tables/activity.table';
|
import { ActivityTable } from 'src/schema/tables/activity.table';
|
||||||
import { asUuid } from 'src/utils/database';
|
import { asUuid, dummy } from 'src/utils/database';
|
||||||
|
|
||||||
export interface ActivitySearch {
|
export interface ActivitySearch {
|
||||||
albumId?: string;
|
albumId?: string;
|
||||||
@@ -31,11 +31,7 @@ export class ActivityRepository {
|
|||||||
join.onRef('user2.id', '=', 'activity.userId').on('user2.deletedAt', 'is', null),
|
join.onRef('user2.id', '=', 'activity.userId').on('user2.deletedAt', 'is', null),
|
||||||
)
|
)
|
||||||
.innerJoinLateral(
|
.innerJoinLateral(
|
||||||
(eb) =>
|
(eb) => eb.selectFrom(dummy).select(columns.userWithPrefix).as('user'),
|
||||||
eb
|
|
||||||
.selectFrom(sql`(select 1)`.as('dummy'))
|
|
||||||
.select(columns.userWithPrefix)
|
|
||||||
.as('user'),
|
|
||||||
(join) => join.onTrue(),
|
(join) => join.onTrue(),
|
||||||
)
|
)
|
||||||
.select((eb) => eb.fn.toJson('user').as('user'))
|
.select((eb) => eb.fn.toJson('user').as('user'))
|
||||||
|
|||||||
@@ -14,10 +14,11 @@ import { InjectKysely } from 'nestjs-kysely';
|
|||||||
import { columns } from 'src/database';
|
import { columns } from 'src/database';
|
||||||
import { Chunked, ChunkedArray, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
|
import { Chunked, ChunkedArray, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { AlbumUserCreateDto } from 'src/dtos/album.dto';
|
import { AlbumUserCreateDto } from 'src/dtos/album.dto';
|
||||||
|
import { AlbumUserRole } from 'src/enum';
|
||||||
import { DB } from 'src/schema';
|
import { DB } from 'src/schema';
|
||||||
import { AlbumTable } from 'src/schema/tables/album.table';
|
import { AlbumTable } from 'src/schema/tables/album.table';
|
||||||
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
||||||
import { withDefaultVisibility } from 'src/utils/database';
|
import { asUuid, dummy, withDefaultVisibility } from 'src/utils/database';
|
||||||
|
|
||||||
export interface AlbumAssetCount {
|
export interface AlbumAssetCount {
|
||||||
albumId: string;
|
albumId: string;
|
||||||
@@ -31,33 +32,25 @@ export interface AlbumInfoOptions {
|
|||||||
withAssets: boolean;
|
withAssets: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const withOwner = (eb: ExpressionBuilder<DB, 'album'>) => {
|
const withAlbumUsers = (authUserId?: string) => (eb: ExpressionBuilder<DB, 'album'>) =>
|
||||||
return jsonObjectFrom(eb.selectFrom('user').select(columns.user).whereRef('user.id', '=', 'album.ownerId'))
|
jsonArrayFrom(
|
||||||
.$notNull()
|
|
||||||
.as('owner');
|
|
||||||
};
|
|
||||||
|
|
||||||
const withAlbumUsers = (eb: ExpressionBuilder<DB, 'album'>) => {
|
|
||||||
return jsonArrayFrom(
|
|
||||||
eb
|
eb
|
||||||
.selectFrom('album_user')
|
.selectFrom('album_user')
|
||||||
|
.innerJoin('user', 'user.id', 'album_user.userId')
|
||||||
|
.whereRef('album_user.albumId', '=', 'album.id')
|
||||||
.select('album_user.role')
|
.select('album_user.role')
|
||||||
.select((eb) =>
|
.select((eb) => jsonObjectFrom(eb.selectFrom(dummy).select(columns.user)).$notNull().as('user'))
|
||||||
jsonObjectFrom(eb.selectFrom('user').select(columns.user).whereRef('user.id', '=', 'album_user.userId'))
|
.orderBy('album_user.role')
|
||||||
.$notNull()
|
.$if(!!authUserId, (qb) => qb.orderBy((eb) => eb('album_user.userId', '=', authUserId!), 'desc'))
|
||||||
.as('user'),
|
.orderBy('user.name', 'asc'),
|
||||||
)
|
|
||||||
.whereRef('album_user.albumId', '=', 'album.id'),
|
|
||||||
)
|
)
|
||||||
.$notNull()
|
.$notNull()
|
||||||
.as('albumUsers');
|
.as('albumUsers');
|
||||||
};
|
|
||||||
|
|
||||||
const withSharedLink = (eb: ExpressionBuilder<DB, 'album'>) => {
|
const withSharedLink = (eb: ExpressionBuilder<DB, 'album'>) =>
|
||||||
return jsonArrayFrom(
|
jsonArrayFrom(
|
||||||
eb.selectFrom('shared_link').selectAll('shared_link').whereRef('shared_link.albumId', '=', 'album.id'),
|
eb.selectFrom('shared_link').selectAll('shared_link').whereRef('shared_link.albumId', '=', 'album.id'),
|
||||||
).as('sharedLinks');
|
).as('sharedLinks');
|
||||||
};
|
|
||||||
|
|
||||||
const withAssets = (eb: ExpressionBuilder<DB, 'album'>) => {
|
const withAssets = (eb: ExpressionBuilder<DB, 'album'>) => {
|
||||||
return eb
|
return eb
|
||||||
@@ -80,19 +73,28 @@ const withAssets = (eb: ExpressionBuilder<DB, 'album'>) => {
|
|||||||
.as('assets');
|
.as('assets');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isAlbumOwned = (ownerId: string) => (eb: ExpressionBuilder<DB, 'album'>) =>
|
||||||
|
eb.exists(
|
||||||
|
eb
|
||||||
|
.selectFrom('album_user')
|
||||||
|
.whereRef('album_user.albumId', '=', 'album.id')
|
||||||
|
.where('album_user.role', '=', AlbumUserRole.Owner)
|
||||||
|
.where('album_user.userId', '=', ownerId),
|
||||||
|
);
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AlbumRepository {
|
export class AlbumRepository {
|
||||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||||
|
|
||||||
@GenerateSql({ params: [DummyValue.UUID, { withAssets: true }] })
|
@GenerateSql({ params: [DummyValue.UUID, { withAssets: true }, DummyValue.UUID] })
|
||||||
async getById(id: string, options: AlbumInfoOptions) {
|
getById(id: string, options: AlbumInfoOptions, authUserId?: string) {
|
||||||
return this.db
|
return this.db
|
||||||
|
.with('album_user', (qb) => qb.selectFrom('album_user').selectAll().where('album_user.albumId', '=', id))
|
||||||
.selectFrom('album')
|
.selectFrom('album')
|
||||||
.selectAll('album')
|
.selectAll('album')
|
||||||
.where('album.id', '=', id)
|
.where('album.id', '=', id)
|
||||||
.where('album.deletedAt', 'is', null)
|
.where('album.deletedAt', 'is', null)
|
||||||
.select(withOwner)
|
.select(withAlbumUsers(authUserId))
|
||||||
.select(withAlbumUsers)
|
|
||||||
.select(withSharedLink)
|
.select(withSharedLink)
|
||||||
.$if(options.withAssets, (eb) => eb.select(withAssets))
|
.$if(options.withAssets, (eb) => eb.select(withAssets))
|
||||||
.$narrowType<{ assets: NotNull }>()
|
.$narrowType<{ assets: NotNull }>()
|
||||||
@@ -100,27 +102,22 @@ export class AlbumRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
|
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
|
||||||
async getByAssetId(ownerId: string, assetId: string) {
|
getByAssetId(ownerId: string, assetId: string) {
|
||||||
return this.db
|
return this.db
|
||||||
.selectFrom('album')
|
.selectFrom('album')
|
||||||
.selectAll('album')
|
.selectAll('album')
|
||||||
.innerJoin('album_asset', 'album_asset.albumId', 'album.id')
|
.innerJoin('album_asset', 'album_asset.albumId', 'album.id')
|
||||||
.where((eb) =>
|
.where((eb) =>
|
||||||
eb.or([
|
|
||||||
eb('album.ownerId', '=', ownerId),
|
|
||||||
eb.exists(
|
eb.exists(
|
||||||
eb
|
eb
|
||||||
.selectFrom('album_user')
|
.selectFrom('album_user')
|
||||||
.whereRef('album_user.albumId', '=', 'album.id')
|
.whereRef('album_user.albumId', '=', 'album.id')
|
||||||
.where('album_user.userId', '=', ownerId),
|
.where('album_user.userId', '=', ownerId),
|
||||||
),
|
),
|
||||||
]),
|
|
||||||
)
|
)
|
||||||
.where('album_asset.assetId', '=', assetId)
|
.where('album_asset.assetId', '=', assetId)
|
||||||
.where('album.deletedAt', 'is', null)
|
.where('album.deletedAt', 'is', null)
|
||||||
.orderBy('album.createdAt', 'desc')
|
.select(withAlbumUsers(ownerId))
|
||||||
.select(withOwner)
|
|
||||||
.select(withAlbumUsers)
|
|
||||||
.orderBy('album.createdAt', 'desc')
|
.orderBy('album.createdAt', 'desc')
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
@@ -137,15 +134,12 @@ export class AlbumRepository {
|
|||||||
.select('album.id')
|
.select('album.id')
|
||||||
.innerJoin('album_asset', 'album_asset.albumId', 'album.id')
|
.innerJoin('album_asset', 'album_asset.albumId', 'album.id')
|
||||||
.where((eb) =>
|
.where((eb) =>
|
||||||
eb.or([
|
|
||||||
eb('album.ownerId', '=', ownerId),
|
|
||||||
eb.exists(
|
eb.exists(
|
||||||
eb
|
eb
|
||||||
.selectFrom('album_user')
|
.selectFrom('album_user')
|
||||||
.whereRef('album_user.albumId', '=', 'album.id')
|
.whereRef('album_user.albumId', '=', 'album.id')
|
||||||
.where('album_user.userId', '=', ownerId),
|
.where('album_user.userId', '=', ownerId),
|
||||||
),
|
),
|
||||||
]),
|
|
||||||
)
|
)
|
||||||
.where('album_asset.assetId', 'in', assetIds)
|
.where('album_asset.assetId', 'in', assetIds)
|
||||||
.where('album.deletedAt', 'is', null)
|
.where('album.deletedAt', 'is', null)
|
||||||
@@ -190,15 +184,19 @@ export class AlbumRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [DummyValue.UUID] })
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
async getOwned(ownerId: string) {
|
getOwned(ownerId: string) {
|
||||||
return this.db
|
return this.db
|
||||||
.selectFrom('album')
|
.selectFrom('album')
|
||||||
.selectAll('album')
|
.selectAll('album')
|
||||||
.select(withOwner)
|
.innerJoin('album_user', (join) =>
|
||||||
.select(withAlbumUsers)
|
join
|
||||||
.select(withSharedLink)
|
.onRef('album_user.albumId', '=', 'album.id')
|
||||||
.where('album.ownerId', '=', ownerId)
|
.on('album_user.userId', '=', ownerId)
|
||||||
|
.on('album_user.role', '=', sql.lit(AlbumUserRole.Owner)),
|
||||||
|
)
|
||||||
.where('album.deletedAt', 'is', null)
|
.where('album.deletedAt', 'is', null)
|
||||||
|
.select(withAlbumUsers(ownerId))
|
||||||
|
.select(withSharedLink)
|
||||||
.orderBy('album.createdAt', 'desc')
|
.orderBy('album.createdAt', 'desc')
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
@@ -207,29 +205,40 @@ export class AlbumRepository {
|
|||||||
* Get albums shared with and shared by owner.
|
* Get albums shared with and shared by owner.
|
||||||
*/
|
*/
|
||||||
@GenerateSql({ params: [DummyValue.UUID] })
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
async getShared(ownerId: string) {
|
getShared(ownerId: string) {
|
||||||
return this.db
|
return this.db
|
||||||
.selectFrom('album')
|
.selectFrom('album')
|
||||||
.selectAll('album')
|
.selectAll('album')
|
||||||
.where((eb) =>
|
.innerJoin(
|
||||||
eb.or([
|
(eb) =>
|
||||||
eb.exists(
|
|
||||||
eb
|
eb
|
||||||
.selectFrom('album_user')
|
.selectFrom('album_user')
|
||||||
.whereRef('album_user.albumId', '=', 'album.id')
|
.select('album_user.albumId as id')
|
||||||
.where((eb) => eb.or([eb('album.ownerId', '=', ownerId), eb('album_user.userId', '=', ownerId)])),
|
.where('album_user.userId', '=', ownerId)
|
||||||
),
|
.where(
|
||||||
eb.exists(
|
'album_user.albumId',
|
||||||
|
'in',
|
||||||
|
eb
|
||||||
|
.selectFrom('album_user')
|
||||||
|
.select('album_user.albumId')
|
||||||
|
.where('album_user.role', '!=', sql.lit(AlbumUserRole.Owner)),
|
||||||
|
)
|
||||||
|
.union(
|
||||||
eb
|
eb
|
||||||
.selectFrom('shared_link')
|
.selectFrom('shared_link')
|
||||||
.whereRef('shared_link.albumId', '=', 'album.id')
|
.where('shared_link.userId', '=', ownerId)
|
||||||
.where('shared_link.userId', '=', ownerId),
|
.where('shared_link.albumId', 'is not', null)
|
||||||
),
|
.select('shared_link.albumId as id')
|
||||||
]),
|
.$narrowType<{ id: NotNull }>(),
|
||||||
|
)
|
||||||
|
.as('matching'),
|
||||||
|
(join) => join.onRef('matching.id', '=', 'album.id'),
|
||||||
|
)
|
||||||
|
.innerJoin('album_user', (join) =>
|
||||||
|
join.onRef('album_user.albumId', '=', 'album.id').on('album_user.role', '=', sql.lit(AlbumUserRole.Owner)),
|
||||||
)
|
)
|
||||||
.where('album.deletedAt', 'is', null)
|
.where('album.deletedAt', 'is', null)
|
||||||
.select(withAlbumUsers)
|
.select(withAlbumUsers(ownerId))
|
||||||
.select(withOwner)
|
|
||||||
.select(withSharedLink)
|
.select(withSharedLink)
|
||||||
.orderBy('album.createdAt', 'desc')
|
.orderBy('album.createdAt', 'desc')
|
||||||
.execute();
|
.execute();
|
||||||
@@ -239,29 +248,45 @@ export class AlbumRepository {
|
|||||||
* Get albums of owner that are _not_ shared
|
* Get albums of owner that are _not_ shared
|
||||||
*/
|
*/
|
||||||
@GenerateSql({ params: [DummyValue.UUID] })
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
async getNotShared(ownerId: string) {
|
getNotShared(ownerId: string) {
|
||||||
return this.db
|
return this.db
|
||||||
.selectFrom('album')
|
.selectFrom('album')
|
||||||
.selectAll('album')
|
.selectAll('album')
|
||||||
.where('album.ownerId', '=', ownerId)
|
.innerJoin('album_user', (join) =>
|
||||||
|
join
|
||||||
|
.onRef('album_user.albumId', '=', 'album.id')
|
||||||
|
.on('album_user.userId', '=', ownerId)
|
||||||
|
.on('album_user.role', '=', sql.lit(AlbumUserRole.Owner)),
|
||||||
|
)
|
||||||
.where('album.deletedAt', 'is', null)
|
.where('album.deletedAt', 'is', null)
|
||||||
.where((eb) => eb.not(eb.exists(eb.selectFrom('album_user').whereRef('album_user.albumId', '=', 'album.id'))))
|
.where(({ not, exists, selectFrom }) =>
|
||||||
.where((eb) => eb.not(eb.exists(eb.selectFrom('shared_link').whereRef('shared_link.albumId', '=', 'album.id'))))
|
not(
|
||||||
.select(withOwner)
|
exists(
|
||||||
|
selectFrom('album_user as au')
|
||||||
|
.whereRef('au.albumId', '=', 'album.id')
|
||||||
|
.where('au.role', '!=', sql.lit(AlbumUserRole.Owner)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.where(({ not, exists, selectFrom }) =>
|
||||||
|
not(exists(selectFrom('shared_link').whereRef('shared_link.albumId', '=', 'album.id'))),
|
||||||
|
)
|
||||||
|
.select(withSharedLink)
|
||||||
|
.select(withAlbumUsers(ownerId))
|
||||||
.orderBy('album.createdAt', 'desc')
|
.orderBy('album.createdAt', 'desc')
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
async restoreAll(userId: string): Promise<void> {
|
async restoreAll(userId: string): Promise<void> {
|
||||||
await this.db.updateTable('album').set({ deletedAt: null }).where('ownerId', '=', userId).execute();
|
await this.db.updateTable('album').set({ deletedAt: null }).where(isAlbumOwned(userId)).execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
async softDeleteAll(userId: string): Promise<void> {
|
async softDeleteAll(userId: string): Promise<void> {
|
||||||
await this.db.updateTable('album').set({ deletedAt: new Date() }).where('ownerId', '=', userId).execute();
|
await this.db.updateTable('album').set({ deletedAt: new Date() }).where(isAlbumOwned(userId)).execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteAll(userId: string): Promise<void> {
|
async deleteAll(userId: string): Promise<void> {
|
||||||
await this.db.deleteFrom('album').where('ownerId', '=', userId).execute();
|
await this.db.deleteFrom('album').where(isAlbumOwned(userId)).execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [[DummyValue.UUID]] })
|
@GenerateSql({ params: [[DummyValue.UUID]] })
|
||||||
@@ -306,52 +331,86 @@ export class AlbumRepository {
|
|||||||
.then((results) => new Set(results.map(({ assetId }) => assetId)));
|
.then((results) => new Set(results.map(({ assetId }) => assetId)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] })
|
||||||
async addAssetIds(albumId: string, assetIds: string[]): Promise<void> {
|
async addAssetIds(albumId: string, assetIds: string[]): Promise<void> {
|
||||||
await this.addAssets(this.db, albumId, assetIds);
|
if (assetIds.length === 0) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
create(album: Insertable<AlbumTable>, assetIds: string[], albumUsers: AlbumUserCreateDto[]) {
|
await this.db
|
||||||
return this.db.transaction().execute(async (tx) => {
|
.insertInto('album_asset')
|
||||||
const newAlbum = await tx.insertInto('album').values(album).returning('album.id').executeTakeFirst();
|
.expression((eb) =>
|
||||||
|
eb.selectFrom(dummy).select([asUuid(albumId).as('albumId'), sql`unnest(${assetIds}::uuid[])`.as('assetId')]),
|
||||||
if (!newAlbum) {
|
|
||||||
throw new Error('Failed to create album');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (assetIds.length > 0) {
|
|
||||||
await this.addAssets(tx, newAlbum.id, assetIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (albumUsers.length > 0) {
|
|
||||||
await tx
|
|
||||||
.insertInto('album_user')
|
|
||||||
.values(
|
|
||||||
albumUsers.map((albumUser) => ({ albumId: newAlbum.id, userId: albumUser.userId, role: albumUser.role })),
|
|
||||||
)
|
)
|
||||||
|
.onConflict((oc) => oc.doNothing())
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
return tx
|
@GenerateSql({
|
||||||
.selectFrom('album')
|
params: [
|
||||||
.selectAll('album')
|
{ albumName: DummyValue.STRING },
|
||||||
.where('id', '=', newAlbum.id)
|
[],
|
||||||
.select(withOwner)
|
[{ userId: DummyValue.UUID, role: AlbumUserRole.Owner }, DummyValue.UUID],
|
||||||
.select(withAssets)
|
],
|
||||||
.select(withAlbumUsers)
|
})
|
||||||
.$narrowType<{ assets: NotNull }>()
|
async create(
|
||||||
.executeTakeFirstOrThrow();
|
album: Insertable<AlbumTable>,
|
||||||
});
|
assetIds: string[],
|
||||||
|
albumUsers: AlbumUserCreateDto[],
|
||||||
|
authUserId: string,
|
||||||
|
) {
|
||||||
|
if (!albumUsers.some((u) => u.role === AlbumUserRole.Owner)) {
|
||||||
|
throw new Error('Album must have an owner');
|
||||||
}
|
}
|
||||||
|
|
||||||
update(id: string, album: Updateable<AlbumTable>) {
|
const userIds = albumUsers.map((u) => u.userId);
|
||||||
|
const roles = albumUsers.map((u) => u.role);
|
||||||
|
|
||||||
|
const result = await this.db
|
||||||
|
.with('album', (db) => db.insertInto('album').values(album).returningAll())
|
||||||
|
.with('album_user', (db) =>
|
||||||
|
db
|
||||||
|
.insertInto('album_user')
|
||||||
|
.expression((eb) =>
|
||||||
|
eb
|
||||||
|
.selectFrom('album')
|
||||||
|
.select(({ ref }) => [
|
||||||
|
ref('album.id').as('albumId'),
|
||||||
|
sql`unnest(${userIds}::uuid[])`.as('userId'),
|
||||||
|
sql`unnest(${roles}::album_user_role_enum[])`.as('role'),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.returning(['album_user.albumId', 'album_user.userId', 'album_user.role']),
|
||||||
|
)
|
||||||
|
.with('album_asset', (db) =>
|
||||||
|
db
|
||||||
|
.insertInto('album_asset')
|
||||||
|
.expression((eb) =>
|
||||||
|
eb
|
||||||
|
.selectFrom('album')
|
||||||
|
.select(({ ref }) => [ref('album.id').as('albumId'), sql`unnest(${assetIds}::uuid[])`.as('assetId')]),
|
||||||
|
)
|
||||||
|
.onConflict((oc) => oc.doNothing())
|
||||||
|
.returning(['album_asset.albumId', 'album_asset.assetId']),
|
||||||
|
)
|
||||||
|
.selectFrom('album')
|
||||||
|
.selectAll('album')
|
||||||
|
.select(withAlbumUsers(authUserId))
|
||||||
|
.select(withAssets)
|
||||||
|
.$narrowType<{ assets: NotNull }>()
|
||||||
|
.executeTakeFirstOrThrow();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(id: string, album: Updateable<AlbumTable>, authUserId: string) {
|
||||||
return this.db
|
return this.db
|
||||||
.updateTable('album')
|
.updateTable('album')
|
||||||
.set(album)
|
.set(album)
|
||||||
.where('id', '=', id)
|
.where('album.id', '=', id)
|
||||||
.returningAll('album')
|
.returningAll('album')
|
||||||
.returning(withOwner)
|
|
||||||
.returning(withSharedLink)
|
.returning(withSharedLink)
|
||||||
.returning(withAlbumUsers)
|
.returning(withAlbumUsers(authUserId))
|
||||||
.executeTakeFirstOrThrow();
|
.executeTakeFirstOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,19 +418,6 @@ export class AlbumRepository {
|
|||||||
await this.db.deleteFrom('album').where('id', '=', id).execute();
|
await this.db.deleteFrom('album').where('id', '=', id).execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Chunked({ paramIndex: 2, chunkSize: 30_000 })
|
|
||||||
private async addAssets(db: Kysely<DB>, albumId: string, assetIds: string[]): Promise<void> {
|
|
||||||
if (assetIds.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await db
|
|
||||||
.insertInto('album_asset')
|
|
||||||
.values(assetIds.map((assetId) => ({ albumId, assetId })))
|
|
||||||
.onConflict((oc) => oc.doNothing())
|
|
||||||
.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Chunked({ chunkSize: 30_000 })
|
@Chunked({ chunkSize: 30_000 })
|
||||||
async addAssetIdsToAlbums(values: { albumId: string; assetId: string }[]): Promise<void> {
|
async addAssetIdsToAlbums(values: { albumId: string; assetId: string }[]): Promise<void> {
|
||||||
if (values.length === 0) {
|
if (values.length === 0) {
|
||||||
@@ -402,7 +448,7 @@ export class AlbumRepository {
|
|||||||
albumThumbnailAssetId: this.updateThumbnailBuilder(eb)
|
albumThumbnailAssetId: this.updateThumbnailBuilder(eb)
|
||||||
.select('album_asset.assetId')
|
.select('album_asset.assetId')
|
||||||
.orderBy('asset.fileCreatedAt', 'desc')
|
.orderBy('asset.fileCreatedAt', 'desc')
|
||||||
.limit(1),
|
.limit(sql.lit(1)),
|
||||||
}))
|
}))
|
||||||
.where((eb) =>
|
.where((eb) =>
|
||||||
eb.or([
|
eb.or([
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ type EventMap = {
|
|||||||
|
|
||||||
// album events
|
// album events
|
||||||
AlbumUpdate: [{ id: string; recipientId: string }];
|
AlbumUpdate: [{ id: string; recipientId: string }];
|
||||||
AlbumInvite: [{ id: string; userId: string }];
|
AlbumInvite: [{ id: string; userId: string; senderName: string }];
|
||||||
|
|
||||||
// asset events
|
// asset events
|
||||||
AssetCreate: [{ asset: Asset }];
|
AssetCreate: [{ asset: Asset }];
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { DB } from 'src/schema';
|
|||||||
import { AssetFaceTable } from 'src/schema/tables/asset-face.table';
|
import { AssetFaceTable } from 'src/schema/tables/asset-face.table';
|
||||||
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
|
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
|
||||||
import { PersonTable } from 'src/schema/tables/person.table';
|
import { PersonTable } from 'src/schema/tables/person.table';
|
||||||
import { removeUndefinedKeys, withFilePath } from 'src/utils/database';
|
import { dummy, removeUndefinedKeys, withFilePath } from 'src/utils/database';
|
||||||
import { paginationHelper, PaginationOptions } from 'src/utils/pagination';
|
import { paginationHelper, PaginationOptions } from 'src/utils/pagination';
|
||||||
|
|
||||||
export interface PersonSearchOptions {
|
export interface PersonSearchOptions {
|
||||||
@@ -418,7 +418,7 @@ export class PersonRepository {
|
|||||||
(query as any) = query.with('added_embeddings', (db) => db.insertInto('face_search').values(embeddingsToAdd));
|
(query as any) = query.with('added_embeddings', (db) => db.insertInto('face_search').values(embeddingsToAdd));
|
||||||
}
|
}
|
||||||
|
|
||||||
await query.selectFrom(sql`(select 1)`.as('dummy')).execute();
|
await query.selectFrom(dummy).execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(person: Updateable<PersonTable> & { id: string }) {
|
async update(person: Updateable<PersonTable> & { id: string }) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import _ from 'lodash';
|
|||||||
import { InjectKysely } from 'nestjs-kysely';
|
import { InjectKysely } from 'nestjs-kysely';
|
||||||
import { Album, columns } from 'src/database';
|
import { Album, columns } from 'src/database';
|
||||||
import { ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
|
import { ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { SharedLinkType } from 'src/enum';
|
import { AlbumUserRole, SharedLinkType } from 'src/enum';
|
||||||
import { DB } from 'src/schema';
|
import { DB } from 'src/schema';
|
||||||
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
||||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||||
@@ -39,7 +39,15 @@ const withAlbumOwner = (eb: ExpressionBuilder<DB, 'album'>) => {
|
|||||||
return eb
|
return eb
|
||||||
.selectFrom('user')
|
.selectFrom('user')
|
||||||
.select(columns.user)
|
.select(columns.user)
|
||||||
.whereRef('user.id', '=', 'album.ownerId')
|
.where((eb) =>
|
||||||
|
eb.exists(
|
||||||
|
eb
|
||||||
|
.selectFrom('album_user')
|
||||||
|
.where('album_user.role', '=', sql.lit(AlbumUserRole.Owner))
|
||||||
|
.whereRef('album_user.albumId', '=', 'album.id')
|
||||||
|
.whereRef('album_user.userId', '=', 'user.id'),
|
||||||
|
),
|
||||||
|
)
|
||||||
.where('user.deletedAt', 'is', null)
|
.where('user.deletedAt', 'is', null)
|
||||||
.as('owner');
|
.as('owner');
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -171,10 +171,9 @@ class AlbumSync extends BaseSync {
|
|||||||
return this.upsertQuery('album', options)
|
return this.upsertQuery('album', options)
|
||||||
.distinctOn(['album.id', 'album.updateId'])
|
.distinctOn(['album.id', 'album.updateId'])
|
||||||
.leftJoin('album_user as album_users', 'album.id', 'album_users.albumId')
|
.leftJoin('album_user as album_users', 'album.id', 'album_users.albumId')
|
||||||
.where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_users.userId', '=', userId)]))
|
.where('album_users.userId', '=', userId)
|
||||||
.select([
|
.select([
|
||||||
'album.id',
|
'album.id',
|
||||||
'album.ownerId',
|
|
||||||
'album.albumName as name',
|
'album.albumName as name',
|
||||||
'album.description',
|
'album.description',
|
||||||
'album.createdAt',
|
'album.createdAt',
|
||||||
@@ -186,6 +185,11 @@ class AlbumSync extends BaseSync {
|
|||||||
])
|
])
|
||||||
.stream();
|
.stream();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
|
async getAlbumUsers(albumId: string) {
|
||||||
|
return this.db.selectFrom('album_user').select(['userId', 'role']).where('albumId', '=', albumId).execute();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AlbumAssetSync extends BaseSync {
|
class AlbumAssetSync extends BaseSync {
|
||||||
@@ -207,9 +211,8 @@ class AlbumAssetSync extends BaseSync {
|
|||||||
.select(columns.syncAsset)
|
.select(columns.syncAsset)
|
||||||
.select('asset.updateId')
|
.select('asset.updateId')
|
||||||
.where('album_asset.updateId', '<=', albumToAssetAck.updateId) // Ensure we only send updates for assets that the client already knows about
|
.where('album_asset.updateId', '<=', albumToAssetAck.updateId) // Ensure we only send updates for assets that the client already knows about
|
||||||
.innerJoin('album', 'album.id', 'album_asset.albumId')
|
.innerJoin('album_user', 'album_user.albumId', 'album_asset.albumId')
|
||||||
.leftJoin('album_user', 'album_user.albumId', 'album_asset.albumId')
|
.where('album_user.userId', '=', userId)
|
||||||
.where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.userId', '=', userId)]))
|
|
||||||
.stream();
|
.stream();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,9 +223,8 @@ class AlbumAssetSync extends BaseSync {
|
|||||||
.select('album_asset.updateId')
|
.select('album_asset.updateId')
|
||||||
.innerJoin('asset', 'asset.id', 'album_asset.assetId')
|
.innerJoin('asset', 'asset.id', 'album_asset.assetId')
|
||||||
.select(columns.syncAsset)
|
.select(columns.syncAsset)
|
||||||
.innerJoin('album', 'album.id', 'album_asset.albumId')
|
.innerJoin('album_user', 'album_user.albumId', 'album_asset.albumId')
|
||||||
.leftJoin('album_user', 'album_user.albumId', 'album_asset.albumId')
|
.where('album_user.userId', '=', userId)
|
||||||
.where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.userId', '=', userId)]))
|
|
||||||
.stream();
|
.stream();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,9 +248,8 @@ class AlbumAssetExifSync extends BaseSync {
|
|||||||
.select(columns.syncAssetExif)
|
.select(columns.syncAssetExif)
|
||||||
.select('asset_exif.updateId')
|
.select('asset_exif.updateId')
|
||||||
.where('album_asset.updateId', '<=', albumToAssetAck.updateId) // Ensure we only send exif updates for assets that the client already knows about
|
.where('album_asset.updateId', '<=', albumToAssetAck.updateId) // Ensure we only send exif updates for assets that the client already knows about
|
||||||
.innerJoin('album', 'album.id', 'album_asset.albumId')
|
.innerJoin('album_user', 'album_user.albumId', 'album_asset.albumId')
|
||||||
.leftJoin('album_user', 'album_user.albumId', 'album_asset.albumId')
|
.where('album_user.userId', '=', userId)
|
||||||
.where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.userId', '=', userId)]))
|
|
||||||
.stream();
|
.stream();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,7 +262,7 @@ class AlbumAssetExifSync extends BaseSync {
|
|||||||
.select(columns.syncAssetExif)
|
.select(columns.syncAssetExif)
|
||||||
.innerJoin('album', 'album.id', 'album_asset.albumId')
|
.innerJoin('album', 'album.id', 'album_asset.albumId')
|
||||||
.leftJoin('album_user', 'album_user.albumId', 'album_asset.albumId')
|
.leftJoin('album_user', 'album_user.albumId', 'album_asset.albumId')
|
||||||
.where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.userId', '=', userId)]))
|
.where('album_user.userId', '=', userId)
|
||||||
.stream();
|
.stream();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,18 +285,7 @@ class AlbumToAssetSync extends BaseSync {
|
|||||||
eb(
|
eb(
|
||||||
'albumId',
|
'albumId',
|
||||||
'in',
|
'in',
|
||||||
eb
|
eb.selectFrom('album_user').select(['album_user.albumId as id']).where('album_user.userId', '=', userId),
|
||||||
.selectFrom('album')
|
|
||||||
.select(['id'])
|
|
||||||
.where('ownerId', '=', userId)
|
|
||||||
.union((eb) =>
|
|
||||||
eb.parens(
|
|
||||||
eb
|
|
||||||
.selectFrom('album_user')
|
|
||||||
.select(['album_user.albumId as id'])
|
|
||||||
.where('album_user.userId', '=', userId),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.stream();
|
.stream();
|
||||||
@@ -310,9 +300,8 @@ class AlbumToAssetSync extends BaseSync {
|
|||||||
const userId = options.userId;
|
const userId = options.userId;
|
||||||
return this.upsertQuery('album_asset', options)
|
return this.upsertQuery('album_asset', options)
|
||||||
.select(['album_asset.assetId as assetId', 'album_asset.albumId as albumId', 'album_asset.updateId'])
|
.select(['album_asset.assetId as assetId', 'album_asset.albumId as albumId', 'album_asset.updateId'])
|
||||||
.innerJoin('album', 'album.id', 'album_asset.albumId')
|
.innerJoin('album_user', 'album_user.albumId', 'album_asset.albumId')
|
||||||
.leftJoin('album_user', 'album_user.albumId', 'album_asset.albumId')
|
.where('album_user.userId', '=', userId)
|
||||||
.where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.userId', '=', userId)]))
|
|
||||||
.stream();
|
.stream();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,18 +325,7 @@ class AlbumUserSync extends BaseSync {
|
|||||||
eb(
|
eb(
|
||||||
'albumId',
|
'albumId',
|
||||||
'in',
|
'in',
|
||||||
eb
|
eb.selectFrom('album_user').select(['album_user.albumId as id']).where('album_user.userId', '=', userId),
|
||||||
.selectFrom('album')
|
|
||||||
.select(['id'])
|
|
||||||
.where('ownerId', '=', userId)
|
|
||||||
.union((eb) =>
|
|
||||||
eb.parens(
|
|
||||||
eb
|
|
||||||
.selectFrom('album_user')
|
|
||||||
.select(['album_user.albumId as id'])
|
|
||||||
.where('album_user.userId', '=', userId),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.stream();
|
.stream();
|
||||||
@@ -367,19 +345,11 @@ class AlbumUserSync extends BaseSync {
|
|||||||
eb(
|
eb(
|
||||||
'album_user.albumId',
|
'album_user.albumId',
|
||||||
'in',
|
'in',
|
||||||
eb
|
|
||||||
.selectFrom('album')
|
|
||||||
.select(['id'])
|
|
||||||
.where('ownerId', '=', userId)
|
|
||||||
.union((eb) =>
|
|
||||||
eb.parens(
|
|
||||||
eb
|
eb
|
||||||
.selectFrom('album_user as albumUsers')
|
.selectFrom('album_user as albumUsers')
|
||||||
.select(['albumUsers.albumId as id'])
|
.select(['albumUsers.albumId as id'])
|
||||||
.where('albumUsers.userId', '=', userId),
|
.where('albumUsers.userId', '=', userId),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.stream();
|
.stream();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { registerEnum } from '@immich/sql-tools';
|
import { registerEnum } from '@immich/sql-tools';
|
||||||
import { AssetStatus, AssetVisibility, ChecksumAlgorithm, SourceType } from 'src/enum';
|
import { AlbumUserRole, AssetStatus, AssetVisibility, ChecksumAlgorithm, SourceType } from 'src/enum';
|
||||||
|
|
||||||
|
export const album_user_role_enum = registerEnum({
|
||||||
|
name: 'album_user_role_enum',
|
||||||
|
values: [AlbumUserRole.Owner, AlbumUserRole.Editor, AlbumUserRole.Viewer],
|
||||||
|
});
|
||||||
|
|
||||||
export const assets_status_enum = registerEnum({
|
export const assets_status_enum = registerEnum({
|
||||||
name: 'assets_status_enum',
|
name: 'assets_status_enum',
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ export const album_user_after_insert = registerFunction({
|
|||||||
body: `
|
body: `
|
||||||
BEGIN
|
BEGIN
|
||||||
UPDATE album SET "updatedAt" = clock_timestamp(), "updateId" = immich_uuid_v7(clock_timestamp())
|
UPDATE album SET "updatedAt" = clock_timestamp(), "updateId" = immich_uuid_v7(clock_timestamp())
|
||||||
WHERE "id" IN (SELECT DISTINCT "albumId" FROM inserted_rows);
|
WHERE "id" IN (SELECT "albumId" FROM inserted_rows)
|
||||||
|
AND NOT EXISTS (SELECT FROM inserted_rows WHERE role = 'owner');
|
||||||
RETURN NULL;
|
RETURN NULL;
|
||||||
END`,
|
END`,
|
||||||
});
|
});
|
||||||
@@ -119,19 +120,6 @@ export const asset_delete_audit = registerFunction({
|
|||||||
END`,
|
END`,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const album_delete_audit = registerFunction({
|
|
||||||
name: 'album_delete_audit',
|
|
||||||
returnType: 'TRIGGER',
|
|
||||||
language: 'PLPGSQL',
|
|
||||||
body: `
|
|
||||||
BEGIN
|
|
||||||
INSERT INTO album_audit ("albumId", "userId")
|
|
||||||
SELECT "id", "ownerId"
|
|
||||||
FROM OLD;
|
|
||||||
RETURN NULL;
|
|
||||||
END`,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const album_asset_delete_audit = registerFunction({
|
export const album_asset_delete_audit = registerFunction({
|
||||||
name: 'album_asset_delete_audit',
|
name: 'album_asset_delete_audit',
|
||||||
returnType: 'TRIGGER',
|
returnType: 'TRIGGER',
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { Database, Extensions, Generated, Int8 } from '@immich/sql-tools';
|
import { Database, Extensions, Generated, Int8 } from '@immich/sql-tools';
|
||||||
import { asset_face_source_type, asset_visibility_enum, assets_status_enum } from 'src/schema/enums';
|
|
||||||
import {
|
import {
|
||||||
album_delete_audit,
|
album_user_role_enum,
|
||||||
|
asset_face_source_type,
|
||||||
|
asset_visibility_enum,
|
||||||
|
assets_status_enum,
|
||||||
|
} from 'src/schema/enums';
|
||||||
|
import {
|
||||||
album_user_after_insert,
|
album_user_after_insert,
|
||||||
album_user_delete_audit,
|
album_user_delete_audit,
|
||||||
asset_delete_audit,
|
asset_delete_audit,
|
||||||
@@ -146,7 +150,6 @@ export class ImmichDatabase {
|
|||||||
user_delete_audit,
|
user_delete_audit,
|
||||||
partner_delete_audit,
|
partner_delete_audit,
|
||||||
asset_delete_audit,
|
asset_delete_audit,
|
||||||
album_delete_audit,
|
|
||||||
album_user_after_insert,
|
album_user_after_insert,
|
||||||
album_user_delete_audit,
|
album_user_delete_audit,
|
||||||
memory_delete_audit,
|
memory_delete_audit,
|
||||||
@@ -158,7 +161,7 @@ export class ImmichDatabase {
|
|||||||
asset_face_audit,
|
asset_face_audit,
|
||||||
];
|
];
|
||||||
|
|
||||||
enum = [assets_status_enum, asset_face_source_type, asset_visibility_enum];
|
enum = [album_user_role_enum, assets_status_enum, asset_face_source_type, asset_visibility_enum];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Migrations {
|
export interface Migrations {
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import { Kysely, sql } from 'kysely';
|
||||||
|
import { AlbumUserRole } from 'src/enum';
|
||||||
|
|
||||||
|
export async function up(db: Kysely<any>): Promise<void> {
|
||||||
|
await sql`CREATE OR REPLACE FUNCTION album_user_after_insert()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
LANGUAGE PLPGSQL
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
UPDATE album SET "updatedAt" = clock_timestamp(), "updateId" = immich_uuid_v7(clock_timestamp())
|
||||||
|
WHERE "id" IN (SELECT "albumId" FROM inserted_rows)
|
||||||
|
AND NOT EXISTS (SELECT FROM inserted_rows WHERE role = 'owner');
|
||||||
|
RETURN NULL;
|
||||||
|
END
|
||||||
|
$$;`.execute(db);
|
||||||
|
await sql`DROP TRIGGER "album_delete_audit" ON "album";`.execute(db);
|
||||||
|
await sql`DROP FUNCTION album_delete_audit;`.execute(db);
|
||||||
|
await sql`CREATE TYPE "album_user_role_enum" AS ENUM ('owner','editor','viewer');`.execute(db);
|
||||||
|
await sql`ALTER TABLE "album_user" ALTER COLUMN "role" DROP DEFAULT;`.execute(db);
|
||||||
|
await sql`ALTER TABLE "album_user" ALTER COLUMN "role" TYPE album_user_role_enum USING "role"::album_user_role_enum;`.execute(db);
|
||||||
|
await sql`ALTER TABLE "album_user" ALTER COLUMN "role" SET DEFAULT 'editor'::album_user_role_enum;`.execute(db);
|
||||||
|
await db
|
||||||
|
.insertInto('album_user')
|
||||||
|
.expression((eb) =>
|
||||||
|
eb
|
||||||
|
.selectFrom('album')
|
||||||
|
.select(['album.id as albumId', 'album.ownerId as userId', eb.val(AlbumUserRole.Owner).as('role')]),
|
||||||
|
)
|
||||||
|
.execute();
|
||||||
|
await sql`ALTER TABLE "album" DROP CONSTRAINT "album_ownerId_fkey";`.execute(db);
|
||||||
|
await sql`ALTER TABLE "album" DROP COLUMN "ownerId";`.execute(db);
|
||||||
|
await sql`CREATE UNIQUE INDEX "album_user_unique_owner" ON "album_user" ("albumId") WHERE (role = 'owner');`.execute(db);
|
||||||
|
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"function","name":"album_user_after_insert","sql":"CREATE OR REPLACE FUNCTION album_user_after_insert()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n UPDATE album SET \\"updatedAt\\" = clock_timestamp(), \\"updateId\\" = immich_uuid_v7(clock_timestamp())\\n WHERE \\"id\\" IN (SELECT \\"albumId\\" FROM inserted_rows)\\n AND NOT EXISTS (SELECT FROM inserted_rows WHERE role = ''owner'');\\n RETURN NULL;\\n END\\n $$;"}'::jsonb WHERE "name" = 'function_album_user_after_insert';`.execute(db);
|
||||||
|
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_album_user_unique_owner', '{"type":"index","name":"album_user_unique_owner","sql":"CREATE UNIQUE INDEX \\"album_user_unique_owner\\" ON \\"album_user\\" (\\"albumId\\") WHERE (role = ''owner'');"}'::jsonb);`.execute(db);
|
||||||
|
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_album_delete_audit';`.execute(db);
|
||||||
|
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_album_delete_audit';`.execute(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(db: Kysely<any>): Promise<void> {
|
||||||
|
await sql`CREATE OR REPLACE FUNCTION public.album_user_after_insert()
|
||||||
|
RETURNS trigger
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $function$
|
||||||
|
BEGIN
|
||||||
|
UPDATE album SET "updatedAt" = clock_timestamp(), "updateId" = immich_uuid_v7(clock_timestamp())
|
||||||
|
WHERE "id" IN (SELECT DISTINCT "albumId" FROM inserted_rows);
|
||||||
|
RETURN NULL;
|
||||||
|
END
|
||||||
|
$function$
|
||||||
|
`.execute(db);
|
||||||
|
await sql`CREATE OR REPLACE FUNCTION public.album_delete_audit()
|
||||||
|
RETURNS trigger
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $function$
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO album_audit ("albumId", "userId")
|
||||||
|
SELECT "id", "ownerId"
|
||||||
|
FROM OLD;
|
||||||
|
RETURN NULL;
|
||||||
|
END
|
||||||
|
$function$
|
||||||
|
`.execute(db);
|
||||||
|
await sql`ALTER TABLE "album" ADD "ownerId" uuid NOT NULL;`.execute(db);
|
||||||
|
await db
|
||||||
|
.updateTable('album')
|
||||||
|
.set((eb) =>
|
||||||
|
({
|
||||||
|
id: eb.ref('album_user.albumId'),
|
||||||
|
ownerId: eb.ref('album_user.userId')
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.from('album_user')
|
||||||
|
.where('album_user.role', '=', AlbumUserRole.Owner)
|
||||||
|
.execute();
|
||||||
|
await sql`DROP INDEX "album_user_unique_owner";`.execute(db);
|
||||||
|
await sql`ALTER TABLE "album_user" ALTER COLUMN "role" DROP DEFAULT;`.execute(db);
|
||||||
|
await sql`ALTER TABLE "album_user" ALTER COLUMN "role" TYPE character varying USING "role"::text;`.execute(db);
|
||||||
|
await sql`ALTER TABLE "album_user" ALTER COLUMN "role" SET DEFAULT 'editor';`.execute(db);
|
||||||
|
await sql`DROP TYPE "album_user_role_enum";`.execute(db);
|
||||||
|
await sql`CREATE INDEX "album_ownerId_idx" ON "album" ("ownerId");`.execute(db);
|
||||||
|
await sql`ALTER TABLE "album" ADD CONSTRAINT "album_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "user" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||||
|
await sql`CREATE OR REPLACE TRIGGER "album_delete_audit"
|
||||||
|
AFTER DELETE ON "album"
|
||||||
|
REFERENCING OLD TABLE AS "old"
|
||||||
|
FOR EACH STATEMENT
|
||||||
|
WHEN ((pg_trigger_depth() = 0))
|
||||||
|
EXECUTE FUNCTION album_delete_audit();`.execute(db);
|
||||||
|
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE OR REPLACE FUNCTION album_user_after_insert()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n UPDATE album SET \\"updatedAt\\" = clock_timestamp(), \\"updateId\\" = immich_uuid_v7(clock_timestamp())\\n WHERE \\"id\\" IN (SELECT DISTINCT \\"albumId\\" FROM inserted_rows);\\n RETURN NULL;\\n END\\n $$;","name":"album_user_after_insert","type":"function"}'::jsonb WHERE "name" = 'function_album_user_after_insert';`.execute(db);
|
||||||
|
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_album_delete_audit', '{"sql":"CREATE OR REPLACE FUNCTION album_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO album_audit (\\"albumId\\", \\"userId\\")\\n SELECT \\"id\\", \\"ownerId\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;","name":"album_delete_audit","type":"function"}'::jsonb);`.execute(db);
|
||||||
|
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_album_delete_audit', '{"sql":"CREATE OR REPLACE TRIGGER \\"album_delete_audit\\"\\n AFTER DELETE ON \\"album\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION album_delete_audit();","name":"album_delete_audit","type":"trigger"}'::jsonb);`.execute(db);
|
||||||
|
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_album_user_unique_owner';`.execute(db);
|
||||||
|
}
|
||||||
@@ -5,17 +5,25 @@ import {
|
|||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
ForeignKeyColumn,
|
ForeignKeyColumn,
|
||||||
Generated,
|
Generated,
|
||||||
|
Index,
|
||||||
Table,
|
Table,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from '@immich/sql-tools';
|
} from '@immich/sql-tools';
|
||||||
import { CreateIdColumn, UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
import { CreateIdColumn, UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
||||||
import { AlbumUserRole } from 'src/enum';
|
import { AlbumUserRole } from 'src/enum';
|
||||||
|
import { album_user_role_enum } from 'src/schema/enums';
|
||||||
import { album_user_after_insert, album_user_delete_audit } from 'src/schema/functions';
|
import { album_user_after_insert, album_user_delete_audit } from 'src/schema/functions';
|
||||||
import { AlbumTable } from 'src/schema/tables/album.table';
|
import { AlbumTable } from 'src/schema/tables/album.table';
|
||||||
import { UserTable } from 'src/schema/tables/user.table';
|
import { UserTable } from 'src/schema/tables/user.table';
|
||||||
|
|
||||||
@Table({ name: 'album_user' })
|
@Table({ name: 'album_user' })
|
||||||
|
@Index({
|
||||||
|
name: 'album_user_unique_owner',
|
||||||
|
columns: ['albumId'],
|
||||||
|
unique: true,
|
||||||
|
where: `role = 'owner'`,
|
||||||
|
})
|
||||||
// Pre-existing indices from original album <--> user ManyToMany mapping
|
// Pre-existing indices from original album <--> user ManyToMany mapping
|
||||||
@UpdatedAtTrigger('album_user_updatedAt')
|
@UpdatedAtTrigger('album_user_updatedAt')
|
||||||
@AfterInsertTrigger({
|
@AfterInsertTrigger({
|
||||||
@@ -47,7 +55,7 @@ export class AlbumUserTable {
|
|||||||
})
|
})
|
||||||
userId!: string;
|
userId!: string;
|
||||||
|
|
||||||
@Column({ type: 'character varying', default: AlbumUserRole.Editor })
|
@Column({ enum: album_user_role_enum, default: AlbumUserRole.Editor })
|
||||||
role!: Generated<AlbumUserRole>;
|
role!: Generated<AlbumUserRole>;
|
||||||
|
|
||||||
@CreateIdColumn({ index: true })
|
@CreateIdColumn({ index: true })
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
AfterDeleteTrigger,
|
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
DeleteDateColumn,
|
DeleteDateColumn,
|
||||||
@@ -12,25 +11,14 @@ import {
|
|||||||
} from '@immich/sql-tools';
|
} from '@immich/sql-tools';
|
||||||
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
||||||
import { AssetOrder } from 'src/enum';
|
import { AssetOrder } from 'src/enum';
|
||||||
import { album_delete_audit } from 'src/schema/functions';
|
|
||||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||||
import { UserTable } from 'src/schema/tables/user.table';
|
|
||||||
|
|
||||||
@Table({ name: 'album' })
|
@Table({ name: 'album' })
|
||||||
@UpdatedAtTrigger('album_updatedAt')
|
@UpdatedAtTrigger('album_updatedAt')
|
||||||
@AfterDeleteTrigger({
|
|
||||||
scope: 'statement',
|
|
||||||
function: album_delete_audit,
|
|
||||||
referencingOldTableAs: 'old',
|
|
||||||
when: 'pg_trigger_depth() = 0',
|
|
||||||
})
|
|
||||||
export class AlbumTable {
|
export class AlbumTable {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id!: Generated<string>;
|
id!: Generated<string>;
|
||||||
|
|
||||||
@ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false })
|
|
||||||
ownerId!: string;
|
|
||||||
|
|
||||||
@Column({ default: 'Untitled Album' })
|
@Column({ default: 'Untitled Album' })
|
||||||
albumName!: Generated<string>;
|
albumName!: Generated<string>;
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ describe(AlbumService.name, () => {
|
|||||||
describe('getAll', () => {
|
describe('getAll', () => {
|
||||||
it('gets list of albums for auth user', async () => {
|
it('gets list of albums for auth user', async () => {
|
||||||
const album = AlbumFactory.from().albumUser().build();
|
const album = AlbumFactory.from().albumUser().build();
|
||||||
const sharedWithUserAlbum = AlbumFactory.from().owner(album.owner).albumUser().build();
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
|
const sharedWithUserAlbum = AlbumFactory.from().owner(owner).albumUser().build();
|
||||||
mocks.album.getOwned.mockResolvedValue([getForAlbum(album), getForAlbum(sharedWithUserAlbum)]);
|
mocks.album.getOwned.mockResolvedValue([getForAlbum(album), getForAlbum(sharedWithUserAlbum)]);
|
||||||
mocks.album.getMetadataForIds.mockResolvedValue([
|
mocks.album.getMetadataForIds.mockResolvedValue([
|
||||||
{
|
{
|
||||||
@@ -63,7 +64,7 @@ describe(AlbumService.name, () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const result = await sut.getAll(AuthFactory.create(album.owner), {});
|
const result = await sut.getAll(AuthFactory.create(owner), {});
|
||||||
expect(result).toHaveLength(2);
|
expect(result).toHaveLength(2);
|
||||||
expect(result[0].id).toEqual(album.id);
|
expect(result[0].id).toEqual(album.id);
|
||||||
expect(result[1].id).toEqual(sharedWithUserAlbum.id);
|
expect(result[1].id).toEqual(sharedWithUserAlbum.id);
|
||||||
@@ -76,6 +77,7 @@ describe(AlbumService.name, () => {
|
|||||||
.asset({}, (builder) => builder.exif())
|
.asset({}, (builder) => builder.exif())
|
||||||
.asset({}, (builder) => builder.exif())
|
.asset({}, (builder) => builder.exif())
|
||||||
.build();
|
.build();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.album.getByAssetId.mockResolvedValue([getForAlbum(album)]);
|
mocks.album.getByAssetId.mockResolvedValue([getForAlbum(album)]);
|
||||||
mocks.album.getMetadataForIds.mockResolvedValue([
|
mocks.album.getMetadataForIds.mockResolvedValue([
|
||||||
{
|
{
|
||||||
@@ -87,7 +89,7 @@ describe(AlbumService.name, () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const result = await sut.getAll(AuthFactory.create(album.owner), { assetId: album.assets[0].id });
|
const result = await sut.getAll(AuthFactory.create(owner), { assetId: album.assets[0].id });
|
||||||
expect(result).toHaveLength(1);
|
expect(result).toHaveLength(1);
|
||||||
expect(result[0].id).toEqual(album.id);
|
expect(result[0].id).toEqual(album.id);
|
||||||
expect(mocks.album.getByAssetId).toHaveBeenCalledTimes(1);
|
expect(mocks.album.getByAssetId).toHaveBeenCalledTimes(1);
|
||||||
@@ -95,6 +97,7 @@ describe(AlbumService.name, () => {
|
|||||||
|
|
||||||
it('gets list of albums that are shared', async () => {
|
it('gets list of albums that are shared', async () => {
|
||||||
const album = AlbumFactory.from().albumUser().build();
|
const album = AlbumFactory.from().albumUser().build();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.album.getShared.mockResolvedValue([getForAlbum(album)]);
|
mocks.album.getShared.mockResolvedValue([getForAlbum(album)]);
|
||||||
mocks.album.getMetadataForIds.mockResolvedValue([
|
mocks.album.getMetadataForIds.mockResolvedValue([
|
||||||
{
|
{
|
||||||
@@ -106,7 +109,7 @@ describe(AlbumService.name, () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const result = await sut.getAll(AuthFactory.create(album.owner), { shared: true });
|
const result = await sut.getAll(AuthFactory.create(owner), { shared: true });
|
||||||
expect(result).toHaveLength(1);
|
expect(result).toHaveLength(1);
|
||||||
expect(result[0].id).toEqual(album.id);
|
expect(result[0].id).toEqual(album.id);
|
||||||
expect(mocks.album.getShared).toHaveBeenCalledTimes(1);
|
expect(mocks.album.getShared).toHaveBeenCalledTimes(1);
|
||||||
@@ -114,6 +117,7 @@ describe(AlbumService.name, () => {
|
|||||||
|
|
||||||
it('gets list of albums that are NOT shared', async () => {
|
it('gets list of albums that are NOT shared', async () => {
|
||||||
const album = AlbumFactory.create();
|
const album = AlbumFactory.create();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.album.getNotShared.mockResolvedValue([getForAlbum(album)]);
|
mocks.album.getNotShared.mockResolvedValue([getForAlbum(album)]);
|
||||||
mocks.album.getMetadataForIds.mockResolvedValue([
|
mocks.album.getMetadataForIds.mockResolvedValue([
|
||||||
{
|
{
|
||||||
@@ -125,7 +129,7 @@ describe(AlbumService.name, () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const result = await sut.getAll(AuthFactory.create(album.owner), { shared: false });
|
const result = await sut.getAll(AuthFactory.create(owner), { shared: false });
|
||||||
expect(result).toHaveLength(1);
|
expect(result).toHaveLength(1);
|
||||||
expect(result[0].id).toEqual(album.id);
|
expect(result[0].id).toEqual(album.id);
|
||||||
expect(mocks.album.getNotShared).toHaveBeenCalledTimes(1);
|
expect(mocks.album.getNotShared).toHaveBeenCalledTimes(1);
|
||||||
@@ -134,6 +138,7 @@ describe(AlbumService.name, () => {
|
|||||||
|
|
||||||
it('counts assets correctly', async () => {
|
it('counts assets correctly', async () => {
|
||||||
const album = AlbumFactory.create();
|
const album = AlbumFactory.create();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.album.getOwned.mockResolvedValue([getForAlbum(album)]);
|
mocks.album.getOwned.mockResolvedValue([getForAlbum(album)]);
|
||||||
mocks.album.getMetadataForIds.mockResolvedValue([
|
mocks.album.getMetadataForIds.mockResolvedValue([
|
||||||
{
|
{
|
||||||
@@ -145,7 +150,7 @@ describe(AlbumService.name, () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const result = await sut.getAll(AuthFactory.create(album.owner), {});
|
const result = await sut.getAll(AuthFactory.create(owner), {});
|
||||||
expect(result).toHaveLength(1);
|
expect(result).toHaveLength(1);
|
||||||
expect(result[0].assetCount).toEqual(1);
|
expect(result[0].assetCount).toEqual(1);
|
||||||
expect(mocks.album.getOwned).toHaveBeenCalledTimes(1);
|
expect(mocks.album.getOwned).toHaveBeenCalledTimes(1);
|
||||||
@@ -159,13 +164,14 @@ describe(AlbumService.name, () => {
|
|||||||
.asset({ id: assetId }, (asset) => asset.exif())
|
.asset({ id: assetId }, (asset) => asset.exif())
|
||||||
.albumUser(albumUser)
|
.albumUser(albumUser)
|
||||||
.build();
|
.build();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
|
|
||||||
mocks.album.create.mockResolvedValue(getForAlbum(album));
|
mocks.album.create.mockResolvedValue(getForAlbum(album));
|
||||||
mocks.user.get.mockResolvedValue(UserFactory.create(album.albumUsers[0].user));
|
mocks.user.get.mockResolvedValue(UserFactory.create(album.albumUsers[0].user));
|
||||||
mocks.user.getMetadata.mockResolvedValue([]);
|
mocks.user.getMetadata.mockResolvedValue([]);
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId]));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId]));
|
||||||
|
|
||||||
await sut.create(AuthFactory.create(album.owner), {
|
await sut.create(AuthFactory.create(owner), {
|
||||||
albumName: 'test',
|
albumName: 'test',
|
||||||
albumUsers: [albumUser],
|
albumUsers: [albumUser],
|
||||||
description: 'description',
|
description: 'description',
|
||||||
@@ -174,20 +180,27 @@ describe(AlbumService.name, () => {
|
|||||||
|
|
||||||
expect(mocks.album.create).toHaveBeenCalledWith(
|
expect(mocks.album.create).toHaveBeenCalledWith(
|
||||||
{
|
{
|
||||||
ownerId: album.owner.id,
|
|
||||||
albumName: 'test',
|
albumName: 'test',
|
||||||
description: 'description',
|
description: 'description',
|
||||||
order: album.order,
|
order: album.order,
|
||||||
albumThumbnailAssetId: assetId,
|
albumThumbnailAssetId: assetId,
|
||||||
},
|
},
|
||||||
[assetId],
|
[assetId],
|
||||||
[{ userId: albumUser.userId, role: AlbumUserRole.Editor }],
|
[
|
||||||
|
{ userId: owner.id, role: AlbumUserRole.Owner },
|
||||||
|
{ userId: albumUser.userId, role: AlbumUserRole.Editor },
|
||||||
|
],
|
||||||
|
owner.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(mocks.user.get).toHaveBeenCalledWith(albumUser.userId, {});
|
expect(mocks.user.get).toHaveBeenCalledWith(albumUser.userId, {});
|
||||||
expect(mocks.user.getMetadata).toHaveBeenCalledWith(album.owner.id);
|
expect(mocks.user.getMetadata).toHaveBeenCalledWith(owner.id);
|
||||||
expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(album.owner.id, new Set([assetId]), false);
|
expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(owner.id, new Set([assetId]), false);
|
||||||
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumInvite', { id: album.id, userId: albumUser.userId });
|
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumInvite', {
|
||||||
|
id: album.id,
|
||||||
|
userId: albumUser.userId,
|
||||||
|
senderName: owner.name,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates album with assetOrder from user preferences', async () => {
|
it('creates album with assetOrder from user preferences', async () => {
|
||||||
@@ -197,8 +210,10 @@ describe(AlbumService.name, () => {
|
|||||||
.asset({ id: assetId }, (asset) => asset.exif())
|
.asset({ id: assetId }, (asset) => asset.exif())
|
||||||
.albumUser(albumUser)
|
.albumUser(albumUser)
|
||||||
.build();
|
.build();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.album.create.mockResolvedValue(getForAlbum(album));
|
mocks.album.create.mockResolvedValue(getForAlbum(album));
|
||||||
mocks.user.get.mockResolvedValue(album.albumUsers[0].user);
|
mocks.albumUser.create.mockResolvedValue(album.albumUsers[0]);
|
||||||
|
mocks.user.get.mockResolvedValue(UserFactory.create(album.albumUsers[1].user));
|
||||||
mocks.user.getMetadata.mockResolvedValue([
|
mocks.user.getMetadata.mockResolvedValue([
|
||||||
{
|
{
|
||||||
key: UserMetadataKey.Preferences,
|
key: UserMetadataKey.Preferences,
|
||||||
@@ -211,7 +226,7 @@ describe(AlbumService.name, () => {
|
|||||||
]);
|
]);
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId]));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId]));
|
||||||
|
|
||||||
await sut.create(AuthFactory.create(album.owner), {
|
await sut.create(AuthFactory.create(owner), {
|
||||||
albumName: album.albumName,
|
albumName: album.albumName,
|
||||||
albumUsers: [albumUser],
|
albumUsers: [albumUser],
|
||||||
description: album.description,
|
description: album.description,
|
||||||
@@ -220,20 +235,24 @@ describe(AlbumService.name, () => {
|
|||||||
|
|
||||||
expect(mocks.album.create).toHaveBeenCalledWith(
|
expect(mocks.album.create).toHaveBeenCalledWith(
|
||||||
{
|
{
|
||||||
ownerId: album.owner.id,
|
|
||||||
albumName: album.albumName,
|
albumName: album.albumName,
|
||||||
description: album.description,
|
description: album.description,
|
||||||
order: 'asc',
|
order: 'asc',
|
||||||
albumThumbnailAssetId: assetId,
|
albumThumbnailAssetId: assetId,
|
||||||
},
|
},
|
||||||
[assetId],
|
[assetId],
|
||||||
[albumUser],
|
[{ userId: owner.id, role: AlbumUserRole.Owner }, albumUser],
|
||||||
|
owner.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(mocks.user.get).toHaveBeenCalledWith(albumUser.userId, {});
|
expect(mocks.user.get).toHaveBeenCalledWith(albumUser.userId, {});
|
||||||
expect(mocks.user.getMetadata).toHaveBeenCalledWith(album.owner.id);
|
expect(mocks.user.getMetadata).toHaveBeenCalledWith(owner.id);
|
||||||
expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(album.owner.id, new Set([assetId]), false);
|
expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(owner.id, new Set([assetId]), false);
|
||||||
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumInvite', { id: album.id, userId: albumUser.userId });
|
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumInvite', {
|
||||||
|
id: album.id,
|
||||||
|
userId: albumUser.userId,
|
||||||
|
senderName: owner.name,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require valid userIds', async () => {
|
it('should require valid userIds', async () => {
|
||||||
@@ -254,12 +273,13 @@ describe(AlbumService.name, () => {
|
|||||||
.asset({ id: assetId }, (asset) => asset.exif())
|
.asset({ id: assetId }, (asset) => asset.exif())
|
||||||
.albumUser()
|
.albumUser()
|
||||||
.build();
|
.build();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.user.get.mockResolvedValue(album.albumUsers[0].user);
|
mocks.user.get.mockResolvedValue(album.albumUsers[0].user);
|
||||||
mocks.album.create.mockResolvedValue(getForAlbum(album));
|
mocks.album.create.mockResolvedValue(getForAlbum(album));
|
||||||
mocks.user.getMetadata.mockResolvedValue([]);
|
mocks.user.getMetadata.mockResolvedValue([]);
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId]));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId]));
|
||||||
|
|
||||||
await sut.create(AuthFactory.create(album.owner), {
|
await sut.create(AuthFactory.create(owner), {
|
||||||
albumName: album.albumName,
|
albumName: album.albumName,
|
||||||
description: album.description,
|
description: album.description,
|
||||||
assetIds: [assetId, 'asset-2'],
|
assetIds: [assetId, 'asset-2'],
|
||||||
@@ -267,29 +287,26 @@ describe(AlbumService.name, () => {
|
|||||||
|
|
||||||
expect(mocks.album.create).toHaveBeenCalledWith(
|
expect(mocks.album.create).toHaveBeenCalledWith(
|
||||||
{
|
{
|
||||||
ownerId: album.owner.id,
|
|
||||||
albumName: album.albumName,
|
albumName: album.albumName,
|
||||||
description: album.description,
|
description: album.description,
|
||||||
order: 'desc',
|
order: 'desc',
|
||||||
albumThumbnailAssetId: assetId,
|
albumThumbnailAssetId: assetId,
|
||||||
},
|
},
|
||||||
[assetId],
|
[assetId],
|
||||||
[],
|
[{ userId: owner.id, role: AlbumUserRole.Owner }],
|
||||||
);
|
owner.id,
|
||||||
expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(
|
|
||||||
album.owner.id,
|
|
||||||
new Set([assetId, 'asset-2']),
|
|
||||||
false,
|
|
||||||
);
|
);
|
||||||
|
expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(owner.id, new Set([assetId, 'asset-2']), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if the userId is the ownerId', async () => {
|
it('should throw an error if the userId is the ownerId', async () => {
|
||||||
const album = AlbumFactory.create();
|
const album = AlbumFactory.create();
|
||||||
mocks.user.get.mockResolvedValue(album.owner);
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
|
mocks.user.get.mockResolvedValue(owner);
|
||||||
await expect(
|
await expect(
|
||||||
sut.create(AuthFactory.create(album.owner), {
|
sut.create(AuthFactory.create(owner), {
|
||||||
albumName: 'Empty album',
|
albumName: 'Empty album',
|
||||||
albumUsers: [{ userId: album.owner.id, role: AlbumUserRole.Editor }],
|
albumUsers: [{ userId: owner.id, role: AlbumUserRole.Editor }],
|
||||||
}),
|
}),
|
||||||
).rejects.toBeInstanceOf(BadRequestException);
|
).rejects.toBeInstanceOf(BadRequestException);
|
||||||
expect(mocks.album.create).not.toHaveBeenCalled();
|
expect(mocks.album.create).not.toHaveBeenCalled();
|
||||||
@@ -312,20 +329,22 @@ describe(AlbumService.name, () => {
|
|||||||
|
|
||||||
it('should prevent updating a not owned album (shared with auth user)', async () => {
|
it('should prevent updating a not owned album (shared with auth user)', async () => {
|
||||||
const album = AlbumFactory.from().albumUser().build();
|
const album = AlbumFactory.from().albumUser().build();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set());
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set());
|
||||||
await expect(
|
await expect(
|
||||||
sut.update(AuthFactory.create(album.owner), album.id, { albumName: 'new album name' }),
|
sut.update(AuthFactory.create(owner), album.id, { albumName: 'new album name' }),
|
||||||
).rejects.toBeInstanceOf(BadRequestException);
|
).rejects.toBeInstanceOf(BadRequestException);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require a valid thumbnail asset id', async () => {
|
it('should require a valid thumbnail asset id', async () => {
|
||||||
const album = AlbumFactory.create();
|
const album = AlbumFactory.create();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||||
mocks.album.getAssetIds.mockResolvedValue(new Set());
|
mocks.album.getAssetIds.mockResolvedValue(new Set());
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.update(AuthFactory.create(album.owner), album.id, { albumThumbnailAssetId: 'not-in-album' }),
|
sut.update(AuthFactory.create(owner), album.id, { albumThumbnailAssetId: 'not-in-album' }),
|
||||||
).rejects.toBeInstanceOf(BadRequestException);
|
).rejects.toBeInstanceOf(BadRequestException);
|
||||||
|
|
||||||
expect(mocks.album.getAssetIds).toHaveBeenCalledWith(album.id, ['not-in-album']);
|
expect(mocks.album.getAssetIds).toHaveBeenCalledWith(album.id, ['not-in-album']);
|
||||||
@@ -334,43 +353,51 @@ describe(AlbumService.name, () => {
|
|||||||
|
|
||||||
it('should allow the owner to update the album', async () => {
|
it('should allow the owner to update the album', async () => {
|
||||||
const album = AlbumFactory.create();
|
const album = AlbumFactory.create();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||||
mocks.album.update.mockResolvedValue(getForAlbum(album));
|
mocks.album.update.mockResolvedValue(getForAlbum(album));
|
||||||
|
|
||||||
await sut.update(AuthFactory.create(album.owner), album.id, { albumName: 'new album name' });
|
await sut.update(AuthFactory.create(owner), album.id, { albumName: 'new album name' });
|
||||||
|
|
||||||
expect(mocks.album.update).toHaveBeenCalledTimes(1);
|
expect(mocks.album.update).toHaveBeenCalledTimes(1);
|
||||||
expect(mocks.album.update).toHaveBeenCalledWith(album.id, { id: album.id, albumName: 'new album name' });
|
expect(mocks.album.update).toHaveBeenCalledWith(
|
||||||
|
album.id,
|
||||||
|
{ id: album.id, albumName: 'new album name' },
|
||||||
|
owner.id,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('delete', () => {
|
describe('delete', () => {
|
||||||
it('should require permissions', async () => {
|
it('should require permissions', async () => {
|
||||||
const album = AlbumFactory.create();
|
const album = AlbumFactory.create();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set());
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set());
|
||||||
|
|
||||||
await expect(sut.delete(AuthFactory.create(album.owner), album.id)).rejects.toBeInstanceOf(BadRequestException);
|
await expect(sut.delete(AuthFactory.create(owner), album.id)).rejects.toBeInstanceOf(BadRequestException);
|
||||||
|
|
||||||
expect(mocks.album.delete).not.toHaveBeenCalled();
|
expect(mocks.album.delete).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not let a shared user delete the album', async () => {
|
it('should not let a shared user delete the album', async () => {
|
||||||
const album = AlbumFactory.create();
|
const album = AlbumFactory.create();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set());
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set());
|
||||||
|
|
||||||
await expect(sut.delete(AuthFactory.create(album.owner), album.id)).rejects.toBeInstanceOf(BadRequestException);
|
await expect(sut.delete(AuthFactory.create(owner), album.id)).rejects.toBeInstanceOf(BadRequestException);
|
||||||
|
|
||||||
expect(mocks.album.delete).not.toHaveBeenCalled();
|
expect(mocks.album.delete).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should let the owner delete an album', async () => {
|
it('should let the owner delete an album', async () => {
|
||||||
const album = AlbumFactory.create();
|
const album = AlbumFactory.create();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||||
|
|
||||||
await sut.delete(AuthFactory.create(album.owner), album.id);
|
await sut.delete(AuthFactory.create(owner), album.id);
|
||||||
|
|
||||||
expect(mocks.album.delete).toHaveBeenCalledTimes(1);
|
expect(mocks.album.delete).toHaveBeenCalledTimes(1);
|
||||||
expect(mocks.album.delete).toHaveBeenCalledWith(album.id);
|
expect(mocks.album.delete).toHaveBeenCalledWith(album.id);
|
||||||
@@ -391,10 +418,11 @@ describe(AlbumService.name, () => {
|
|||||||
it('should throw an error if the userId is already added', async () => {
|
it('should throw an error if the userId is already added', async () => {
|
||||||
const userId = newUuid();
|
const userId = newUuid();
|
||||||
const album = AlbumFactory.from().albumUser({ userId }).build();
|
const album = AlbumFactory.from().albumUser({ userId }).build();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||||
await expect(
|
await expect(
|
||||||
sut.addUsers(AuthFactory.create(album.owner), album.id, { albumUsers: [{ userId }] }),
|
sut.addUsers(AuthFactory.create(owner), album.id, { albumUsers: [{ userId }] }),
|
||||||
).rejects.toBeInstanceOf(BadRequestException);
|
).rejects.toBeInstanceOf(BadRequestException);
|
||||||
expect(mocks.album.update).not.toHaveBeenCalled();
|
expect(mocks.album.update).not.toHaveBeenCalled();
|
||||||
expect(mocks.user.get).not.toHaveBeenCalled();
|
expect(mocks.user.get).not.toHaveBeenCalled();
|
||||||
@@ -402,11 +430,12 @@ describe(AlbumService.name, () => {
|
|||||||
|
|
||||||
it('should throw an error if the userId does not exist', async () => {
|
it('should throw an error if the userId does not exist', async () => {
|
||||||
const album = AlbumFactory.create();
|
const album = AlbumFactory.create();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||||
mocks.user.get.mockResolvedValue(void 0);
|
mocks.user.get.mockResolvedValue(void 0);
|
||||||
await expect(
|
await expect(
|
||||||
sut.addUsers(AuthFactory.create(album.owner), album.id, { albumUsers: [{ userId: 'unknown-user' }] }),
|
sut.addUsers(AuthFactory.create(owner), album.id, { albumUsers: [{ userId: 'unknown-user' }] }),
|
||||||
).rejects.toBeInstanceOf(BadRequestException);
|
).rejects.toBeInstanceOf(BadRequestException);
|
||||||
expect(mocks.album.update).not.toHaveBeenCalled();
|
expect(mocks.album.update).not.toHaveBeenCalled();
|
||||||
expect(mocks.user.get).toHaveBeenCalledWith('unknown-user', {});
|
expect(mocks.user.get).toHaveBeenCalledWith('unknown-user', {});
|
||||||
@@ -414,11 +443,12 @@ describe(AlbumService.name, () => {
|
|||||||
|
|
||||||
it('should throw an error if the userId is the ownerId', async () => {
|
it('should throw an error if the userId is the ownerId', async () => {
|
||||||
const album = AlbumFactory.create();
|
const album = AlbumFactory.create();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||||
await expect(
|
await expect(
|
||||||
sut.addUsers(AuthFactory.create(album.owner), album.id, {
|
sut.addUsers(AuthFactory.create(owner), album.id, {
|
||||||
albumUsers: [{ userId: album.owner.id }],
|
albumUsers: [{ userId: owner.id }],
|
||||||
}),
|
}),
|
||||||
).rejects.toBeInstanceOf(BadRequestException);
|
).rejects.toBeInstanceOf(BadRequestException);
|
||||||
expect(mocks.album.update).not.toHaveBeenCalled();
|
expect(mocks.album.update).not.toHaveBeenCalled();
|
||||||
@@ -427,6 +457,7 @@ describe(AlbumService.name, () => {
|
|||||||
|
|
||||||
it('should add valid shared users', async () => {
|
it('should add valid shared users', async () => {
|
||||||
const album = AlbumFactory.create();
|
const album = AlbumFactory.create();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
const user = UserFactory.create();
|
const user = UserFactory.create();
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||||
@@ -434,7 +465,7 @@ describe(AlbumService.name, () => {
|
|||||||
mocks.user.get.mockResolvedValue(user);
|
mocks.user.get.mockResolvedValue(user);
|
||||||
mocks.albumUser.create.mockResolvedValue(AlbumUserFactory.from().album(album).user(user).build());
|
mocks.albumUser.create.mockResolvedValue(AlbumUserFactory.from().album(album).user(user).build());
|
||||||
|
|
||||||
await sut.addUsers(AuthFactory.create(album.owner), album.id, { albumUsers: [{ userId: user.id }] });
|
await sut.addUsers(AuthFactory.create(owner), album.id, { albumUsers: [{ userId: user.id }] });
|
||||||
|
|
||||||
expect(mocks.albumUser.create).toHaveBeenCalledWith({
|
expect(mocks.albumUser.create).toHaveBeenCalledWith({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
@@ -443,6 +474,7 @@ describe(AlbumService.name, () => {
|
|||||||
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumInvite', {
|
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumInvite', {
|
||||||
id: album.id,
|
id: album.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
senderName: owner.name,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -460,15 +492,16 @@ describe(AlbumService.name, () => {
|
|||||||
it('should remove a shared user from an owned album', async () => {
|
it('should remove a shared user from an owned album', async () => {
|
||||||
const userId = newUuid();
|
const userId = newUuid();
|
||||||
const album = AlbumFactory.from().albumUser({ userId }).build();
|
const album = AlbumFactory.from().albumUser({ userId }).build();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||||
mocks.albumUser.delete.mockResolvedValue();
|
mocks.albumUser.delete.mockResolvedValue();
|
||||||
|
|
||||||
await expect(sut.removeUser(AuthFactory.create(album.owner), album.id, userId)).resolves.toBeUndefined();
|
await expect(sut.removeUser(AuthFactory.create(owner), album.id, userId)).resolves.toBeUndefined();
|
||||||
|
|
||||||
expect(mocks.albumUser.delete).toHaveBeenCalledTimes(1);
|
expect(mocks.albumUser.delete).toHaveBeenCalledTimes(1);
|
||||||
expect(mocks.albumUser.delete).toHaveBeenCalledWith({ albumId: album.id, userId });
|
expect(mocks.albumUser.delete).toHaveBeenCalledWith({ albumId: album.id, userId });
|
||||||
expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false });
|
expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false }, owner.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should prevent removing a shared user from a not-owned album (shared with auth user)', async () => {
|
it('should prevent removing a shared user from a not-owned album (shared with auth user)', async () => {
|
||||||
@@ -511,9 +544,10 @@ describe(AlbumService.name, () => {
|
|||||||
|
|
||||||
it('should not allow the owner to be removed', async () => {
|
it('should not allow the owner to be removed', async () => {
|
||||||
const album = AlbumFactory.from().albumUser().build();
|
const album = AlbumFactory.from().albumUser().build();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||||
|
|
||||||
await expect(sut.removeUser(AuthFactory.create(album.owner), album.id, album.owner.id)).rejects.toBeInstanceOf(
|
await expect(sut.removeUser(AuthFactory.create(owner), album.id, owner.id)).rejects.toBeInstanceOf(
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -522,9 +556,10 @@ describe(AlbumService.name, () => {
|
|||||||
|
|
||||||
it('should throw an error for a user not in the album', async () => {
|
it('should throw an error for a user not in the album', async () => {
|
||||||
const album = AlbumFactory.from().albumUser().build();
|
const album = AlbumFactory.from().albumUser().build();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||||
|
|
||||||
await expect(sut.removeUser(AuthFactory.create(album.owner), album.id, 'user-3')).rejects.toBeInstanceOf(
|
await expect(sut.removeUser(AuthFactory.create(owner), album.id, 'user-3')).rejects.toBeInstanceOf(
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -536,10 +571,11 @@ describe(AlbumService.name, () => {
|
|||||||
it('should update user role', async () => {
|
it('should update user role', async () => {
|
||||||
const user = UserFactory.create();
|
const user = UserFactory.create();
|
||||||
const album = AlbumFactory.from().albumUser({ userId: user.id }).build();
|
const album = AlbumFactory.from().albumUser({ userId: user.id }).build();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
||||||
mocks.albumUser.update.mockResolvedValue();
|
mocks.albumUser.update.mockResolvedValue();
|
||||||
|
|
||||||
await sut.updateUser(AuthFactory.create(album.owner), album.id, user.id, { role: AlbumUserRole.Viewer });
|
await sut.updateUser(AuthFactory.create(owner), album.id, user.id, { role: AlbumUserRole.Viewer });
|
||||||
|
|
||||||
expect(mocks.albumUser.update).toHaveBeenCalledWith(
|
expect(mocks.albumUser.update).toHaveBeenCalledWith(
|
||||||
{ albumId: album.id, userId: user.id },
|
{ albumId: album.id, userId: user.id },
|
||||||
@@ -551,6 +587,7 @@ describe(AlbumService.name, () => {
|
|||||||
describe('getAlbumInfo', () => {
|
describe('getAlbumInfo', () => {
|
||||||
it('should get a shared album', async () => {
|
it('should get a shared album', async () => {
|
||||||
const album = AlbumFactory.from().albumUser().build();
|
const album = AlbumFactory.from().albumUser().build();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
||||||
mocks.album.getMetadataForIds.mockResolvedValue([
|
mocks.album.getMetadataForIds.mockResolvedValue([
|
||||||
@@ -563,10 +600,10 @@ describe(AlbumService.name, () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await sut.get(AuthFactory.create(album.owner), album.id);
|
await sut.get(AuthFactory.create(owner), album.id);
|
||||||
|
|
||||||
expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false });
|
expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false }, owner.id);
|
||||||
expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalledWith(album.owner.id, new Set([album.id]));
|
expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalledWith(owner.id, new Set([album.id]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get a shared album via a shared link', async () => {
|
it('should get a shared album via a shared link', async () => {
|
||||||
@@ -586,7 +623,7 @@ describe(AlbumService.name, () => {
|
|||||||
const auth = AuthFactory.from().sharedLink().build();
|
const auth = AuthFactory.from().sharedLink().build();
|
||||||
await sut.get(auth, album.id);
|
await sut.get(auth, album.id);
|
||||||
|
|
||||||
expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false });
|
expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false }, auth.user.id);
|
||||||
expect(mocks.access.album.checkSharedLinkAccess).toHaveBeenCalledWith(auth.sharedLink!.id, new Set([album.id]));
|
expect(mocks.access.album.checkSharedLinkAccess).toHaveBeenCalledWith(auth.sharedLink!.id, new Set([album.id]));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -607,7 +644,7 @@ describe(AlbumService.name, () => {
|
|||||||
|
|
||||||
await sut.get(AuthFactory.create(user), album.id);
|
await sut.get(AuthFactory.create(user), album.id);
|
||||||
|
|
||||||
expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false });
|
expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false }, user.id);
|
||||||
expect(mocks.access.album.checkSharedAlbumAccess).toHaveBeenCalledWith(
|
expect(mocks.access.album.checkSharedAlbumAccess).toHaveBeenCalledWith(
|
||||||
user.id,
|
user.id,
|
||||||
new Set([album.id]),
|
new Set([album.id]),
|
||||||
@@ -631,7 +668,7 @@ describe(AlbumService.name, () => {
|
|||||||
describe('addAssets', () => {
|
describe('addAssets', () => {
|
||||||
it('should allow the owner to add assets', async () => {
|
it('should allow the owner to add assets', async () => {
|
||||||
const owner = UserFactory.create({ isAdmin: true });
|
const owner = UserFactory.create({ isAdmin: true });
|
||||||
const album = AlbumFactory.from({ ownerId: owner.id }).owner(owner).build();
|
const album = AlbumFactory.from().owner(owner).build();
|
||||||
const [asset1, asset2, asset3] = [AssetFactory.create(), AssetFactory.create(), AssetFactory.create()];
|
const [asset1, asset2, asset3] = [AssetFactory.create(), AssetFactory.create(), AssetFactory.create()];
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id]));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id]));
|
||||||
@@ -646,37 +683,47 @@ describe(AlbumService.name, () => {
|
|||||||
{ success: true, id: asset3.id },
|
{ success: true, id: asset3.id },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(mocks.album.update).toHaveBeenCalledWith(album.id, {
|
expect(mocks.album.update).toHaveBeenCalledWith(
|
||||||
|
album.id,
|
||||||
|
{
|
||||||
id: album.id,
|
id: album.id,
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
albumThumbnailAssetId: asset1.id,
|
albumThumbnailAssetId: asset1.id,
|
||||||
});
|
},
|
||||||
|
owner.id,
|
||||||
|
);
|
||||||
expect(mocks.album.addAssetIds).toHaveBeenCalledWith(album.id, [asset1.id, asset2.id, asset3.id]);
|
expect(mocks.album.addAssetIds).toHaveBeenCalledWith(album.id, [asset1.id, asset2.id, asset3.id]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not set the thumbnail if the album has one already', async () => {
|
it('should not set the thumbnail if the album has one already', async () => {
|
||||||
const [asset1, asset2] = [AssetFactory.create(), AssetFactory.create()];
|
const [asset1, asset2] = [AssetFactory.create(), AssetFactory.create()];
|
||||||
const album = AlbumFactory.from({ albumThumbnailAssetId: asset1.id }).build();
|
const album = AlbumFactory.from({ albumThumbnailAssetId: asset1.id }).build();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset2.id]));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset2.id]));
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||||
mocks.album.getAssetIds.mockResolvedValueOnce(new Set());
|
mocks.album.getAssetIds.mockResolvedValueOnce(new Set());
|
||||||
|
|
||||||
await expect(sut.addAssets(AuthFactory.create(album.owner), album.id, { ids: [asset2.id] })).resolves.toEqual([
|
await expect(sut.addAssets(AuthFactory.create(owner), album.id, { ids: [asset2.id] })).resolves.toEqual([
|
||||||
{ success: true, id: asset2.id },
|
{ success: true, id: asset2.id },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(mocks.album.update).toHaveBeenCalledWith(album.id, {
|
expect(mocks.album.update).toHaveBeenCalledWith(
|
||||||
|
album.id,
|
||||||
|
{
|
||||||
id: album.id,
|
id: album.id,
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
albumThumbnailAssetId: asset1.id,
|
albumThumbnailAssetId: asset1.id,
|
||||||
});
|
},
|
||||||
|
owner.id,
|
||||||
|
);
|
||||||
expect(mocks.album.addAssetIds).toHaveBeenCalled();
|
expect(mocks.album.addAssetIds).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow a shared user to add assets', async () => {
|
it('should allow a shared user to add assets', async () => {
|
||||||
const user = UserFactory.create();
|
const user = UserFactory.create();
|
||||||
const album = AlbumFactory.from().albumUser({ userId: user.id, role: AlbumUserRole.Editor }).build();
|
const album = AlbumFactory.from().albumUser({ userId: user.id, role: AlbumUserRole.Editor }).build();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
const [asset1, asset2, asset3] = [AssetFactory.create(), AssetFactory.create(), AssetFactory.create()];
|
const [asset1, asset2, asset3] = [AssetFactory.create(), AssetFactory.create(), AssetFactory.create()];
|
||||||
mocks.access.album.checkSharedAlbumAccess.mockResolvedValue(new Set([album.id]));
|
mocks.access.album.checkSharedAlbumAccess.mockResolvedValue(new Set([album.id]));
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id]));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id]));
|
||||||
@@ -691,15 +738,19 @@ describe(AlbumService.name, () => {
|
|||||||
{ success: true, id: asset3.id },
|
{ success: true, id: asset3.id },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(mocks.album.update).toHaveBeenCalledWith(album.id, {
|
expect(mocks.album.update).toHaveBeenCalledWith(
|
||||||
|
album.id,
|
||||||
|
{
|
||||||
id: album.id,
|
id: album.id,
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
albumThumbnailAssetId: asset1.id,
|
albumThumbnailAssetId: asset1.id,
|
||||||
});
|
},
|
||||||
|
user.id,
|
||||||
|
);
|
||||||
expect(mocks.album.addAssetIds).toHaveBeenCalledWith(album.id, [asset1.id, asset2.id, asset3.id]);
|
expect(mocks.album.addAssetIds).toHaveBeenCalledWith(album.id, [asset1.id, asset2.id, asset3.id]);
|
||||||
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', {
|
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', {
|
||||||
id: album.id,
|
id: album.id,
|
||||||
recipientId: album.ownerId,
|
recipientId: owner.id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -719,33 +770,39 @@ describe(AlbumService.name, () => {
|
|||||||
|
|
||||||
it('should allow adding assets shared via partner sharing', async () => {
|
it('should allow adding assets shared via partner sharing', async () => {
|
||||||
const album = AlbumFactory.create();
|
const album = AlbumFactory.create();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
const asset = AssetFactory.create();
|
const asset = AssetFactory.create();
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
||||||
mocks.access.asset.checkPartnerAccess.mockResolvedValue(new Set([asset.id]));
|
mocks.access.asset.checkPartnerAccess.mockResolvedValue(new Set([asset.id]));
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||||
mocks.album.getAssetIds.mockResolvedValueOnce(new Set());
|
mocks.album.getAssetIds.mockResolvedValueOnce(new Set());
|
||||||
|
|
||||||
await expect(sut.addAssets(AuthFactory.create(album.owner), album.id, { ids: [asset.id] })).resolves.toEqual([
|
await expect(sut.addAssets(AuthFactory.create(owner), album.id, { ids: [asset.id] })).resolves.toEqual([
|
||||||
{ success: true, id: asset.id },
|
{ success: true, id: asset.id },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(mocks.album.update).toHaveBeenCalledWith(album.id, {
|
expect(mocks.album.update).toHaveBeenCalledWith(
|
||||||
|
album.id,
|
||||||
|
{
|
||||||
id: album.id,
|
id: album.id,
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
albumThumbnailAssetId: asset.id,
|
albumThumbnailAssetId: asset.id,
|
||||||
});
|
},
|
||||||
expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(album.ownerId, new Set([asset.id]));
|
owner.id,
|
||||||
|
);
|
||||||
|
expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(owner.id, new Set([asset.id]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should skip duplicate assets', async () => {
|
it('should skip duplicate assets', async () => {
|
||||||
const asset = AssetFactory.create();
|
const asset = AssetFactory.create();
|
||||||
const album = AlbumFactory.create();
|
const album = AlbumFactory.create();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id]));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id]));
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||||
mocks.album.getAssetIds.mockResolvedValueOnce(new Set([asset.id]));
|
mocks.album.getAssetIds.mockResolvedValueOnce(new Set([asset.id]));
|
||||||
|
|
||||||
await expect(sut.addAssets(AuthFactory.create(album.owner), album.id, { ids: [asset.id] })).resolves.toEqual([
|
await expect(sut.addAssets(AuthFactory.create(owner), album.id, { ids: [asset.id] })).resolves.toEqual([
|
||||||
{ success: false, id: asset.id, error: BulkIdErrorReason.DUPLICATE },
|
{ success: false, id: asset.id, error: BulkIdErrorReason.DUPLICATE },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -755,16 +812,17 @@ describe(AlbumService.name, () => {
|
|||||||
it('should skip assets not shared with user', async () => {
|
it('should skip assets not shared with user', async () => {
|
||||||
const asset = AssetFactory.create();
|
const asset = AssetFactory.create();
|
||||||
const album = AlbumFactory.create();
|
const album = AlbumFactory.create();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||||
mocks.album.getAssetIds.mockResolvedValueOnce(new Set());
|
mocks.album.getAssetIds.mockResolvedValueOnce(new Set());
|
||||||
|
|
||||||
await expect(sut.addAssets(AuthFactory.create(album.owner), album.id, { ids: [asset.id] })).resolves.toEqual([
|
await expect(sut.addAssets(AuthFactory.create(owner), album.id, { ids: [asset.id] })).resolves.toEqual([
|
||||||
{ success: false, id: asset.id, error: BulkIdErrorReason.NO_PERMISSION },
|
{ success: false, id: asset.id, error: BulkIdErrorReason.NO_PERMISSION },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(album.ownerId, new Set([asset.id]), false);
|
expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(owner.id, new Set([asset.id]), false);
|
||||||
expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(album.ownerId, new Set([asset.id]));
|
expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(owner.id, new Set([asset.id]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not allow unauthorized access to the album', async () => {
|
it('should not allow unauthorized access to the album', async () => {
|
||||||
@@ -797,6 +855,7 @@ describe(AlbumService.name, () => {
|
|||||||
describe('addAssetsToAlbums', () => {
|
describe('addAssetsToAlbums', () => {
|
||||||
it('should allow the owner to add assets', async () => {
|
it('should allow the owner to add assets', async () => {
|
||||||
const album1 = AlbumFactory.create();
|
const album1 = AlbumFactory.create();
|
||||||
|
const { user: owner } = album1.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
const album2 = AlbumFactory.create();
|
const album2 = AlbumFactory.create();
|
||||||
const [asset1, asset2, asset3] = [AssetFactory.create(), AssetFactory.create(), AssetFactory.create()];
|
const [asset1, asset2, asset3] = [AssetFactory.create(), AssetFactory.create(), AssetFactory.create()];
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValueOnce(new Set([album1.id, album2.id]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValueOnce(new Set([album1.id, album2.id]));
|
||||||
@@ -805,23 +864,33 @@ describe(AlbumService.name, () => {
|
|||||||
mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set());
|
mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set());
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.addAssetsToAlbums(AuthFactory.create(album1.owner), {
|
sut.addAssetsToAlbums(AuthFactory.create(owner), {
|
||||||
albumIds: [album1.id, album2.id],
|
albumIds: [album1.id, album2.id],
|
||||||
assetIds: [asset1.id, asset2.id, asset3.id],
|
assetIds: [asset1.id, asset2.id, asset3.id],
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual({ success: true, error: undefined });
|
).resolves.toEqual({ success: true, error: undefined });
|
||||||
|
|
||||||
expect(mocks.album.update).toHaveBeenCalledTimes(2);
|
expect(mocks.album.update).toHaveBeenCalledTimes(2);
|
||||||
expect(mocks.album.update).toHaveBeenNthCalledWith(1, album1.id, {
|
expect(mocks.album.update).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
album1.id,
|
||||||
|
{
|
||||||
id: album1.id,
|
id: album1.id,
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
albumThumbnailAssetId: asset1.id,
|
albumThumbnailAssetId: asset1.id,
|
||||||
});
|
},
|
||||||
expect(mocks.album.update).toHaveBeenNthCalledWith(2, album2.id, {
|
owner.id,
|
||||||
|
);
|
||||||
|
expect(mocks.album.update).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
album2.id,
|
||||||
|
{
|
||||||
id: album2.id,
|
id: album2.id,
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
albumThumbnailAssetId: asset1.id,
|
albumThumbnailAssetId: asset1.id,
|
||||||
});
|
},
|
||||||
|
owner.id,
|
||||||
|
);
|
||||||
expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([
|
expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([
|
||||||
{ albumId: album1.id, assetId: asset1.id },
|
{ albumId: album1.id, assetId: asset1.id },
|
||||||
{ albumId: album1.id, assetId: asset2.id },
|
{ albumId: album1.id, assetId: asset2.id },
|
||||||
@@ -835,6 +904,7 @@ describe(AlbumService.name, () => {
|
|||||||
it('should not set the thumbnail if the album has one already', async () => {
|
it('should not set the thumbnail if the album has one already', async () => {
|
||||||
const asset = AssetFactory.create();
|
const asset = AssetFactory.create();
|
||||||
const album1 = AlbumFactory.from({ albumThumbnailAssetId: asset.id }).build();
|
const album1 = AlbumFactory.from({ albumThumbnailAssetId: asset.id }).build();
|
||||||
|
const { user: owner } = album1.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
const album2 = AlbumFactory.from({ albumThumbnailAssetId: asset.id }).build();
|
const album2 = AlbumFactory.from({ albumThumbnailAssetId: asset.id }).build();
|
||||||
const [asset1, asset2, asset3] = [AssetFactory.create(), AssetFactory.create(), AssetFactory.create()];
|
const [asset1, asset2, asset3] = [AssetFactory.create(), AssetFactory.create(), AssetFactory.create()];
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValueOnce(new Set([album1.id, album2.id]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValueOnce(new Set([album1.id, album2.id]));
|
||||||
@@ -843,23 +913,33 @@ describe(AlbumService.name, () => {
|
|||||||
mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set());
|
mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set());
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.addAssetsToAlbums(AuthFactory.create(album1.owner), {
|
sut.addAssetsToAlbums(AuthFactory.create(owner), {
|
||||||
albumIds: [album1.id, album2.id],
|
albumIds: [album1.id, album2.id],
|
||||||
assetIds: [asset1.id, asset2.id, asset3.id],
|
assetIds: [asset1.id, asset2.id, asset3.id],
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual({ success: true, error: undefined });
|
).resolves.toEqual({ success: true, error: undefined });
|
||||||
|
|
||||||
expect(mocks.album.update).toHaveBeenCalledTimes(2);
|
expect(mocks.album.update).toHaveBeenCalledTimes(2);
|
||||||
expect(mocks.album.update).toHaveBeenNthCalledWith(1, album1.id, {
|
expect(mocks.album.update).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
album1.id,
|
||||||
|
{
|
||||||
id: album1.id,
|
id: album1.id,
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
albumThumbnailAssetId: asset.id,
|
albumThumbnailAssetId: asset.id,
|
||||||
});
|
},
|
||||||
expect(mocks.album.update).toHaveBeenNthCalledWith(2, album2.id, {
|
owner.id,
|
||||||
|
);
|
||||||
|
expect(mocks.album.update).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
album2.id,
|
||||||
|
{
|
||||||
id: album2.id,
|
id: album2.id,
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
albumThumbnailAssetId: asset.id,
|
albumThumbnailAssetId: asset.id,
|
||||||
});
|
},
|
||||||
|
owner.id,
|
||||||
|
);
|
||||||
expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([
|
expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([
|
||||||
{ albumId: album1.id, assetId: asset1.id },
|
{ albumId: album1.id, assetId: asset1.id },
|
||||||
{ albumId: album1.id, assetId: asset2.id },
|
{ albumId: album1.id, assetId: asset2.id },
|
||||||
@@ -873,7 +953,9 @@ describe(AlbumService.name, () => {
|
|||||||
it('should allow a shared user to add assets', async () => {
|
it('should allow a shared user to add assets', async () => {
|
||||||
const user = UserFactory.create();
|
const user = UserFactory.create();
|
||||||
const album1 = AlbumFactory.from().albumUser({ userId: user.id, role: AlbumUserRole.Editor }).build();
|
const album1 = AlbumFactory.from().albumUser({ userId: user.id, role: AlbumUserRole.Editor }).build();
|
||||||
|
const { user: owner1 } = album1.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
const album2 = AlbumFactory.from().albumUser({ userId: user.id, role: AlbumUserRole.Editor }).build();
|
const album2 = AlbumFactory.from().albumUser({ userId: user.id, role: AlbumUserRole.Editor }).build();
|
||||||
|
const { user: owner2 } = album2.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
const [asset1, asset2, asset3] = [AssetFactory.create(), AssetFactory.create(), AssetFactory.create()];
|
const [asset1, asset2, asset3] = [AssetFactory.create(), AssetFactory.create(), AssetFactory.create()];
|
||||||
mocks.access.album.checkSharedAlbumAccess.mockResolvedValueOnce(new Set([album1.id, album2.id]));
|
mocks.access.album.checkSharedAlbumAccess.mockResolvedValueOnce(new Set([album1.id, album2.id]));
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id]));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id]));
|
||||||
@@ -888,16 +970,26 @@ describe(AlbumService.name, () => {
|
|||||||
).resolves.toEqual({ success: true, error: undefined });
|
).resolves.toEqual({ success: true, error: undefined });
|
||||||
|
|
||||||
expect(mocks.album.update).toHaveBeenCalledTimes(2);
|
expect(mocks.album.update).toHaveBeenCalledTimes(2);
|
||||||
expect(mocks.album.update).toHaveBeenNthCalledWith(1, album1.id, {
|
expect(mocks.album.update).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
album1.id,
|
||||||
|
{
|
||||||
id: album1.id,
|
id: album1.id,
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
albumThumbnailAssetId: asset1.id,
|
albumThumbnailAssetId: asset1.id,
|
||||||
});
|
},
|
||||||
expect(mocks.album.update).toHaveBeenNthCalledWith(2, album2.id, {
|
user.id,
|
||||||
|
);
|
||||||
|
expect(mocks.album.update).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
album2.id,
|
||||||
|
{
|
||||||
id: album2.id,
|
id: album2.id,
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
albumThumbnailAssetId: asset1.id,
|
albumThumbnailAssetId: asset1.id,
|
||||||
});
|
},
|
||||||
|
user.id,
|
||||||
|
);
|
||||||
expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([
|
expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([
|
||||||
{ albumId: album1.id, assetId: asset1.id },
|
{ albumId: album1.id, assetId: asset1.id },
|
||||||
{ albumId: album1.id, assetId: asset2.id },
|
{ albumId: album1.id, assetId: asset2.id },
|
||||||
@@ -908,11 +1000,11 @@ describe(AlbumService.name, () => {
|
|||||||
]);
|
]);
|
||||||
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', {
|
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', {
|
||||||
id: album1.id,
|
id: album1.id,
|
||||||
recipientId: album1.ownerId,
|
recipientId: owner1.id,
|
||||||
});
|
});
|
||||||
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', {
|
expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', {
|
||||||
id: album2.id,
|
id: album2.id,
|
||||||
recipientId: album2.ownerId,
|
recipientId: owner2.id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -942,6 +1034,7 @@ describe(AlbumService.name, () => {
|
|||||||
it('should allow adding assets shared via partner sharing', async () => {
|
it('should allow adding assets shared via partner sharing', async () => {
|
||||||
const user = UserFactory.create();
|
const user = UserFactory.create();
|
||||||
const album1 = AlbumFactory.create();
|
const album1 = AlbumFactory.create();
|
||||||
|
const { user: owner } = album1.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
const album2 = AlbumFactory.create();
|
const album2 = AlbumFactory.create();
|
||||||
const [asset1, asset2, asset3] = [
|
const [asset1, asset2, asset3] = [
|
||||||
AssetFactory.create({ ownerId: user.id }),
|
AssetFactory.create({ ownerId: user.id }),
|
||||||
@@ -954,23 +1047,33 @@ describe(AlbumService.name, () => {
|
|||||||
mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set());
|
mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set());
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.addAssetsToAlbums(AuthFactory.create(album1.owner), {
|
sut.addAssetsToAlbums(AuthFactory.create(owner), {
|
||||||
albumIds: [album1.id, album2.id],
|
albumIds: [album1.id, album2.id],
|
||||||
assetIds: [asset1.id, asset2.id, asset3.id],
|
assetIds: [asset1.id, asset2.id, asset3.id],
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual({ success: true, error: undefined });
|
).resolves.toEqual({ success: true, error: undefined });
|
||||||
|
|
||||||
expect(mocks.album.update).toHaveBeenCalledTimes(2);
|
expect(mocks.album.update).toHaveBeenCalledTimes(2);
|
||||||
expect(mocks.album.update).toHaveBeenNthCalledWith(1, album1.id, {
|
expect(mocks.album.update).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
album1.id,
|
||||||
|
{
|
||||||
id: album1.id,
|
id: album1.id,
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
albumThumbnailAssetId: asset1.id,
|
albumThumbnailAssetId: asset1.id,
|
||||||
});
|
},
|
||||||
expect(mocks.album.update).toHaveBeenNthCalledWith(2, album2.id, {
|
owner.id,
|
||||||
|
);
|
||||||
|
expect(mocks.album.update).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
album2.id,
|
||||||
|
{
|
||||||
id: album2.id,
|
id: album2.id,
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
albumThumbnailAssetId: asset1.id,
|
albumThumbnailAssetId: asset1.id,
|
||||||
});
|
},
|
||||||
|
owner.id,
|
||||||
|
);
|
||||||
expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([
|
expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([
|
||||||
{ albumId: album1.id, assetId: asset1.id },
|
{ albumId: album1.id, assetId: asset1.id },
|
||||||
{ albumId: album1.id, assetId: asset2.id },
|
{ albumId: album1.id, assetId: asset2.id },
|
||||||
@@ -980,7 +1083,7 @@ describe(AlbumService.name, () => {
|
|||||||
{ albumId: album2.id, assetId: asset3.id },
|
{ albumId: album2.id, assetId: asset3.id },
|
||||||
]);
|
]);
|
||||||
expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(
|
expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(
|
||||||
album1.ownerId,
|
owner.id,
|
||||||
new Set([asset1.id, asset2.id, asset3.id]),
|
new Set([asset1.id, asset2.id, asset3.id]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -988,7 +1091,9 @@ describe(AlbumService.name, () => {
|
|||||||
it('should skip some duplicate assets', async () => {
|
it('should skip some duplicate assets', async () => {
|
||||||
const [asset1, asset2, asset3] = [AssetFactory.create(), AssetFactory.create(), AssetFactory.create()];
|
const [asset1, asset2, asset3] = [AssetFactory.create(), AssetFactory.create(), AssetFactory.create()];
|
||||||
const album1 = AlbumFactory.create();
|
const album1 = AlbumFactory.create();
|
||||||
|
const { user: owner } = album1.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
const album2 = AlbumFactory.create();
|
const album2 = AlbumFactory.create();
|
||||||
|
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValueOnce(new Set([album1.id, album2.id]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValueOnce(new Set([album1.id, album2.id]));
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id]));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id]));
|
||||||
mocks.album.getAssetIds
|
mocks.album.getAssetIds
|
||||||
@@ -997,18 +1102,23 @@ describe(AlbumService.name, () => {
|
|||||||
mocks.album.getById.mockResolvedValueOnce(getForAlbum(album1)).mockResolvedValueOnce(getForAlbum(album2));
|
mocks.album.getById.mockResolvedValueOnce(getForAlbum(album1)).mockResolvedValueOnce(getForAlbum(album2));
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.addAssetsToAlbums(AuthFactory.create(album1.owner), {
|
sut.addAssetsToAlbums(AuthFactory.create(owner), {
|
||||||
albumIds: [album1.id, album2.id],
|
albumIds: [album1.id, album2.id],
|
||||||
assetIds: [asset1.id, asset2.id, asset3.id],
|
assetIds: [asset1.id, asset2.id, asset3.id],
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual({ success: true, error: undefined });
|
).resolves.toEqual({ success: true, error: undefined });
|
||||||
|
|
||||||
expect(mocks.album.update).toHaveBeenCalledTimes(1);
|
expect(mocks.album.update).toHaveBeenCalledTimes(1);
|
||||||
expect(mocks.album.update).toHaveBeenNthCalledWith(1, album2.id, {
|
expect(mocks.album.update).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
album2.id,
|
||||||
|
{
|
||||||
id: album2.id,
|
id: album2.id,
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
albumThumbnailAssetId: asset1.id,
|
albumThumbnailAssetId: asset1.id,
|
||||||
});
|
},
|
||||||
|
owner.id,
|
||||||
|
);
|
||||||
expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([
|
expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([
|
||||||
{ albumId: album2.id, assetId: asset1.id },
|
{ albumId: album2.id, assetId: asset1.id },
|
||||||
{ albumId: album2.id, assetId: asset2.id },
|
{ albumId: album2.id, assetId: asset2.id },
|
||||||
@@ -1019,6 +1129,7 @@ describe(AlbumService.name, () => {
|
|||||||
it('should skip all duplicate assets', async () => {
|
it('should skip all duplicate assets', async () => {
|
||||||
const [asset1, asset2, asset3] = [AssetFactory.create(), AssetFactory.create(), AssetFactory.create()];
|
const [asset1, asset2, asset3] = [AssetFactory.create(), AssetFactory.create(), AssetFactory.create()];
|
||||||
const album1 = AlbumFactory.create();
|
const album1 = AlbumFactory.create();
|
||||||
|
const { user: owner } = album1.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
const album2 = AlbumFactory.create();
|
const album2 = AlbumFactory.create();
|
||||||
mocks.access.album.checkOwnerAccess
|
mocks.access.album.checkOwnerAccess
|
||||||
.mockResolvedValueOnce(new Set([album1.id]))
|
.mockResolvedValueOnce(new Set([album1.id]))
|
||||||
@@ -1028,7 +1139,7 @@ describe(AlbumService.name, () => {
|
|||||||
mocks.album.getAssetIds.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id]));
|
mocks.album.getAssetIds.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id]));
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.addAssetsToAlbums(AuthFactory.create(album1.owner), {
|
sut.addAssetsToAlbums(AuthFactory.create(owner), {
|
||||||
albumIds: [album1.id, album2.id],
|
albumIds: [album1.id, album2.id],
|
||||||
assetIds: [asset1.id, asset2.id, asset3.id],
|
assetIds: [asset1.id, asset2.id, asset3.id],
|
||||||
}),
|
}),
|
||||||
@@ -1044,6 +1155,7 @@ describe(AlbumService.name, () => {
|
|||||||
it('should skip assets not shared with user', async () => {
|
it('should skip assets not shared with user', async () => {
|
||||||
const user = UserFactory.create();
|
const user = UserFactory.create();
|
||||||
const album1 = AlbumFactory.create();
|
const album1 = AlbumFactory.create();
|
||||||
|
const { user: owner } = album1.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
const album2 = AlbumFactory.create();
|
const album2 = AlbumFactory.create();
|
||||||
const [asset1, asset2, asset3] = [
|
const [asset1, asset2, asset3] = [
|
||||||
AssetFactory.create({ ownerId: user.id }),
|
AssetFactory.create({ ownerId: user.id }),
|
||||||
@@ -1057,7 +1169,7 @@ describe(AlbumService.name, () => {
|
|||||||
mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set());
|
mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set());
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.addAssetsToAlbums(AuthFactory.create(album1.owner), {
|
sut.addAssetsToAlbums(AuthFactory.create(owner), {
|
||||||
albumIds: [album1.id, album2.id],
|
albumIds: [album1.id, album2.id],
|
||||||
assetIds: [asset1.id, asset2.id, asset3.id],
|
assetIds: [asset1.id, asset2.id, asset3.id],
|
||||||
}),
|
}),
|
||||||
@@ -1069,12 +1181,12 @@ describe(AlbumService.name, () => {
|
|||||||
expect(mocks.album.update).not.toHaveBeenCalled();
|
expect(mocks.album.update).not.toHaveBeenCalled();
|
||||||
expect(mocks.album.addAssetIds).not.toHaveBeenCalled();
|
expect(mocks.album.addAssetIds).not.toHaveBeenCalled();
|
||||||
expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(
|
expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(
|
||||||
album1.ownerId,
|
owner.id,
|
||||||
new Set([asset1.id, asset2.id, asset3.id]),
|
new Set([asset1.id, asset2.id, asset3.id]),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(
|
expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(
|
||||||
album1.ownerId,
|
owner.id,
|
||||||
new Set([asset1.id, asset2.id, asset3.id]),
|
new Set([asset1.id, asset2.id, asset3.id]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1126,12 +1238,13 @@ describe(AlbumService.name, () => {
|
|||||||
it('should allow the owner to remove assets', async () => {
|
it('should allow the owner to remove assets', async () => {
|
||||||
const asset = AssetFactory.create();
|
const asset = AssetFactory.create();
|
||||||
const album = AlbumFactory.create();
|
const album = AlbumFactory.create();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id]));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id]));
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||||
mocks.album.getAssetIds.mockResolvedValue(new Set([asset.id]));
|
mocks.album.getAssetIds.mockResolvedValue(new Set([asset.id]));
|
||||||
|
|
||||||
await expect(sut.removeAssets(AuthFactory.create(album.owner), album.id, { ids: [asset.id] })).resolves.toEqual([
|
await expect(sut.removeAssets(AuthFactory.create(owner), album.id, { ids: [asset.id] })).resolves.toEqual([
|
||||||
{ success: true, id: asset.id },
|
{ success: true, id: asset.id },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -1141,11 +1254,12 @@ describe(AlbumService.name, () => {
|
|||||||
it('should skip assets not in the album', async () => {
|
it('should skip assets not in the album', async () => {
|
||||||
const asset = AssetFactory.create();
|
const asset = AssetFactory.create();
|
||||||
const album = AlbumFactory.create();
|
const album = AlbumFactory.create();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||||
mocks.album.getAssetIds.mockResolvedValue(new Set());
|
mocks.album.getAssetIds.mockResolvedValue(new Set());
|
||||||
|
|
||||||
await expect(sut.removeAssets(AuthFactory.create(album.owner), album.id, { ids: [asset.id] })).resolves.toEqual([
|
await expect(sut.removeAssets(AuthFactory.create(owner), album.id, { ids: [asset.id] })).resolves.toEqual([
|
||||||
{ success: false, id: asset.id, error: BulkIdErrorReason.NOT_FOUND },
|
{ success: false, id: asset.id, error: BulkIdErrorReason.NOT_FOUND },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -1155,11 +1269,12 @@ describe(AlbumService.name, () => {
|
|||||||
it('should allow owner to remove all assets from the album', async () => {
|
it('should allow owner to remove all assets from the album', async () => {
|
||||||
const asset = AssetFactory.create();
|
const asset = AssetFactory.create();
|
||||||
const album = AlbumFactory.create();
|
const album = AlbumFactory.create();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||||
mocks.album.getAssetIds.mockResolvedValue(new Set([asset.id]));
|
mocks.album.getAssetIds.mockResolvedValue(new Set([asset.id]));
|
||||||
|
|
||||||
await expect(sut.removeAssets(AuthFactory.create(album.owner), album.id, { ids: [asset.id] })).resolves.toEqual([
|
await expect(sut.removeAssets(AuthFactory.create(owner), album.id, { ids: [asset.id] })).resolves.toEqual([
|
||||||
{ success: true, id: asset.id },
|
{ success: true, id: asset.id },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -1168,12 +1283,13 @@ describe(AlbumService.name, () => {
|
|||||||
const asset1 = AssetFactory.create();
|
const asset1 = AssetFactory.create();
|
||||||
const asset2 = AssetFactory.create();
|
const asset2 = AssetFactory.create();
|
||||||
const album = AlbumFactory.from({ albumThumbnailAssetId: asset1.id }).build();
|
const album = AlbumFactory.from({ albumThumbnailAssetId: asset1.id }).build();
|
||||||
|
const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!;
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id]));
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id]));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id]));
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||||
mocks.album.getAssetIds.mockResolvedValue(new Set([asset1.id, asset2.id]));
|
mocks.album.getAssetIds.mockResolvedValue(new Set([asset1.id, asset2.id]));
|
||||||
|
|
||||||
await expect(sut.removeAssets(AuthFactory.create(album.owner), album.id, { ids: [asset1.id] })).resolves.toEqual([
|
await expect(sut.removeAssets(AuthFactory.create(owner), album.id, { ids: [asset1.id] })).resolves.toEqual([
|
||||||
{ success: true, id: asset1.id },
|
{ success: true, id: asset1.id },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
import { BulkIdErrorReason, BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
import { BulkIdErrorReason, BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { MapMarkerResponseDto } from 'src/dtos/map.dto';
|
import { MapMarkerResponseDto } from 'src/dtos/map.dto';
|
||||||
import { Permission } from 'src/enum';
|
import { AlbumUserRole, Permission } from 'src/enum';
|
||||||
import { AlbumAssetCount, AlbumInfoOptions } from 'src/repositories/album.repository';
|
import { AlbumAssetCount, AlbumInfoOptions } from 'src/repositories/album.repository';
|
||||||
import { BaseService } from 'src/services/base.service';
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { addAssets, removeAssets } from 'src/utils/asset.util';
|
import { addAssets, removeAssets } from 'src/utils/asset.util';
|
||||||
@@ -74,10 +74,10 @@ export class AlbumService extends BaseService {
|
|||||||
async get(auth: AuthDto, id: string): Promise<AlbumResponseDto> {
|
async get(auth: AuthDto, id: string): Promise<AlbumResponseDto> {
|
||||||
await this.requireAccess({ auth, permission: Permission.AlbumRead, ids: [id] });
|
await this.requireAccess({ auth, permission: Permission.AlbumRead, ids: [id] });
|
||||||
await this.albumRepository.updateThumbnails();
|
await this.albumRepository.updateThumbnails();
|
||||||
const album = await this.findOrFail(id, { withAssets: false });
|
const album = await this.findOrFail(id, auth.user.id, { withAssets: false });
|
||||||
const [albumMetadataForIds] = await this.albumRepository.getMetadataForIds([album.id]);
|
const [albumMetadataForIds] = await this.albumRepository.getMetadataForIds([album.id]);
|
||||||
|
|
||||||
const hasSharedUsers = album.albumUsers && album.albumUsers.length > 0;
|
const hasSharedUsers = album.albumUsers && album.albumUsers.length > 1;
|
||||||
const hasSharedLink = album.sharedLinks && album.sharedLinks.length > 0;
|
const hasSharedLink = album.sharedLinks && album.sharedLinks.length > 0;
|
||||||
const isShared = hasSharedUsers || hasSharedLink;
|
const isShared = hasSharedUsers || hasSharedLink;
|
||||||
|
|
||||||
@@ -114,6 +114,7 @@ export class AlbumService extends BaseService {
|
|||||||
throw new BadRequestException('Cannot share album with owner');
|
throw new BadRequestException('Cannot share album with owner');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
albumUsers.unshift({ userId: auth.user.id, role: AlbumUserRole.Owner });
|
||||||
|
|
||||||
const allowedAssetIdsSet = await this.checkAccess({
|
const allowedAssetIdsSet = await this.checkAccess({
|
||||||
auth,
|
auth,
|
||||||
@@ -126,7 +127,6 @@ export class AlbumService extends BaseService {
|
|||||||
|
|
||||||
const album = await this.albumRepository.create(
|
const album = await this.albumRepository.create(
|
||||||
{
|
{
|
||||||
ownerId: auth.user.id,
|
|
||||||
albumName: dto.albumName,
|
albumName: dto.albumName,
|
||||||
description: dto.description,
|
description: dto.description,
|
||||||
albumThumbnailAssetId: assetIds[0] || null,
|
albumThumbnailAssetId: assetIds[0] || null,
|
||||||
@@ -134,10 +134,11 @@ export class AlbumService extends BaseService {
|
|||||||
},
|
},
|
||||||
assetIds,
|
assetIds,
|
||||||
albumUsers,
|
albumUsers,
|
||||||
|
auth.user.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const { userId } of albumUsers) {
|
for (const { userId } of albumUsers) {
|
||||||
await this.eventRepository.emit('AlbumInvite', { id: album.id, userId });
|
await this.eventRepository.emit('AlbumInvite', { id: album.id, userId, senderName: auth.user.name });
|
||||||
}
|
}
|
||||||
|
|
||||||
return mapAlbum(album);
|
return mapAlbum(album);
|
||||||
@@ -146,7 +147,7 @@ export class AlbumService extends BaseService {
|
|||||||
async update(auth: AuthDto, id: string, dto: UpdateAlbumDto): Promise<AlbumResponseDto> {
|
async update(auth: AuthDto, id: string, dto: UpdateAlbumDto): Promise<AlbumResponseDto> {
|
||||||
await this.requireAccess({ auth, permission: Permission.AlbumUpdate, ids: [id] });
|
await this.requireAccess({ auth, permission: Permission.AlbumUpdate, ids: [id] });
|
||||||
|
|
||||||
const album = await this.findOrFail(id, { withAssets: true });
|
const album = await this.findOrFail(id, auth.user.id, { withAssets: true });
|
||||||
|
|
||||||
if (dto.albumThumbnailAssetId) {
|
if (dto.albumThumbnailAssetId) {
|
||||||
const results = await this.albumRepository.getAssetIds(id, [dto.albumThumbnailAssetId]);
|
const results = await this.albumRepository.getAssetIds(id, [dto.albumThumbnailAssetId]);
|
||||||
@@ -154,14 +155,18 @@ export class AlbumService extends BaseService {
|
|||||||
throw new BadRequestException('Invalid album thumbnail');
|
throw new BadRequestException('Invalid album thumbnail');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const updatedAlbum = await this.albumRepository.update(album.id, {
|
const updatedAlbum = await this.albumRepository.update(
|
||||||
|
album.id,
|
||||||
|
{
|
||||||
id: album.id,
|
id: album.id,
|
||||||
albumName: dto.albumName,
|
albumName: dto.albumName,
|
||||||
description: dto.description,
|
description: dto.description,
|
||||||
albumThumbnailAssetId: dto.albumThumbnailAssetId,
|
albumThumbnailAssetId: dto.albumThumbnailAssetId,
|
||||||
isActivityEnabled: dto.isActivityEnabled,
|
isActivityEnabled: dto.isActivityEnabled,
|
||||||
order: dto.order,
|
order: dto.order,
|
||||||
});
|
},
|
||||||
|
auth.user.id,
|
||||||
|
);
|
||||||
|
|
||||||
return mapAlbum({ ...updatedAlbum, assets: album.assets });
|
return mapAlbum({ ...updatedAlbum, assets: album.assets });
|
||||||
}
|
}
|
||||||
@@ -172,7 +177,7 @@ export class AlbumService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async addAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
|
async addAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
|
||||||
const album = await this.findOrFail(id, { withAssets: false });
|
const album = await this.findOrFail(id, auth.user.id, { withAssets: false });
|
||||||
await this.requireAccess({ auth, permission: Permission.AlbumAssetCreate, ids: [id] });
|
await this.requireAccess({ auth, permission: Permission.AlbumAssetCreate, ids: [id] });
|
||||||
|
|
||||||
const results = await addAssets(
|
const results = await addAssets(
|
||||||
@@ -183,16 +188,18 @@ export class AlbumService extends BaseService {
|
|||||||
|
|
||||||
const { id: firstNewAssetId } = results.find(({ success }) => success) || {};
|
const { id: firstNewAssetId } = results.find(({ success }) => success) || {};
|
||||||
if (firstNewAssetId) {
|
if (firstNewAssetId) {
|
||||||
await this.albumRepository.update(id, {
|
await this.albumRepository.update(
|
||||||
|
id,
|
||||||
|
{
|
||||||
id,
|
id,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
albumThumbnailAssetId: album.albumThumbnailAssetId ?? firstNewAssetId,
|
albumThumbnailAssetId: album.albumThumbnailAssetId ?? firstNewAssetId,
|
||||||
});
|
},
|
||||||
|
auth.user.id,
|
||||||
const allUsersExceptUs = [...album.albumUsers.map(({ user }) => user.id), album.owner.id].filter(
|
|
||||||
(userId) => userId !== auth.user.id,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const allUsersExceptUs = album.albumUsers.map(({ user }) => user.id).filter((userId) => userId !== auth.user.id);
|
||||||
|
|
||||||
for (const recipientId of allUsersExceptUs) {
|
for (const recipientId of allUsersExceptUs) {
|
||||||
await this.eventRepository.emit('AlbumUpdate', { id, recipientId });
|
await this.eventRepository.emit('AlbumUpdate', { id, recipientId });
|
||||||
}
|
}
|
||||||
@@ -231,21 +238,23 @@ export class AlbumService extends BaseService {
|
|||||||
if (notPresentAssetIds.length === 0) {
|
if (notPresentAssetIds.length === 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const album = await this.findOrFail(albumId, { withAssets: false });
|
const album = await this.findOrFail(albumId, auth.user.id, { withAssets: false });
|
||||||
results.error = undefined;
|
results.error = undefined;
|
||||||
results.success = true;
|
results.success = true;
|
||||||
|
|
||||||
for (const assetId of notPresentAssetIds) {
|
for (const assetId of notPresentAssetIds) {
|
||||||
albumAssetValues.push({ albumId, assetId });
|
albumAssetValues.push({ albumId, assetId });
|
||||||
}
|
}
|
||||||
await this.albumRepository.update(albumId, {
|
await this.albumRepository.update(
|
||||||
|
albumId,
|
||||||
|
{
|
||||||
id: albumId,
|
id: albumId,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
albumThumbnailAssetId: album.albumThumbnailAssetId ?? notPresentAssetIds[0],
|
albumThumbnailAssetId: album.albumThumbnailAssetId ?? notPresentAssetIds[0],
|
||||||
});
|
},
|
||||||
const allUsersExceptUs = [...album.albumUsers.map(({ user }) => user.id), album.owner.id].filter(
|
auth.user.id,
|
||||||
(userId) => userId !== auth.user.id,
|
|
||||||
);
|
);
|
||||||
|
const allUsersExceptUs = album.albumUsers.map(({ user }) => user.id).filter((userId) => userId !== auth.user.id);
|
||||||
events.push({ id: albumId, recipients: allUsersExceptUs });
|
events.push({ id: albumId, recipients: allUsersExceptUs });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,7 +271,7 @@ export class AlbumService extends BaseService {
|
|||||||
async removeAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
|
async removeAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
|
||||||
await this.requireAccess({ auth, permission: Permission.AlbumAssetDelete, ids: [id] });
|
await this.requireAccess({ auth, permission: Permission.AlbumAssetDelete, ids: [id] });
|
||||||
|
|
||||||
const album = await this.findOrFail(id, { withAssets: false });
|
const album = await this.findOrFail(id, auth.user.id, { withAssets: false });
|
||||||
const results = await removeAssets(
|
const results = await removeAssets(
|
||||||
auth,
|
auth,
|
||||||
{ access: this.accessRepository, bulk: this.albumRepository },
|
{ access: this.accessRepository, bulk: this.albumRepository },
|
||||||
@@ -280,11 +289,11 @@ export class AlbumService extends BaseService {
|
|||||||
async addUsers(auth: AuthDto, id: string, { albumUsers }: AddUsersDto): Promise<AlbumResponseDto> {
|
async addUsers(auth: AuthDto, id: string, { albumUsers }: AddUsersDto): Promise<AlbumResponseDto> {
|
||||||
await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] });
|
await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] });
|
||||||
|
|
||||||
const album = await this.findOrFail(id, { withAssets: false });
|
const album = await this.findOrFail(id, auth.user.id, { withAssets: false });
|
||||||
|
|
||||||
for (const { userId, role } of albumUsers) {
|
for (const { userId, role } of albumUsers) {
|
||||||
if (album.ownerId === userId) {
|
if (role === AlbumUserRole.Owner) {
|
||||||
throw new BadRequestException('Cannot be shared with owner');
|
throw new BadRequestException('Cannot add another owner');
|
||||||
}
|
}
|
||||||
|
|
||||||
const exists = album.albumUsers.find(({ user: { id } }) => id === userId);
|
const exists = album.albumUsers.find(({ user: { id } }) => id === userId);
|
||||||
@@ -298,10 +307,10 @@ export class AlbumService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.albumUserRepository.create({ userId, albumId: id, role });
|
await this.albumUserRepository.create({ userId, albumId: id, role });
|
||||||
await this.eventRepository.emit('AlbumInvite', { id, userId });
|
await this.eventRepository.emit('AlbumInvite', { id, userId, senderName: auth.user.name });
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.findOrFail(id, { withAssets: true }).then(mapAlbum);
|
return this.findOrFail(id, auth.user.id, { withAssets: true }).then(mapAlbum);
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeUser(auth: AuthDto, id: string, userId: string | 'me'): Promise<void> {
|
async removeUser(auth: AuthDto, id: string, userId: string | 'me'): Promise<void> {
|
||||||
@@ -309,17 +318,20 @@ export class AlbumService extends BaseService {
|
|||||||
userId = auth.user.id;
|
userId = auth.user.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const album = await this.findOrFail(id, { withAssets: false });
|
const album = await this.findOrFail(id, auth.user.id, { withAssets: false });
|
||||||
|
|
||||||
if (album.ownerId === userId) {
|
|
||||||
throw new BadRequestException('Cannot remove album owner');
|
|
||||||
}
|
|
||||||
|
|
||||||
const exists = album.albumUsers.find(({ user: { id } }) => id === userId);
|
const exists = album.albumUsers.find(({ user: { id } }) => id === userId);
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
throw new BadRequestException('Album not shared with user');
|
throw new BadRequestException('Album not shared with user');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
exists.role === AlbumUserRole.Owner &&
|
||||||
|
album.albumUsers.filter(({ role }) => role === AlbumUserRole.Owner).length === 1
|
||||||
|
) {
|
||||||
|
throw new BadRequestException('Cannot remove the last album owner');
|
||||||
|
}
|
||||||
|
|
||||||
// non-admin can remove themselves
|
// non-admin can remove themselves
|
||||||
if (auth.user.id !== userId) {
|
if (auth.user.id !== userId) {
|
||||||
await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] });
|
await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] });
|
||||||
@@ -333,8 +345,8 @@ export class AlbumService extends BaseService {
|
|||||||
await this.albumUserRepository.update({ albumId: id, userId }, { role: dto.role });
|
await this.albumUserRepository.update({ albumId: id, userId }, { role: dto.role });
|
||||||
}
|
}
|
||||||
|
|
||||||
private async findOrFail(id: string, options: AlbumInfoOptions) {
|
private async findOrFail(id: string, authUserId: string, options: AlbumInfoOptions) {
|
||||||
const album = await this.albumRepository.getById(id, options);
|
const album = await this.albumRepository.getById(id, options, authUserId);
|
||||||
if (!album) {
|
if (!album) {
|
||||||
throw new BadRequestException('Album not found');
|
throw new BadRequestException('Album not found');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,10 +168,10 @@ describe(NotificationService.name, () => {
|
|||||||
|
|
||||||
describe('onAlbumInviteEvent', () => {
|
describe('onAlbumInviteEvent', () => {
|
||||||
it('should queue notify album invite event', async () => {
|
it('should queue notify album invite event', async () => {
|
||||||
await sut.onAlbumInvite({ id: '', userId: '42' });
|
await sut.onAlbumInvite({ id: '', userId: '42', senderName: 'foo' });
|
||||||
expect(mocks.job.queue).toHaveBeenCalledWith({
|
expect(mocks.job.queue).toHaveBeenCalledWith({
|
||||||
name: JobName.NotifyAlbumInvite,
|
name: JobName.NotifyAlbumInvite,
|
||||||
data: { id: '', recipientId: '42' },
|
data: { id: '', recipientId: '42', senderName: 'foo' },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -264,14 +264,18 @@ describe(NotificationService.name, () => {
|
|||||||
|
|
||||||
describe('handleAlbumInvite', () => {
|
describe('handleAlbumInvite', () => {
|
||||||
it('should skip if album could not be found', async () => {
|
it('should skip if album could not be found', async () => {
|
||||||
await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Skipped);
|
await expect(sut.handleAlbumInvite({ id: '', recipientId: '', senderName: 'foo' })).resolves.toBe(
|
||||||
|
JobStatus.Skipped,
|
||||||
|
);
|
||||||
expect(mocks.user.get).not.toHaveBeenCalled();
|
expect(mocks.user.get).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should skip if recipient could not be found', async () => {
|
it('should skip if recipient could not be found', async () => {
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(AlbumFactory.create()));
|
mocks.album.getById.mockResolvedValue(getForAlbum(AlbumFactory.create()));
|
||||||
|
|
||||||
await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Skipped);
|
await expect(sut.handleAlbumInvite({ id: '', recipientId: '', senderName: 'foo' })).resolves.toBe(
|
||||||
|
JobStatus.Skipped,
|
||||||
|
);
|
||||||
expect(mocks.job.queue).not.toHaveBeenCalled();
|
expect(mocks.job.queue).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -288,7 +292,9 @@ describe(NotificationService.name, () => {
|
|||||||
});
|
});
|
||||||
mocks.notification.create.mockResolvedValue(notificationStub.albumEvent);
|
mocks.notification.create.mockResolvedValue(notificationStub.albumEvent);
|
||||||
|
|
||||||
await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Skipped);
|
await expect(sut.handleAlbumInvite({ id: '', recipientId: '', senderName: 'foo' })).resolves.toBe(
|
||||||
|
JobStatus.Skipped,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should skip if the recipient has email notifications for album invite disabled', async () => {
|
it('should skip if the recipient has email notifications for album invite disabled', async () => {
|
||||||
@@ -304,7 +310,9 @@ describe(NotificationService.name, () => {
|
|||||||
});
|
});
|
||||||
mocks.notification.create.mockResolvedValue(notificationStub.albumEvent);
|
mocks.notification.create.mockResolvedValue(notificationStub.albumEvent);
|
||||||
|
|
||||||
await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Skipped);
|
await expect(sut.handleAlbumInvite({ id: '', recipientId: '', senderName: 'foo' })).resolves.toBe(
|
||||||
|
JobStatus.Skipped,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should send invite email', async () => {
|
it('should send invite email', async () => {
|
||||||
@@ -322,7 +330,9 @@ describe(NotificationService.name, () => {
|
|||||||
mocks.notification.create.mockResolvedValue(notificationStub.albumEvent);
|
mocks.notification.create.mockResolvedValue(notificationStub.albumEvent);
|
||||||
mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' });
|
mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' });
|
||||||
|
|
||||||
await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Success);
|
await expect(sut.handleAlbumInvite({ id: '', recipientId: '', senderName: 'foo' })).resolves.toBe(
|
||||||
|
JobStatus.Success,
|
||||||
|
);
|
||||||
expect(mocks.job.queue).toHaveBeenCalledWith({
|
expect(mocks.job.queue).toHaveBeenCalledWith({
|
||||||
name: JobName.SendMail,
|
name: JobName.SendMail,
|
||||||
data: expect.objectContaining({ subject: expect.stringContaining('You have been added to a shared album') }),
|
data: expect.objectContaining({ subject: expect.stringContaining('You have been added to a shared album') }),
|
||||||
@@ -346,7 +356,9 @@ describe(NotificationService.name, () => {
|
|||||||
mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' });
|
mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' });
|
||||||
mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([]);
|
mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([]);
|
||||||
|
|
||||||
await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Success);
|
await expect(sut.handleAlbumInvite({ id: '', recipientId: '', senderName: 'foo' })).resolves.toBe(
|
||||||
|
JobStatus.Success,
|
||||||
|
);
|
||||||
expect(mocks.assetJob.getAlbumThumbnailFiles).toHaveBeenCalledWith(
|
expect(mocks.assetJob.getAlbumThumbnailFiles).toHaveBeenCalledWith(
|
||||||
album.albumThumbnailAssetId,
|
album.albumThumbnailAssetId,
|
||||||
AssetFileType.Thumbnail,
|
AssetFileType.Thumbnail,
|
||||||
@@ -378,7 +390,9 @@ describe(NotificationService.name, () => {
|
|||||||
mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' });
|
mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' });
|
||||||
mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([assetFile]);
|
mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([assetFile]);
|
||||||
|
|
||||||
await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Success);
|
await expect(sut.handleAlbumInvite({ id: '', recipientId: '', senderName: 'foo' })).resolves.toBe(
|
||||||
|
JobStatus.Success,
|
||||||
|
);
|
||||||
expect(mocks.assetJob.getAlbumThumbnailFiles).toHaveBeenCalledWith(
|
expect(mocks.assetJob.getAlbumThumbnailFiles).toHaveBeenCalledWith(
|
||||||
album.albumThumbnailAssetId,
|
album.albumThumbnailAssetId,
|
||||||
AssetFileType.Thumbnail,
|
AssetFileType.Thumbnail,
|
||||||
@@ -412,7 +426,9 @@ describe(NotificationService.name, () => {
|
|||||||
mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' });
|
mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' });
|
||||||
mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([asset.files[0]]);
|
mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([asset.files[0]]);
|
||||||
|
|
||||||
await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Success);
|
await expect(sut.handleAlbumInvite({ id: '', recipientId: '', senderName: 'foo' })).resolves.toBe(
|
||||||
|
JobStatus.Success,
|
||||||
|
);
|
||||||
expect(mocks.assetJob.getAlbumThumbnailFiles).toHaveBeenCalledWith(
|
expect(mocks.assetJob.getAlbumThumbnailFiles).toHaveBeenCalledWith(
|
||||||
album.albumThumbnailAssetId,
|
album.albumThumbnailAssetId,
|
||||||
AssetFileType.Thumbnail,
|
AssetFileType.Thumbnail,
|
||||||
@@ -434,7 +450,7 @@ describe(NotificationService.name, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should skip if owner could not be found', async () => {
|
it('should skip if owner could not be found', async () => {
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(AlbumFactory.create({ ownerId: 'non-existent' })));
|
mocks.album.getById.mockResolvedValue(getForAlbum(AlbumFactory.from().owner({ id: 'non-existent' }).build()));
|
||||||
|
|
||||||
await expect(sut.handleAlbumUpdate({ id: '', recipientId: '1' })).resolves.toBe(JobStatus.Skipped);
|
await expect(sut.handleAlbumUpdate({ id: '', recipientId: '1' })).resolves.toBe(JobStatus.Skipped);
|
||||||
expect(mocks.systemMetadata.get).not.toHaveBeenCalled();
|
expect(mocks.systemMetadata.get).not.toHaveBeenCalled();
|
||||||
@@ -443,7 +459,6 @@ describe(NotificationService.name, () => {
|
|||||||
it('should skip recipient that could not be looked up', async () => {
|
it('should skip recipient that could not be looked up', async () => {
|
||||||
const album = AlbumFactory.from().albumUser({ userId: 'non-existent' }).build();
|
const album = AlbumFactory.from().albumUser({ userId: 'non-existent' }).build();
|
||||||
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
mocks.album.getById.mockResolvedValue(getForAlbum(album));
|
||||||
mocks.user.get.mockResolvedValueOnce(album.owner);
|
|
||||||
mocks.notification.create.mockResolvedValue(notificationStub.albumEvent);
|
mocks.notification.create.mockResolvedValue(notificationStub.albumEvent);
|
||||||
mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' });
|
mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' });
|
||||||
mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([]);
|
mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([]);
|
||||||
|
|||||||
@@ -226,8 +226,8 @@ export class NotificationService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent({ name: 'AlbumInvite' })
|
@OnEvent({ name: 'AlbumInvite' })
|
||||||
async onAlbumInvite({ id, userId }: ArgOf<'AlbumInvite'>) {
|
async onAlbumInvite({ id, userId, senderName }: ArgOf<'AlbumInvite'>) {
|
||||||
await this.jobRepository.queue({ name: JobName.NotifyAlbumInvite, data: { id, recipientId: userId } });
|
await this.jobRepository.queue({ name: JobName.NotifyAlbumInvite, data: { id, recipientId: userId, senderName } });
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent({ name: 'SessionDelete' })
|
@OnEvent({ name: 'SessionDelete' })
|
||||||
@@ -303,7 +303,7 @@ export class NotificationService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@OnJob({ name: JobName.NotifyAlbumInvite, queue: QueueName.Notification })
|
@OnJob({ name: JobName.NotifyAlbumInvite, queue: QueueName.Notification })
|
||||||
async handleAlbumInvite({ id, recipientId }: JobOf<JobName.NotifyAlbumInvite>) {
|
async handleAlbumInvite({ id, recipientId, senderName }: JobOf<JobName.NotifyAlbumInvite>) {
|
||||||
const album = await this.albumRepository.getById(id, { withAssets: false });
|
const album = await this.albumRepository.getById(id, { withAssets: false });
|
||||||
if (!album) {
|
if (!album) {
|
||||||
return JobStatus.Skipped;
|
return JobStatus.Skipped;
|
||||||
@@ -314,7 +314,7 @@ export class NotificationService extends BaseService {
|
|||||||
return JobStatus.Skipped;
|
return JobStatus.Skipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.sendAlbumLocalNotification(album, recipientId, NotificationType.AlbumInvite, album.owner.name);
|
await this.sendAlbumLocalNotification(album, recipientId, NotificationType.AlbumInvite, senderName);
|
||||||
|
|
||||||
const { emailNotifications } = getPreferences(recipient.metadata);
|
const { emailNotifications } = getPreferences(recipient.metadata);
|
||||||
|
|
||||||
@@ -331,7 +331,7 @@ export class NotificationService extends BaseService {
|
|||||||
baseUrl: getExternalDomain(server),
|
baseUrl: getExternalDomain(server),
|
||||||
albumId: album.id,
|
albumId: album.id,
|
||||||
albumName: album.albumName,
|
albumName: album.albumName,
|
||||||
senderName: album.owner.name,
|
senderName,
|
||||||
recipientName: recipient.name,
|
recipientName: recipient.name,
|
||||||
cid: attachment ? attachment.cid : undefined,
|
cid: attachment ? attachment.cid : undefined,
|
||||||
},
|
},
|
||||||
@@ -360,8 +360,8 @@ export class NotificationService extends BaseService {
|
|||||||
return JobStatus.Skipped;
|
return JobStatus.Skipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
const owner = await this.userRepository.get(album.ownerId, { withDeleted: false });
|
const recipient = await this.userRepository.get(recipientId, { withDeleted: false });
|
||||||
if (!owner) {
|
if (!recipient) {
|
||||||
return JobStatus.Skipped;
|
return JobStatus.Skipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { AuthDto } from 'src/dtos/auth.dto';
|
|||||||
import {
|
import {
|
||||||
SyncAckDeleteDto,
|
SyncAckDeleteDto,
|
||||||
SyncAckSetDto,
|
SyncAckSetDto,
|
||||||
|
syncAlbumV2ToV1,
|
||||||
syncAssetFaceV2ToV1,
|
syncAssetFaceV2ToV1,
|
||||||
SyncAssetV1,
|
SyncAssetV1,
|
||||||
SyncItem,
|
SyncItem,
|
||||||
@@ -60,6 +61,7 @@ export const SYNC_TYPES_ORDER = [
|
|||||||
SyncRequestType.PartnerStacksV1,
|
SyncRequestType.PartnerStacksV1,
|
||||||
SyncRequestType.AlbumAssetsV1,
|
SyncRequestType.AlbumAssetsV1,
|
||||||
SyncRequestType.AlbumsV1,
|
SyncRequestType.AlbumsV1,
|
||||||
|
SyncRequestType.AlbumsV2,
|
||||||
SyncRequestType.AlbumUsersV1,
|
SyncRequestType.AlbumUsersV1,
|
||||||
SyncRequestType.AlbumToAssetsV1,
|
SyncRequestType.AlbumToAssetsV1,
|
||||||
SyncRequestType.AssetExifsV1,
|
SyncRequestType.AssetExifsV1,
|
||||||
@@ -165,6 +167,7 @@ export class SyncService extends BaseService {
|
|||||||
[SyncRequestType.PartnerAssetExifsV1]: () =>
|
[SyncRequestType.PartnerAssetExifsV1]: () =>
|
||||||
this.syncPartnerAssetExifsV1(options, response, checkpointMap, session.id),
|
this.syncPartnerAssetExifsV1(options, response, checkpointMap, session.id),
|
||||||
[SyncRequestType.AlbumsV1]: () => this.syncAlbumsV1(options, response, checkpointMap),
|
[SyncRequestType.AlbumsV1]: () => this.syncAlbumsV1(options, response, checkpointMap),
|
||||||
|
[SyncRequestType.AlbumsV2]: () => this.syncAlbumsV2(options, response, checkpointMap),
|
||||||
[SyncRequestType.AlbumUsersV1]: () => this.syncAlbumUsersV1(options, response, checkpointMap, session.id),
|
[SyncRequestType.AlbumUsersV1]: () => this.syncAlbumUsersV1(options, response, checkpointMap, session.id),
|
||||||
[SyncRequestType.AlbumAssetsV1]: () => this.syncAlbumAssetsV1(options, response, checkpointMap, session.id),
|
[SyncRequestType.AlbumAssetsV1]: () => this.syncAlbumAssetsV1(options, response, checkpointMap, session.id),
|
||||||
[SyncRequestType.AlbumToAssetsV1]: () => this.syncAlbumToAssetsV1(options, response, checkpointMap, session.id),
|
[SyncRequestType.AlbumToAssetsV1]: () => this.syncAlbumToAssetsV1(options, response, checkpointMap, session.id),
|
||||||
@@ -412,6 +415,21 @@ export class SyncService extends BaseService {
|
|||||||
|
|
||||||
const upsertType = SyncEntityType.AlbumV1;
|
const upsertType = SyncEntityType.AlbumV1;
|
||||||
const upserts = this.syncRepository.album.getUpserts({ ...options, ack: checkpointMap[upsertType] });
|
const upserts = this.syncRepository.album.getUpserts({ ...options, ack: checkpointMap[upsertType] });
|
||||||
|
for await (const { updateId, ...data } of upserts) {
|
||||||
|
const albumUsers = await this.syncRepository.album.getAlbumUsers(data.id);
|
||||||
|
send(response, { type: upsertType, ids: [updateId], data: syncAlbumV2ToV1(data, albumUsers) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async syncAlbumsV2(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) {
|
||||||
|
const deleteType = SyncEntityType.AlbumDeleteV1;
|
||||||
|
const deletes = this.syncRepository.album.getDeletes({ ...options, ack: checkpointMap[deleteType] });
|
||||||
|
for await (const { id, ...data } of deletes) {
|
||||||
|
send(response, { type: deleteType, ids: [id], data });
|
||||||
|
}
|
||||||
|
|
||||||
|
const upsertType = SyncEntityType.AlbumV2;
|
||||||
|
const upserts = this.syncRepository.album.getUpserts({ ...options, ack: checkpointMap[upsertType] });
|
||||||
for await (const { updateId, ...data } of upserts) {
|
for await (const { updateId, ...data } of upserts) {
|
||||||
send(response, { type: upsertType, ids: [updateId], data });
|
send(response, { type: upsertType, ids: [updateId], data });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -249,6 +249,7 @@ export interface INotifySignupJob extends IEntityJob {
|
|||||||
|
|
||||||
export interface INotifyAlbumInviteJob extends IEntityJob {
|
export interface INotifyAlbumInviteJob extends IEntityJob {
|
||||||
recipientId: string;
|
recipientId: string;
|
||||||
|
senderName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INotifyAlbumUpdateJob extends IEntityJob, IDelayedJob {
|
export interface INotifyAlbumUpdateJob extends IEntityJob, IDelayedJob {
|
||||||
|
|||||||
@@ -455,3 +455,5 @@ export const updateLockedColumns = <T extends Record<string, unknown> & { locked
|
|||||||
exif.lockedProperties = lockableProperties.filter((property) => property in exif);
|
exif.lockedProperties = lockableProperties.filter((property) => property in exif);
|
||||||
return exif;
|
return exif;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const dummy = sql`(select 1)`.as('dummy');
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Selectable } from 'kysely';
|
import { Selectable } from 'kysely';
|
||||||
import { AssetOrder } from 'src/enum';
|
import { AlbumUserRole, AssetOrder } from 'src/enum';
|
||||||
import { AlbumTable } from 'src/schema/tables/album.table';
|
import { AlbumTable } from 'src/schema/tables/album.table';
|
||||||
import { SharedLinkTable } from 'src/schema/tables/shared-link.table';
|
import { SharedLinkTable } from 'src/schema/tables/shared-link.table';
|
||||||
import { AlbumUserFactory } from 'test/factories/album-user.factory';
|
import { AlbumUserFactory } from 'test/factories/album-user.factory';
|
||||||
@@ -10,15 +10,12 @@ import { UserFactory } from 'test/factories/user.factory';
|
|||||||
import { newDate, newUuid, newUuidV7 } from 'test/small.factory';
|
import { newDate, newUuid, newUuidV7 } from 'test/small.factory';
|
||||||
|
|
||||||
export class AlbumFactory {
|
export class AlbumFactory {
|
||||||
#owner: UserFactory;
|
#owner!: UserFactory;
|
||||||
#sharedLinks: Selectable<SharedLinkTable>[] = [];
|
#sharedLinks: Selectable<SharedLinkTable>[] = [];
|
||||||
#albumUsers: AlbumUserFactory[] = [];
|
#albumUsers: AlbumUserFactory[] = [];
|
||||||
#assets: AssetFactory[] = [];
|
#assets: AssetFactory[] = [];
|
||||||
|
|
||||||
private constructor(private readonly value: Selectable<AlbumTable>) {
|
private constructor(private readonly value: Selectable<AlbumTable>) {}
|
||||||
value.ownerId ??= newUuid();
|
|
||||||
this.#owner = UserFactory.from({ id: value.ownerId });
|
|
||||||
}
|
|
||||||
|
|
||||||
static create(dto: AlbumLike = {}) {
|
static create(dto: AlbumLike = {}) {
|
||||||
return AlbumFactory.from(dto).build();
|
return AlbumFactory.from(dto).build();
|
||||||
@@ -27,7 +24,6 @@ export class AlbumFactory {
|
|||||||
static from(dto: AlbumLike = {}) {
|
static from(dto: AlbumLike = {}) {
|
||||||
return new AlbumFactory({
|
return new AlbumFactory({
|
||||||
id: newUuid(),
|
id: newUuid(),
|
||||||
ownerId: newUuid(),
|
|
||||||
albumName: 'My Album',
|
albumName: 'My Album',
|
||||||
albumThumbnailAssetId: null,
|
albumThumbnailAssetId: null,
|
||||||
createdAt: newDate(),
|
createdAt: newDate(),
|
||||||
@@ -43,7 +39,7 @@ export class AlbumFactory {
|
|||||||
|
|
||||||
owner(dto: UserLike = {}, builder?: FactoryBuilder<UserFactory>) {
|
owner(dto: UserLike = {}, builder?: FactoryBuilder<UserFactory>) {
|
||||||
this.#owner = build(UserFactory.from(dto), builder);
|
this.#owner = build(UserFactory.from(dto), builder);
|
||||||
this.value.ownerId = this.#owner.build().id;
|
this.albumUser({ userId: this.#owner.build().id, role: AlbumUserRole.Owner });
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +49,7 @@ export class AlbumFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
albumUser(dto: AlbumUserLike = {}, builder?: FactoryBuilder<AlbumUserFactory>) {
|
albumUser(dto: AlbumUserLike = {}, builder?: FactoryBuilder<AlbumUserFactory>) {
|
||||||
const albumUser = build(AlbumUserFactory.from(dto).album(this.value), builder);
|
const albumUser = build(AlbumUserFactory.from(dto), builder);
|
||||||
this.#albumUsers.push(albumUser);
|
this.#albumUsers.push(albumUser);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -78,7 +74,6 @@ export class AlbumFactory {
|
|||||||
build() {
|
build() {
|
||||||
return {
|
return {
|
||||||
...this.value,
|
...this.value,
|
||||||
owner: this.#owner.build(),
|
|
||||||
assets: this.#assets.map((asset) => asset.build()),
|
assets: this.#assets.map((asset) => asset.build()),
|
||||||
albumUsers: this.#albumUsers.map((albumUser) => albumUser.build()),
|
albumUsers: this.#albumUsers.map((albumUser) => albumUser.build()),
|
||||||
sharedLinks: this.#sharedLinks,
|
sharedLinks: this.#sharedLinks,
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ export const getForAlbum = (album: ReturnType<AlbumFactory['build']>) => ({
|
|||||||
createdAt: albumUser.createdAt.toISOString(),
|
createdAt: albumUser.createdAt.toISOString(),
|
||||||
user: getDehydrated(albumUser.user),
|
user: getDehydrated(albumUser.user),
|
||||||
})),
|
})),
|
||||||
owner: getDehydrated(album.owner),
|
|
||||||
sharedLinks: album.sharedLinks.map((sharedLink) => getDehydrated(sharedLink)),
|
sharedLinks: album.sharedLinks.map((sharedLink) => getDehydrated(sharedLink)),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -219,7 +218,6 @@ export const getForSharedLink = (sharedLink: ReturnType<SharedLinkFactory['build
|
|||||||
album: sharedLink.album
|
album: sharedLink.album
|
||||||
? {
|
? {
|
||||||
...getDehydrated(sharedLink.album),
|
...getDehydrated(sharedLink.album),
|
||||||
owner: getDehydrated(sharedLink.album.owner),
|
|
||||||
assets: sharedLink.album.assets.map((asset) => getDehydrated(asset)),
|
assets: sharedLink.album.assets.map((asset) => getDehydrated(asset)),
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
|
|||||||
@@ -222,9 +222,14 @@ export class MediumTestContext<S extends BaseService = BaseService> {
|
|||||||
return { result };
|
return { result };
|
||||||
}
|
}
|
||||||
|
|
||||||
async newAlbum(dto: Insertable<AlbumTable>, assetIds?: string[]) {
|
async newAlbum({ ownerId, ...dto }: Insertable<AlbumTable> & { ownerId: string }, assetIds?: string[]) {
|
||||||
const album = mediumFactory.albumInsert(dto);
|
const album = mediumFactory.albumInsert(dto);
|
||||||
const result = await this.get(AlbumRepository).create(album, assetIds ?? [], []);
|
const result = await this.get(AlbumRepository).create(
|
||||||
|
album,
|
||||||
|
assetIds ?? [],
|
||||||
|
[{ userId: ownerId, role: AlbumUserRole.Owner }],
|
||||||
|
ownerId,
|
||||||
|
);
|
||||||
return { album, result };
|
return { album, result };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -570,9 +575,9 @@ const assetInsert = (asset: Partial<Insertable<AssetTable>> = {}) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const albumInsert = (album: Partial<Insertable<AlbumTable>> & { ownerId: string }) => {
|
const albumInsert = (album: Partial<Insertable<AlbumTable>>) => {
|
||||||
const id = album.id || newUuid();
|
const id = album.id || newUuid();
|
||||||
const defaults: Omit<Insertable<AlbumTable>, 'ownerId'> = {
|
const defaults: Insertable<AlbumTable> = {
|
||||||
albumName: 'Album',
|
albumName: 'Album',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,14 @@ describe(SyncRequestType.AlbumUsersV1, () => {
|
|||||||
const { albumUser } = await ctx.newAlbumUser({ albumId: album.id, userId: user.id, role: AlbumUserRole.Editor });
|
const { albumUser } = await ctx.newAlbumUser({ albumId: album.id, userId: user.id, role: AlbumUserRole.Editor });
|
||||||
|
|
||||||
await expect(ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1])).resolves.toEqual([
|
await expect(ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1])).resolves.toEqual([
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: expect.objectContaining({
|
||||||
|
albumId: album.id,
|
||||||
|
role: AlbumUserRole.Owner,
|
||||||
|
}),
|
||||||
|
type: SyncEntityType.AlbumUserV1,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
data: expect.objectContaining({
|
data: expect.objectContaining({
|
||||||
@@ -47,6 +55,14 @@ describe(SyncRequestType.AlbumUsersV1, () => {
|
|||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]);
|
||||||
expect(response).toEqual([
|
expect(response).toEqual([
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: expect.objectContaining({
|
||||||
|
albumId: album.id,
|
||||||
|
role: AlbumUserRole.Owner,
|
||||||
|
}),
|
||||||
|
type: SyncEntityType.AlbumUserV1,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
data: expect.objectContaining({
|
data: expect.objectContaining({
|
||||||
@@ -136,6 +152,14 @@ describe(SyncRequestType.AlbumUsersV1, () => {
|
|||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]);
|
||||||
expect(response).toEqual([
|
expect(response).toEqual([
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: expect.objectContaining({
|
||||||
|
albumId: album.id,
|
||||||
|
role: AlbumUserRole.Owner,
|
||||||
|
}),
|
||||||
|
type: SyncEntityType.AlbumUserV1,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
data: expect.objectContaining({
|
data: expect.objectContaining({
|
||||||
@@ -163,6 +187,7 @@ describe(SyncRequestType.AlbumUsersV1, () => {
|
|||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]);
|
||||||
expect(response).toEqual([
|
expect(response).toEqual([
|
||||||
|
expect.objectContaining({ type: SyncEntityType.AlbumUserV1 }),
|
||||||
expect.objectContaining({ type: SyncEntityType.AlbumUserV1 }),
|
expect.objectContaining({ type: SyncEntityType.AlbumUserV1 }),
|
||||||
expect.objectContaining({ type: SyncEntityType.AlbumUserV1 }),
|
expect.objectContaining({ type: SyncEntityType.AlbumUserV1 }),
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
@@ -201,6 +226,7 @@ describe(SyncRequestType.AlbumUsersV1, () => {
|
|||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]);
|
||||||
expect(response).toEqual([
|
expect(response).toEqual([
|
||||||
|
expect.objectContaining({ type: SyncEntityType.AlbumUserV1 }),
|
||||||
expect.objectContaining({ type: SyncEntityType.AlbumUserV1 }),
|
expect.objectContaining({ type: SyncEntityType.AlbumUserV1 }),
|
||||||
expect.objectContaining({ type: SyncEntityType.AlbumUserV1 }),
|
expect.objectContaining({ type: SyncEntityType.AlbumUserV1 }),
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
@@ -229,10 +255,11 @@ describe(SyncRequestType.AlbumUsersV1, () => {
|
|||||||
|
|
||||||
it('should backfill album users when a user shares an album with you', async () => {
|
it('should backfill album users when a user shares an album with you', async () => {
|
||||||
const { auth, ctx } = await setup();
|
const { auth, ctx } = await setup();
|
||||||
|
const { user } = await ctx.newUser();
|
||||||
const { user: user1 } = await ctx.newUser();
|
const { user: user1 } = await ctx.newUser();
|
||||||
const { user: user2 } = await ctx.newUser();
|
const { user: user2 } = await ctx.newUser();
|
||||||
const { album: album1 } = await ctx.newAlbum({ ownerId: user1.id });
|
const { album: album1 } = await ctx.newAlbum({ ownerId: user.id });
|
||||||
const { album: album2 } = await ctx.newAlbum({ ownerId: user1.id });
|
const { album: album2 } = await ctx.newAlbum({ ownerId: user.id });
|
||||||
// backfill album user
|
// backfill album user
|
||||||
await ctx.newAlbumUser({ albumId: album1.id, userId: user1.id, role: AlbumUserRole.Editor });
|
await ctx.newAlbumUser({ albumId: album1.id, userId: user1.id, role: AlbumUserRole.Editor });
|
||||||
await wait(2);
|
await wait(2);
|
||||||
@@ -244,6 +271,15 @@ describe(SyncRequestType.AlbumUsersV1, () => {
|
|||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]);
|
||||||
expect(response).toEqual([
|
expect(response).toEqual([
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: expect.objectContaining({
|
||||||
|
albumId: album2.id,
|
||||||
|
role: AlbumUserRole.Owner,
|
||||||
|
userId: user.id,
|
||||||
|
}),
|
||||||
|
type: SyncEntityType.AlbumUserV1,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
data: expect.objectContaining({
|
data: expect.objectContaining({
|
||||||
@@ -264,6 +300,15 @@ describe(SyncRequestType.AlbumUsersV1, () => {
|
|||||||
// should backfill the album user
|
// should backfill the album user
|
||||||
const newResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]);
|
const newResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]);
|
||||||
expect(newResponse).toEqual([
|
expect(newResponse).toEqual([
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: expect.objectContaining({
|
||||||
|
albumId: album1.id,
|
||||||
|
role: AlbumUserRole.Owner,
|
||||||
|
userId: user.id,
|
||||||
|
}),
|
||||||
|
type: SyncEntityType.AlbumUserBackfillV1,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
data: expect.objectContaining({
|
data: expect.objectContaining({
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ describe(SyncRequestType.AlbumsV1, () => {
|
|||||||
data: expect.objectContaining({
|
data: expect.objectContaining({
|
||||||
id: album.id,
|
id: album.id,
|
||||||
name: album.albumName,
|
name: album.albumName,
|
||||||
ownerId: album.ownerId,
|
|
||||||
}),
|
}),
|
||||||
type: SyncEntityType.AlbumV1,
|
type: SyncEntityType.AlbumV1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||||
import { getContextMenuPositionFromEvent, type ContextMenuPosition } from '$lib/utils/context-menu';
|
import { getContextMenuPositionFromEvent, type ContextMenuPosition } from '$lib/utils/context-menu';
|
||||||
import { getShortDateRange } from '$lib/utils/date-time';
|
import { getShortDateRange } from '$lib/utils/date-time';
|
||||||
import type { AlbumResponseDto } from '@immich/sdk';
|
import { type AlbumResponseDto } from '@immich/sdk';
|
||||||
import { IconButton } from '@immich/ui';
|
import { IconButton } from '@immich/ui';
|
||||||
import { mdiDotsVertical } from '@mdi/js';
|
import { mdiDotsVertical } from '@mdi/js';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
@@ -85,12 +85,13 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if showOwner}
|
{#if showOwner}
|
||||||
{#if authManager.user.id === album.ownerId}
|
{@const owner = album.albumUsers[0].user}
|
||||||
|
{#if owner.id === authManager.user.id}
|
||||||
<p>{$t('owned')}</p>
|
<p>{$t('owned')}</p>
|
||||||
{:else if album.owner}
|
|
||||||
<p>{$t('shared_by_user', { values: { user: album.owner.name } })}</p>
|
|
||||||
{:else}
|
{:else}
|
||||||
<p>{$t('shared')}</p>
|
<p>
|
||||||
|
{$t('shared_by_user', { values: { user: owner.name } })}
|
||||||
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
{:else if album.shared}
|
{:else if album.shared}
|
||||||
<p>{$t('shared')}</p>
|
<p>{$t('shared')}</p>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
import { getSelectedAlbumGroupOption, sortAlbums, stringToSortOrder, type AlbumGroup } from '$lib/utils/album-utils';
|
import { getSelectedAlbumGroupOption, sortAlbums, stringToSortOrder, type AlbumGroup } from '$lib/utils/album-utils';
|
||||||
import type { ContextMenuPosition } from '$lib/utils/context-menu';
|
import type { ContextMenuPosition } from '$lib/utils/context-menu';
|
||||||
import { normalizeSearchString } from '$lib/utils/string-utils';
|
import { normalizeSearchString } from '$lib/utils/string-utils';
|
||||||
import { type AlbumResponseDto, type SharedLinkResponseDto } from '@immich/sdk';
|
import { AlbumUserRole, type AlbumResponseDto, type SharedLinkResponseDto } from '@immich/sdk';
|
||||||
import { modalManager } from '@immich/ui';
|
import { modalManager } from '@immich/ui';
|
||||||
import { mdiDeleteOutline, mdiDownload, mdiRenameOutline, mdiShareVariantOutline } from '@mdi/js';
|
import { mdiDeleteOutline, mdiDownload, mdiRenameOutline, mdiShareVariantOutline } from '@mdi/js';
|
||||||
import { groupBy } from 'lodash-es';
|
import { groupBy } from 'lodash-es';
|
||||||
@@ -98,24 +98,26 @@
|
|||||||
/** Group by owner */
|
/** Group by owner */
|
||||||
[AlbumGroupBy.Owner]: (order, albums): AlbumGroup[] => {
|
[AlbumGroupBy.Owner]: (order, albums): AlbumGroup[] => {
|
||||||
const currentUserId = authManager.user.id;
|
const currentUserId = authManager.user.id;
|
||||||
const groupedByOwnerIds = groupBy(albums, 'ownerId');
|
const groupedByOwnerIds = groupBy(albums, (album) => album.albumUsers[0].user.id);
|
||||||
|
|
||||||
const sortSign = order === SortOrder.Desc ? -1 : 1;
|
const sortSign = order === SortOrder.Desc ? -1 : 1;
|
||||||
const sortedByOwnerNames = Object.entries(groupedByOwnerIds).sort(([ownerA, albumsA], [ownerB, albumsB]) => {
|
const sortedByOwnerNames = Object.entries(groupedByOwnerIds).sort(([ownerIdA, albumsA], [ownerIdB, albumsB]) => {
|
||||||
// We make sure owned albums stay either at the beginning or the end
|
// We make sure owned albums stay either at the beginning or the end
|
||||||
// of the list
|
// of the list
|
||||||
if (ownerA === currentUserId) {
|
if (ownerIdA === currentUserId) {
|
||||||
return -sortSign;
|
return -sortSign;
|
||||||
} else if (ownerB === currentUserId) {
|
} else if (ownerIdB === currentUserId) {
|
||||||
return sortSign;
|
return sortSign;
|
||||||
} else {
|
} else {
|
||||||
return albumsA[0].owner.name.localeCompare(albumsB[0].owner.name, $locale) * sortSign;
|
const ownerA = albumsA[0].albumUsers[0].user;
|
||||||
|
const ownerB = albumsB[0].albumUsers[0].user;
|
||||||
|
return ownerA.name.localeCompare(ownerB.name, $locale) * sortSign;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return sortedByOwnerNames.map(([ownerId, albums]) => ({
|
return sortedByOwnerNames.map(([ownerId, albums]) => ({
|
||||||
id: ownerId,
|
id: ownerId,
|
||||||
name: ownerId === currentUserId ? $t('my_albums') : albums[0].owner.name,
|
name: ownerId === currentUserId ? $t('my_albums') : albums[0].albumUsers[0].user.name,
|
||||||
albums,
|
albums,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
@@ -130,7 +132,10 @@
|
|||||||
return sharedAlbums;
|
return sharedAlbums;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
const nonOwnedAlbums = sharedAlbums.filter((album) => album.ownerId !== authManager.user.id);
|
const nonOwnedAlbums = sharedAlbums.filter(
|
||||||
|
(album) =>
|
||||||
|
album.albumUsers.find(({ user: { id } }) => id === authManager.user.id)?.role !== AlbumUserRole.Owner,
|
||||||
|
);
|
||||||
return nonOwnedAlbums.length > 0 ? ownedAlbums.concat(nonOwnedAlbums) : ownedAlbums;
|
return nonOwnedAlbums.length > 0 ? ownedAlbums.concat(nonOwnedAlbums) : ownedAlbums;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,7 +172,9 @@
|
|||||||
albumGroupIds = groupedAlbums.map(({ id }) => id);
|
albumGroupIds = groupedAlbums.map(({ id }) => id);
|
||||||
});
|
});
|
||||||
|
|
||||||
let showFullContextMenu = $derived(allowEdit && selectedAlbum && selectedAlbum.ownerId === authManager.user.id);
|
let showFullContextMenu = $derived(
|
||||||
|
allowEdit && selectedAlbum && selectedAlbum.albumUsers[0].user.id === authManager.user.id,
|
||||||
|
);
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (allowEdit) {
|
if (allowEdit) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import { Route } from '$lib/route';
|
import { Route } from '$lib/route';
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import type { ContextMenuPosition } from '$lib/utils/context-menu';
|
import type { ContextMenuPosition } from '$lib/utils/context-menu';
|
||||||
import type { AlbumResponseDto } from '@immich/sdk';
|
import { AlbumUserRole, type AlbumResponseDto } from '@immich/sdk';
|
||||||
import { Icon } from '@immich/ui';
|
import { Icon } from '@immich/ui';
|
||||||
import { mdiShareVariantOutline } from '@mdi/js';
|
import { mdiShareVariantOutline } from '@mdi/js';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
@@ -43,9 +43,11 @@
|
|||||||
icon={mdiShareVariantOutline}
|
icon={mdiShareVariantOutline}
|
||||||
size="16"
|
size="16"
|
||||||
class="inline ms-1 opacity-70"
|
class="inline ms-1 opacity-70"
|
||||||
title={album.ownerId === authManager.user.id
|
title={album.albumUsers.find(({ user: { id } }) => id === authManager.user.id)?.role === AlbumUserRole.Owner
|
||||||
? $t('shared_by_you')
|
? $t('shared_by_you')
|
||||||
: $t('shared_by_user', { values: { user: album.owner.name } })}
|
: $t('shared_by_user', {
|
||||||
|
values: { user: album.albumUsers[0].user.name },
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
import { getAssetType } from '$lib/utils/asset-utils';
|
import { getAssetType } from '$lib/utils/asset-utils';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { isTenMinutesApart } from '$lib/utils/timesince';
|
import { isTenMinutesApart } from '$lib/utils/timesince';
|
||||||
import { ReactionType, type ActivityResponseDto, type AssetTypeEnum } from '@immich/sdk';
|
import { ReactionType, type ActivityResponseDto, type AlbumUserResponseDto, type AssetTypeEnum } from '@immich/sdk';
|
||||||
import { Icon, IconButton, LoadingSpinner, Textarea, toastManager } from '@immich/ui';
|
import { Icon, IconButton, LoadingSpinner, Textarea, toastManager } from '@immich/ui';
|
||||||
import { mdiClose, mdiDeleteOutline, mdiDotsVertical, mdiSend, mdiThumbUp } from '@mdi/js';
|
import { mdiClose, mdiDeleteOutline, mdiDotsVertical, mdiSend, mdiThumbUp } from '@mdi/js';
|
||||||
import * as luxon from 'luxon';
|
import * as luxon from 'luxon';
|
||||||
@@ -43,11 +43,11 @@
|
|||||||
assetId?: string | undefined;
|
assetId?: string | undefined;
|
||||||
albumId: string;
|
albumId: string;
|
||||||
assetType?: AssetTypeEnum | undefined;
|
assetType?: AssetTypeEnum | undefined;
|
||||||
albumOwnerId: string;
|
albumUsers: AlbumUserResponseDto[];
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { assetId = undefined, albumId, assetType = undefined, albumOwnerId, disabled }: Props = $props();
|
let { assetId = undefined, albumId, assetType = undefined, albumUsers, disabled }: Props = $props();
|
||||||
|
|
||||||
let innerHeight: number = $state(0);
|
let innerHeight: number = $state(0);
|
||||||
let activityHeight: number = $state(0);
|
let activityHeight: number = $state(0);
|
||||||
@@ -56,6 +56,7 @@
|
|||||||
let previousAssetId: string | undefined = $state(assetId);
|
let previousAssetId: string | undefined = $state(assetId);
|
||||||
let message = $state('');
|
let message = $state('');
|
||||||
let isSendingMessage = $state(false);
|
let isSendingMessage = $state(false);
|
||||||
|
const isAlbumOwner = $derived(albumUsers[0].user.id === authManager.user.id);
|
||||||
|
|
||||||
const timeOptions: Intl.DateTimeFormatOptions = {
|
const timeOptions: Intl.DateTimeFormatOptions = {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
@@ -147,7 +148,7 @@
|
|||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
{#if reaction.user.id === authManager.user.id || albumOwnerId === authManager.user.id}
|
{#if reaction.user.id === authManager.user.id || isAlbumOwner}
|
||||||
<div class="me-4">
|
<div class="me-4">
|
||||||
<ButtonContextMenu
|
<ButtonContextMenu
|
||||||
icon={mdiDotsVertical}
|
icon={mdiDotsVertical}
|
||||||
@@ -200,7 +201,7 @@
|
|||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
{#if reaction.user.id === authManager.user.id || albumOwnerId === authManager.user.id}
|
{#if reaction.user.id === authManager.user.id || isAlbumOwner}
|
||||||
<div class="me-4">
|
<div class="me-4">
|
||||||
<ButtonContextMenu
|
<ButtonContextMenu
|
||||||
icon={mdiDotsVertical}
|
icon={mdiDotsVertical}
|
||||||
|
|||||||
@@ -638,7 +638,7 @@
|
|||||||
<ActivityViewer
|
<ActivityViewer
|
||||||
disabled={!album.isActivityEnabled}
|
disabled={!album.isActivityEnabled}
|
||||||
assetType={asset.type}
|
assetType={asset.type}
|
||||||
albumOwnerId={album.ownerId}
|
albumUsers={album.albumUsers}
|
||||||
albumId={album.id}
|
albumId={album.id}
|
||||||
assetId={asset.id}
|
assetId={asset.id}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -82,7 +82,7 @@
|
|||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
const isOwner = $derived(authManager.authenticated && asset.ownerId === authManager.user.id);
|
const isOwner = $derived(authManager.authenticated && asset.ownerId === authManager.user.id);
|
||||||
const isAlbumOwner = $derived(authManager.authenticated && album?.ownerId === authManager.user.id);
|
const isAlbumOwner = $derived(authManager.authenticated && album?.albumUsers[0].user.id === authManager.user.id);
|
||||||
const isLocked = $derived(asset.visibility === AssetVisibility.Locked);
|
const isLocked = $derived(asset.visibility === AssetVisibility.Locked);
|
||||||
const smartSearchEnabled = $derived(featureFlagsManager.value.smartSearch);
|
const smartSearchEnabled = $derived(featureFlagsManager.value.smartSearch);
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
const { album, onClose }: Props = $props();
|
const { album, onClose }: Props = $props();
|
||||||
|
|
||||||
let users: UserResponseDto[] = $state([]);
|
let users: UserResponseDto[] = $state([]);
|
||||||
const excludedUserIds = $derived([album.ownerId, ...album.albumUsers.map(({ user: { id } }) => id)]);
|
const excludedUserIds = $derived(album.albumUsers.map(({ user: { id } }) => id));
|
||||||
const filteredUsers = $derived(
|
const filteredUsers = $derived(
|
||||||
sortBy(
|
sortBy(
|
||||||
users.filter(
|
users.filter(
|
||||||
|
|||||||
@@ -105,16 +105,6 @@
|
|||||||
<HeaderActionButton action={AddUsers} />
|
<HeaderActionButton action={AddUsers} />
|
||||||
</HStack>
|
</HStack>
|
||||||
<div class="ps-2">
|
<div class="ps-2">
|
||||||
<div class="flex items-center gap-2 mb-2">
|
|
||||||
<div>
|
|
||||||
<UserAvatar user={album.owner} size="md" />
|
|
||||||
</div>
|
|
||||||
<Text class="w-full" size="small">{album.owner.name}</Text>
|
|
||||||
<Field disabled class="w-32 shrink-0">
|
|
||||||
<Select options={[{ label: $t('owner'), value: 'owner' }]} value="owner" />
|
|
||||||
</Field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#each album.albumUsers as { user, role } (user.id)}
|
{#each album.albumUsers as { user, role } (user.id)}
|
||||||
<div class="flex items-center justify-between gap-4 py-2">
|
<div class="flex items-center justify-between gap-4 py-2">
|
||||||
<div class="flex flex-row items-center gap-2">
|
<div class="flex flex-row items-center gap-2">
|
||||||
@@ -123,12 +113,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<Text size="small">{user.name}</Text>
|
<Text size="small">{user.name}</Text>
|
||||||
</div>
|
</div>
|
||||||
<Field class="w-32">
|
<Field class="w-32" disabled={role === AlbumUserRole.Owner}>
|
||||||
<Select
|
<Select
|
||||||
value={role}
|
value={role}
|
||||||
options={[
|
options={[
|
||||||
{ label: $t('role_editor'), value: AlbumUserRole.Editor },
|
{ label: $t('role_editor'), value: AlbumUserRole.Editor },
|
||||||
{ label: $t('role_viewer'), value: AlbumUserRole.Viewer },
|
{ label: $t('role_viewer'), value: AlbumUserRole.Viewer },
|
||||||
|
{ label: $t('owner'), value: AlbumUserRole.Owner },
|
||||||
{ label: $t('remove_user'), value: 'none' },
|
{ label: $t('remove_user'), value: 'none' },
|
||||||
] as SelectOption<AlbumUserRole | 'none'>[]}
|
] as SelectOption<AlbumUserRole | 'none'>[]}
|
||||||
onChange={(value) => handleRoleSelect(user, value)}
|
onChange={(value) => handleRoleSelect(user, value)}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const getAlbumsActions = ($t: MessageFormatter) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getAlbumActions = ($t: MessageFormatter, album: AlbumResponseDto) => {
|
export const getAlbumActions = ($t: MessageFormatter, album: AlbumResponseDto) => {
|
||||||
const isOwned = authManager.user.id === album.ownerId;
|
const isOwned = album.albumUsers[0].user.id === authManager.user.id;
|
||||||
|
|
||||||
const Share: ActionItem = {
|
const Share: ActionItem = {
|
||||||
title: $t('share'),
|
title: $t('share'),
|
||||||
|
|||||||
+10
-18
@@ -210,9 +210,7 @@
|
|||||||
let albumId = $derived(album.id);
|
let albumId = $derived(album.id);
|
||||||
|
|
||||||
const containsEditors = $derived(album?.shared && album.albumUsers.some(({ role }) => role === AlbumUserRole.Editor));
|
const containsEditors = $derived(album?.shared && album.albumUsers.some(({ role }) => role === AlbumUserRole.Editor));
|
||||||
const albumUsers = $derived(
|
const albumUsers = $derived(showAlbumUsers && containsEditors ? album.albumUsers.map(({ user }) => user) : []);
|
||||||
showAlbumUsers && containsEditors ? [album.owner, ...album.albumUsers.map(({ user }) => user)] : [],
|
|
||||||
);
|
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!album.isActivityEnabled && activityManager.commentCount === 0) {
|
if (!album.isActivityEnabled && activityManager.commentCount === 0) {
|
||||||
@@ -231,7 +229,7 @@
|
|||||||
return { albumId, order: album.order };
|
return { albumId, order: album.order };
|
||||||
});
|
});
|
||||||
|
|
||||||
const isShared = $derived(viewMode === AlbumPageViewMode.SELECT_ASSETS ? false : album.albumUsers.length > 0);
|
const isShared = $derived(viewMode === AlbumPageViewMode.SELECT_ASSETS ? false : album.albumUsers.length > 1);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (assetViewerManager.isViewing || !isShared) {
|
if (assetViewerManager.isViewing || !isShared) {
|
||||||
@@ -243,16 +241,15 @@
|
|||||||
|
|
||||||
onDestroy(() => activityManager.reset());
|
onDestroy(() => activityManager.reset());
|
||||||
|
|
||||||
let isOwned = $derived(authManager.user.id == album.ownerId);
|
const isOwned = $derived(album.albumUsers[0].user.id === authManager.user.id);
|
||||||
|
|
||||||
let showActivityStatus = $derived(
|
let showActivityStatus = $derived(
|
||||||
album.albumUsers.length > 0 &&
|
album.albumUsers.length > 1 &&
|
||||||
!assetViewerManager.isViewing &&
|
!assetViewerManager.isViewing &&
|
||||||
(album.isActivityEnabled || activityManager.commentCount > 0),
|
(album.isActivityEnabled || activityManager.commentCount > 0),
|
||||||
);
|
);
|
||||||
let isEditor = $derived(
|
const isEditor = $derived(
|
||||||
album.albumUsers.find(({ user: { id } }) => id === authManager.user.id)?.role === AlbumUserRole.Editor ||
|
album.albumUsers.find(({ user: { id } }) => id === authManager.user.id)?.role === AlbumUserRole.Editor || isOwned,
|
||||||
album.ownerId === authManager.user.id,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let albumHasViewers = $derived(album.albumUsers.some(({ role }) => role === AlbumUserRole.Viewer));
|
let albumHasViewers = $derived(album.albumUsers.some(({ role }) => role === AlbumUserRole.Viewer));
|
||||||
@@ -374,7 +371,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- ALBUM SHARING -->
|
<!-- ALBUM SHARING -->
|
||||||
{#if album.albumUsers.length > 0 || (album.hasSharedLink && isOwned)}
|
{#if album.albumUsers.length > 1 || (album.hasSharedLink && isOwned)}
|
||||||
<div class="my-3 flex gap-x-1">
|
<div class="my-3 flex gap-x-1">
|
||||||
<!-- link -->
|
<!-- link -->
|
||||||
{#if album.hasSharedLink && isOwned}
|
{#if album.hasSharedLink && isOwned}
|
||||||
@@ -388,13 +385,8 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- owner -->
|
|
||||||
<button type="button" onclick={() => modalManager.show(AlbumOptionsModal, { album })}>
|
|
||||||
<UserAvatar user={album.owner} size="md" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- users with write access (collaborators) -->
|
<!-- users with write access (collaborators) -->
|
||||||
{#each album.albumUsers.filter(({ role }) => role === AlbumUserRole.Editor) as { user } (user.id)}
|
{#each album.albumUsers.filter(({ role }) => role === AlbumUserRole.Editor || role === AlbumUserRole.Owner) as { user } (user.id)}
|
||||||
<button type="button" onclick={() => modalManager.show(AlbumOptionsModal, { album })}>
|
<button type="button" onclick={() => modalManager.show(AlbumOptionsModal, { album })}>
|
||||||
<UserAvatar {user} size="md" />
|
<UserAvatar {user} size="md" />
|
||||||
</button>
|
</button>
|
||||||
@@ -620,7 +612,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if album.albumUsers.length > 0 && album && assetViewerManager.isShowActivityPanel && authManager.authenticated && !assetViewerManager.isViewing}
|
{#if album.albumUsers.length > 1 && album && assetViewerManager.isShowActivityPanel && authManager.authenticated && !assetViewerManager.isViewing}
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div
|
<div
|
||||||
transition:fly={{ duration: 150 }}
|
transition:fly={{ duration: 150 }}
|
||||||
@@ -628,7 +620,7 @@
|
|||||||
class="z-2 w-90 md:w-115 overflow-y-auto transition-all dark:border-l dark:border-s-immich-dark-gray"
|
class="z-2 w-90 md:w-115 overflow-y-auto transition-all dark:border-l dark:border-s-immich-dark-gray"
|
||||||
translate="yes"
|
translate="yes"
|
||||||
>
|
>
|
||||||
<ActivityViewer disabled={!album.isActivityEnabled} albumOwnerId={album.ownerId} albumId={album.id} />
|
<ActivityViewer disabled={!album.isActivityEnabled} albumUsers={album.albumUsers} albumId={album.id} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
import { AssetOrder, type AlbumResponseDto } from '@immich/sdk';
|
import { AssetOrder, type AlbumResponseDto } from '@immich/sdk';
|
||||||
import { Sync } from 'factory.ts';
|
import { Sync } from 'factory.ts';
|
||||||
import { userFactory } from './user-factory';
|
|
||||||
|
|
||||||
export const albumFactory = Sync.makeFactory<AlbumResponseDto>({
|
export const albumFactory = Sync.makeFactory<AlbumResponseDto>({
|
||||||
albumName: Sync.each(() => faker.commerce.product()),
|
albumName: Sync.each(() => faker.commerce.product()),
|
||||||
@@ -11,8 +10,6 @@ export const albumFactory = Sync.makeFactory<AlbumResponseDto>({
|
|||||||
createdAt: Sync.each(() => faker.date.past().toISOString()),
|
createdAt: Sync.each(() => faker.date.past().toISOString()),
|
||||||
updatedAt: Sync.each(() => faker.date.past().toISOString()),
|
updatedAt: Sync.each(() => faker.date.past().toISOString()),
|
||||||
id: Sync.each(() => faker.string.uuid()),
|
id: Sync.each(() => faker.string.uuid()),
|
||||||
ownerId: Sync.each(() => faker.string.uuid()),
|
|
||||||
owner: userFactory.build(),
|
|
||||||
shared: false,
|
shared: false,
|
||||||
albumUsers: [],
|
albumUsers: [],
|
||||||
hasSharedLink: false,
|
hasSharedLink: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user