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
|
node_modules
|
||||||
dist
|
dist
|
||||||
coverage
|
coverage
|
||||||
.env.development
|
.env
|
||||||
.env.test
|
.env.test
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
"uuid": "^8.3.2"
|
"uuid": "^8.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@faker-js/faker": "^7.3.0",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/jest": "^28.1.4",
|
"@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)
|
const projecToBeDeleted = userProjects.find(project => project.id === projectId)
|
||||||
|
|
||||||
if (projecToBeDeleted) {
|
if (projecToBeDeleted) {
|
||||||
const success = await ProjectService.destroyProject(projecToBeDeleted)
|
const success = await ProjectService.destroy(projecToBeDeleted)
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
res.json({ 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 { AppDataSource } from "../../infra/dataSource";
|
||||||
import { projectRepository } from "../../repository/project.repository";
|
import { projectRepository } from "../../repository/project.repository";
|
||||||
import { userRepository } from "../../repository/user.repository";
|
import { userRepository } from "../../repository/user.repository";
|
||||||
@@ -10,23 +11,23 @@ describe("Project", () => {
|
|||||||
});
|
});
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await cleanDataSource(AppDataSource);
|
await cleanDataSource(AppDataSource);
|
||||||
await AppDataSource.destroy()
|
await AppDataSource.destroy();
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("relations", () => {
|
describe("relations", () => {
|
||||||
it("should have many projects", async () => {
|
it("should belongs a one user", async () => {
|
||||||
const user = await userRepository.save({
|
const user = await userRepository.save({
|
||||||
name: "John Doe",
|
name: "John Doe",
|
||||||
email: "john.doe@example.com",
|
email: faker.internet.email(),
|
||||||
encryptedPassword: 'encryptedPassword'
|
encryptedPassword: "encryptedPassword",
|
||||||
})
|
});
|
||||||
|
|
||||||
const project = await projectRepository.save({
|
const project = await projectRepository.save({
|
||||||
name: "My first project",
|
name: "My first project",
|
||||||
user,
|
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 { AppDataSource } from "../../infra/dataSource";
|
||||||
import { userRepository } from "../../repository/user.repository";
|
import { userRepository } from "../../repository/user.repository";
|
||||||
import { cleanDataSource } from "../../utils/cleanDataSource";
|
import { cleanDataSource } from "../../utils/cleanDataSource";
|
||||||
@@ -11,8 +12,8 @@ describe("User", () => {
|
|||||||
});
|
});
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await cleanDataSource(AppDataSource);
|
await cleanDataSource(AppDataSource);
|
||||||
await AppDataSource.destroy()
|
await AppDataSource.destroy();
|
||||||
})
|
});
|
||||||
|
|
||||||
describe("relations", () => {
|
describe("relations", () => {
|
||||||
it("should have many projects", async () => {
|
it("should have many projects", async () => {
|
||||||
@@ -21,13 +22,13 @@ describe("User", () => {
|
|||||||
|
|
||||||
Object.assign(user, {
|
Object.assign(user, {
|
||||||
name: "John Doe",
|
name: "John Doe",
|
||||||
email: "john.doe@example.com",
|
email: faker.internet.email(),
|
||||||
encryptedPassword: "encryptedPassword",
|
encryptedPassword: "encryptedPassword",
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.assign(user2, {
|
Object.assign(user2, {
|
||||||
name: "Luis Doe",
|
name: "Luis Doe",
|
||||||
email: "luis.doe@example.com",
|
email: faker.internet.email(),
|
||||||
encryptedPassword: "encryptedPassword",
|
encryptedPassword: "encryptedPassword",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -40,10 +41,10 @@ describe("User", () => {
|
|||||||
user.projects = [project1, proejct2];
|
user.projects = [project1, proejct2];
|
||||||
|
|
||||||
await userRepository.save(user);
|
await userRepository.save(user);
|
||||||
await userRepository.save(user2)
|
await userRepository.save(user2);
|
||||||
|
|
||||||
expect(user.projects).toHaveLength(2);
|
expect(user.projects).toHaveLength(2);
|
||||||
expect(user2.projects).toBeUndefined()
|
expect(user2.projects).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,18 +1,24 @@
|
|||||||
import { IsNotEmpty } from "class-validator"
|
import { IsNotEmpty } from "class-validator";
|
||||||
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, ManyToOne, JoinColumn } from "typeorm"
|
import {
|
||||||
import { User } from "./user.entity"
|
Column, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn
|
||||||
|
} from "typeorm";
|
||||||
|
import { Task } from "./task.entity";
|
||||||
|
import { User } from "./user.entity";
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Project {
|
export class Project {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id: number
|
id: number;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
name: string
|
name: string;
|
||||||
|
|
||||||
@ManyToOne((_type) => User, (user) => user.projects)
|
@ManyToOne((_type) => User, (user) => user.projects)
|
||||||
@JoinColumn()
|
@JoinColumn()
|
||||||
@IsNotEmpty()
|
@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 { IsEmail, IsNotEmpty } from "class-validator";
|
||||||
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, AfterLoad } from "typeorm"
|
import {
|
||||||
import { Project } from "./project.entity"
|
Column, Entity, OneToMany, PrimaryGeneratedColumn
|
||||||
|
} from "typeorm";
|
||||||
|
import { Project } from "./project.entity";
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class User {
|
export class User {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id: number
|
id: number;
|
||||||
|
|
||||||
@Column({ unique: true })
|
@Column({ unique: true })
|
||||||
@IsEmail()
|
@IsEmail()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
email: string
|
email: string;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
encryptedPassword: string
|
encryptedPassword: string;
|
||||||
|
|
||||||
@OneToMany((_type) => Project, (item) => item.user)
|
@OneToMany((_type) => Project, (item) => item.user)
|
||||||
projects?: Project[]
|
projects?: Project[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import * as dotenv from 'dotenv';
|
|||||||
import "reflect-metadata";
|
import "reflect-metadata";
|
||||||
import { DataSource } from "typeorm";
|
import { DataSource } from "typeorm";
|
||||||
import { Project } from "../entity/project.entity";
|
import { Project } from "../entity/project.entity";
|
||||||
|
import { Task } from '../entity/task.entity';
|
||||||
import { User } from "../entity/user.entity";
|
import { User } from "../entity/user.entity";
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
@@ -14,7 +15,7 @@ export const AppDataSource = new DataSource({
|
|||||||
database: process.env.DB_NAME,
|
database: process.env.DB_NAME,
|
||||||
synchronize: true,
|
synchronize: true,
|
||||||
logging: false,
|
logging: false,
|
||||||
entities: [User, Project],
|
entities: [User, Project, Task],
|
||||||
migrations: [],
|
migrations: [],
|
||||||
subscribers: [],
|
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();
|
return query.getMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function destroyProject(project: Project): Promise<boolean>{
|
async function destroy(project: Project): Promise<boolean>{
|
||||||
const query = projectRepository.createQueryBuilder();
|
const query = projectRepository.createQueryBuilder();
|
||||||
query.where('"id" = :projectId', { projectId: project.id });
|
query.where('"id" = :projectId', { projectId: project.id });
|
||||||
|
|
||||||
@@ -40,5 +40,5 @@ async function destroyProject(project: Project): Promise<boolean>{
|
|||||||
export const ProjectService = {
|
export const ProjectService = {
|
||||||
create,
|
create,
|
||||||
listAllByUserId,
|
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 (
|
export const cleanDataSource = async (
|
||||||
dataSource: DataSource,
|
dataSource: DataSource,
|
||||||
entityNames: string[] = ["project", "user"]
|
entityNames: string[] = ["task", "project", "user"]
|
||||||
) => {
|
) => {
|
||||||
if (process.env.NODE_ENV !== "test") {
|
if (process.env.NODE_ENV !== "test") {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
@@ -291,6 +291,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@jridgewell/trace-mapping" "0.3.9"
|
"@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":
|
"@istanbuljs/load-nyc-config@^1.0.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
|
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
|
||||||
|
|||||||
Reference in New Issue
Block a user