This commit is contained in:
João Geonizeli
2022-07-09 14:28:36 -03:00
parent 97dac843fe
commit 82c078f5db
17 changed files with 200 additions and 50 deletions

View File

@@ -1,5 +0,0 @@
ENV=development
PORT=5000
SECRET=aE8efkEP8+V/ibEQl8IKbw==
DB_NAME=todoListDev

2
server/.gitignore vendored
View File

@@ -1,5 +1,5 @@
node_modules
dist
coverage
.env.development
.env
.env.test

View File

@@ -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",

View File

@@ -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 })

View File

@@ -0,0 +1,3 @@
export type NewTaskDto = {
description: string;
}

View File

@@ -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);
});
});
});

View 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);
});
});
});

View File

@@ -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();
});
});
});

View File

@@ -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
@PrimaryGeneratedColumn()
id: number;
@Column()
@IsNotEmpty()
name: string
@Column()
@IsNotEmpty()
name: string;
@ManyToOne((_type) => User, (user) => user.projects)
@JoinColumn()
@IsNotEmpty()
user: User
@ManyToOne((_type) => User, (user) => user.projects)
@JoinColumn()
@IsNotEmpty()
user: User;
@OneToMany((_type) => Task, (item) => item.project)
tasks?: Task[];
}

View 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;
}

View File

@@ -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
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
@IsEmail()
@IsNotEmpty()
email: string
@Column({ unique: true })
@IsEmail()
@IsNotEmpty()
email: string;
@Column()
encryptedPassword: string
@Column()
encryptedPassword: string;
@OneToMany((_type) => Project, (item) => item.user)
projects?: Project[]
@OneToMany((_type) => Project, (item) => item.user)
projects?: Project[];
}

View File

@@ -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: [],
})

View File

@@ -0,0 +1,4 @@
import { Task } from "../entity/task.entity";
import { AppDataSource } from "../infra/dataSource";
export const taskRepository = AppDataSource.getRepository(Task)

View File

@@ -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
};

View 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,
};

View File

@@ -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(

View File

@@ -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"