1
0
forked from Cutlery/immich

crud implementation

This commit is contained in:
Alex Tran 2024-04-03 11:57:04 -05:00
parent ca06105d00
commit 683a56fc07
8 changed files with 182 additions and 84 deletions

View File

@ -332,15 +332,15 @@
]
}
},
"/album/sub-album": {
"/album/nested-album": {
"post": {
"operationId": "createSubAlbum",
"operationId": "createNestedAlbum",
"parameters": [],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateSubAlbumDto"
"$ref": "#/components/schemas/CreateNestedAlbumDto"
}
}
},
@ -348,6 +348,13 @@
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AlbumResponseDto"
}
}
},
"description": ""
}
},
@ -624,9 +631,9 @@
]
}
},
"/album/{id}/sub-album": {
"/album/{id}/nested-album": {
"get": {
"operationId": "getAlbumTree",
"operationId": "getNestedAlbums",
"parameters": [
{
"name": "id",
@ -640,6 +647,13 @@
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NestedAlbumResponseDto"
}
}
},
"description": ""
}
},
@ -659,9 +673,9 @@
]
}
},
"/album/{id}/sub-album/{childAlbumId}": {
"/album/{id}/nested-album/{childAlbumId}": {
"delete": {
"operationId": "removeSubAlbum",
"operationId": "removeNestedAlbum",
"parameters": [
{
"name": "childAlbumId",
@ -683,6 +697,13 @@
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AlbumResponseDto"
}
}
},
"description": ""
}
},
@ -7196,12 +7217,6 @@
},
"type": "array"
},
"childAlbums": {
"items": {
"$ref": "#/components/schemas/SubAlbumResponseDto"
},
"type": "array"
},
"createdAt": {
"format": "date-time",
"type": "string"
@ -7235,12 +7250,6 @@
"ownerId": {
"type": "string"
},
"parentAlbums": {
"items": {
"$ref": "#/components/schemas/SubAlbumResponseDto"
},
"type": "array"
},
"shared": {
"type": "boolean"
},
@ -8145,6 +8154,22 @@
],
"type": "object"
},
"CreateNestedAlbumDto": {
"properties": {
"childId": {
"format": "uuid",
"type": "string"
},
"parentId": {
"type": "string"
}
},
"required": [
"childId",
"parentId"
],
"type": "object"
},
"CreateProfileImageDto": {
"properties": {
"file": {
@ -8172,22 +8197,6 @@
],
"type": "object"
},
"CreateSubAlbumDto": {
"properties": {
"childrenId": {
"format": "uuid",
"type": "string"
},
"parentId": {
"type": "string"
}
},
"required": [
"childrenId",
"parentId"
],
"type": "object"
},
"CreateTagDto": {
"properties": {
"name": {
@ -9218,6 +9227,27 @@
],
"type": "string"
},
"NestedAlbumResponseDto": {
"properties": {
"children": {
"items": {
"$ref": "#/components/schemas/AlbumResponseDto"
},
"type": "array"
},
"parents": {
"items": {
"$ref": "#/components/schemas/AlbumResponseDto"
},
"type": "array"
}
},
"required": [
"children",
"parents"
],
"type": "object"
},
"OAuthAuthorizeResponseDto": {
"properties": {
"url": {
@ -10347,26 +10377,6 @@
],
"type": "object"
},
"SubAlbumResponseDto": {
"properties": {
"albumName": {
"type": "string"
},
"albumThumbnailAssetId": {
"nullable": true,
"type": "string"
},
"id": {
"type": "string"
}
},
"required": [
"albumName",
"albumThumbnailAssetId",
"id"
],
"type": "object"
},
"SystemConfigDto": {
"properties": {
"ffmpeg": {

View File

@ -6,8 +6,9 @@ import {
AlbumInfoDto,
AlbumResponseDto,
CreateAlbumDto,
CreateSubAlbumDto,
CreateNestedAlbumDto,
GetAlbumsDto,
NestedAlbumResponseDto,
UpdateAlbumDto,
} from 'src/dtos/album.dto';
import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
@ -98,22 +99,22 @@ export class AlbumController {
return this.service.removeUser(auth, id, userId);
}
@Post('sub-album')
createSubAlbum(@Auth() auth: AuthDto, @Body() dto: CreateSubAlbumDto) {
// TODO
@Post('nested-album')
createNestedAlbum(@Auth() auth: AuthDto, @Body() dto: CreateNestedAlbumDto): Promise<AlbumResponseDto> {
return this.service.createNestedAlbum(auth, dto.parentId, dto.childId);
}
@Delete(':id/sub-album/:childAlbumId')
removeSubAlbum(
@Delete(':id/nested-album/:childAlbumId')
removeNestedAlbum(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Param('childAlbumId', new ParseMeUUIDPipe({ version: '4' })) childAlbumId: string,
) {
// TODO
): Promise<AlbumResponseDto> {
return this.service.removeNestedAlbum(auth, id, childAlbumId);
}
@Get(':id/sub-album')
getAlbumTree(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto) {
// TODO
@Get(':id/nested-album')
getNestedAlbums(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<NestedAlbumResponseDto> {
return this.service.getNestedAlbums(auth, id);
}
}

View File

@ -83,12 +83,6 @@ export class AlbumCountResponseDto {
notShared!: number;
}
export class SubAlbumResponseDto {
id!: string;
albumName!: string;
albumThumbnailAssetId!: string | null;
}
export class AlbumResponseDto {
id!: string;
ownerId!: string;
@ -111,17 +105,22 @@ export class AlbumResponseDto {
@Optional()
@ApiProperty({ enumName: 'AssetOrder', enum: AssetOrder })
order?: AssetOrder;
parentAlbums?: SubAlbumResponseDto[];
childAlbums?: SubAlbumResponseDto[];
}
export class CreateSubAlbumDto {
export class CreateNestedAlbumDto {
@IsString()
parentId!: string;
@ValidateUUID()
childrenId!: string;
childId!: string;
}
export class NestedAlbumResponseDto {
@ApiProperty()
parents!: AlbumResponseDto[];
@ApiProperty()
children!: AlbumResponseDto[];
}
export const mapAlbum = (entity: AlbumEntity, withAssets: boolean, auth?: AuthDto): AlbumResponseDto => {

View File

@ -73,9 +73,9 @@ export class AlbumEntity {
@Entity('nested_albums')
export class NestedAlbumEntity {
@PrimaryColumn()
@PrimaryColumn('uuid')
parentId!: string;
@PrimaryColumn()
@PrimaryColumn('uuid')
childId!: string;
}

View File

@ -24,6 +24,11 @@ export interface AlbumAssets {
assetIds: string[];
}
export interface NestedAlbums {
parents: AlbumEntity[];
children: AlbumEntity[];
}
export interface IAlbumRepository extends IBulkAsset {
getById(id: string, options: AlbumInfoOptions): Promise<AlbumEntity | null>;
getByIds(ids: string[]): Promise<AlbumEntity[]>;
@ -45,4 +50,8 @@ export interface IAlbumRepository extends IBulkAsset {
update(album: Partial<AlbumEntity>): Promise<AlbumEntity>;
delete(album: AlbumEntity): Promise<void>;
updateThumbnails(): Promise<number | undefined>;
createNestedAlbum(parentId: string, childId: string): Promise<AlbumEntity>;
removeNestedAlbum(parentId: string, childId: string): Promise<AlbumEntity>;
getNestedAlbums(id: string): Promise<NestedAlbums>;
}

View File

@ -1,10 +1,10 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class AddNestedAlbumTable1712155807366 implements MigrationInterface {
name = 'AddNestedAlbumTable1712155807366'
export class AddNestedAlbumTable1712161154542 implements MigrationInterface {
name = 'AddNestedAlbumTable1712161154542'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "nested_albums" ("parentId" character varying NOT NULL, "childId" character varying NOT NULL, CONSTRAINT "PK_702e1e5d9ed4b85d3bdffc934bd" PRIMARY KEY ("parentId", "childId"))`);
await queryRunner.query(`CREATE TABLE "nested_albums" ("parentId" uuid NOT NULL, "childId" uuid NOT NULL, CONSTRAINT "PK_702e1e5d9ed4b85d3bdffc934bd" PRIMARY KEY ("parentId", "childId"))`);
}
public async down(queryRunner: QueryRunner): Promise<void> {

View File

@ -3,9 +3,15 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import _ from 'lodash';
import { dataSource } from 'src/database.config';
import { Chunked, ChunkedArray, DATABASE_PARAMETER_CHUNK_SIZE, DummyValue, GenerateSql } from 'src/decorators';
import { AlbumEntity } from 'src/entities/album.entity';
import { AlbumEntity, NestedAlbumEntity } from 'src/entities/album.entity';
import { AssetEntity } from 'src/entities/asset.entity';
import { AlbumAsset, AlbumAssetCount, AlbumInfoOptions, IAlbumRepository } from 'src/interfaces/album.interface';
import {
AlbumAsset,
AlbumAssetCount,
AlbumInfoOptions,
IAlbumRepository,
NestedAlbums,
} from 'src/interfaces/album.interface';
import { Instrumentation } from 'src/utils/instrumentation';
import { setUnion } from 'src/utils/set';
import { DataSource, FindOptionsOrder, FindOptionsRelations, In, IsNull, Not, Repository } from 'typeorm';
@ -16,6 +22,7 @@ export class AlbumRepository implements IAlbumRepository {
constructor(
@InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>,
@InjectRepository(AlbumEntity) private repository: Repository<AlbumEntity>,
@InjectRepository(NestedAlbumEntity) private nestedAlbumRepository: Repository<NestedAlbumEntity>,
@InjectDataSource() private dataSource: DataSource,
) {}
@ -331,4 +338,48 @@ export class AlbumRepository implements IAlbumRepository {
return result.affected;
}
@GenerateSql()
async createNestedAlbum(parentId: string, childId: string): Promise<AlbumEntity> {
const nestedAlbum = new NestedAlbumEntity();
nestedAlbum.parentId = parentId;
nestedAlbum.childId = childId;
await this.nestedAlbumRepository.save(nestedAlbum);
return this.repository.findOneOrFail({
where: { id: childId },
});
}
@GenerateSql()
async removeNestedAlbum(parentId: string, childId: string): Promise<AlbumEntity> {
await this.nestedAlbumRepository.delete({ parentId, childId });
return this.repository.findOneOrFail({
where: { id: childId },
});
}
@GenerateSql()
async getNestedAlbums(id: string): Promise<NestedAlbums> {
const children = await this.repository
.createQueryBuilder('albums')
.innerJoin('nested_albums', 'nested', 'nested.childId = albums.id')
.leftJoinAndSelect('albums.owner', 'owner')
.where('nested.parentId = :id', { id })
.getMany();
const parents = await this.repository
.createQueryBuilder('albums')
.innerJoin('nested_albums', 'nested', 'nested.parentId = albums.id')
.leftJoinAndSelect('albums.owner', 'owner')
.where('nested.childId = :id', { id })
.getMany();
return {
parents,
children,
};
}
}

View File

@ -7,6 +7,7 @@ import {
AlbumResponseDto,
CreateAlbumDto,
GetAlbumsDto,
NestedAlbumResponseDto as NestedAlbumsResponseDto,
UpdateAlbumDto,
mapAlbum,
mapAlbumWithAssets,
@ -266,6 +267,33 @@ export class AlbumService {
});
}
async createNestedAlbum(auth: AuthDto, parentId: string, childId: string): Promise<AlbumResponseDto> {
await this.access.requirePermission(auth, Permission.ALBUM_UPDATE, parentId);
const nestedAlbum = await this.albumRepository.createNestedAlbum(parentId, childId);
return mapAlbumWithoutAssets(nestedAlbum);
}
async removeNestedAlbum(auth: AuthDto, parentId: string, childId: string): Promise<AlbumResponseDto> {
await this.access.requirePermission(auth, Permission.ALBUM_UPDATE, parentId);
const deletedNestedAlbum = await this.albumRepository.removeNestedAlbum(parentId, childId);
return mapAlbumWithoutAssets(deletedNestedAlbum);
}
async getNestedAlbums(auth: AuthDto, id: string): Promise<NestedAlbumsResponseDto> {
await this.access.requirePermission(auth, Permission.ALBUM_READ, id);
const { parents, children } = await this.albumRepository.getNestedAlbums(id);
return {
parents: parents.length > 0 ? parents.map(mapAlbumWithoutAssets) : [],
children: children.length > 0 ? children.map(mapAlbumWithoutAssets) : [],
};
}
private async findOrFail(id: string, options: AlbumInfoOptions) {
const album = await this.albumRepository.getById(id, options);
if (!album) {