mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	chore(server) Add user FK to album entity (#1569)
This commit is contained in:
		
							
								
								
									
										1
									
								
								mobile/openapi/doc/AlbumResponseDto.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								mobile/openapi/doc/AlbumResponseDto.md
									
									
									
										generated
									
									
									
								
							@@ -18,6 +18,7 @@ Name | Type | Description | Notes
 | 
			
		||||
**shared** | **bool** |  | 
 | 
			
		||||
**sharedUsers** | [**List<UserResponseDto>**](UserResponseDto.md) |  | [default to const []]
 | 
			
		||||
**assets** | [**List<AssetResponseDto>**](AssetResponseDto.md) |  | [default to const []]
 | 
			
		||||
**owner** | [**UserResponseDto**](UserResponseDto.md) |  | [optional] 
 | 
			
		||||
 | 
			
		||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								mobile/openapi/doc/AssetResponseDto.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/doc/AssetResponseDto.md
									
									
									
										generated
									
									
									
								
							@@ -26,7 +26,7 @@ Name | Type | Description | Notes
 | 
			
		||||
**exifInfo** | [**ExifResponseDto**](ExifResponseDto.md) |  | [optional] 
 | 
			
		||||
**smartInfo** | [**SmartInfoResponseDto**](SmartInfoResponseDto.md) |  | [optional] 
 | 
			
		||||
**livePhotoVideoId** | **String** |  | [optional] 
 | 
			
		||||
**tags** | [**List<TagResponseDto>**](TagResponseDto.md) |  | [default to const []]
 | 
			
		||||
**tags** | [**List<TagResponseDto>**](TagResponseDto.md) |  | [optional] [default to const []]
 | 
			
		||||
 | 
			
		||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								mobile/openapi/lib/model/album_response_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										23
									
								
								mobile/openapi/lib/model/album_response_dto.dart
									
									
									
										generated
									
									
									
								
							@@ -23,6 +23,7 @@ class AlbumResponseDto {
 | 
			
		||||
    required this.shared,
 | 
			
		||||
    this.sharedUsers = const [],
 | 
			
		||||
    this.assets = const [],
 | 
			
		||||
    this.owner,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  int assetCount;
 | 
			
		||||
@@ -45,6 +46,14 @@ class AlbumResponseDto {
 | 
			
		||||
 | 
			
		||||
  List<AssetResponseDto> assets;
 | 
			
		||||
 | 
			
		||||
  ///
 | 
			
		||||
  /// Please note: This property should have been non-nullable! Since the specification file
 | 
			
		||||
  /// does not include a default value (using the "default:" property), however, the generated
 | 
			
		||||
  /// source code must fall back to having a nullable type.
 | 
			
		||||
  /// Consider adding a "default:" property in the specification file to hide this note.
 | 
			
		||||
  ///
 | 
			
		||||
  UserResponseDto? owner;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  bool operator ==(Object other) => identical(this, other) || other is AlbumResponseDto &&
 | 
			
		||||
     other.assetCount == assetCount &&
 | 
			
		||||
@@ -56,7 +65,8 @@ class AlbumResponseDto {
 | 
			
		||||
     other.albumThumbnailAssetId == albumThumbnailAssetId &&
 | 
			
		||||
     other.shared == shared &&
 | 
			
		||||
     other.sharedUsers == sharedUsers &&
 | 
			
		||||
     other.assets == assets;
 | 
			
		||||
     other.assets == assets &&
 | 
			
		||||
     other.owner == owner;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  int get hashCode =>
 | 
			
		||||
@@ -70,10 +80,11 @@ class AlbumResponseDto {
 | 
			
		||||
    (albumThumbnailAssetId == null ? 0 : albumThumbnailAssetId!.hashCode) +
 | 
			
		||||
    (shared.hashCode) +
 | 
			
		||||
    (sharedUsers.hashCode) +
 | 
			
		||||
    (assets.hashCode);
 | 
			
		||||
    (assets.hashCode) +
 | 
			
		||||
    (owner == null ? 0 : owner!.hashCode);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String toString() => 'AlbumResponseDto[assetCount=$assetCount, id=$id, ownerId=$ownerId, albumName=$albumName, createdAt=$createdAt, updatedAt=$updatedAt, albumThumbnailAssetId=$albumThumbnailAssetId, shared=$shared, sharedUsers=$sharedUsers, assets=$assets]';
 | 
			
		||||
  String toString() => 'AlbumResponseDto[assetCount=$assetCount, id=$id, ownerId=$ownerId, albumName=$albumName, createdAt=$createdAt, updatedAt=$updatedAt, albumThumbnailAssetId=$albumThumbnailAssetId, shared=$shared, sharedUsers=$sharedUsers, assets=$assets, owner=$owner]';
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() {
 | 
			
		||||
    final json = <String, dynamic>{};
 | 
			
		||||
@@ -91,6 +102,11 @@ class AlbumResponseDto {
 | 
			
		||||
      json[r'shared'] = this.shared;
 | 
			
		||||
      json[r'sharedUsers'] = this.sharedUsers;
 | 
			
		||||
      json[r'assets'] = this.assets;
 | 
			
		||||
    if (this.owner != null) {
 | 
			
		||||
      json[r'owner'] = this.owner;
 | 
			
		||||
    } else {
 | 
			
		||||
      // json[r'owner'] = null;
 | 
			
		||||
    }
 | 
			
		||||
    return json;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -123,6 +139,7 @@ class AlbumResponseDto {
 | 
			
		||||
        shared: mapValueOfType<bool>(json, r'shared')!,
 | 
			
		||||
        sharedUsers: UserResponseDto.listFromJson(json[r'sharedUsers'])!,
 | 
			
		||||
        assets: AssetResponseDto.listFromJson(json[r'assets'])!,
 | 
			
		||||
        owner: UserResponseDto.fromJson(json[r'owner']),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								mobile/openapi/lib/model/asset_response_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								mobile/openapi/lib/model/asset_response_dto.dart
									
									
									
										generated
									
									
									
								
							@@ -221,7 +221,7 @@ class AssetResponseDto {
 | 
			
		||||
        exifInfo: ExifResponseDto.fromJson(json[r'exifInfo']),
 | 
			
		||||
        smartInfo: SmartInfoResponseDto.fromJson(json[r'smartInfo']),
 | 
			
		||||
        livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
 | 
			
		||||
        tags: TagResponseDto.listFromJson(json[r'tags'])!,
 | 
			
		||||
        tags: TagResponseDto.listFromJson(json[r'tags']) ?? const [],
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
@@ -285,7 +285,6 @@ class AssetResponseDto {
 | 
			
		||||
    'mimeType',
 | 
			
		||||
    'duration',
 | 
			
		||||
    'webpPath',
 | 
			
		||||
    'tags',
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										5
									
								
								mobile/openapi/test/album_response_dto_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								mobile/openapi/test/album_response_dto_test.dart
									
									
									
										generated
									
									
									
								
							@@ -66,6 +66,11 @@ void main() {
 | 
			
		||||
      // TODO
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // UserResponseDto owner
 | 
			
		||||
    test('to test the property `owner`', () async {
 | 
			
		||||
      // TODO
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -180,6 +180,9 @@ export class AlbumRepository implements IAlbumRepository {
 | 
			
		||||
    // Get information of shared links in albums
 | 
			
		||||
    query = query.leftJoinAndSelect('album.sharedLinks', 'sharedLink');
 | 
			
		||||
 | 
			
		||||
    // get information of owner of albums
 | 
			
		||||
    query = query.leftJoinAndSelect('album.owner', 'owner');
 | 
			
		||||
 | 
			
		||||
    const albums = await query.getMany();
 | 
			
		||||
 | 
			
		||||
    albums.sort((a, b) => new Date(b.createdAt).valueOf() - new Date(a.createdAt).valueOf());
 | 
			
		||||
@@ -202,6 +205,7 @@ export class AlbumRepository implements IAlbumRepository {
 | 
			
		||||
          .getQuery();
 | 
			
		||||
        return `album.id IN ${subQuery}`;
 | 
			
		||||
      })
 | 
			
		||||
      .leftJoinAndSelect('album.owner', 'owner')
 | 
			
		||||
      .leftJoinAndSelect('album.assets', 'assets')
 | 
			
		||||
      .leftJoinAndSelect('assets.assetInfo', 'assetInfo')
 | 
			
		||||
      .leftJoinAndSelect('album.sharedUsers', 'sharedUser')
 | 
			
		||||
@@ -216,6 +220,7 @@ export class AlbumRepository implements IAlbumRepository {
 | 
			
		||||
    const album = await this.albumRepository.findOne({
 | 
			
		||||
      where: { id: albumId },
 | 
			
		||||
      relations: {
 | 
			
		||||
        owner: true,
 | 
			
		||||
        sharedUsers: {
 | 
			
		||||
          userInfo: true,
 | 
			
		||||
        },
 | 
			
		||||
 
 | 
			
		||||
@@ -37,20 +37,19 @@ describe('Album', () => {
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('with auth', () => {
 | 
			
		||||
    let authUser: AuthUserDto;
 | 
			
		||||
    let userService: UserService;
 | 
			
		||||
    let authService: AuthService;
 | 
			
		||||
    let authUser: AuthUserDto;
 | 
			
		||||
 | 
			
		||||
    beforeAll(async () => {
 | 
			
		||||
      const builder = Test.createTestingModule({ imports: [AppModule] });
 | 
			
		||||
      authUser = getAuthUser(); // set default auth user
 | 
			
		||||
      authUser = getAuthUser();
 | 
			
		||||
      const moduleFixture: TestingModule = await authCustom(builder, () => authUser).compile();
 | 
			
		||||
 | 
			
		||||
      app = moduleFixture.createNestApplication();
 | 
			
		||||
      userService = app.get(UserService);
 | 
			
		||||
      authService = app.get(AuthService);
 | 
			
		||||
      database = app.get(DataSource);
 | 
			
		||||
 | 
			
		||||
      await app.init();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@@ -58,25 +57,25 @@ describe('Album', () => {
 | 
			
		||||
      await app.close();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('with empty DB', () => {
 | 
			
		||||
      afterEach(async () => {
 | 
			
		||||
        await clearDb(database);
 | 
			
		||||
      });
 | 
			
		||||
    // TODO - Until someone figure out how to passed in a logged in user to the request.
 | 
			
		||||
    // describe('with empty DB', () => {
 | 
			
		||||
    //   it('creates an album', async () => {
 | 
			
		||||
    //     const data: CreateAlbumDto = {
 | 
			
		||||
    //       albumName: 'first albbum',
 | 
			
		||||
    //     };
 | 
			
		||||
 | 
			
		||||
      it('creates an album', async () => {
 | 
			
		||||
        const data: CreateAlbumDto = {
 | 
			
		||||
          albumName: 'first albbum',
 | 
			
		||||
        };
 | 
			
		||||
        const { status, body } = await _createAlbum(app, data);
 | 
			
		||||
        expect(status).toEqual(201);
 | 
			
		||||
        expect(body).toEqual(
 | 
			
		||||
          expect.objectContaining({
 | 
			
		||||
            ownerId: authUser.id,
 | 
			
		||||
            albumName: data.albumName,
 | 
			
		||||
          }),
 | 
			
		||||
        );
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    //     const { status, body } = await _createAlbum(app, data);
 | 
			
		||||
 | 
			
		||||
    //     expect(status).toEqual(201);
 | 
			
		||||
 | 
			
		||||
    //     expect(body).toEqual(
 | 
			
		||||
    //       expect.objectContaining({
 | 
			
		||||
    //         ownerId: authUser.id,
 | 
			
		||||
    //         albumName: data.albumName,
 | 
			
		||||
    //       }),
 | 
			
		||||
    //     );
 | 
			
		||||
    //   });
 | 
			
		||||
    // });
 | 
			
		||||
 | 
			
		||||
    describe('with albums in DB', () => {
 | 
			
		||||
      const userOneShared = 'userOneShared';
 | 
			
		||||
 
 | 
			
		||||
@@ -3343,8 +3343,7 @@
 | 
			
		||||
          "isFavorite",
 | 
			
		||||
          "mimeType",
 | 
			
		||||
          "duration",
 | 
			
		||||
          "webpPath",
 | 
			
		||||
          "tags"
 | 
			
		||||
          "webpPath"
 | 
			
		||||
        ]
 | 
			
		||||
      },
 | 
			
		||||
      "AlbumResponseDto": {
 | 
			
		||||
@@ -3386,6 +3385,9 @@
 | 
			
		||||
            "items": {
 | 
			
		||||
              "$ref": "#/components/schemas/AssetResponseDto"
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
          "owner": {
 | 
			
		||||
            "$ref": "#/components/schemas/UserResponseDto"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "required": [
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ export class AlbumResponseDto {
 | 
			
		||||
  shared!: boolean;
 | 
			
		||||
  sharedUsers!: UserResponseDto[];
 | 
			
		||||
  assets!: AssetResponseDto[];
 | 
			
		||||
 | 
			
		||||
  owner?: UserResponseDto;
 | 
			
		||||
  @ApiProperty({ type: 'integer' })
 | 
			
		||||
  assetCount!: number;
 | 
			
		||||
}
 | 
			
		||||
@@ -27,6 +27,7 @@ export function mapAlbum(entity: AlbumEntity): AlbumResponseDto {
 | 
			
		||||
      sharedUsers.push(user);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    albumName: entity.albumName,
 | 
			
		||||
    albumThumbnailAssetId: entity.albumThumbnailAssetId,
 | 
			
		||||
@@ -34,6 +35,7 @@ export function mapAlbum(entity: AlbumEntity): AlbumResponseDto {
 | 
			
		||||
    updatedAt: entity.updatedAt,
 | 
			
		||||
    id: entity.id,
 | 
			
		||||
    ownerId: entity.ownerId,
 | 
			
		||||
    owner: entity.owner ? mapUser(entity.owner) : undefined,
 | 
			
		||||
    sharedUsers,
 | 
			
		||||
    shared: sharedUsers.length > 0 || entity.sharedLinks?.length > 0,
 | 
			
		||||
    assets: entity.assets?.map((assetAlbum) => mapAsset(assetAlbum.assetInfo)) || [],
 | 
			
		||||
@@ -50,6 +52,7 @@ export function mapAlbumExcludeAssetInfo(entity: AlbumEntity): AlbumResponseDto
 | 
			
		||||
      sharedUsers.push(user);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    albumName: entity.albumName,
 | 
			
		||||
    albumThumbnailAssetId: entity.albumThumbnailAssetId,
 | 
			
		||||
@@ -57,6 +60,7 @@ export function mapAlbumExcludeAssetInfo(entity: AlbumEntity): AlbumResponseDto
 | 
			
		||||
    updatedAt: entity.updatedAt,
 | 
			
		||||
    id: entity.id,
 | 
			
		||||
    ownerId: entity.ownerId,
 | 
			
		||||
    owner: entity.owner ? mapUser(entity.owner) : undefined,
 | 
			
		||||
    sharedUsers,
 | 
			
		||||
    shared: sharedUsers.length > 0 || entity.sharedLinks?.length > 0,
 | 
			
		||||
    assets: [],
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ export class AssetResponseDto {
 | 
			
		||||
  exifInfo?: ExifResponseDto;
 | 
			
		||||
  smartInfo?: SmartInfoResponseDto;
 | 
			
		||||
  livePhotoVideoId?: string | null;
 | 
			
		||||
  tags!: TagResponseDto[];
 | 
			
		||||
  tags?: TagResponseDto[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function mapAsset(entity: AssetEntity): AssetResponseDto {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,14 @@ import {
 | 
			
		||||
  UserEntity,
 | 
			
		||||
  UserTokenEntity,
 | 
			
		||||
} from '@app/infra/db/entities';
 | 
			
		||||
import { AlbumResponseDto, AssetResponseDto, AuthUserDto, ExifResponseDto, SharedLinkResponseDto } from '../src';
 | 
			
		||||
import {
 | 
			
		||||
  AlbumResponseDto,
 | 
			
		||||
  AssetResponseDto,
 | 
			
		||||
  AuthUserDto,
 | 
			
		||||
  ExifResponseDto,
 | 
			
		||||
  mapUser,
 | 
			
		||||
  SharedLinkResponseDto,
 | 
			
		||||
} from '../src';
 | 
			
		||||
 | 
			
		||||
const today = new Date();
 | 
			
		||||
const tomorrow = new Date();
 | 
			
		||||
@@ -15,68 +22,6 @@ const yesterday = new Date();
 | 
			
		||||
tomorrow.setDate(today.getDate() + 1);
 | 
			
		||||
yesterday.setDate(yesterday.getDate() - 1);
 | 
			
		||||
 | 
			
		||||
const assetInfo: ExifResponseDto = {
 | 
			
		||||
  id: 1,
 | 
			
		||||
  make: 'camera-make',
 | 
			
		||||
  model: 'camera-model',
 | 
			
		||||
  imageName: 'fancy-image',
 | 
			
		||||
  exifImageWidth: 500,
 | 
			
		||||
  exifImageHeight: 500,
 | 
			
		||||
  fileSizeInByte: 100,
 | 
			
		||||
  orientation: 'orientation',
 | 
			
		||||
  dateTimeOriginal: today,
 | 
			
		||||
  modifyDate: today,
 | 
			
		||||
  lensModel: 'fancy',
 | 
			
		||||
  fNumber: 100,
 | 
			
		||||
  focalLength: 100,
 | 
			
		||||
  iso: 100,
 | 
			
		||||
  exposureTime: '1/16',
 | 
			
		||||
  latitude: 100,
 | 
			
		||||
  longitude: 100,
 | 
			
		||||
  city: 'city',
 | 
			
		||||
  state: 'state',
 | 
			
		||||
  country: 'country',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const assetResponse: AssetResponseDto = {
 | 
			
		||||
  id: 'id_1',
 | 
			
		||||
  deviceAssetId: 'device_asset_id_1',
 | 
			
		||||
  ownerId: 'user_id_1',
 | 
			
		||||
  deviceId: 'device_id_1',
 | 
			
		||||
  type: AssetType.VIDEO,
 | 
			
		||||
  originalPath: 'fake_path/jpeg',
 | 
			
		||||
  resizePath: '',
 | 
			
		||||
  createdAt: today.toISOString(),
 | 
			
		||||
  modifiedAt: today.toISOString(),
 | 
			
		||||
  updatedAt: today.toISOString(),
 | 
			
		||||
  isFavorite: false,
 | 
			
		||||
  mimeType: 'image/jpeg',
 | 
			
		||||
  smartInfo: {
 | 
			
		||||
    id: 'should-be-a-number',
 | 
			
		||||
    tags: [],
 | 
			
		||||
    objects: ['a', 'b', 'c'],
 | 
			
		||||
  },
 | 
			
		||||
  webpPath: '',
 | 
			
		||||
  encodedVideoPath: '',
 | 
			
		||||
  duration: '0:00:00.00000',
 | 
			
		||||
  exifInfo: assetInfo,
 | 
			
		||||
  livePhotoVideoId: null,
 | 
			
		||||
  tags: [],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const albumResponse: AlbumResponseDto = {
 | 
			
		||||
  albumName: 'Test Album',
 | 
			
		||||
  albumThumbnailAssetId: null,
 | 
			
		||||
  createdAt: today.toISOString(),
 | 
			
		||||
  updatedAt: today.toISOString(),
 | 
			
		||||
  id: 'album-123',
 | 
			
		||||
  ownerId: 'admin_id',
 | 
			
		||||
  sharedUsers: [],
 | 
			
		||||
  shared: false,
 | 
			
		||||
  assets: [],
 | 
			
		||||
  assetCount: 1,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const authStub = {
 | 
			
		||||
  admin: Object.freeze<AuthUserDto>({
 | 
			
		||||
    id: 'admin_id',
 | 
			
		||||
@@ -145,6 +90,69 @@ export const userEntityStub = {
 | 
			
		||||
  }),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const assetInfo: ExifResponseDto = {
 | 
			
		||||
  id: 1,
 | 
			
		||||
  make: 'camera-make',
 | 
			
		||||
  model: 'camera-model',
 | 
			
		||||
  imageName: 'fancy-image',
 | 
			
		||||
  exifImageWidth: 500,
 | 
			
		||||
  exifImageHeight: 500,
 | 
			
		||||
  fileSizeInByte: 100,
 | 
			
		||||
  orientation: 'orientation',
 | 
			
		||||
  dateTimeOriginal: today,
 | 
			
		||||
  modifyDate: today,
 | 
			
		||||
  lensModel: 'fancy',
 | 
			
		||||
  fNumber: 100,
 | 
			
		||||
  focalLength: 100,
 | 
			
		||||
  iso: 100,
 | 
			
		||||
  exposureTime: '1/16',
 | 
			
		||||
  latitude: 100,
 | 
			
		||||
  longitude: 100,
 | 
			
		||||
  city: 'city',
 | 
			
		||||
  state: 'state',
 | 
			
		||||
  country: 'country',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const assetResponse: AssetResponseDto = {
 | 
			
		||||
  id: 'id_1',
 | 
			
		||||
  deviceAssetId: 'device_asset_id_1',
 | 
			
		||||
  ownerId: 'user_id_1',
 | 
			
		||||
  deviceId: 'device_id_1',
 | 
			
		||||
  type: AssetType.VIDEO,
 | 
			
		||||
  originalPath: 'fake_path/jpeg',
 | 
			
		||||
  resizePath: '',
 | 
			
		||||
  createdAt: today.toISOString(),
 | 
			
		||||
  modifiedAt: today.toISOString(),
 | 
			
		||||
  updatedAt: today.toISOString(),
 | 
			
		||||
  isFavorite: false,
 | 
			
		||||
  mimeType: 'image/jpeg',
 | 
			
		||||
  smartInfo: {
 | 
			
		||||
    id: 'should-be-a-number',
 | 
			
		||||
    tags: [],
 | 
			
		||||
    objects: ['a', 'b', 'c'],
 | 
			
		||||
  },
 | 
			
		||||
  webpPath: '',
 | 
			
		||||
  encodedVideoPath: '',
 | 
			
		||||
  duration: '0:00:00.00000',
 | 
			
		||||
  exifInfo: assetInfo,
 | 
			
		||||
  livePhotoVideoId: null,
 | 
			
		||||
  tags: [],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const albumResponse: AlbumResponseDto = {
 | 
			
		||||
  albumName: 'Test Album',
 | 
			
		||||
  albumThumbnailAssetId: null,
 | 
			
		||||
  createdAt: today.toISOString(),
 | 
			
		||||
  updatedAt: today.toISOString(),
 | 
			
		||||
  id: 'album-123',
 | 
			
		||||
  ownerId: 'admin_id',
 | 
			
		||||
  owner: mapUser(userEntityStub.admin),
 | 
			
		||||
  sharedUsers: [],
 | 
			
		||||
  shared: false,
 | 
			
		||||
  assets: [],
 | 
			
		||||
  assetCount: 1,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const userTokenEntityStub = {
 | 
			
		||||
  userToken: Object.freeze<UserTokenEntity>({
 | 
			
		||||
    id: 'token-id',
 | 
			
		||||
@@ -331,6 +339,7 @@ export const sharedLinkStub = {
 | 
			
		||||
    album: {
 | 
			
		||||
      id: 'album-123',
 | 
			
		||||
      ownerId: authStub.admin.id,
 | 
			
		||||
      owner: userEntityStub.admin,
 | 
			
		||||
      albumName: 'Test Album',
 | 
			
		||||
      createdAt: today.toISOString(),
 | 
			
		||||
      updatedAt: today.toISOString(),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,16 @@
 | 
			
		||||
import { Column, CreateDateColumn, Entity, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
 | 
			
		||||
import {
 | 
			
		||||
  Column,
 | 
			
		||||
  CreateDateColumn,
 | 
			
		||||
  Entity,
 | 
			
		||||
  ManyToOne,
 | 
			
		||||
  OneToMany,
 | 
			
		||||
  PrimaryGeneratedColumn,
 | 
			
		||||
  UpdateDateColumn,
 | 
			
		||||
} from 'typeorm';
 | 
			
		||||
import { AssetAlbumEntity } from './asset-album.entity';
 | 
			
		||||
import { SharedLinkEntity } from './shared-link.entity';
 | 
			
		||||
import { UserAlbumEntity } from './user-album.entity';
 | 
			
		||||
import { UserEntity } from './user.entity';
 | 
			
		||||
 | 
			
		||||
@Entity('albums')
 | 
			
		||||
export class AlbumEntity {
 | 
			
		||||
@@ -11,6 +20,9 @@ export class AlbumEntity {
 | 
			
		||||
  @Column()
 | 
			
		||||
  ownerId!: string;
 | 
			
		||||
 | 
			
		||||
  @ManyToOne(() => UserEntity)
 | 
			
		||||
  owner!: UserEntity;
 | 
			
		||||
 | 
			
		||||
  @Column({ default: 'Untitled Album' })
 | 
			
		||||
  albumName!: string;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,18 @@
 | 
			
		||||
import { MigrationInterface, QueryRunner } from 'typeorm';
 | 
			
		||||
 | 
			
		||||
export class AddAlbumUserForeignKeyConstraint1675701909594 implements MigrationInterface {
 | 
			
		||||
  name = 'AddAlbumUserForeignKeyConstraint1675701909594';
 | 
			
		||||
 | 
			
		||||
  public async up(queryRunner: QueryRunner): Promise<void> {
 | 
			
		||||
    await queryRunner.query(`ALTER TABLE "albums" ALTER COLUMN "ownerId" TYPE varchar(36)`);
 | 
			
		||||
    await queryRunner.query(`ALTER TABLE "albums" ALTER COLUMN "ownerId" TYPE uuid using "ownerId"::uuid`);
 | 
			
		||||
    await queryRunner.query(
 | 
			
		||||
      `ALTER TABLE "albums" ADD CONSTRAINT "FK_b22c53f35ef20c28c21637c85f4" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async down(queryRunner: QueryRunner): Promise<void> {
 | 
			
		||||
    await queryRunner.query(`ALTER TABLE "albums" DROP CONSTRAINT "FK_b22c53f35ef20c28c21637c85f4"`);
 | 
			
		||||
    await queryRunner.query(`ALTER TABLE "albums" ALTER COLUMN "ownerId" TYPE character varying`);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							@@ -276,6 +276,12 @@ export interface AlbumResponseDto {
 | 
			
		||||
     * @memberof AlbumResponseDto
 | 
			
		||||
     */
 | 
			
		||||
    'assets': Array<AssetResponseDto>;
 | 
			
		||||
    /**
 | 
			
		||||
     * 
 | 
			
		||||
     * @type {UserResponseDto}
 | 
			
		||||
     * @memberof AlbumResponseDto
 | 
			
		||||
     */
 | 
			
		||||
    'owner'?: UserResponseDto;
 | 
			
		||||
}
 | 
			
		||||
/**
 | 
			
		||||
 * 
 | 
			
		||||
@@ -527,7 +533,7 @@ export interface AssetResponseDto {
 | 
			
		||||
     * @type {Array<TagResponseDto>}
 | 
			
		||||
     * @memberof AssetResponseDto
 | 
			
		||||
     */
 | 
			
		||||
    'tags': Array<TagResponseDto>;
 | 
			
		||||
    'tags'?: Array<TagResponseDto>;
 | 
			
		||||
}
 | 
			
		||||
/**
 | 
			
		||||
 * 
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user