forked from Cutlery/immich
crud implementation
This commit is contained in:
parent
ca06105d00
commit
683a56fc07
@ -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": {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 => {
|
||||
|
@ -73,9 +73,9 @@ export class AlbumEntity {
|
||||
|
||||
@Entity('nested_albums')
|
||||
export class NestedAlbumEntity {
|
||||
@PrimaryColumn()
|
||||
@PrimaryColumn('uuid')
|
||||
parentId!: string;
|
||||
|
||||
@PrimaryColumn()
|
||||
@PrimaryColumn('uuid')
|
||||
childId!: string;
|
||||
}
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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> {
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user