Skip to content

Commit

Permalink
Merge pull request #10 from Excali-Studio/EXD-63-crud-for-tags
Browse files Browse the repository at this point in the history
Exd 63 crud for tags
  • Loading branch information
marcin-piela-ssh authored May 15, 2024
2 parents 8ed7c4e + 4acb935 commit 16e07d4
Show file tree
Hide file tree
Showing 7 changed files with 274 additions and 7 deletions.
66 changes: 66 additions & 0 deletions src/canvas/canvas-tag.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Put,
Query,
UseGuards,
} from '@nestjs/common';
import { CanvasTagService } from './canvas-tag.service';
import { CanvasTagCreateOrUpdateDTO, CanvasTagDTO } from './canvas.interface';
import { AuthenticatedGuard } from '../auth/guard/authenticated.guard';
import { Uuid } from '../common/common.interface';
import {ListFilter, PagedResult} from '../common/pageable.utils';

@Controller('/canvas-tag')
export class CanvasTagController {
constructor(private readonly canvasTagService: CanvasTagService) {}

@Post()
@UseGuards(AuthenticatedGuard)
public async create(
@Body() dto: CanvasTagCreateOrUpdateDTO,
): Promise<CanvasTagDTO> {
const tag = await this.canvasTagService.create(dto);
return {
id: tag.id,
name: tag.name,
color: tag.color,
};
}

@Get('/:id')
@UseGuards(AuthenticatedGuard)
public async readById(@Param('id') id: Uuid): Promise<CanvasTagDTO> {
return await this.canvasTagService.readById(id);
}

@Get('/')
@UseGuards(AuthenticatedGuard)
public async readAll(
@Query() filter: ListFilter,
): Promise<PagedResult<CanvasTagDTO>> {
return await this.canvasTagService.readAll(filter);
}

@Put('/:id')
@UseGuards(AuthenticatedGuard)
public async update(
@Param('id') id: Uuid,
@Body() dto: CanvasTagCreateOrUpdateDTO,
): Promise<CanvasTagDTO> {
return await this.canvasTagService.update({
...dto,
id,
});
}

@Delete('/:id')
@UseGuards(AuthenticatedGuard)
public async delete(@Param('id') id: Uuid) {
await this.canvasTagService.delete({ id });
}
}
84 changes: 84 additions & 0 deletions src/canvas/canvas-tag.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { ConflictException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { CanvasTagEntity } from './entity/canvas-tag.entity';
import { Repository } from 'typeorm';
import {
CanvasTagCreateCommand,
CanvasTagDeleteCommand,
CanvasTagUpdateCommand,
} from './canvas.interface';
import {
ListFilter,
PageableUtils,
PagedResult,
} from '../common/pageable.utils';
import { Uuid } from '../common/common.interface';

@Injectable()
export class CanvasTagService {
constructor(
@InjectRepository(CanvasTagEntity)
private readonly canvasTagRepository: Repository<CanvasTagEntity>,
) {}

public async create(
command: CanvasTagCreateCommand,
): Promise<CanvasTagEntity> {
const tagName = command.name?.trim().toUpperCase();
const existingTag = await this.canvasTagRepository.findOne({
where: { name: tagName },
});
if (existingTag) {
throw new ConflictException();
}
const tag = new CanvasTagEntity();
tag.name = tagName;
tag.color = command.color;
await this.canvasTagRepository.save(tag);
return tag;
}

public async readById(id: Uuid): Promise<CanvasTagEntity> {
return this.canvasTagRepository.findOne({
where: {
id,
},
});
}

public async readAll(
filter: ListFilter,
): Promise<PagedResult<CanvasTagEntity>> {
return PageableUtils.producePagedResult(
filter,
await this.canvasTagRepository.findAndCount({ order: { name: 'asc' } }),
);
}

public async update(
command: CanvasTagUpdateCommand,
): Promise<CanvasTagEntity> {
const tagName = command.name?.trim().toUpperCase();
const tagToBeUpdated = await this.canvasTagRepository.findOne({
where: { id: command.id },
});
const existingTag = await this.canvasTagRepository.findOne({
where: { name: tagName },
});
if (existingTag && existingTag.id != command.id) {
throw new ConflictException();
}
tagToBeUpdated.name = tagName;
tagToBeUpdated.color = command.color;
await this.canvasTagRepository.save(tagToBeUpdated);
return tagToBeUpdated;
}

public async delete(command: CanvasTagDeleteCommand) {
try {
await this.canvasTagRepository.delete(command.id);
} catch (e) {
throw new ConflictException();
}
}
}
37 changes: 34 additions & 3 deletions src/canvas/canvas.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ import {
CanvasCreateDTO,
CanvasDTO,
CanvasMetadataUpdateDTO,
CanvasModifyTagDTO,
CanvasStateFilter,
} from './canvas.interface';
import { Uuid } from '../common/common.interface';
import { ListFilter } from '../common/pageable.utils';
import {ListFilter, PagedResult} from '../common/pageable.utils';
import { AuthenticatedGuard } from '../auth/guard/authenticated.guard';
import { CanvasGuard } from './guard/canvas.guard';
import { Log } from '@algoan/nestjs-logging-interceptor';
Expand Down Expand Up @@ -235,14 +236,14 @@ export class CanvasController {
*
* @param {ListFilter} filter - The filter to apply when retrieving items.
* @param req - HTTP request object
* @return {Promise<any>} - A Promise that resolves to the retrieved items.
* @return {Promise<CanvasDTO>} - A Promise that resolves to the retrieved items.
*/
@Get('/')
@UseGuards(AuthenticatedGuard)
public async readAll(
@Query() filter: ListFilter,
@Req() req: Request,
): Promise<any> {
): Promise<PagedResult<CanvasDTO>> {
return await this.canvasService.readAll(filter, req.user.toString());
}

Expand Down Expand Up @@ -275,4 +276,34 @@ export class CanvasController {
const userId = dto.userId;
await this.canvasService.cancelAccess({ canvasId, userId });
}

/**
* Adds a single tag to a canvas
* @param canvasId
* @param dto - an object containing 'tagId'
*/
@Post('/:id/tags')
@UseGuards(AuthenticatedGuard, CanvasGuard)
public async addTags(
@Param('id') canvasId: Uuid,
@Body() dto: CanvasModifyTagDTO,
) {
const tagIds = dto.tagIds;
await this.canvasService.addTags({ canvasId, tagIds });
}

/**
* Removes a single tag from a canvas
* @param canvasId
* @param dto - an object containing 'tagId'
*/
@Delete('/:id/tags')
@UseGuards(AuthenticatedGuard, CanvasGuard)
public async removeTags(
@Param('id') canvasId: Uuid,
@Body() dto: CanvasModifyTagDTO,
) {
const tagIds = dto.tagIds;
await this.canvasService.removeTags({ canvasId, tagIds });
}
}
42 changes: 42 additions & 0 deletions src/canvas/canvas.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ export interface CancelAccessCommand {
userId: Uuid;
}

export interface CanvasAddTagCommand {
canvasId: Uuid;
tagIds: [Uuid];
}

export interface CanvasRemoveTagCommand {
canvasId: Uuid;
tagIds: [Uuid];
}

export class CanvasMetadataUpdateDTO {
@MinLength(3)
@MaxLength(255)
Expand Down Expand Up @@ -81,3 +91,35 @@ export class CanvasStateFilter {
@IsOptional()
versionTimestamp?: string;
}

export interface CanvasTagCreateCommand {
name: string;
color?: string;
}

export interface CanvasTagUpdateCommand {
id: Uuid;
name: string;
color?: string;
}

export interface CanvasTagDeleteCommand {
id: Uuid;
}

export class CanvasTagCreateOrUpdateDTO {
@MinLength(3)
@MaxLength(12)
name: string;
@IsOptional()
@MinLength(7)
@MaxLength(7)
color?: string;
}

export class CanvasModifyTagDTO {
@IsUUID('all', { each: true })
@IsNotEmpty({ each: true })
@IsNotEmpty()
tagIds: [Uuid];
}
8 changes: 5 additions & 3 deletions src/canvas/canvas.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { CanvasStateEntity } from './entity/canvas-state.entity';
import { CanvasTagEntity } from './entity/canvas-tag.entity';
import { CanvasAccessEntity } from './entity/canvas-access.entity';
import { UserEntity } from '../user/entity/user.entity';
import { CanvasTagController } from './canvas-tag.controller';
import { CanvasTagService } from './canvas-tag.service';

@Module({
imports: [
Expand All @@ -18,8 +20,8 @@ import { UserEntity } from '../user/entity/user.entity';
UserEntity,
]),
],
controllers: [CanvasController],
providers: [CanvasService],
exports: [CanvasService],
controllers: [CanvasController, CanvasTagController],
providers: [CanvasService, CanvasTagService],
exports: [CanvasService, CanvasTagService],
})
export class CanvasModule {}
42 changes: 42 additions & 0 deletions src/canvas/canvas.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { CanvasEntity } from './entity/canvas.entity';
import { InjectRepository } from '@nestjs/typeorm';
import {
CancelAccessCommand,
CanvasAddTagCommand,
CanvasContentUpdateCommand,
CanvasCreateCommand,
CanvasMetadataUpdateCommand,
CanvasRemoveTagCommand,
CanvasStateFilter,
GiveAccessCommand,
} from './canvas.interface';
Expand All @@ -19,6 +21,7 @@ import {
} from '../common/pageable.utils';
import { CanvasAccessEntity } from './entity/canvas-access.entity';
import { UserEntity } from '../user/entity/user.entity';
import { CanvasTagEntity } from './entity/canvas-tag.entity';

@Injectable()
export class CanvasService {
Expand All @@ -31,6 +34,8 @@ export class CanvasService {
private readonly canvasAccessRepository: Repository<CanvasAccessEntity>,
@InjectRepository(UserEntity)
private readonly userRepository: Repository<UserEntity>,
@InjectRepository(CanvasTagEntity)
private readonly canvasTagRepository: Repository<CanvasTagEntity>,
) {}

/**
Expand Down Expand Up @@ -188,4 +193,41 @@ export class CanvasService {
canvas: { id: command.canvasId },
});
}

public async addTags(command: CanvasAddTagCommand) {
const canvas = await this.canvasRepository.findOne({
where: { id: command.canvasId },
relations: { tags: true },
});
if (!canvas) {
throw new NotFoundException();
}
command.tagIds.forEach((tagId) => this.addTag(canvas, tagId));
await this.canvasRepository.save(canvas);
}

public async removeTags(command: CanvasRemoveTagCommand) {
const canvas = await this.canvasRepository.findOne({
where: { id: command.canvasId },
relations: { tags: true },
});
if (!canvas) {
throw new NotFoundException();
}
canvas.tags = canvas.tags.filter((tag) => !command.tagIds.includes(tag.id));
await this.canvasRepository.save(canvas);
}

private async addTag(canvas: CanvasEntity, tagId: Uuid) {
const tag = await this.canvasTagRepository.findOne({
where: { id: tagId },
});
if (!tag) {
throw new NotFoundException();
}
if (canvas.tags.includes(tag)) {
return;
}
canvas.tags.push(tag);
}
}
2 changes: 1 addition & 1 deletion src/canvas/entity/canvas-tag.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export class CanvasTagEntity {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column({ nullable: false })
@Column({ nullable: false, unique: true })
name: string;

@Column({
Expand Down

0 comments on commit 16e07d4

Please sign in to comment.