diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7574353 --- /dev/null +++ b/.gitignore @@ -0,0 +1,128 @@ +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ +package-lock.json +build/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test +.env*.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Storybook build outputs +.out +.storybook-out +storybook-static + +# rollup.js default build output +dist/ + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# Temporary folders +tmp/ +temp/ +build/ \ No newline at end of file diff --git a/README.md b/README.md index 978a24d..e5745f3 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,65 @@ -## LabenuSystem: - -Você estuda na Labenu_ há tanto tempo que já parecem anos, não é? Então, hoje, vamos pedir para criar um sistema que represente o básico da nossa organização. - -Ele deve possuir, ao menos, as 3 entidades importantes: - -1. Estudantes - - Representa estudantes da nossa instituição. Eles devem possuir: id, nome, email, data de nascimento e os principais hobbies dele. - -2. Docente - - Representa docentes da nossa instituição. Eles devem possuir: id, nome, email, data de nascimento e todas as especialidades dele. Há 7 especialidades: React, Redux, CSS, Testes, Typescript, Programação Orientada a Objetos e Backend - -3. Turma - - Toda turma é composta das seguintes características: id, nome, data de início, data de término, lista de professores responsáveis, uma lista de alunos e módulo atual em que a turma está. - - O módulo pode assumir os valores de 1 a 7 ou `undefined`, indicando que as aulas dessa turma ainda não começaram. Para esse exercício, vamos considerar que existam dois tipos de turma: integral ou noturna. Há uma restrição para o nome das turmas noturnas: tem que terminar com `-na-night`. - -As funcionalidades básicas são: - -→ Criar estudante; - -→ Criar docente; - -→ Criar turma; - -→ Adicionar estudante na turma; - -→ Adicionar docente na turma; - -→ Pegar a idade de algum estudante a partir do id +# LabenuSystem + +## :memo: Funcionalidades +* Criar estudante +* Criar docente +* Criar turma +* Adicionar estudante a turma +* Adicionar docente a turma +* Pegar a idade de algum estudante a partir do ID +* Exibir estudantes de uma turma +* Exibir docentes de uma turma +* Exibir estudantes que possuam o mesmo hobby +* Remover estudante de uma turma +* Remover estudante +* Remover docente de uma turma +* Mudar turma de módulo + +## 💻 Documentação +Teste as rotas com a documentação, basta importar ela para seu postman. + +[Postman - LabenuSystem](https://documenter.getpostman.com/view/14145831/TzCMeTwk#a0cffc9e-31bf-48a6-94c5-573cdfa24464) + +## ✨ Tecnologias +Esse projeto foi desenvolvido com as seguintes tecnologias: + +* Node +* Express +* Typescript +* Cors +* Knex +* MySQL +* Dotenv + +## 🚀 Como executar +* Clone o repositório +``` +$https://github.com/future4code/epps-labenu-system6.git + ``` +* Instale as dependências com +``` +npm install + ``` +* Crie um arquivo .env +``` +touch .env + ``` +* Preencha o arquivo .env +``` +DB_HOST = Coloque aqui seu endereço do banco de dados +DB_USER = Coloque aqui seu usuário +DB_PASSWORD = Coloque aqui sua senha +DB_SCHEMA = Coloque aqui o nome do banco de dados + ``` +* Crie as tabelas com + ``` +npm run table + ``` +* Inicie o servidor com + ``` +npm run dev + ``` + + ## Desenvolvido por: + - [José Victor](https://www.linkedin.com/in/jose-victor-tf/) + - [Daniel Ratti](https://www.linkedin.com/in/daniel-ratti-b81721208/) diff --git a/package.json b/package.json new file mode 100644 index 0000000..f323fec --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "epps-labenu-system6", + "version": "1.0.0", + "description": "Você estuda na Labenu_ há tanto tempo que já parecem anos, não é? Então, hoje, vamos pedir para criar um sistema que represente o básico da nossa organização.", + "main": "index.js", + "scripts": { + "start": "tsc && node ./build/index.js", + "table": "tsc && node ./build/database/migrations/createTables.js", + "dev": "ts-node-dev ./src/server.ts", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/future4code/epps-labenu-system6.git" + }, + "keywords": [], + "author": "José Victor, Daniel Ratti", + "license": "ISC", + "bugs": { + "url": "https://github.com/future4code/epps-labenu-system6/issues" + }, + "homepage": "https://github.com/future4code/epps-labenu-system6#readme", + "devDependencies": { + "@types/cors": "^2.8.10", + "@types/express": "^4.17.11", + "ts-node-dev": "^1.1.6", + "typescript": "^4.2.3" + }, + "dependencies": { + "@types/knex": "^0.16.1", + "cors": "^2.8.5", + "dotenv": "^8.2.0", + "express": "^4.17.1", + "knex": "^0.95.4", + "mysql": "^2.18.1" + } +} diff --git a/src/controllers/ClassController.ts b/src/controllers/ClassController.ts new file mode 100644 index 0000000..8010c79 --- /dev/null +++ b/src/controllers/ClassController.ts @@ -0,0 +1,67 @@ +import { Request, Response } from "express"; +import { changeClassModule } from "../models/changeClassModule"; +import { insertClass } from "../models/insertClass"; +import { ClassInfo } from "../types/class"; +import { checkDate, formatDate } from "../utilities/verifiers"; + +class ClassController { + async create(req: Request, res: Response) { + let errorCode: number = 404; + try { + const { + name, + start_date, + end_date, + module, + type, + } = req.body as ClassInfo; + const checkingStartDate = checkDate(start_date); + const checkingEndDate = checkDate(end_date); + let editName = name; + if (type === "Noturna") { + editName = (name as string) + "-na-night"; + } + if (!name || !module || !type) { + errorCode = 422; + throw new Error( + "Preencha corretamente os campos de name, start_date, end_date, module e type!" + ); + } + if (!checkingStartDate) { + errorCode = 406; + throw new Error("Coloque a data de inicio no formato DD/MM/YYYY"); + } + if (!checkingEndDate) { + errorCode = 406; + throw new Error("Coloque a data final no formato DD/MM/YYYY"); + } + req.body.start_date = formatDate(start_date); + req.body.end_date = formatDate(end_date); + await insertClass(req.body); + res.status(201).send({ message: "Turma criada com sucesso." }); + } catch (error) { + res.status(errorCode).send({ message: error.message }); + } + } + + async update(req: Request, res: Response) { + let errorCode: number = 404; + try { + const { id, module } = req.body; + if (!id || !module) { + errorCode = 422; + throw new Error( + "Preencha os campos do ID da turma e o número do módulo(1 a 7) para realizar a alteração." + ); + } + (await changeClassModule(id, module)) as string; + res + .status(200) + .send({ message: "O módulo da turma foi alterado com sucesso!" }); + } catch (error) { + res.status(errorCode).send({ message: error.message }); + } + } +} + +export { ClassController }; diff --git a/src/controllers/ClassInfoController.ts b/src/controllers/ClassInfoController.ts new file mode 100644 index 0000000..0cb37e8 --- /dev/null +++ b/src/controllers/ClassInfoController.ts @@ -0,0 +1,25 @@ +import { Request, Response } from "express"; +import { selectStudentByClass } from "../models/selectStudentByClass"; + +class ClassInfoController { + async show(req: Request, res: Response) { + let errorCode: number = 400; + try { + const { id } = req.params; + if (!id) { + errorCode = 404; + throw new Error("Insira um ID válido."); + } + const result = await selectStudentByClass(id); + if (!result.length) { + errorCode = 404; + throw new Error("Nenhum estudante está na turma!"); + } + res.status(200).send({ message: result }); + } catch (error) { + res.status(errorCode).send({ message: error.message }); + } + } +} + +export { ClassInfoController }; diff --git a/src/controllers/StudentController.ts b/src/controllers/StudentController.ts new file mode 100644 index 0000000..8bfde87 --- /dev/null +++ b/src/controllers/StudentController.ts @@ -0,0 +1,101 @@ +import { removeStudentFromClass } from "./../models/removeStudentFromClass"; +import { formatDate } from "./../utilities/verifiers"; +import { insertStudent } from "./../models/insertStudent"; +import { Request, Response } from "express"; +import { checkDate } from "../utilities/verifiers"; +import { selectStudentAgeById } from "../models/selectStudentAgeById"; +import { insertStudentInClass } from "../models/insertStudentInClass"; +import { deleteStudent } from "../models/deleteStudent"; +import { Student } from "../types/student"; + +class StudentController { + async create(req: Request, res: Response) { + let errorCode: number = 400; + try { + const { name, email, birthdate, hobby } = req.body as Student; + + if (!name || !email || !birthdate || !hobby) { + errorCode = 422; + throw new Error( + "Preencha todas as informações necessarias para adicionar um estudante !" + ); + } + + const checkingDate = checkDate(birthdate); + if (!checkingDate) { + errorCode = 406; + throw new Error("Coloque uma data formato DD/MM/YYYY"); + } + req.body.birthdate = formatDate(birthdate); + await insertStudent(req.body); + res.status(201).send({ + message: `Estudante ${name} adicionado a turma!`, + }); + } catch (error) { + res.status(errorCode).send({ message: error.message }); + } + } + + async execute(req: Request, res: Response) { + let errorCode: number = 400; + try { + const { class_id, student_id } = req.body; + if (!class_id || !student_id) { + errorCode = 422; + throw new Error("Preencha os campos do ID da class e do ID do aluno!"); + } + await insertStudentInClass(class_id, student_id); + res.status(200).send({ message: "O aluno foi inserido na turma" }); + } catch (error) { + res.status(errorCode).send({ message: error.message }); + } + } + + async show(req: Request, res: Response) { + let errorCode: number = 400; + try { + const id = req.params.id; + if (!id) { + errorCode = 422; + throw new Error("Id não existe!"); + } + const result = (await selectStudentAgeById(id)) as string; + res.status(200).send({ message: result }); + } catch (error) { + console.log(error); + res.status(errorCode).send({ message: error.message }); + } + } + + async update(req: Request, res: Response) { + let errorCode: number = 400; + try { + const id = req.params.id; + if (!id) { + errorCode = 422; + throw new Error("Informe o ID do estudante!"); + } + await removeStudentFromClass(id); + res.status(200).send({ message: "O aluno foi removido da turma" }); + } catch (error) { + res.status(errorCode).send({ message: error.message }); + } + } + + async delete(req: Request, res: Response) { + let errorCode: number = 400; + try { + const id = req.params.id; + if (!id) { + errorCode = 422; + throw new Error("Informe o ID do estudante!"); + } + await deleteStudent(id); + res.status(200).send({ message: "O aluno foi removido do sistema." }); + } catch (error) { + res.status(errorCode).send({ message: error.message }); + } + } +} + +export { StudentController }; diff --git a/src/controllers/StudentHobbyController.ts b/src/controllers/StudentHobbyController.ts new file mode 100644 index 0000000..4aa4787 --- /dev/null +++ b/src/controllers/StudentHobbyController.ts @@ -0,0 +1,28 @@ +import { selectStudentByHobby } from "./../models/selectStudentByHobby"; +import { Request, Response } from "express"; + +class StudentHobbyController { + async show(req: Request, res: Response) { + let errorCode: number = 400; + try { + const hobby = req.query.hobby as string; + + if (!hobby) { + errorCode = 404; + throw new Error("Por favor digite um hobby!"); + } + const result = await selectStudentByHobby(hobby); + if (!result.length) { + errorCode = 404; + throw new Error( + "Não existe nenhum estudante relacionado a este hobby!" + ); + } + res.status(200).send({ message: result }); + } catch (error) { + res.status(errorCode).send({ message: error.message }); + } + } +} + +export { StudentHobbyController }; diff --git a/src/controllers/TeacherController.ts b/src/controllers/TeacherController.ts new file mode 100644 index 0000000..66e4fa9 --- /dev/null +++ b/src/controllers/TeacherController.ts @@ -0,0 +1,87 @@ +import { Request, Response } from "express"; +import { insertTeacher } from "../models/insertTeacher"; +import { insertTeacherInClass } from "../models/insertTeacherInClass"; +import { removeTeacher } from "../models/removeTeacher"; +import { selectTeachersFromClass } from "../models/selectTeachersFromClass"; +import { Teacher } from "../types/teacher"; +import { checkDate, formatDate } from "../utilities/verifiers"; + +class TeacherController { + async create(req: Request, res: Response) { + let errorCode: number = 400; + try { + const { name, email, birthdate, speciality } = req.body as Teacher; + const checkingDate = checkDate(birthdate); + if (!name || !email || !birthdate || !speciality) { + errorCode = 422; + throw new Error( + "Por favor preencha todas as informações, nome, email, data de nascimento e especialidade." + ); + } + if (!checkingDate) { + errorCode = 406; + throw new Error("Coloque uma data formato DD/MM/YYYY"); + } + req.body.birthdate = formatDate(birthdate); + await insertTeacher(req.body); + res.status(201).send({ message: "Docente criado com sucesso." }); + } catch (error) { + res.status(errorCode).send({ message: error.message }); + } + } + + async execute(req: Request, res: Response) { + let errorCode: number = 400; + try { + const { class_id, teacher_id } = req.body; + if (!class_id || !teacher_id) { + errorCode = 422; + throw new Error( + "Preencha os campos do ID da class e do ID do docente para prosseguir." + ); + } + (await insertTeacherInClass(class_id, teacher_id)) as string; + res.status(200).send({ message: "O professor foi inserido na turma" }); + } catch (error) { + res.status(errorCode).send({ message: error.message }); + } + } + + async show(req: Request, res: Response) { + let errorCode: number = 400; + try { + const { id } = req.params; + if (!id) { + errorCode = 404; + throw new Error("Insira um ID válido."); + } + const result = await selectTeachersFromClass(id); + if (!result.length) { + errorCode = 404; + throw new Error("Nenhum docente está na turma!"); + } + res.status(200).send({ message: result }); + } catch (error) { + res.status(errorCode).send({ message: error.message }); + } + } + + async update(req: Request, res: Response) { + let errorCode: number = 400; + try { + const { id } = req.params; + if (!id) { + errorCode = 404; + throw new Error( + "Insira um ID válido para a exclusão do professor da turma." + ); + } + await removeTeacher(id); + res.status(200).send({ message: "O professor foi removido da turma." }); + } catch (error) { + res.status(errorCode).send({ message: error.message }); + } + } +} + +export { TeacherController }; diff --git a/src/database/connection.ts b/src/database/connection.ts new file mode 100644 index 0000000..7133655 --- /dev/null +++ b/src/database/connection.ts @@ -0,0 +1,18 @@ +import knex from "knex"; +import dotenv from "dotenv"; + +dotenv.config(); + +export const connection = knex({ + client: "mysql", + connection: { + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_SCHEMA, + port: 3306, + multipleStatements: true, + }, +}); + +export default connection; diff --git a/src/database/migrations/createTables.ts b/src/database/migrations/createTables.ts new file mode 100644 index 0000000..f3ff3bd --- /dev/null +++ b/src/database/migrations/createTables.ts @@ -0,0 +1,44 @@ +import connection from "../connection"; + +const createTables = async (): Promise => { + try { + await connection.raw(` + CREATE TABLE Class( + id VARCHAR(255) PRIMARY KEY NOT NULL, + name VARCHAR(50) NOT NULL, + start_date DATE NOT NULL, + end_date DATE NOT NULL, + module ENUM ('1', '2', '3', '4', '5', '6', '7', '0') DEFAULT "0", + type ENUM ("Integral", "Noturna") + ); + `); + await connection.raw(` + CREATE TABLE Teacher( + id VARCHAR(255) PRIMARY KEY NOT NULL, + name VARCHAR(50) NOT NULL, + email VARCHAR(50) NOT NULL UNIQUE, + birthdate DATE NOT NULL, + speciality ENUM ("React", "Redux", "CSS", "Testes", "Typescript", "Programação Orientada a Objetos", "Backend"), + class_id VARCHAR(255) NULL, + FOREIGN KEY (class_id) REFERENCES Class(id) + ); + `); + await connection.raw(` + CREATE TABLE Student( + id VARCHAR(255) PRIMARY KEY NOT NULL, + name VARCHAR(50) NOT NULL, + email VARCHAR(50) NOT NULL UNIQUE, + birthdate DATE NOT NULL, + hobby VARCHAR(50) NULL, + class_id VARCHAR(255) NULL, + FOREIGN KEY (class_id) REFERENCES Class(id) + ); + `); + console.log("Tabelas criadas."); + connection.destroy(); + } catch (error) { + throw new Error(error.message || error.sqlMessage); + } +}; + +createTables(); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..2bb0a88 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,11 @@ +import express, { Express } from "express"; +import cors from "cors"; +import { router } from "./routes"; + +const app: Express = express(); + +app.use(express.json()); +app.use(cors()); +app.use(router); + +export { app }; \ No newline at end of file diff --git a/src/models/changeClassModule.ts b/src/models/changeClassModule.ts new file mode 100644 index 0000000..ad15f7a --- /dev/null +++ b/src/models/changeClassModule.ts @@ -0,0 +1,12 @@ +import connection from "../database/connection"; + +export const changeClassModule = async ( + id: string, + module: string +): Promise => { + try { + await connection.raw(`UPDATE Class SET module=${module} WHERE id=${id}`); + } catch (error) { + throw new Error(error.message || error.sqlMessage); + } +}; diff --git a/src/models/deleteStudent.ts b/src/models/deleteStudent.ts new file mode 100644 index 0000000..205cb3d --- /dev/null +++ b/src/models/deleteStudent.ts @@ -0,0 +1,9 @@ +import connection from "../database/connection"; + +export const deleteStudent = async (id: string): Promise => { + try { + await connection.delete().from("Student").where({ id }); + } catch (error) { + throw new Error(error.message || error.sqlMessage); + } +}; diff --git a/src/models/insertClass.ts b/src/models/insertClass.ts new file mode 100644 index 0000000..a097a46 --- /dev/null +++ b/src/models/insertClass.ts @@ -0,0 +1,19 @@ +import connection from "../database/connection"; +import { ClassInfo } from "../types/class"; + +export const insertClass = async (classInfo: ClassInfo): Promise => { + try { + await connection + .insert({ + id: Date.now(), + name: classInfo.name, + start_date: classInfo.start_date, + end_date: classInfo.end_date, + module: classInfo.module, + type: classInfo.module, + }) + .into("Class"); + } catch (error) { + throw new Error(error.message || error.sqlMessage); + } +}; diff --git a/src/models/insertStudent.ts b/src/models/insertStudent.ts new file mode 100644 index 0000000..a98da63 --- /dev/null +++ b/src/models/insertStudent.ts @@ -0,0 +1,18 @@ +import { Student } from "../types/student"; +import { connection } from "./../database/connection"; + +export const insertStudent = async (student: Student): Promise => { + try { + await connection + .insert({ + id: Date.now(), + name: student.name, + email: student.email, + birthdate: student.birthdate, + hobby: student.hobby, + }) + .into("Student"); + } catch (error) { + console.log(error); + } +}; diff --git a/src/models/insertStudentInClass.ts b/src/models/insertStudentInClass.ts new file mode 100644 index 0000000..3ea699b --- /dev/null +++ b/src/models/insertStudentInClass.ts @@ -0,0 +1,22 @@ +import connection from "../database/connection"; + +export const insertStudentInClass = async ( + class_id: string, + id: string +): Promise => { + try { + await connection + .raw( + ` + UPDATE Student SET class_id = ${class_id} WHERE id = ${id} + ` + ) + .then((res) => { + console.log(res); + }); + } catch (error) { + console.log(error); + + throw new Error(error.message || error.sqlMessage); + } +}; diff --git a/src/models/insertTeacher.ts b/src/models/insertTeacher.ts new file mode 100644 index 0000000..9f29da1 --- /dev/null +++ b/src/models/insertTeacher.ts @@ -0,0 +1,18 @@ +import connection from "../database/connection"; +import { Teacher } from "../types/teacher"; + +export const insertTeacher = async (teacher: Teacher): Promise => { + try { + await connection + .insert({ + id: Date.now(), + name: teacher.name, + email: teacher.email, + birthdate: teacher.birthdate, + speciality: teacher.speciality, + }) + .into("Teacher"); + } catch (error) { + throw new Error(error.message || error.sqlMessage); + } +}; diff --git a/src/models/insertTeacherInClass.ts b/src/models/insertTeacherInClass.ts new file mode 100644 index 0000000..9907a72 --- /dev/null +++ b/src/models/insertTeacherInClass.ts @@ -0,0 +1,14 @@ +import connection from "../database/connection"; + +export const insertTeacherInClass = async ( + class_id: string, + id: string +): Promise => { + try { + await connection.raw(` + UPDATE Teacher SET class_id = ${class_id} WHERE id = ${id} + `); + } catch (error) { + throw new Error(error.message || error.sqlMessage); + } +}; diff --git a/src/models/removeStudentFromClass.ts b/src/models/removeStudentFromClass.ts new file mode 100644 index 0000000..54b5513 --- /dev/null +++ b/src/models/removeStudentFromClass.ts @@ -0,0 +1,19 @@ +import connection from "../database/connection"; + +export const removeStudentFromClass = async (id: string): Promise => { + try { + await connection + .raw( + ` + UPDATE Student SET class_id = null WHERE id = "${id}" + ` + ) + .then((res) => { + console.log(res); + }); + } catch (error) { + console.log(error); + + throw new Error(error.message || error.sqlMessage); + } +}; diff --git a/src/models/removeTeacher.ts b/src/models/removeTeacher.ts new file mode 100644 index 0000000..76fc97c --- /dev/null +++ b/src/models/removeTeacher.ts @@ -0,0 +1,11 @@ +import connection from "../database/connection"; + +export const removeTeacher = async (id: string): Promise => { + try { + await connection.raw(` + UPDATE Teacher SET class_id=null WHERE id="${id}" + `); + } catch (error) { + throw new Error(error.message || error.sqlMessage); + } +}; diff --git a/src/models/selectStudentAgeById.ts b/src/models/selectStudentAgeById.ts new file mode 100644 index 0000000..c93ea9e --- /dev/null +++ b/src/models/selectStudentAgeById.ts @@ -0,0 +1,15 @@ +import connection from "../database/connection"; + +export const selectStudentAgeById = async (id: string): Promise => { + try { + const result = await connection.raw(` + SELECT name, FLOOR( DATEDIFF(CURDATE(),birthdate)/365) as Age + FROM Student + WHERE id=${id} + `); + + return result[0][0]; + } catch (error) { + throw new Error(error.message || error.sqlMessage); + } +}; diff --git a/src/models/selectStudentByClass.ts b/src/models/selectStudentByClass.ts new file mode 100644 index 0000000..4715fb5 --- /dev/null +++ b/src/models/selectStudentByClass.ts @@ -0,0 +1,15 @@ +import connection from "../database/connection"; + +export const selectStudentByClass = async (id: string): Promise => { + try { + const result = await connection.raw(` + SELECT Student.name as Student,Student.hobby as Hobby, Class.name + as Class FROM Student LEFT JOIN Class on Student.class_id = Class.id + WHERE Student.class_id = "${id}" + `); + + return result[0]; + } catch (error) { + throw new Error(error.message || error.sqlMessage); + } +}; diff --git a/src/models/selectStudentByHobby.ts b/src/models/selectStudentByHobby.ts new file mode 100644 index 0000000..359f5c0 --- /dev/null +++ b/src/models/selectStudentByHobby.ts @@ -0,0 +1,16 @@ +import connection from "../database/connection"; + +export const selectStudentByHobby = async (hobby: string): Promise => { + try { + const result = await connection.raw(` + SELECT hobby, id, name + FROM Student + WHERE hobby = '${hobby}' + GROUP BY hobby, id, name; + `); + + return result[0]; + } catch (error) { + throw new Error(error.message || error.sqlMessage); + } +}; diff --git a/src/models/selectTeachersFromClass.ts b/src/models/selectTeachersFromClass.ts new file mode 100644 index 0000000..5ef8f82 --- /dev/null +++ b/src/models/selectTeachersFromClass.ts @@ -0,0 +1,17 @@ +import connection from "../database/connection"; + +export const selectTeachersFromClass = async (id: string): Promise => { + try { + const result = await connection.raw(` + SELECT Teacher.name + as Teacher,Teacher.speciality + as Speciality, Class.name + as Class FROM Teacher + LEFT JOIN Class on Teacher.class_id = Class.id + WHERE Teacher.class_id = "${id}" + `); + return result[0]; + } catch (error) { + throw new Error(error.message || error.sqlMessage); + } +}; diff --git a/src/routes.ts b/src/routes.ts new file mode 100644 index 0000000..6af8ea6 --- /dev/null +++ b/src/routes.ts @@ -0,0 +1,32 @@ +import { Router } from "express"; +import { ClassController } from "./controllers/ClassController"; +import { StudentController } from "./controllers/StudentController"; +import { TeacherController } from "./controllers/TeacherController"; +import { ClassInfoController } from "./controllers/ClassInfoController"; +import { StudentHobbyController } from "./controllers/StudentHobbyController"; + +const router = Router(); + +const classController = new ClassController(); +const studentController = new StudentController(); +const teacherController = new TeacherController(); +const classInfoController = new ClassInfoController(); +const studentHobbyController = new StudentHobbyController(); + +router.put("/class/create", classController.create); +router.post("/class/update", classController.update); + +router.put("/student/create", studentController.create); +router.get("/students/:id", studentController.show); +router.post("/students/class", studentController.execute); +router.get("/students/class/:id", classInfoController.show); +router.get("/students", studentHobbyController.show); +router.delete("/students/remove-class/:id", studentController.update); +router.delete("/students/remove-student/:id", studentController.delete); + +router.put("/teacher/create", teacherController.create); +router.post("/teacher/class", teacherController.execute); +router.get("/teacher/class/:id", teacherController.show); +router.delete("/teacher/class/remove/:id", teacherController.update); + +export { router }; diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..bbca370 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,11 @@ +import { app } from "."; +import { AddressInfo } from "net"; + +const server = app.listen(3333, () => { + if (server) { + const address = server.address() as AddressInfo; + console.log(`Server is running in http://localhost:${address.port}`); + } else { + console.error(`Failure upon starting server.`); + } +}); \ No newline at end of file diff --git a/src/types/class.ts b/src/types/class.ts new file mode 100644 index 0000000..deac2a8 --- /dev/null +++ b/src/types/class.ts @@ -0,0 +1,7 @@ +export type ClassInfo = { + name: string; + start_date: string; + end_date: string; + module: string; + type: string; +}; diff --git a/src/types/student.ts b/src/types/student.ts new file mode 100644 index 0000000..b1dd3a5 --- /dev/null +++ b/src/types/student.ts @@ -0,0 +1,6 @@ +export type Student = { + name: string; + email: string; + birthdate: string; + hobby: string; +}; diff --git a/src/types/teacher.ts b/src/types/teacher.ts new file mode 100644 index 0000000..157d0cd --- /dev/null +++ b/src/types/teacher.ts @@ -0,0 +1,6 @@ +export type Teacher = { + name: string; + email: string; + birthdate: string; + speciality: string; +}; diff --git a/src/utilities/verifiers.ts b/src/utilities/verifiers.ts new file mode 100644 index 0000000..697472c --- /dev/null +++ b/src/utilities/verifiers.ts @@ -0,0 +1,14 @@ +export const formatDate = (date: string): string => { + const [day, month, year] = date.split("/"); + return `${year}-${month}-${day}`; +}; + +export const checkDate = (date: string): boolean => { + const useBar = date.includes("/"); + + if (useBar) { + return true; + } else { + return false; + } +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..89fe48f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,71 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./build", /* Redirect output structure to the directory. */ + "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + } +}