first commit

This commit is contained in:
efaviere
2022-12-13 23:58:19 +01:00
commit 11836cb408
21 changed files with 5232 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/node_modules
+5
View File
@@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/docker-node-api.iml" filepath="$PROJECT_DIR$/.idea/docker-node-api.iml" />
</modules>
</component>
</project>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+4821
View File
File diff suppressed because it is too large Load Diff
+31
View File
@@ -0,0 +1,31 @@
{
"name": "docker-node-api",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "nodemon --watch src -e js,ts,json --exec \"ts-node src/index.ts\"",
"build": "tsup src/index.ts --format cjs --clean",
"start": "node dist/index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/cors": "^2.8.13",
"@types/express": "^4.17.14",
"@types/node": "^18.11.11",
"nodemon": "^2.0.20",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.1.1",
"tsup": "^6.5.0",
"typescript": "^4.9.3"
},
"dependencies": {
"cors": "^2.8.5",
"express": "^4.18.2",
"pg": "^8.8.0",
"pg-hstore": "^2.3.4",
"sequelize": "^6.26.0"
}
}
+13
View File
@@ -0,0 +1,13 @@
export const config = {
API_PORT: 8080,
PRODUCTION: false,
database: {
USERNAME: "root",
PASSWORD: "root",
DATABASE: "root",
PORT: 5432,
HOST: "localhost"
}
}
+19
View File
@@ -0,0 +1,19 @@
import {config} from "~/config";
import {Sequelize, Dialect} from "sequelize";
const connection = new Sequelize(config.database.DATABASE, config.database.USERNAME, config.database.PASSWORD, {
host: config.database.HOST,
dialect: 'postgres'
});
const tryConnection = async () => {
try {
await connection.authenticate();
console.log('Connection has been established successfully.');
} catch (error) {
console.error('Unable to connect to the database:', error);
}
}
export default connection
+9
View File
@@ -0,0 +1,9 @@
import UserModel from "~/resources/users/user.model";
import {config} from "~/config";
const isDev = !config.PRODUCTION;
const dbInit = () => {
UserModel.sync({ alter: isDev })
}
export default dbInit
+30
View File
@@ -0,0 +1,30 @@
import cors from 'cors'
import express from 'express'
import { config } from '~/config'
import { ExceptionsHandler } from '~/middlewares/exceptions.handler'
import { UnknownRoutesHandler } from '~/middlewares/unknownRoutes.handler'
import {UsersController} from "~/resources/users/users.controller";
const app = express()
app.use(express.json())
app.use(cors())
app.use('/users', UsersController)
app.get('/', (req, res) => res.send('API DOCKER-android'))
/**
* Pour toutes les autres routes non définies, on retourne une erreur
*/
app.all('*', UnknownRoutesHandler)
app.use(ExceptionsHandler)
app.listen(config.API_PORT, () => console.log("Server running on port " + config.API_PORT))
+18
View File
@@ -0,0 +1,18 @@
import { NextFunction, Request, Response } from 'express'
export const ExceptionsHandler = (err: any, req: Request, res: Response, next: NextFunction) => {
if (res.headersSent) {
return next(err)
}
if (err.status && err.error) {
return res
.status(err.status)
.json({ error: err.error })
}
return res
.status(500)
.json({ error: 'Erreur interne' })
}
+6
View File
@@ -0,0 +1,6 @@
import {NotFoundException} from "~/utils/exception";
export const UnknownRoutesHandler = () => {
throw new NotFoundException(`La resource demandée n'existe pas`)
}
+11
View File
@@ -0,0 +1,11 @@
import {Optional} from "sequelize";
export interface UserInterface {
id: number;
username: string;
password: string;
role: string;
}
export interface UserInput extends Optional<UserInterface, 'id'> {}
export interface UserOutput extends Required<UserInterface> {}
+40
View File
@@ -0,0 +1,40 @@
import {DataTypes, Model} from "sequelize";
import {UserInput, UserInterface} from "~/resources/users/user.interface";
import connection from "~/database/config";
class UserModel extends Model<UserInterface, UserInput> implements UserInterface {
id: number;
username: string;
password: string;
role: string;
}
UserModel.init({
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true
},
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
password: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
role: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
}, {
timestamps: true,
sequelize: connection,
paranoid: true
})
export default UserModel;
+21
View File
@@ -0,0 +1,21 @@
import {UserInput, UserOutput} from "~/resources/users/user.interface";
import UserModel from "~/resources/users/user.model";
import {NotFoundException} from "~/utils/exception";
class UserRepository {
async create (payload: UserInput): Promise<UserOutput> {
return await UserModel.create(payload)
}
async findAll(): Promise<UserOutput[]> {
return await UserModel.findAll();
}
async findOneById(id: number): Promise<UserModel | null> {
const user = UserModel.findByPk(id);
if (!user)
throw new NotFoundException("User with id " + id + " not found")
return await UserModel.findOne({where: {id: id}});
}
}
+68
View File
@@ -0,0 +1,68 @@
import { Router } from 'express'
import {UsersService} from "~/resources/users/users.service";
import {BadRequestException, NotFoundException} from "~/utils/exception";
const UsersController = Router()
const service = new UsersService()
UsersController.get('/', (req, res) => {
return res
.status(200)
.json(service.findAll())
})
UsersController.get('/:id', (req, res) => {
const id = Number(req.params.id)
if (!Number.isInteger(id)) {
throw new BadRequestException('ID non valide')
}
const pet = service.findOne(id)
if (!pet) {
throw new NotFoundException('Animal introuvable')
}
return res
.status(200)
.json(pet)
})
UsersController.post('/', (req, res) => {
const createdPet = service.create(req.body)
return res
.status(201)
.json(createdPet)
})
UsersController.patch('/:id', (req, res) => {
const id = Number(req.params.id)
if (!Number.isInteger(id)) {
throw new BadRequestException('ID invalide')
}
const updatedPet = service.update(req.body, id)
return res
.status(200)
.json(updatedPet)
})
UsersController.delete('/:id', (req, res) => {
const id = Number(req.params.id)
if (!Number.isInteger(id)) {
throw new BadRequestException('ID invalide')
}
return res
.status(200)
.json(service.delete(id))
})
export { UsersController }
+64
View File
@@ -0,0 +1,64 @@
import UserModel from "~/resources/users/user.model";
import {UserOutput} from "~/resources/users/user.interface";
import {NotFoundException} from "~/utils/exception";
export class UsersService {
async findAll(): Promise<UserModel[]> {
return await UserModel.findAll();
}
async findOne(id: number): Promise<UserModel> {
const user = await UserModel.findByPk(id);
if (!user)
throw new NotFoundException("user with id " + id + " not found.")
return user;
}
/**
* Met à jour un animal en particulier
*
* /!\ Idéalement, il faudrait vérifier le contenu de la requête avant de le sauvegarder.
*
* @param petData - Un objet correspondant à un animal, il ne contient pas forcément tout un animal. Attention, on ne prend pas l'id avec.
* @param id - ID unique de l'animal
*/
update(petData: Partial<Pet>, id: number): Pet | undefined {
const index = this.pets.findIndex(pet => pet.id === id)
if (index === -1) {
throw new NotFoundException('Animal introuvable')
}
/* On ne met jamais l'id à jour */
delete petData.id
this.pets[index] = { ...this.pets[index], ...petData }
return this.pets[index]
}
/**
* Créé un animal
*
* /!\ Idéalement, il faudrait vérifier le contenu de la requête avant de le sauvegarder.
*
* @param petData - Un objet correspondant à un animal. Attention, on ne prend pas l'id avec.
*/
create(petData: Omit<Pet, 'id'>): Pet {
const newPet: Pet = {
...petData,
/* /!\ Ne pas faire ceci dans un vrai projet */
id: Math.floor(Math.random() * 100)
}
this.pets.push(newPet)
return newPet
}
/**
* Suppression d'un animal
*/
delete(id: number) {
this.pets = this.pets.filter(pet => pet.id !== id)
}
}
+20
View File
@@ -0,0 +1,20 @@
import {ApiException} from "~~/types/exception";
class Exception implements ApiException {
constructor(readonly error: any, readonly status: number) {}
}
export class NotFoundException extends Exception {
constructor(error: any) {
super(error, 404)
}
}
export class BadRequestException extends Exception {
constructor(error: any) {
super(error, 400)
}
}
+25
View File
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"baseUrl": ".",
"target": "es2017",
"module": "CommonJS",
"lib": ["esnext"],
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"strictNullChecks": true,
"resolveJsonModule": true,
"skipDefaultLibCheck": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"outDir": "./dist",
"strictPropertyInitialization": false,
"paths": {
"~/*": ["./src/*"],
"~~/*": ["./*"]
}
},
"ts-node": {
"require": ["tsconfig-paths/register"]
}
}
+4
View File
@@ -0,0 +1,4 @@
export interface ApiException {
error: any
status: number
}