Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
599601c
docs: add database model
May 21, 2022
e03f302
feat(database): add DDL script
May 21, 2022
ba06245
feat(api): init back-end with typescript
May 21, 2022
0c39a0b
feat(database): create database connection using typeorm
May 21, 2022
0e0f506
feat(login): add login endpoint
May 22, 2022
c161f01
feat(jest): add jest config
May 22, 2022
3903b78
fix(user): add Object.assign on User entity constructor
May 22, 2022
d2e8873
feat(login): add unit tests for LoginUserService
May 22, 2022
7744f85
fix(database): change type on typeorm connection config
May 22, 2022
28d1a79
feat(teacher): add list teachers endpoint
May 22, 2022
dbbe258
feat(teacher): add unit test for ListTeachersService
May 22, 2022
34450e4
fix(teacher): correct typo on ListTeachersService
May 22, 2022
57f3078
feat(classroom): add list classrooms endpoint
May 22, 2022
12bec02
feat(classroom): add unit test for ListClassroomsService
May 22, 2022
4f75142
feat(teacher): add findByIds method on teachers repository
May 22, 2022
3449fc3
feat(classroom): add findByIds method on classrooms repository
May 22, 2022
1c654a9
feat(course): add create course endpoint
May 22, 2022
e197a83
fix(course): add empty array guard clause on CreateCourseService
May 22, 2022
8dd420e
feat(teacher): add findByIds method on unity test stub repository
May 23, 2022
dba64f9
feat(classroom): add findByIds method on unity test stub repository
May 23, 2022
95abbf3
feat(course): add unit tests for CreateCourseService
May 23, 2022
0f71ede
feat(course): add list courses endpoint
May 23, 2022
1b7a800
refactor(unit test): extract test stubs to repositories/stubs
May 23, 2022
8e62981
fix(database): add 'on cascade delete' on junction tables
May 23, 2022
bdbe2bb
feat(course): add delete course endpoint
May 23, 2022
debd558
feat(teacher) add find by ids service
May 23, 2022
250f216
feat(classroom) add find by ids service
May 23, 2022
4968531
feat(course): add edit course endpoint
May 23, 2022
45e6a86
feat(course): add update on repository stub
May 23, 2022
55ee65d
feat(api): add cors on app
May 23, 2022
fd423d2
feat(front-end): create initial project
May 23, 2022
df6d77a
feat(login): add login page for mobile and desktop
May 23, 2022
b96f6fa
feat(login): add login validation
May 23, 2022
fc8cea6
refactor(styles): extract background color to body css
May 23, 2022
ad3489a
feat(courses): add courses list page
May 23, 2022
8facf09
feat(courses): add integration with api to list the courses"
May 23, 2022
c0290cb
feat(course): add delete function on list courses page
May 23, 2022
68aa5f1
feat(course): add create course page and functionality
May 24, 2022
c077b85
feat(api): add find course by id endpoint
May 24, 2022
7ff05a0
feat(course): add edit course funcionality
May 24, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions back-end/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#Database
DATABASE_TYPE=
DATABASE_HOST=
DATABASE_PORT=
DATABASE_NAME=
DATABASE_USERNAME=
DATABASE_PASSWORD=
2 changes: 2 additions & 0 deletions back-end/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
.env
6 changes: 6 additions & 0 deletions back-end/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
clearMocks: true,
coverageProvider: "v8",
preset: "ts-jest",
testMatch: ["**/**/*.spec.ts"],
};
27 changes: 27 additions & 0 deletions back-end/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "back-end",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"dev": "ts-node-dev --transpile-only --respawn --ignore-watch node_modules src/server.ts",
"test": "jest"
},
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.0.1",
"express": "^4.18.1",
"pg": "^8.7.3",
"reflect-metadata": "^0.1.13",
"typeorm": "^0.3.6"
},
"devDependencies": {
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
"@types/jest": "^27.5.1",
"jest": "^28.1.0",
"ts-jest": "^28.0.2",
"ts-node-dev": "^1.1.8",
"typescript": "^4.6.4"
}
}
15 changes: 15 additions & 0 deletions back-end/src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import express from "express";
import cors from "cors";
import dotenv from "dotenv";
import "reflect-metadata";
import "./database";
import { routes } from "./routes";

dotenv.config();
const app = express();

app.use(cors());
app.use(express.json());
app.use(routes);

export { app };
22 changes: 22 additions & 0 deletions back-end/src/database/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { DataSource } from "typeorm"
import 'dotenv/config';

const AppDataSource = new DataSource({
type: process.env.DATABASE_TYPE as any,
host: process.env.DATABASE_HOST,
port: parseInt(process.env.DATABASE_PORT),
username: process.env.DATABASE_USERNAME,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
entities: [__dirname + '/../entities/*.ts']
});

AppDataSource.initialize()
.then(() => {
console.log("Data Source has been initialized!")
})
.catch((err) => {
console.error("Error during Data Source initialization", err)
});

export { AppDataSource };
19 changes: 19 additions & 0 deletions back-end/src/entities/Classroom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Entity, Column, PrimaryColumn } from "typeorm";
import { v4 as uuid } from "uuid";

@Entity('classrooms')
export class Classroom {
@PrimaryColumn()
id: string;

@Column()
number: string;

constructor(props: Omit<Classroom, 'id'>, id?: string) {
Object.assign(this, props);

if (!this.id) {
this.id = uuid();
}
}
}
61 changes: 61 additions & 0 deletions back-end/src/entities/Course.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Entity, Column, PrimaryColumn, ManyToMany, JoinTable } from "typeorm";
import { Classroom } from "./Classroom";
import { Teacher } from "./Teacher";
import { v4 as uuid } from "uuid";

@Entity('courses')
export class Course {
@PrimaryColumn()
id: string;

@Column()
name: string;

@Column()
start_time: string

@Column()
end_time: string

@ManyToMany(() => Teacher, {
cascade: true,
onDelete: 'CASCADE'
})
@JoinTable({
name: 'courses_teachers',
joinColumn: {
name: "course_id",
referencedColumnName: "id"
},
inverseJoinColumn: {
name: "teacher_id",
referencedColumnName: "id"
}
})
teachers: Teacher[]

@ManyToMany(() => Classroom, {
cascade: true,
onDelete: 'CASCADE'
})
@JoinTable({
name: 'courses_classrooms',
joinColumn: {
name: "course_id",
referencedColumnName: "id"
},
inverseJoinColumn: {
name: "classroom_id",
referencedColumnName: "id"
}
})
classrooms: Classroom[]

constructor(props: Omit<Course, 'id'>, id?: string) {
Object.assign(this, props);

if (!this.id) {
this.id = uuid();
}
}
}
19 changes: 19 additions & 0 deletions back-end/src/entities/Teacher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Entity, Column, PrimaryColumn } from "typeorm";
import { v4 as uuid } from "uuid";

@Entity('teachers')
export class Teacher {
@PrimaryColumn()
id: string;

@Column()
name: string;

constructor(props: Omit<Teacher, 'id'>, id?: string) {
Object.assign(this, props);

if (!this.id) {
this.id = uuid();
}
}
}
22 changes: 22 additions & 0 deletions back-end/src/entities/User.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Entity, Column, PrimaryColumn } from "typeorm";
import { v4 as uuid } from "uuid";

@Entity('users')
export class User {
@PrimaryColumn()
id: string;

@Column()
email: string;

@Column()
password: string;

constructor(props: Omit<User, 'id'>, id?: string) {
Object.assign(this, props);

if (!this.id) {
this.id = uuid();
}
}
}
19 changes: 19 additions & 0 deletions back-end/src/modules/createCourse/CreateCourseController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Request, Response } from 'express';
import { CreateCourseService } from './CreateCourseService';
import { ICreateCourseDTO } from './CreateCourseDTO';

export class CreateCourseController {
constructor(private createCourseService: CreateCourseService) {}

async handle (request: Request, response: Response) : Promise<Response> {
const data: ICreateCourseDTO = request.body;

try {
const course = await this.createCourseService.execute(data);

return response.status(201).json(course);
} catch (err) {
return response.status(400).json({ message: err.message || 'Unexpected error.'})
}
}
}
7 changes: 7 additions & 0 deletions back-end/src/modules/createCourse/CreateCourseDTO.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface ICreateCourseDTO {
name: string;
start_time: string;
end_time: string;
teacher_ids: string[];
classroom_ids: string[];
}
11 changes: 11 additions & 0 deletions back-end/src/modules/createCourse/CreateCourseFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { PostgresCoursesRepository } from "../../repositories/implementations/PostgresCoursesRepository";
import { CreateCourseService } from "./CreateCourseService";
import { CreateCourseController } from "./CreateCourseController";

const postgresCoursesRepository = new PostgresCoursesRepository();

const createCourseService = new CreateCourseService(postgresCoursesRepository);

const createCourseController = new CreateCourseController(createCourseService);

export { createCourseService, createCourseController };
49 changes: 49 additions & 0 deletions back-end/src/modules/createCourse/CreateCourseService.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { CreateCourseService } from "./CreateCourseService";
import { CoursesRepositoryStub } from "../../repositories/stub/CoursesRepositoryStub";


describe("Create course", () => {
let sut: CreateCourseService;

beforeAll(() => {
sut = new CreateCourseService(new CoursesRepositoryStub());
});

it("should create a new course", async () => {
const courseData = {
name: "test course",
start_time: "10:00:00",
end_time: "12:00:00",
teacher_ids: ["test_id"],
classroom_ids: ["test_id"]
}

const course = await sut.execute(courseData);

expect(course).toHaveProperty('id');
});

it("should not create a course without at least one teacher", async () => {
const courseData = {
name: "test course",
start_time: "10:00:00",
end_time: "12:00:00",
teacher_ids: [],
classroom_ids: ["test_id"]
}

await expect(sut.execute(courseData)).rejects.toEqual(new Error('A course must have at least one teacher'));
});

it("should not create a course without at least one classroom", async () => {
const courseData = {
name: "test course",
start_time: "10:00:00",
end_time: "12:00:00",
teacher_ids: ["test_id"],
classroom_ids: []
}

await expect(sut.execute(courseData)).rejects.toEqual(new Error('A course must have at least one classroom'));
});
});
18 changes: 18 additions & 0 deletions back-end/src/modules/createCourse/CreateCourseService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ICoursesRepository } from "../../repositories/ICoursesRepository";
import { ICreateCourseDTO } from "./CreateCourseDTO";

export class CreateCourseService {
constructor(private coursesRepository: ICoursesRepository) {}

async execute(data: ICreateCourseDTO) {
if (!data.teacher_ids || data.teacher_ids.length === 0) {
throw new Error('A course must have at least one teacher');
}

if (!data.classroom_ids || data.classroom_ids.length === 0) {
throw new Error('A course must have at least one classroom');
}

return await this.coursesRepository.save(data);
}
}
18 changes: 18 additions & 0 deletions back-end/src/modules/deleteCourse/DeleteCourseController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Request, Response } from "express";
import { DeleteCourseService } from "./DeleteCourseService";

export class DeleteCourseController {
constructor(private deleteCourseService: DeleteCourseService) {}

async handle (request: Request, response: Response) {
try {
const { id } = request.params;

await this.deleteCourseService.execute(id);

return response.status(200).json({ message: `Course ${id} deleted!`});
} catch (err) {
return response.status(400).json({ message: err.message || 'Unexpected error.'})
}
}
}
11 changes: 11 additions & 0 deletions back-end/src/modules/deleteCourse/DeleteCourseFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { PostgresCoursesRepository } from "../../repositories/implementations/PostgresCoursesRepository";
import { DeleteCourseService } from "./DeleteCourseService";
import { DeleteCourseController } from "./DeleteCourseController";

const postgresCoursesRepository = new PostgresCoursesRepository();

const deleteCourseService = new DeleteCourseService(postgresCoursesRepository);

const deleteCourseController = new DeleteCourseController(deleteCourseService);

export { deleteCourseService, deleteCourseController};
9 changes: 9 additions & 0 deletions back-end/src/modules/deleteCourse/DeleteCourseService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ICoursesRepository } from "../../repositories/ICoursesRepository";

export class DeleteCourseService {
constructor(private coursesRepository: ICoursesRepository) {}

async execute (id: string): Promise<void> {
await this.coursesRepository.delete(id);
}
}
20 changes: 20 additions & 0 deletions back-end/src/modules/editCourse/EditCourseController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Request, Response } from "express";
import { EditCourseService } from "./EditCourseService";
import { IEditCourseDTO } from "./EditCourseDTO";

export class EditCourseController {
constructor(private editCourseService: EditCourseService) {}

async handle (request: Request, response: Response) {
const { id } = request.params;
const data: IEditCourseDTO = request.body;

try {
const course = await this.editCourseService.execute(id, data);

return response.status(200).json(course);
} catch (err) {
return response.status(400).json({ message: err.message || 'Unexpected error.'})
}
}
}
Loading