add project creation and listing
This commit is contained in:
5
server/.env
Normal file
5
server/.env
Normal file
@@ -0,0 +1,5 @@
|
||||
ENV=development
|
||||
PORT=5000
|
||||
SECRET=aE8efkEP8+V/ibEQl8IKbw==
|
||||
|
||||
DB_NAME=todoListDev
|
||||
@@ -1,2 +0,0 @@
|
||||
PORT=5000
|
||||
SECRET=aE8efkEP8+V/ibEQl8IKbw==
|
||||
5
server/.env.template
Normal file
5
server/.env.template
Normal file
@@ -0,0 +1,5 @@
|
||||
ENV=development
|
||||
PORT=5000
|
||||
SECRET=aE8efkEP8+V/ibEQl8IKbw==
|
||||
|
||||
DB_NAME=todoListDev
|
||||
5
server/.env.test.template
Normal file
5
server/.env.test.template
Normal file
@@ -0,0 +1,5 @@
|
||||
ENV=test
|
||||
PORT=5000
|
||||
SECRET=aE8efkEP8+V/ibEQl8IKbw==
|
||||
|
||||
DB_NAME=todoListTest
|
||||
3
server/.gitignore
vendored
3
server/.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
node_modules
|
||||
dist
|
||||
coverage
|
||||
.env
|
||||
.env.development
|
||||
.env.test
|
||||
@@ -4,10 +4,11 @@
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "node",
|
||||
clearMocks: true,
|
||||
collectCoverage: true,
|
||||
coverageDirectory: "coverage",
|
||||
coverageProvider: "v8",
|
||||
}
|
||||
setupFilesAfterEnv: ["<rootDir>/src/setupTests.ts"],
|
||||
};
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
"scripts": {
|
||||
"build": "npx tsc",
|
||||
"start": "node dist/index.js",
|
||||
"dev": "concurrently \"npx tsc --watch\" \"nodemon -q dist/index.js\"",
|
||||
"test": "jest"
|
||||
"dev": "rm -rf ./dist && concurrently \"npx tsc --watch\" \"nodemon -q dist/index.js\"",
|
||||
"test": "NODE_ENV=test jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"bcrypt": "^5.0.1",
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export { UserRoutes } from './users.controller'
|
||||
export { UserRoutes } from './user.controller'
|
||||
export { ProjectRoutes } from './project.controller'
|
||||
40
server/src/controller/project.controller.ts
Normal file
40
server/src/controller/project.controller.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Router } from "express";
|
||||
import { ProjectDto } from "../dto/poject.dto";
|
||||
import { ProjectService } from "../service/project.service";
|
||||
|
||||
const router = Router();
|
||||
|
||||
const BASE_PATH = "/projects";
|
||||
|
||||
export const getAllPath = BASE_PATH;
|
||||
router.get(getAllPath, async (req, res) => {
|
||||
const projects = await ProjectService.listAllByUserId(req.userId)
|
||||
|
||||
const response: ProjectDto[] = projects.map<ProjectDto>(project => ({
|
||||
name: project.name
|
||||
}))
|
||||
|
||||
res.json(response)
|
||||
});
|
||||
|
||||
export const createPath = BASE_PATH;
|
||||
router.post(createPath, (req, res) => {
|
||||
const { name } = req.body;
|
||||
|
||||
ProjectService.create({
|
||||
name,
|
||||
userId: req.userId
|
||||
}).then(project => {
|
||||
const respose: ProjectDto = {
|
||||
name: project.name
|
||||
}
|
||||
|
||||
res.json(respose);
|
||||
}).catch(err => {
|
||||
res.status(422).json({
|
||||
error: err.message
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
export const ProjectRoutes = router;
|
||||
4
server/src/dto/newProject.dto.ts
Normal file
4
server/src/dto/newProject.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export type NewProjectDto = {
|
||||
name: string
|
||||
userId: number
|
||||
}
|
||||
3
server/src/dto/poject.dto.ts
Normal file
3
server/src/dto/poject.dto.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export type ProjectDto = {
|
||||
name: string
|
||||
}
|
||||
4
server/src/dto/updateProject.dto.ts
Normal file
4
server/src/dto/updateProject.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export type UpdateProjectDto = {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
28
server/src/entity/__test__/project.entity.spec.ts
Normal file
28
server/src/entity/__test__/project.entity.spec.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { AppDataSource } from "../../infra/dataSource";
|
||||
import { projectRepository } from "../../repository/project.repository";
|
||||
import { userRepository } from "../../repository/user.repository";
|
||||
import { cleanDataSource } from "../../utils/cleanDataSource";
|
||||
|
||||
describe("Project", () => {
|
||||
beforeAll(async () => {
|
||||
await AppDataSource.initialize();
|
||||
await cleanDataSource(AppDataSource, ["project", "user"]);
|
||||
});
|
||||
|
||||
describe("relations", () => {
|
||||
it("should have many projects", async () => {
|
||||
const user = await userRepository.save({
|
||||
name: "John Doe",
|
||||
email: "john.doe@example.com",
|
||||
encryptedPassword: 'encryptedPassword'
|
||||
})
|
||||
|
||||
const project = await projectRepository.save({
|
||||
name: "My first project",
|
||||
user,
|
||||
})
|
||||
|
||||
expect(project.user.id).toBe(user.id)
|
||||
});
|
||||
});
|
||||
});
|
||||
45
server/src/entity/__test__/user.entity.spec.ts
Normal file
45
server/src/entity/__test__/user.entity.spec.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { AppDataSource } from "../../infra/dataSource";
|
||||
import { userRepository } from "../../repository/user.repository";
|
||||
import { cleanDataSource } from "../../utils/cleanDataSource";
|
||||
import { Project } from "../project.entity";
|
||||
import { User } from "../user.entity";
|
||||
|
||||
describe("User", () => {
|
||||
beforeAll(async () => {
|
||||
await AppDataSource.initialize();
|
||||
await cleanDataSource(AppDataSource, ["project", "user"]);
|
||||
});
|
||||
|
||||
describe("relations", () => {
|
||||
it("should have many projects", async () => {
|
||||
const user = new User();
|
||||
const user2 = new User();
|
||||
|
||||
Object.assign(user, {
|
||||
name: "John Doe",
|
||||
email: "john.doe@example.com",
|
||||
encryptedPassword: "encryptedPassword",
|
||||
});
|
||||
|
||||
Object.assign(user2, {
|
||||
name: "Luis Doe",
|
||||
email: "luis.doe@example.com",
|
||||
encryptedPassword: "encryptedPassword",
|
||||
});
|
||||
|
||||
const project1 = new Project();
|
||||
const proejct2 = new Project();
|
||||
|
||||
project1.name = "My first project";
|
||||
proejct2.name = "My favorite project";
|
||||
|
||||
user.projects = [project1, proejct2];
|
||||
|
||||
await userRepository.save(user);
|
||||
await userRepository.save(user2)
|
||||
|
||||
expect(user.projects).toHaveLength(2);
|
||||
expect(user2.projects).toBeUndefined()
|
||||
});
|
||||
});
|
||||
});
|
||||
18
server/src/entity/project.entity.ts
Normal file
18
server/src/entity/project.entity.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { IsNotEmpty } from "class-validator"
|
||||
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, ManyToOne, JoinColumn } from "typeorm"
|
||||
import { User } from "./user.entity"
|
||||
|
||||
@Entity()
|
||||
export class Project {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number
|
||||
|
||||
@Column()
|
||||
@IsNotEmpty()
|
||||
name: string
|
||||
|
||||
@ManyToOne((_type) => User, (user) => user.projects)
|
||||
@JoinColumn()
|
||||
@IsNotEmpty()
|
||||
user: User
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { IsEmail, IsNotEmpty } from "class-validator"
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
|
||||
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, AfterLoad } from "typeorm"
|
||||
import { Project } from "./project.entity"
|
||||
|
||||
@Entity()
|
||||
export class User {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number
|
||||
|
||||
@@ -14,4 +14,7 @@ export class User {
|
||||
|
||||
@Column()
|
||||
encryptedPassword: string
|
||||
|
||||
@OneToMany((_type) => Project, (item) => item.user)
|
||||
projects?: Project[]
|
||||
}
|
||||
|
||||
6
server/src/env.d.ts
vendored
Normal file
6
server/src/env.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
declare namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
DB_NAME?: string;
|
||||
NODE_ENV?: 'test' | 'development' | 'production';
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import "reflect-metadata"
|
||||
import * as dotenv from 'dotenv';
|
||||
import * as express from 'express';
|
||||
import { AppDataSource } from "./infra/dataSource";
|
||||
import { UserRoutes } from "./controller";
|
||||
import { ProjectRoutes, UserRoutes } from "./controller";
|
||||
import { RedisConnection } from "./infra/redis";
|
||||
import { sessionMiddleware } from "./middleware/session.middleware";
|
||||
|
||||
@@ -11,10 +11,10 @@ dotenv.config();
|
||||
const app = express();
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
app.use(sessionMiddleware)
|
||||
app.use(UserRoutes)
|
||||
|
||||
app.use(UserRoutes)
|
||||
app.use(ProjectRoutes)
|
||||
|
||||
const startApp = async () => {
|
||||
console.log('[redis]: connecting')
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import "reflect-metadata"
|
||||
import { DataSource } from "typeorm"
|
||||
import * as dotenv from 'dotenv';
|
||||
import "reflect-metadata";
|
||||
import { DataSource } from "typeorm";
|
||||
import { Project } from "../entity/project.entity";
|
||||
import { User } from "../entity/user.entity";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
export const AppDataSource = new DataSource({
|
||||
type: "postgres",
|
||||
host: "localhost",
|
||||
port: 5432,
|
||||
username: "joao",
|
||||
database: "todoListDev",
|
||||
database: process.env.DB_NAME,
|
||||
synchronize: true,
|
||||
logging: false,
|
||||
entities: [User],
|
||||
entities: [User, Project],
|
||||
migrations: [],
|
||||
subscribers: [],
|
||||
})
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { signInPath } from '../../controller/users.controller';
|
||||
import { signInPath, createPath } from '../../controller/users.controller';
|
||||
import { UNPROTECTED_ROUTES } from '../session.middleware'
|
||||
|
||||
describe('Unprotected Routes', () => {
|
||||
it('check content', () => {
|
||||
expect(UNPROTECTED_ROUTES.sort()).toEqual([signInPath].sort());
|
||||
expect(UNPROTECTED_ROUTES.sort()).toEqual([createPath, signInPath].sort());
|
||||
})
|
||||
})
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Handler, Request, Response } from 'express';
|
||||
import { verify } from 'jsonwebtoken';
|
||||
import { signInPath } from '../controller/users.controller';
|
||||
import { signInPath, createPath } from '../controller/user.controller';
|
||||
import { AuthService } from '../service/auth.service';
|
||||
|
||||
export const UNPROTECTED_ROUTES = [signInPath];
|
||||
export const UNPROTECTED_ROUTES = [signInPath, createPath];
|
||||
|
||||
export const sessionMiddleware: Handler = (req: Request, res: Response, next) => {
|
||||
const token = req.headers['x-access-token'];
|
||||
|
||||
4
server/src/repository/project.repository.ts
Normal file
4
server/src/repository/project.repository.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { Project } from "../entity/project.entity";
|
||||
import { AppDataSource } from "../infra/dataSource";
|
||||
|
||||
export const projectRepository = AppDataSource.getRepository(Project)
|
||||
34
server/src/service/project.service.ts
Normal file
34
server/src/service/project.service.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { validate } from "class-validator";
|
||||
import { NewProjectDto } from "../dto/newProject.dto";
|
||||
import { Project } from "../entity/project.entity";
|
||||
import { User } from "../entity/user.entity";
|
||||
import { projectRepository } from "../repository/project.repository";
|
||||
import { UserService } from "./user.service";
|
||||
|
||||
async function create(newProject: NewProjectDto): Promise<Project> {
|
||||
const project = new Project();
|
||||
const user = await UserService.findUserById(newProject.userId);
|
||||
|
||||
project.name = newProject.name;
|
||||
project.user = user
|
||||
|
||||
const errors = await validate(project);
|
||||
|
||||
if (errors.length) {
|
||||
throw new Error("Invalid project data");
|
||||
}
|
||||
|
||||
return projectRepository.save(project);
|
||||
}
|
||||
|
||||
async function listAllByUserId(userId: User["id"]): Promise<Project[]> {
|
||||
const query = projectRepository.createQueryBuilder();
|
||||
query.where('"userId" = :userId', { userId });
|
||||
|
||||
return query.getMany();
|
||||
}
|
||||
|
||||
export const ProjectService = {
|
||||
create,
|
||||
listAllByUserId,
|
||||
};
|
||||
@@ -8,14 +8,12 @@ async function create(newUserDto: NewUserDto): Promise<User> {
|
||||
const user = new User()
|
||||
user.email = newUserDto.email
|
||||
|
||||
|
||||
const errors = await validate(user);
|
||||
|
||||
if (errors.length > 0) {
|
||||
if (errors.length) {
|
||||
throw new Error("Invalid user data")
|
||||
}
|
||||
|
||||
|
||||
const result = await findByEmail(user.email)
|
||||
|
||||
if (result) {
|
||||
|
||||
5
server/src/setupTests.ts
Normal file
5
server/src/setupTests.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as dotenv from 'dotenv';
|
||||
|
||||
dotenv.config({
|
||||
path: '.env.test'
|
||||
});
|
||||
18
server/src/utils/cleanDataSource.ts
Normal file
18
server/src/utils/cleanDataSource.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { DataSource } from "typeorm";
|
||||
|
||||
export const cleanDataSource = async (
|
||||
dataSource: DataSource,
|
||||
entityNames: string[]
|
||||
) => {
|
||||
if (process.env.NODE_ENV !== "test") {
|
||||
throw new Error(
|
||||
`You tried to run a cleanDataSource into ${process.env.NODE_ENV} enviroment`
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
entityNames.map((tableName) => {
|
||||
return dataSource.query(`DELETE FROM "${tableName}";`);
|
||||
})
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user