Skip to content

Commit

Permalink
Merge pull request #7 from gbgabiola/feature/task-ownership-and-restr…
Browse files Browse the repository at this point in the history
…ictions

Add task ownership and restrictions
  • Loading branch information
gbgabiola committed Oct 18, 2021
2 parents 28087e7 + 6ae28ed commit ec00874
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 24 deletions.
6 changes: 5 additions & 1 deletion src/auth/user.entity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
import { Task } from 'src/tasks/task.entity';
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class User {
Expand All @@ -10,4 +11,7 @@ export class User {

@Column()
password: string;

@OneToMany((_type) => Task, (task) => task.user, { eager: true })
tasks: Task[];
}
2 changes: 2 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { TransformInterceptor } from './transform.interceptor';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
app.useGlobalInterceptors(new TransformInterceptor());
await app.listen(3000);
}
bootstrap();
8 changes: 7 additions & 1 deletion src/tasks/task.entity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
import { Exclude } from 'class-transformer';
import { User } from 'src/auth/user.entity';
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { TaskStatus } from './task-status.enum';

@Entity()
Expand All @@ -14,4 +16,8 @@ export class Task {

@Column()
status: TaskStatus;

@ManyToOne((_type) => User, (user) => user.tasks, { eager: false })
@Exclude({ toPlainOnly: true })
user: User;
}
27 changes: 18 additions & 9 deletions src/tasks/tasks.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
UseGuards,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { GetUser } from 'src/auth/get-user.decorator';
import { User } from 'src/auth/user.entity';
import { CreateTaskDto } from './dto/create-task.dto';
import { GetTasksFilterDto } from './dto/get-tasks-filter.dto';
import { UpdateTaskStatusDto } from './dto/update-task-status.dto';
Expand All @@ -22,31 +24,38 @@ export class TasksController {
constructor(private tasksService: TasksService) {}

@Get()
getTasks(@Query() filterDto: GetTasksFilterDto): Promise<Task[]> {
return this.tasksService.getTasks(filterDto);
getTasks(
@Query() filterDto: GetTasksFilterDto,
@GetUser() user: User,
): Promise<Task[]> {
return this.tasksService.getTasks(filterDto, user);
}

@Get('/:id')
getTaskById(@Param('id') id: string): Promise<Task> {
return this.tasksService.getTaskById(id);
getTaskById(@Param('id') id: string, @GetUser() user: User): Promise<Task> {
return this.tasksService.getTaskById(id, user);
}

@Post()
createTask(@Body() createTaskDto: CreateTaskDto): Promise<Task> {
return this.tasksService.createTask(createTaskDto);
createTask(
@Body() createTaskDto: CreateTaskDto,
@GetUser() user: User,
): Promise<Task> {
return this.tasksService.createTask(createTaskDto, user);
}

@Delete('/:id')
deleteTask(@Param('id') id: string): Promise<void> {
return this.tasksService.deleteTask(id);
deleteTask(@Param('id') id: string, @GetUser() user: User): Promise<void> {
return this.tasksService.deleteTask(id, user);
}

@Patch('/:id/status')
updateTaskStatus(
@Param('id') id: string,
@Body() updateTaskStatusDto: UpdateTaskStatusDto,
@GetUser() user: User,
): Promise<Task> {
const { status } = updateTaskStatusDto;
return this.tasksService.updateTaskStatus(id, status);
return this.tasksService.updateTaskStatus(id, status, user);
}
}
10 changes: 7 additions & 3 deletions src/tasks/tasks.repository.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { User } from 'src/auth/user.entity';
import { EntityRepository, Repository } from 'typeorm';
import { CreateTaskDto } from './dto/create-task.dto';
import { GetTasksFilterDto } from './dto/get-tasks-filter.dto';
Expand All @@ -6,16 +7,18 @@ import { Task } from './task.entity';

@EntityRepository(Task)
export class TasksRepository extends Repository<Task> {
async getTasks(filterDto: GetTasksFilterDto): Promise<Task[]> {
async getTasks(filterDto: GetTasksFilterDto, user: User): Promise<Task[]> {
const { status, search } = filterDto;
const query = this.createQueryBuilder('task');
query.where({ user });

if (status) {
query.andWhere('task.status = :status', { status });
}

if (search) {
query.andWhere(
'LOWER(task.title) LIKE LOWER(:search) OR LOWER(task.description) LIKE LOWER(:search)',
'(LOWER(task.title) LIKE LOWER(:search) OR LOWER(task.description) LIKE LOWER(:search))',
{ search: `%${search}%` },
);
}
Expand All @@ -24,12 +27,13 @@ export class TasksRepository extends Repository<Task> {
return tasks;
}

async createTask(createTaskDto: CreateTaskDto): Promise<Task> {
async createTask(createTaskDto: CreateTaskDto, user: User): Promise<Task> {
const { title, description } = createTaskDto;
const task = this.create({
title,
description,
status: TaskStatus.OPEN,
user,
});

await this.save(task);
Expand Down
25 changes: 15 additions & 10 deletions src/tasks/tasks.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { GetTasksFilterDto } from './dto/get-tasks-filter.dto';
import { TasksRepository } from './tasks.repository';
import { InjectRepository } from '@nestjs/typeorm';
import { Task } from './task.entity';
import { User } from 'src/auth/user.entity';

@Injectable()
export class TasksService {
Expand All @@ -13,32 +14,36 @@ export class TasksService {
private tasksRepository: TasksRepository,
) {}

getTasks(filterDto: GetTasksFilterDto): Promise<Task[]> {
return this.tasksRepository.getTasks(filterDto);
getTasks(filterDto: GetTasksFilterDto, user: User): Promise<Task[]> {
return this.tasksRepository.getTasks(filterDto, user);
}

async getTaskById(id: string): Promise<Task> {
const foundTask = await this.tasksRepository.findOne(id);
async getTaskById(id: string, user: User): Promise<Task> {
const foundTask = await this.tasksRepository.findOne({ id, user });

if (!foundTask) {
throw new NotFoundException(`Task with ID ${id} is not found.`);
}
return foundTask;
}

createTask(createTaskDto: CreateTaskDto): Promise<Task> {
return this.tasksRepository.createTask(createTaskDto);
createTask(createTaskDto: CreateTaskDto, user: User): Promise<Task> {
return this.tasksRepository.createTask(createTaskDto, user);
}

async deleteTask(id: string): Promise<void> {
const result = await this.tasksRepository.delete(id);
async deleteTask(id: string, user: User): Promise<void> {
const result = await this.tasksRepository.delete({ id, user });
if (result.affected === 0) {
throw new NotFoundException(`Task with ID "${id}" is not found.`);
}
}

async updateTaskStatus(id: string, status: TaskStatus): Promise<Task> {
const task = await this.getTaskById(id);
async updateTaskStatus(
id: string,
status: TaskStatus,
user: User,
): Promise<Task> {
const task = await this.getTaskById(id, user);
task.status = status;
await this.tasksRepository.save(task);
return task;
Expand Down
15 changes: 15 additions & 0 deletions src/transform.interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {
NestInterceptor,
ExecutionContext,
Injectable,
CallHandler,
} from '@nestjs/common';
import { classToPlain } from 'class-transformer';
import { map } from 'rxjs/operators';

@Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler<any>) {
return next.handle().pipe(map((data) => classToPlain(data)));
}
}

0 comments on commit ec00874

Please sign in to comment.