mirror of
https://github.com/immich-app/immich.git
synced 2025-12-19 17:23:21 +03:00
feat(server/web): album description (#3558)
* feat(server): add album description * chore: open api * fix: tests * show and edit description on the web * fix test * remove unused code * type event * format fix --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
@@ -7,6 +7,7 @@ export class AlbumResponseDto {
|
||||
id!: string;
|
||||
ownerId!: string;
|
||||
albumName!: string;
|
||||
description!: string;
|
||||
createdAt!: Date;
|
||||
updatedAt!: Date;
|
||||
albumThumbnailAssetId!: string | null;
|
||||
@@ -19,7 +20,7 @@ export class AlbumResponseDto {
|
||||
lastModifiedAssetTimestamp?: Date;
|
||||
}
|
||||
|
||||
export function mapAlbum(entity: AlbumEntity): AlbumResponseDto {
|
||||
const _map = (entity: AlbumEntity, withAssets: boolean): AlbumResponseDto => {
|
||||
const sharedUsers: UserResponseDto[] = [];
|
||||
|
||||
entity.sharedUsers?.forEach((user) => {
|
||||
@@ -29,6 +30,7 @@ export function mapAlbum(entity: AlbumEntity): AlbumResponseDto {
|
||||
|
||||
return {
|
||||
albumName: entity.albumName,
|
||||
description: entity.description,
|
||||
albumThumbnailAssetId: entity.albumThumbnailAssetId,
|
||||
createdAt: entity.createdAt,
|
||||
updatedAt: entity.updatedAt,
|
||||
@@ -37,33 +39,13 @@ export function mapAlbum(entity: AlbumEntity): AlbumResponseDto {
|
||||
owner: mapUser(entity.owner),
|
||||
sharedUsers,
|
||||
shared: sharedUsers.length > 0 || entity.sharedLinks?.length > 0,
|
||||
assets: entity.assets?.map((asset) => mapAsset(asset)) || [],
|
||||
assets: withAssets ? entity.assets?.map((asset) => mapAsset(asset)) || [] : [],
|
||||
assetCount: entity.assets?.length || 0,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export function mapAlbumExcludeAssetInfo(entity: AlbumEntity): AlbumResponseDto {
|
||||
const sharedUsers: UserResponseDto[] = [];
|
||||
|
||||
entity.sharedUsers?.forEach((user) => {
|
||||
const userDto = mapUser(user);
|
||||
sharedUsers.push(userDto);
|
||||
});
|
||||
|
||||
return {
|
||||
albumName: entity.albumName,
|
||||
albumThumbnailAssetId: entity.albumThumbnailAssetId,
|
||||
createdAt: entity.createdAt,
|
||||
updatedAt: entity.updatedAt,
|
||||
id: entity.id,
|
||||
ownerId: entity.ownerId,
|
||||
owner: mapUser(entity.owner),
|
||||
sharedUsers,
|
||||
shared: sharedUsers.length > 0 || entity.sharedLinks?.length > 0,
|
||||
assets: [],
|
||||
assetCount: entity.assets?.length || 0,
|
||||
};
|
||||
}
|
||||
export const mapAlbum = (entity: AlbumEntity) => _map(entity, true);
|
||||
export const mapAlbumExcludeAssetInfo = (entity: AlbumEntity) => _map(entity, false);
|
||||
|
||||
export class AlbumCountResponseDto {
|
||||
@ApiProperty({ type: 'integer' })
|
||||
|
||||
@@ -156,6 +156,7 @@ describe(AlbumService.name, () => {
|
||||
|
||||
await expect(sut.create(authStub.admin, { albumName: 'Empty album' })).resolves.toEqual({
|
||||
albumName: 'Empty album',
|
||||
description: '',
|
||||
albumThumbnailAssetId: null,
|
||||
assetCount: 0,
|
||||
assets: [],
|
||||
|
||||
@@ -94,6 +94,7 @@ export class AlbumService {
|
||||
const album = await this.albumRepository.create({
|
||||
ownerId: authUser.id,
|
||||
albumName: dto.albumName,
|
||||
description: dto.description,
|
||||
sharedUsers: dto.sharedWithUserIds?.map((value) => ({ id: value } as UserEntity)) ?? [],
|
||||
assets: (dto.assetIds || []).map((id) => ({ id } as AssetEntity)),
|
||||
albumThumbnailAssetId: dto.assetIds?.[0] || null,
|
||||
@@ -118,6 +119,7 @@ export class AlbumService {
|
||||
const updatedAlbum = await this.albumRepository.update({
|
||||
id: album.id,
|
||||
albumName: dto.albumName,
|
||||
description: dto.description,
|
||||
albumThumbnailAssetId: dto.albumThumbnailAssetId,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
import { ValidateUUID } from '../../domain.util';
|
||||
|
||||
export class CreateAlbumDto {
|
||||
@@ -8,6 +8,10 @@ export class CreateAlbumDto {
|
||||
@ApiProperty()
|
||||
albumName!: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
|
||||
@ValidateUUID({ optional: true, each: true })
|
||||
sharedWithUserIds?: string[];
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsOptional } from 'class-validator';
|
||||
import { IsOptional, IsString } from 'class-validator';
|
||||
import { ValidateUUID } from '../../domain.util';
|
||||
|
||||
export class UpdateAlbumDto {
|
||||
@IsOptional()
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
albumName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string;
|
||||
|
||||
@ValidateUUID({ optional: true })
|
||||
albumThumbnailAssetId?: string;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
AuthUserDto,
|
||||
BulkIdResponseDto,
|
||||
BulkIdsDto,
|
||||
CreateAlbumDto,
|
||||
UpdateAlbumDto,
|
||||
CreateAlbumDto as CreateDto,
|
||||
UpdateAlbumDto as UpdateDto,
|
||||
} from '@app/domain';
|
||||
import { GetAlbumsDto } from '@app/domain/album/dto/get-albums.dto';
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query } from '@nestjs/common';
|
||||
@@ -34,7 +34,7 @@ export class AlbumController {
|
||||
}
|
||||
|
||||
@Post()
|
||||
createAlbum(@AuthUser() authUser: AuthUserDto, @Body() dto: CreateAlbumDto) {
|
||||
createAlbum(@AuthUser() authUser: AuthUserDto, @Body() dto: CreateDto) {
|
||||
return this.service.create(authUser, dto);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export class AlbumController {
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
updateAlbumInfo(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto, @Body() dto: UpdateAlbumDto) {
|
||||
updateAlbumInfo(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto, @Body() dto: UpdateDto) {
|
||||
return this.service.update(authUser, id, dto);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@ export class AlbumEntity {
|
||||
@Column({ default: 'Untitled Album' })
|
||||
albumName!: string;
|
||||
|
||||
@Column({ type: 'text', default: '' })
|
||||
description!: string;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt!: Date;
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddAlbumDescription1691209138541 implements MigrationInterface {
|
||||
name = 'AddAlbumDescription1691209138541';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "albums" ADD "description" text NOT NULL DEFAULT ''`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "albums" DROP COLUMN "description"`);
|
||||
}
|
||||
}
|
||||
@@ -234,7 +234,7 @@ export class TypesenseRepository implements ISearchRepository {
|
||||
.documents()
|
||||
.search({
|
||||
q: query,
|
||||
query_by: 'albumName',
|
||||
query_by: ['albumName', 'description'].join(','),
|
||||
filter_by: this.getAlbumFilters(filters),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { CollectionCreateSchema } from 'typesense/lib/Typesense/Collections';
|
||||
|
||||
export const albumSchemaVersion = 1;
|
||||
export const albumSchemaVersion = 2;
|
||||
export const albumSchema: CollectionCreateSchema = {
|
||||
name: `albums-v${albumSchemaVersion}`,
|
||||
fields: [
|
||||
{ name: 'ownerId', type: 'string', facet: false },
|
||||
{ name: 'albumName', type: 'string', facet: false, sort: true },
|
||||
{ name: 'description', type: 'string', facet: false },
|
||||
{ name: 'createdAt', type: 'string', facet: false, sort: true },
|
||||
{ name: 'updatedAt', type: 'string', facet: false, sort: true },
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user