Initial commit
This commit is contained in:
parent
a13ceef804
commit
8641fc9723
19 changed files with 303532 additions and 0 deletions
1
.eslintignore
Normal file
1
.eslintignore
Normal file
|
@ -0,0 +1 @@
|
|||
/node_modules/
|
31
.eslintrc.js
Normal file
31
.eslintrc.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
module.exports = {
|
||||
parser: "babel-eslint",
|
||||
extends: ["airbnb-base", "prettier", "plugin:jest/recommended"],
|
||||
plugins: ["prettier", "jest"],
|
||||
env: {
|
||||
es6: true,
|
||||
browser: false,
|
||||
node: true,
|
||||
"jest/globals": true
|
||||
},
|
||||
globals: {
|
||||
__DEV__: true
|
||||
},
|
||||
rules: {
|
||||
// enable additional rules
|
||||
"no-plusplus": [2, { allowForLoopAfterthoughts: true }],
|
||||
"no-underscore-dangle": "off",
|
||||
indent: ["error", 2, { SwitchCase: 1 }],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
quotes: ["error", "double"],
|
||||
semi: ["error", "always"],
|
||||
// Forbid the use of extraneous packages
|
||||
// https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-extraneous-dependencies.md
|
||||
"import/no-extraneous-dependencies": ["error", { packageDir: "." }],
|
||||
// ESLint plugin for prettier formatting
|
||||
// https://github.com/prettier/eslint-plugin-prettier
|
||||
"prettier/prettier": "error",
|
||||
"func-names": ["error", "never"],
|
||||
"no-restricted-globals": ["error", "event", "fdescribe"]
|
||||
}
|
||||
};
|
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
/public/gasStations.xml
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
38
app.js
Normal file
38
app.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import express from "express";
|
||||
import cookieParser from "cookie-parser";
|
||||
import Pino from "express-pino-logger";
|
||||
import cors from "cors";
|
||||
import routerStations from "./routes/v1/stations";
|
||||
import config from "./config";
|
||||
import { formatResponseError } from "./libs/Format";
|
||||
|
||||
// Initialisation du logger
|
||||
const pino = new Pino({
|
||||
prettyPrint: true
|
||||
});
|
||||
pino.logger.info(`Server started on port ${config.port}`);
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(cors());
|
||||
|
||||
// Permet de sauvegarder l'ip du client et non du proxy
|
||||
app.set("trust proxy", config.trustProxy);
|
||||
app.use(cookieParser());
|
||||
// Utilisation de Pino
|
||||
app.use(pino);
|
||||
|
||||
// Déclaration des routes
|
||||
app.use("/v1/stations", routerStations);
|
||||
|
||||
// Gestion des erreurs
|
||||
app.use((err, req, res, next) => {
|
||||
res.error = err;
|
||||
if (res.headersSent) {
|
||||
next(err);
|
||||
} else {
|
||||
formatResponseError(res, err);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = app;
|
11
config/index.js
Normal file
11
config/index.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
module.exports = {
|
||||
port: process.env.PORT || 3000,
|
||||
trustProxy: process.env.TRUST_PROXY || "loopback",
|
||||
mongodbUrl:
|
||||
process.env.DATABASE_URL || "mongodb://localhost:27017/prix-carburants",
|
||||
mongodbDebug: process.env.DEBUG || true,
|
||||
mondeDbOptions: {
|
||||
useUnifiedTopology: true,
|
||||
useNewUrlParser: true
|
||||
}
|
||||
};
|
143
importGasStations.js
Normal file
143
importGasStations.js
Normal file
|
@ -0,0 +1,143 @@
|
|||
/* eslint-disable no-console */
|
||||
/* eslint-disable global-require */
|
||||
/* eslint-disable import/no-dynamic-require */
|
||||
const fs = require("fs");
|
||||
const parser = require("xml2json");
|
||||
const moment = require("moment");
|
||||
const Mongoose = require("mongoose");
|
||||
const iconv = require("iconv-lite");
|
||||
const config = require("./config");
|
||||
|
||||
// TODO: envoyer mail lors d'une erreur
|
||||
|
||||
Mongoose.set("useNewUrlParser", true);
|
||||
Mongoose.set("useFindAndModify", false);
|
||||
Mongoose.set("useCreateIndex", true);
|
||||
Mongoose.set("debug", config.mongodbDebug);
|
||||
|
||||
Mongoose.connect(config.mongodbUrl, config.mondeDbOptions);
|
||||
|
||||
const db = () => {
|
||||
const m = {};
|
||||
|
||||
fs.readdirSync("./models")
|
||||
.filter(file => {
|
||||
return (
|
||||
file.indexOf(".") !== 0 &&
|
||||
file.slice(-3) === ".js" &&
|
||||
file !== "index.js"
|
||||
);
|
||||
})
|
||||
.forEach(file => {
|
||||
const model = require(`./models/${file.replace(".js", "")}`)(Mongoose);
|
||||
m[model.modelName] = model;
|
||||
});
|
||||
|
||||
return m;
|
||||
};
|
||||
|
||||
const models = db();
|
||||
|
||||
let foundGasStations = 0;
|
||||
let done = 0;
|
||||
|
||||
const extractPrice = station => {
|
||||
const prices = [];
|
||||
|
||||
if (station.prix) {
|
||||
for (let i = 0; i < station.prix.length; i += 1) {
|
||||
const currentPrice = station.prix[i];
|
||||
prices.push({
|
||||
gasType: currentPrice.nom,
|
||||
price: parseInt(currentPrice.valeur, 10) / 1000,
|
||||
updatedAt: moment(currentPrice.maj)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return prices;
|
||||
};
|
||||
|
||||
const createOrUpdateGasStation = (station, callback) => {
|
||||
models.Stations.findOne({
|
||||
stationId: station.stationId
|
||||
})
|
||||
.then(item => {
|
||||
if (item) {
|
||||
item
|
||||
.update(station)
|
||||
.then(() => {
|
||||
callback(null);
|
||||
})
|
||||
.catch(callback);
|
||||
} else {
|
||||
// Create item
|
||||
const newItem = new models.Stations(station);
|
||||
newItem
|
||||
.save()
|
||||
.then(() => {
|
||||
callback(null);
|
||||
})
|
||||
.catch(callback);
|
||||
}
|
||||
})
|
||||
.catch(callback);
|
||||
};
|
||||
|
||||
const next = () => {
|
||||
if (done === foundGasStations) {
|
||||
console.log(`DONE ${done} !`);
|
||||
process.exit();
|
||||
}
|
||||
};
|
||||
|
||||
fs.readFile("public/gas-stations.xml", function(err, data) {
|
||||
if (err) {
|
||||
console.log("ERR:", err);
|
||||
process.exit();
|
||||
return false;
|
||||
}
|
||||
|
||||
const json = parser.toJson(iconv.decode(data, "ISO-8859-1"));
|
||||
|
||||
const rawStations = JSON.parse(json).pdv_liste.pdv;
|
||||
|
||||
// console.log("STATIONS:", rawStations.length);
|
||||
// console.log("RANDOM STATION:", rawStations[12]);
|
||||
|
||||
const stations = [];
|
||||
|
||||
for (let i = 0; i < rawStations.length; i += 1) {
|
||||
const currentStation = rawStations[i];
|
||||
stations.push({
|
||||
stationId: currentStation.id,
|
||||
location: {
|
||||
type: "Point",
|
||||
coordinates: [
|
||||
Number(currentStation.longitude) / 100000,
|
||||
Number(currentStation.latitude) / 100000
|
||||
]
|
||||
},
|
||||
prices: extractPrice(currentStation),
|
||||
services:
|
||||
currentStation.services && currentStation.services.service
|
||||
? currentStation.services.service
|
||||
: [],
|
||||
postCode: currentStation.cp.toString(),
|
||||
address: currentStation.adresse,
|
||||
city: currentStation.ville
|
||||
});
|
||||
}
|
||||
|
||||
foundGasStations = stations.length;
|
||||
|
||||
// console.log(stations[12]);
|
||||
|
||||
stations.forEach(type => {
|
||||
createOrUpdateGasStation(type, () => {
|
||||
done += 1;
|
||||
next();
|
||||
});
|
||||
});
|
||||
return true;
|
||||
});
|
62
libs/Format.js
Normal file
62
libs/Format.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* Fonction permettant de formater une réponse API
|
||||
* @param {Object} req
|
||||
* @param {Object} res
|
||||
* @param {Functiont} next
|
||||
* @param {Object} err
|
||||
* @param {Object} response {code: Integer, res: Array/Object}
|
||||
*/
|
||||
const _formatResponse = (req, res, next, err, response) => {
|
||||
if (err) {
|
||||
req.response = response;
|
||||
next(err);
|
||||
} else {
|
||||
switch (req.method) {
|
||||
case "GET":
|
||||
res
|
||||
.status(response ? 200 : 204)
|
||||
.json(response)
|
||||
.end();
|
||||
break;
|
||||
case "PATCH":
|
||||
res
|
||||
.status(200)
|
||||
.json(response)
|
||||
.end();
|
||||
break;
|
||||
case "DELETE":
|
||||
res
|
||||
.status(200)
|
||||
.json(response)
|
||||
.end();
|
||||
break;
|
||||
case "POST":
|
||||
res
|
||||
.status(201)
|
||||
.json(response)
|
||||
.end();
|
||||
break;
|
||||
default:
|
||||
next(new Error("Not implemented"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fonction permettant de formater une erreur
|
||||
* @param {Object} res
|
||||
* @param {Object} err
|
||||
*/
|
||||
const _formatResponseError = (res, err) => {
|
||||
const code = err.errorCode || 500;
|
||||
const response = {
|
||||
code,
|
||||
message: err.message
|
||||
};
|
||||
|
||||
res.status(Math.trunc(code)).json(response);
|
||||
};
|
||||
|
||||
export const formatResponse = _formatResponse;
|
||||
export const formatResponseError = _formatResponseError;
|
34
middleware/Stations.js
Normal file
34
middleware/Stations.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
import models from "../models";
|
||||
|
||||
class Stations {
|
||||
static getAll(req, callback) {
|
||||
const { radius, lon, lat, start, limit } = req.query;
|
||||
const query = {
|
||||
location: {
|
||||
$near: {
|
||||
$maxDistance: Number(radius),
|
||||
$geometry: {
|
||||
type: "Point",
|
||||
coordinates: [Number(lon), Number(lat)]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const skip = start || 0;
|
||||
const _limit = limit && limit <= 50 ? limit : 20;
|
||||
|
||||
models.Stations.count(query)
|
||||
.then(count => {
|
||||
models.Stations.find(query)
|
||||
.skip(parseInt(skip, 10))
|
||||
.limit(parseInt(_limit, 10))
|
||||
.then(items => {
|
||||
callback(null, { total: count, items });
|
||||
});
|
||||
})
|
||||
.catch(callback);
|
||||
}
|
||||
}
|
||||
|
||||
export default Stations;
|
42
models/Stations.js
Normal file
42
models/Stations.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* Model permettant de bufferiser les events reçus de Kafka
|
||||
*/
|
||||
module.exports = mongoose => {
|
||||
const schema = new mongoose.Schema({
|
||||
stationId: String,
|
||||
location: {
|
||||
type: { type: String },
|
||||
coordinates: []
|
||||
},
|
||||
prices: [
|
||||
{
|
||||
gasType: String,
|
||||
price: Number,
|
||||
updatedAt: Object
|
||||
}
|
||||
],
|
||||
services: [],
|
||||
postCode: String,
|
||||
address: String,
|
||||
city: String
|
||||
});
|
||||
|
||||
schema.index({ location: "2dsphere" });
|
||||
|
||||
const Stations = mongoose.model("Stations", schema);
|
||||
|
||||
Stations.createIndexes();
|
||||
|
||||
return Stations;
|
||||
};
|
||||
|
||||
// INFO:
|
||||
/*
|
||||
schema.location = {
|
||||
type: 'Point',
|
||||
coordinates: [
|
||||
Number(longitude),
|
||||
Number(latitude)
|
||||
]
|
||||
}
|
||||
*/
|
37
models/index.js
Normal file
37
models/index.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
/* eslint-disable global-require */
|
||||
/* eslint-disable import/no-dynamic-require */
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import Mongoose from "mongoose";
|
||||
import config from "../config";
|
||||
|
||||
const basename = path.basename(__filename);
|
||||
|
||||
Mongoose.set("useNewUrlParser", true);
|
||||
Mongoose.set("useFindAndModify", false);
|
||||
Mongoose.set("useCreateIndex", true);
|
||||
Mongoose.set("debug", config.mongodbDebug);
|
||||
|
||||
Mongoose.connect(config.mongodbUrl, config.mondeDbOptions);
|
||||
|
||||
const db = () => {
|
||||
const m = {};
|
||||
|
||||
fs.readdirSync(__dirname)
|
||||
.filter(file => {
|
||||
return (
|
||||
file.indexOf(".") !== 0 && file !== basename && file.slice(-3) === ".js"
|
||||
);
|
||||
})
|
||||
.forEach(file => {
|
||||
const model = require(path.resolve(__dirname, file))(Mongoose);
|
||||
m[model.modelName] = model;
|
||||
});
|
||||
|
||||
return m;
|
||||
};
|
||||
|
||||
const models = db();
|
||||
|
||||
export const mongoose = Mongoose;
|
||||
export default models;
|
70
package.json
Normal file
70
package.json
Normal file
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"name": "prix-carburants-api",
|
||||
"version": "0.1.0",
|
||||
"description": "API pour le projet prix-carburants",
|
||||
"private": false,
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node start-server.js",
|
||||
"start:local": "nodemon start-server.js",
|
||||
"lint": "./node_modules/.bin/eslint . --fix"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@framagit.org:dbroqua/prix-carburants-api.git"
|
||||
},
|
||||
"author": "Damien Broqua <contact@darkou.fr>",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "13.x",
|
||||
"yarn": "1.x"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.js": [
|
||||
"./node_modules/.bin/eslint --fix",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"nodemonConfig": {
|
||||
"ext": "*.js,*.json,*yaml"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie-parser": "~1.4.4",
|
||||
"cors": "^2.8.5",
|
||||
"debug": "~2.6.9",
|
||||
"express": "~4.16.1",
|
||||
"express-pino-logger": "^4.0.0",
|
||||
"file-stream-rotator": "^0.5.7",
|
||||
"http-errors": "~1.6.3",
|
||||
"iconv-lite": "^0.5.1",
|
||||
"moment": "^2.24.0",
|
||||
"mongoose": "^5.9.2",
|
||||
"morgan": "~1.9.1",
|
||||
"pino-pretty": "^3.6.1",
|
||||
"xml2json": "^0.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.8.6",
|
||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.8.3",
|
||||
"@babel/plugin-transform-runtime": "^7.8.3",
|
||||
"@babel/preset-env": "^7.8.6",
|
||||
"@babel/register": "^7.8.6",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-airbnb-base": "^14.0.0",
|
||||
"eslint-config-prettier": "^6.10.0",
|
||||
"eslint-plugin-import": "^2.20.1",
|
||||
"eslint-plugin-jest": "^23.8.1",
|
||||
"eslint-plugin-prettier": "^3.1.2",
|
||||
"husky": "^4.2.3",
|
||||
"lint-staged": "^10.0.8",
|
||||
"nodemon": "^2.0.2",
|
||||
"prettier": "^1.19.1"
|
||||
}
|
||||
}
|
298704
public/gas-stations.xml
Normal file
298704
public/gas-stations.xml
Normal file
File diff suppressed because it is too large
Load diff
15
routes/v1/stations.js
Normal file
15
routes/v1/stations.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import express from "express";
|
||||
import Stations from "../../middleware/Stations";
|
||||
import { formatResponse } from "../../libs/Format";
|
||||
|
||||
const router = express.Router();
|
||||
/**
|
||||
* Ensemble des routes permettant à un client de s'inscrire sur des events
|
||||
*/
|
||||
router.route("/").get(function(req, res, next) {
|
||||
Stations.getAll(req, (err, response) => {
|
||||
formatResponse(req, res, next, err, response);
|
||||
});
|
||||
});
|
||||
|
||||
export default router;
|
7
server.js
Normal file
7
server.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import app from "./app";
|
||||
|
||||
const config = require("./config");
|
||||
|
||||
const server = app.listen(config.port, () => {});
|
||||
|
||||
module.exports = server;
|
19
start-server.js
Normal file
19
start-server.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
require("@babel/register")({
|
||||
plugins: [
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
"@babel/plugin-transform-runtime"
|
||||
],
|
||||
presets: [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
targets: {
|
||||
node: "current"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
});
|
||||
|
||||
module.exports = require("./server.js");
|
6
views/error.jade
Normal file
6
views/error.jade
Normal file
|
@ -0,0 +1,6 @@
|
|||
extends layout
|
||||
|
||||
block content
|
||||
h1= message
|
||||
h2= error.status
|
||||
pre #{error.stack}
|
5
views/index.jade
Normal file
5
views/index.jade
Normal file
|
@ -0,0 +1,5 @@
|
|||
extends layout
|
||||
|
||||
block content
|
||||
h1= title
|
||||
p Welcome to #{title}
|
7
views/layout.jade
Normal file
7
views/layout.jade
Normal file
|
@ -0,0 +1,7 @@
|
|||
doctype html
|
||||
html
|
||||
head
|
||||
title= title
|
||||
link(rel='stylesheet', href='/stylesheets/style.css')
|
||||
body
|
||||
block content
|
Loading…
Reference in a new issue