add task
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
ENV=development
|
||||
PORT=5000
|
||||
SECRET=aE8efkEP8+V/ibEQl8IKbw==
|
||||
|
||||
DB_NAME=todoListDev
|
||||
2
server/.gitignore
vendored
2
server/.gitignore
vendored
@@ -1,5 +1,5 @@
|
||||
node_modules
|
||||
dist
|
||||
coverage
|
||||
.env.development
|
||||
.env
|
||||
.env.test
|
||||
@@ -22,6 +22,7 @@
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^7.3.0",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/jest": "^28.1.4",
|
||||
|
||||
@@ -45,7 +45,7 @@ router.delete(`${apiNamespace}/:id`, async (req, res, next) => {
|
||||
const projecToBeDeleted = userProjects.find(project => project.id === projectId)
|
||||
|
||||
if (projecToBeDeleted) {
|
||||
const success = await ProjectService.destroyProject(projecToBeDeleted)
|
||||
const success = await ProjectService.destroy(projecToBeDeleted)
|
||||
|
||||
if (success) {
|
||||
res.json({ success })
|
||||
|
||||
3
server/src/dto/newTaskDto.ts
Normal file
3
server/src/dto/newTaskDto.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export type NewTaskDto = {
|
||||
description: string;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { AppDataSource } from "../../infra/dataSource";
|
||||
import { projectRepository } from "../../repository/project.repository";
|
||||
import { userRepository } from "../../repository/user.repository";
|
||||
@@ -10,23 +11,23 @@ describe("Project", () => {
|
||||
});
|
||||
afterAll(async () => {
|
||||
await cleanDataSource(AppDataSource);
|
||||
await AppDataSource.destroy()
|
||||
})
|
||||
await AppDataSource.destroy();
|
||||
});
|
||||
|
||||
describe("relations", () => {
|
||||
it("should have many projects", async () => {
|
||||
it("should belongs a one user", async () => {
|
||||
const user = await userRepository.save({
|
||||
name: "John Doe",
|
||||
email: "john.doe@example.com",
|
||||
encryptedPassword: 'encryptedPassword'
|
||||
})
|
||||
email: faker.internet.email(),
|
||||
encryptedPassword: "encryptedPassword",
|
||||
});
|
||||
|
||||
const project = await projectRepository.save({
|
||||
name: "My first project",
|
||||
user,
|
||||
})
|
||||
});
|
||||
|
||||
expect(project.user.id).toBe(user.id)
|
||||
expect(project.user.id).toBe(user.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
63
server/src/entity/__test__/task.entity.spec.ts
Normal file
63
server/src/entity/__test__/task.entity.spec.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { AppDataSource } from "../../infra/dataSource";
|
||||
import { projectRepository } from "../../repository/project.repository";
|
||||
import { taskRepository } from "../../repository/task.repository";
|
||||
import { userRepository } from "../../repository/user.repository";
|
||||
import { cleanDataSource } from "../../utils/cleanDataSource";
|
||||
|
||||
describe("Task", () => {
|
||||
beforeAll(async () => {
|
||||
await AppDataSource.initialize();
|
||||
await cleanDataSource(AppDataSource);
|
||||
});
|
||||
afterAll(async () => {
|
||||
await cleanDataSource(AppDataSource);
|
||||
await AppDataSource.destroy();
|
||||
});
|
||||
|
||||
describe("relations", () => {
|
||||
it("should belongs a one project", async () => {
|
||||
const user = await userRepository.save({
|
||||
email: faker.internet.email(),
|
||||
encryptedPassword: "encryptedPassword",
|
||||
});
|
||||
|
||||
const project = await projectRepository.save({
|
||||
name: "My first project",
|
||||
user,
|
||||
});
|
||||
|
||||
const task = await taskRepository.save({
|
||||
description: "My first task",
|
||||
project,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
|
||||
expect(task.project.id).toBe(project.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe("date fields", () => {
|
||||
it("should return date fields as Date objects", async () => {
|
||||
const user = await userRepository.save({
|
||||
email: faker.internet.email(),
|
||||
encryptedPassword: "encryptedPassword",
|
||||
});
|
||||
|
||||
const project = await projectRepository.save({
|
||||
name: "My secound project",
|
||||
user,
|
||||
});
|
||||
|
||||
const task = await taskRepository.save({
|
||||
description: "My secound task",
|
||||
project,
|
||||
createdAt: new Date(),
|
||||
finishedAt: new Date(),
|
||||
});
|
||||
|
||||
expect(task.createdAt).toBeInstanceOf(Date);
|
||||
expect(task.finishedAt).toBeInstanceOf(Date);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,4 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { AppDataSource } from "../../infra/dataSource";
|
||||
import { userRepository } from "../../repository/user.repository";
|
||||
import { cleanDataSource } from "../../utils/cleanDataSource";
|
||||
@@ -11,8 +12,8 @@ describe("User", () => {
|
||||
});
|
||||
afterAll(async () => {
|
||||
await cleanDataSource(AppDataSource);
|
||||
await AppDataSource.destroy()
|
||||
})
|
||||
await AppDataSource.destroy();
|
||||
});
|
||||
|
||||
describe("relations", () => {
|
||||
it("should have many projects", async () => {
|
||||
@@ -21,13 +22,13 @@ describe("User", () => {
|
||||
|
||||
Object.assign(user, {
|
||||
name: "John Doe",
|
||||
email: "john.doe@example.com",
|
||||
email: faker.internet.email(),
|
||||
encryptedPassword: "encryptedPassword",
|
||||
});
|
||||
|
||||
Object.assign(user2, {
|
||||
name: "Luis Doe",
|
||||
email: "luis.doe@example.com",
|
||||
email: faker.internet.email(),
|
||||
encryptedPassword: "encryptedPassword",
|
||||
});
|
||||
|
||||
@@ -40,10 +41,10 @@ describe("User", () => {
|
||||
user.projects = [project1, proejct2];
|
||||
|
||||
await userRepository.save(user);
|
||||
await userRepository.save(user2)
|
||||
await userRepository.save(user2);
|
||||
|
||||
expect(user.projects).toHaveLength(2);
|
||||
expect(user2.projects).toBeUndefined()
|
||||
expect(user2.projects).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
import { IsNotEmpty } from "class-validator"
|
||||
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, ManyToOne, JoinColumn } from "typeorm"
|
||||
import { User } from "./user.entity"
|
||||
import { IsNotEmpty } from "class-validator";
|
||||
import {
|
||||
Column, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn
|
||||
} from "typeorm";
|
||||
import { Task } from "./task.entity";
|
||||
import { User } from "./user.entity";
|
||||
|
||||
@Entity()
|
||||
export class Project {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
@IsNotEmpty()
|
||||
name: string
|
||||
name: string;
|
||||
|
||||
@ManyToOne((_type) => User, (user) => user.projects)
|
||||
@JoinColumn()
|
||||
@IsNotEmpty()
|
||||
user: User
|
||||
user: User;
|
||||
|
||||
@OneToMany((_type) => Task, (item) => item.project)
|
||||
tasks?: Task[];
|
||||
}
|
||||
|
||||
31
server/src/entity/task.entity.ts
Normal file
31
server/src/entity/task.entity.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { IsNotEmpty } from "class-validator";
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn
|
||||
} from "typeorm";
|
||||
import { Project } from "./project.entity";
|
||||
|
||||
@Entity()
|
||||
export class Task {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
@IsNotEmpty()
|
||||
description: string;
|
||||
|
||||
@Column({ type: "timestamptz" })
|
||||
@IsNotEmpty()
|
||||
createdAt: Date;
|
||||
|
||||
@Column({ type: "timestamptz", nullable: true })
|
||||
finishedAt?: Date;
|
||||
|
||||
@ManyToOne((_type) => Project, (project) => project.tasks)
|
||||
@JoinColumn()
|
||||
@IsNotEmpty()
|
||||
project: Project;
|
||||
}
|
||||
@@ -1,20 +1,22 @@
|
||||
import { IsEmail, IsNotEmpty } from "class-validator"
|
||||
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, AfterLoad } from "typeorm"
|
||||
import { Project } from "./project.entity"
|
||||
import { IsEmail, IsNotEmpty } from "class-validator";
|
||||
import {
|
||||
Column, Entity, OneToMany, PrimaryGeneratedColumn
|
||||
} from "typeorm";
|
||||
import { Project } from "./project.entity";
|
||||
|
||||
@Entity()
|
||||
export class User {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number
|
||||
id: number;
|
||||
|
||||
@Column({ unique: true })
|
||||
@IsEmail()
|
||||
@IsNotEmpty()
|
||||
email: string
|
||||
email: string;
|
||||
|
||||
@Column()
|
||||
encryptedPassword: string
|
||||
encryptedPassword: string;
|
||||
|
||||
@OneToMany((_type) => Project, (item) => item.user)
|
||||
projects?: Project[]
|
||||
projects?: Project[];
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as dotenv from 'dotenv';
|
||||
import "reflect-metadata";
|
||||
import { DataSource } from "typeorm";
|
||||
import { Project } from "../entity/project.entity";
|
||||
import { Task } from '../entity/task.entity';
|
||||
import { User } from "../entity/user.entity";
|
||||
|
||||
dotenv.config();
|
||||
@@ -14,7 +15,7 @@ export const AppDataSource = new DataSource({
|
||||
database: process.env.DB_NAME,
|
||||
synchronize: true,
|
||||
logging: false,
|
||||
entities: [User, Project],
|
||||
entities: [User, Project, Task],
|
||||
migrations: [],
|
||||
subscribers: [],
|
||||
})
|
||||
|
||||
4
server/src/repository/task.repository.ts
Normal file
4
server/src/repository/task.repository.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { Task } from "../entity/task.entity";
|
||||
import { AppDataSource } from "../infra/dataSource";
|
||||
|
||||
export const taskRepository = AppDataSource.getRepository(Task)
|
||||
@@ -28,7 +28,7 @@ async function listAllByUserId(userId: User["id"]): Promise<Project[]> {
|
||||
return query.getMany();
|
||||
}
|
||||
|
||||
async function destroyProject(project: Project): Promise<boolean>{
|
||||
async function destroy(project: Project): Promise<boolean>{
|
||||
const query = projectRepository.createQueryBuilder();
|
||||
query.where('"id" = :projectId', { projectId: project.id });
|
||||
|
||||
@@ -40,5 +40,5 @@ async function destroyProject(project: Project): Promise<boolean>{
|
||||
export const ProjectService = {
|
||||
create,
|
||||
listAllByUserId,
|
||||
destroyProject
|
||||
destroy
|
||||
};
|
||||
|
||||
37
server/src/service/task.service.ts
Normal file
37
server/src/service/task.service.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { NewTaskDto } from "../dto/newTaskDto";
|
||||
import { Project } from "../entity/project.entity";
|
||||
import { Task } from "../entity/task.entity";
|
||||
import { taskRepository } from "../repository/task.repository";
|
||||
|
||||
async function finish(task: Task) {
|
||||
if (task.finishedAt) {
|
||||
throw new Error("Task already finished");
|
||||
}
|
||||
|
||||
task.finishedAt = new Date();
|
||||
|
||||
return taskRepository.save(task);
|
||||
}
|
||||
|
||||
async function destroy(task: Task) {
|
||||
if (task.finishedAt) {
|
||||
throw new Error("You can't destroy a finished task");
|
||||
}
|
||||
|
||||
return taskRepository.delete(task);
|
||||
}
|
||||
|
||||
async function create(newTaskDto: NewTaskDto, project: Project) {
|
||||
const task = new Task();
|
||||
|
||||
task.description = newTaskDto.description;
|
||||
task.project = project;
|
||||
|
||||
return taskRepository.save(task);
|
||||
}
|
||||
|
||||
export const TaskService = {
|
||||
finish,
|
||||
destroy,
|
||||
create,
|
||||
};
|
||||
@@ -2,7 +2,7 @@ import { DataSource } from "typeorm";
|
||||
|
||||
export const cleanDataSource = async (
|
||||
dataSource: DataSource,
|
||||
entityNames: string[] = ["project", "user"]
|
||||
entityNames: string[] = ["task", "project", "user"]
|
||||
) => {
|
||||
if (process.env.NODE_ENV !== "test") {
|
||||
throw new Error(
|
||||
|
||||
@@ -291,6 +291,11 @@
|
||||
dependencies:
|
||||
"@jridgewell/trace-mapping" "0.3.9"
|
||||
|
||||
"@faker-js/faker@^7.3.0":
|
||||
version "7.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-7.3.0.tgz#a508df35ded585c4e071cb5d9d7c89623c837fae"
|
||||
integrity sha512-1W0PZezq2rxlAssoWemi9gFRD8IQxvf0FPL5Km3TOmGHFG7ib0TbFBJ0yC7D/1NsxunjNTK6WjUXV8ao/mKZ5w==
|
||||
|
||||
"@istanbuljs/load-nyc-config@^1.0.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
|
||||
|
||||
Reference in New Issue
Block a user