Skip to content

Commit

Permalink
Merge pull request #8 from Excali-Studio/EXD-55-canvas-access
Browse files Browse the repository at this point in the history
Exd 55 canvas access
  • Loading branch information
marcin-piela-ssh authored May 14, 2024
2 parents dd54b13 + 907e8ad commit 933dd02
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 9 deletions.
58 changes: 50 additions & 8 deletions src/canvas/canvas.controller.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
Query,
Req,
UseGuards,
} from '@nestjs/common';
import { CanvasService } from './canvas.service';
import {
CanvasAccessDTO,
CanvasContentUpdateDto,
CanvasCreateDTO,
CanvasDTO,
Expand All @@ -19,7 +22,9 @@ import {
import { Uuid } from '../common/common.interface';
import { ListFilter } from '../common/pageable.utils';
import { AuthenticatedGuard } from '../auth/guard/authenticated.guard';
import { CanvasGuard } from './guard/canvas.guard';
import { Log } from '@algoan/nestjs-logging-interceptor';
import { Request } from 'express';

@Controller('/canvas')
export class CanvasController {
Expand All @@ -30,6 +35,7 @@ export class CanvasController {
*
* @param {CanvasCreateDTO} createDto - The data transfer object containing the canvas details.
*
* @param req - HTTP request object
* @returns {Promise<CanvasDTO>} - The promise of a CanvasDTO object representing the created canvas.
*
* Example input:
Expand All @@ -52,8 +58,10 @@ export class CanvasController {
@UseGuards(AuthenticatedGuard)
public async createNewCanvas(
@Body() createDto: CanvasCreateDTO,
@Req() req: Request,
): Promise<CanvasDTO> {
const canvas = await this.canvasService.create(createDto);
const userId = req.user.toString();
const canvas = await this.canvasService.create({ ...createDto, userId });
return {
id: canvas.id,
name: canvas.name,
Expand Down Expand Up @@ -87,7 +95,7 @@ export class CanvasController {
* @returns {Promise<CanvasDTO>} - A promise that resolves to the updated canvas.
*/
@Patch('/:id')
@UseGuards(AuthenticatedGuard)
@UseGuards(AuthenticatedGuard, CanvasGuard)
public async updateCanvasMetadata(
@Param('id') id: Uuid,
@Body() updateDto: CanvasMetadataUpdateDTO,
Expand Down Expand Up @@ -141,7 +149,7 @@ export class CanvasController {
* @returns A Promise that resolves to a CanvasDTO object representing the updated canvas.
*/
@Post('/:id/state')
@UseGuards(AuthenticatedGuard)
@UseGuards(AuthenticatedGuard, CanvasGuard)
public async appendCanvasState(
@Param('id') id: Uuid,
@Body() appendStateDto: CanvasContentUpdateDto,
Expand Down Expand Up @@ -186,9 +194,9 @@ export class CanvasController {
* @param {string} uuid - The UUID of the canvas to be retrieved.
* @returns {Promise<CanvasDTO>} - A Promise that resolves to the retrieved CanvasDTO object.
*/
@Get('/:uuid')
@UseGuards(AuthenticatedGuard)
public async readById(@Param('uuid') uuid: Uuid): Promise<CanvasDTO> {
@Get('/:id')
@UseGuards(AuthenticatedGuard, CanvasGuard)
public async readById(@Param('id') uuid: Uuid): Promise<CanvasDTO> {
return await this.canvasService.readById(uuid);
}

Expand Down Expand Up @@ -221,11 +229,45 @@ 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.
*/
@Get('/')
@UseGuards(AuthenticatedGuard)
public async readAll(@Query() filter: ListFilter): Promise<any> {
return this.canvasService.readAll(filter);
public async readAll(
@Query() filter: ListFilter,
@Req() req: Request,
): Promise<any> {
return await this.canvasService.readAll(filter, req.user.toString());
}

/**
* Gives access to a single canvas for a single user
* @param canvasId
* @param dto - an object containing 'userId'
*/
@Post('/:id/access')
@UseGuards(AuthenticatedGuard, CanvasGuard)
public async giveAccess(
@Param('id') canvasId: Uuid,
@Body() dto: CanvasAccessDTO,
) {
const userId = dto.userId;
await this.canvasService.giveAccess({ canvasId, userId });
}

/**
* Removes access to a single canvas for a single user
* @param canvasId
* @param dto - an object containing 'userId'
*/
@Delete('/:id/access')
@UseGuards(AuthenticatedGuard, CanvasGuard)
public async cancelAccess(
@Param('id') canvasId: Uuid,
@Body() dto: CanvasAccessDTO,
) {
const userId = dto.userId;
await this.canvasService.cancelAccess({ canvasId, userId });
}
}
17 changes: 17 additions & 0 deletions src/canvas/canvas.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {

export interface CanvasCreateCommand {
name: string;
userId: Uuid;
}

export interface CanvasContentUpdateCommand {
Expand All @@ -28,6 +29,16 @@ export interface CanvasMetadataUpdateCommand {
name: string;
}

export interface GiveAccessCommand {
canvasId: Uuid;
userId: Uuid;
}

export interface CancelAccessCommand {
canvasId: Uuid;
userId: Uuid;
}

export class CanvasMetadataUpdateDTO {
@MinLength(3)
@MaxLength(255)
Expand Down Expand Up @@ -60,6 +71,12 @@ export interface CanvasTagDTO {
color: string;
}

export class CanvasAccessDTO {
@IsUUID()
@IsNotEmpty()
userId: Uuid;
}

export class CanvasStateFilter {
@IsOptional()
versionTimestamp?: string;
Expand Down
2 changes: 2 additions & 0 deletions src/canvas/canvas.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { CanvasService } from './canvas.service';
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';

@Module({
imports: [
Expand All @@ -14,6 +15,7 @@ import { CanvasAccessEntity } from './entity/canvas-access.entity';
CanvasTagEntity,
CanvasStateEntity,
CanvasAccessEntity,
UserEntity,
]),
],
controllers: [CanvasController],
Expand Down
66 changes: 65 additions & 1 deletion src/canvas/canvas.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { Repository } from 'typeorm';
import { CanvasEntity } from './entity/canvas.entity';
import { InjectRepository } from '@nestjs/typeorm';
import {
CancelAccessCommand,
CanvasContentUpdateCommand,
CanvasCreateCommand,
CanvasMetadataUpdateCommand,
CanvasStateFilter,
GiveAccessCommand,
} from './canvas.interface';
import { CanvasStateEntity } from './entity/canvas-state.entity';
import { Uuid } from '../common/common.interface';
Expand All @@ -15,6 +17,8 @@ import {
PageableUtils,
PagedResult,
} from '../common/pageable.utils';
import { CanvasAccessEntity } from './entity/canvas-access.entity';
import { UserEntity } from '../user/entity/user.entity';

@Injectable()
export class CanvasService {
Expand All @@ -23,15 +27,27 @@ export class CanvasService {
private readonly canvasStateRepository: Repository<CanvasStateEntity>,
@InjectRepository(CanvasEntity)
private readonly canvasRepository: Repository<CanvasEntity>,
@InjectRepository(CanvasAccessEntity)
private readonly canvasAccessRepository: Repository<CanvasAccessEntity>,
@InjectRepository(UserEntity)
private readonly userRepository: Repository<UserEntity>,
) {}

/**
* Create new canvas
*/
public async create(command: CanvasCreateCommand): Promise<CanvasEntity> {
const user = await this.userRepository.findOne({
where: { id: command.userId },
});
const canvas = new CanvasEntity();
canvas.name = command.name;
await this.canvasRepository.save(canvas);
const canvasAccess = new CanvasAccessEntity();
canvasAccess.isOwner = true;
canvasAccess.canvas = canvas;
canvasAccess.user = user;
await this.canvasAccessRepository.save(canvasAccess);
return canvas;
}

Expand Down Expand Up @@ -84,12 +100,30 @@ export class CanvasService {
});
}

public async readAll(filter: ListFilter): Promise<PagedResult<CanvasEntity>> {
public async readAll(
filter: ListFilter,
userId: Uuid,
): Promise<PagedResult<CanvasEntity>> {
const accessibleCanvases = (
await this.canvasAccessRepository.find({
relations: {
canvas: true,
},
where: {
user: {
id: userId,
},
},
})
).map((access) => access.canvas.id);

const qb = PageableUtils.producePagedQueryBuilder(
filter,
this.canvasRepository.createQueryBuilder('canvas'),
);

qb.whereInIds(accessibleCanvases);

return PageableUtils.producePagedResult(
filter,
await qb.leftJoinAndSelect('canvas.tags', 'tags').getManyAndCount(),
Expand Down Expand Up @@ -123,4 +157,34 @@ export class CanvasService {
(this.produceEmptyCanvasState(filter.canvasId) as CanvasStateEntity) //If state is empty return default one
);
}

public async giveAccess(command: GiveAccessCommand) {
const user = await this.userRepository.findOne({
where: { id: command.userId },
});
const canvas = await this.canvasRepository.findOne({
where: { id: command.canvasId },
});
if (!user || !canvas) {
throw new NotFoundException();
}
let canvasAccess = await this.canvasAccessRepository.findOne({
where: { user: { id: command.userId }, canvas: { id: command.canvasId } },
});
if (canvasAccess) {
return;
}
canvasAccess = new CanvasAccessEntity();
canvasAccess.isOwner = false;
canvasAccess.canvas = canvas;
canvasAccess.user = user;
await this.canvasAccessRepository.save(canvasAccess);
}

public async cancelAccess(command: CancelAccessCommand) {
await this.canvasAccessRepository.delete({
user: { id: command.userId },
canvas: { id: command.canvasId },
});
}
}
25 changes: 25 additions & 0 deletions src/canvas/guard/canvas.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { CanvasAccessEntity } from '../entity/canvas-access.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

@Injectable()
export class CanvasGuard implements CanActivate {
constructor(
@InjectRepository(CanvasAccessEntity)
private readonly canvasAccessRepository: Repository<CanvasAccessEntity>,
) {}

async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();

const canvasId = request.params.id;
const userId = request.user.toString();

const canvasAccess = await this.canvasAccessRepository.findOne({
where: { canvas: { id: canvasId }, user: { id: userId } },
});

return !!canvasAccess;
}
}

0 comments on commit 933dd02

Please sign in to comment.