first commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/node_modules
|
||||
Generated
+5
@@ -0,0 +1,5 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
Generated
+12
@@ -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>
|
||||
Generated
+8
@@ -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
@@ -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>
|
||||
Generated
+4821
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export const config = {
|
||||
API_PORT: 8080,
|
||||
PRODUCTION: false,
|
||||
database: {
|
||||
USERNAME: "root",
|
||||
PASSWORD: "root",
|
||||
DATABASE: "root",
|
||||
PORT: 5432,
|
||||
HOST: "localhost"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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))
|
||||
@@ -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' })
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import {NotFoundException} from "~/utils/exception";
|
||||
|
||||
|
||||
export const UnknownRoutesHandler = () => {
|
||||
throw new NotFoundException(`La resource demandée n'existe pas`)
|
||||
}
|
||||
@@ -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> {}
|
||||
@@ -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;
|
||||
@@ -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}});
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface ApiException {
|
||||
error: any
|
||||
status: number
|
||||
}
|
||||
Reference in New Issue
Block a user