Compare commits
5 Commits
b3abeaa264
...
743f1566ae
Author | SHA1 | Date | |
---|---|---|---|
743f1566ae | |||
164285cf17 | |||
af8f77a268 | |||
e98d1d48ab | |||
25a66b060c |
35
app.ts
35
app.ts
|
@ -2,6 +2,7 @@ import express from 'express';
|
||||||
import * as http from 'http';
|
import * as http from 'http';
|
||||||
import * as dotenv from 'dotenv';
|
import * as dotenv from 'dotenv';
|
||||||
import swaggerUi from 'swagger-ui-express';
|
import swaggerUi from 'swagger-ui-express';
|
||||||
|
import swaggerJSDoc from 'swagger-jsdoc';
|
||||||
// Router imports
|
// Router imports
|
||||||
import {partyPlanerRouter} from './src/models/partyplaner/PartyPlaner.router';
|
import {partyPlanerRouter} from './src/models/partyplaner/PartyPlaner.router';
|
||||||
import {highlightMarkerRouter} from './src/models/twitch-highlight-marker/HighlightMarker.router';
|
import {highlightMarkerRouter} from './src/models/twitch-highlight-marker/HighlightMarker.router';
|
||||||
|
@ -10,6 +11,7 @@ import logger from './src/middleware/logger';
|
||||||
import {dhbwRaPlaChangesRouter} from './src/models/dhbw-rapla-changes/DHBWRaPlaChanges.router';
|
import {dhbwRaPlaChangesRouter} from './src/models/dhbw-rapla-changes/DHBWRaPlaChanges.router';
|
||||||
import {raPlaMiddlewareRouter} from './src/models/rapla-middleware/RaPlaMiddleware.router';
|
import {raPlaMiddlewareRouter} from './src/models/rapla-middleware/RaPlaMiddleware.router';
|
||||||
import {betterzonRouter} from './src/models/betterzon/Betterzon.router';
|
import {betterzonRouter} from './src/models/betterzon/Betterzon.router';
|
||||||
|
import {crrRouter} from './src/models/climbing-route-rating/ClimbingRouteRating.router';
|
||||||
|
|
||||||
let cors = require('cors');
|
let cors = require('cors');
|
||||||
|
|
||||||
|
@ -32,14 +34,36 @@ app.use(express.json());
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
|
|
||||||
// Swagger documentation
|
// Swagger documentation
|
||||||
|
const swaggerDefinition = {
|
||||||
|
openapi: '3.0.0',
|
||||||
|
info: {
|
||||||
|
title: 'Pluto Development REST API',
|
||||||
|
version: '2.0.0',
|
||||||
|
license: {
|
||||||
|
name: 'Licensed Under MIT',
|
||||||
|
url: 'https://spdx.org/licenses/MIT.html'
|
||||||
|
},
|
||||||
|
contact: {
|
||||||
|
name: 'Pluto Development',
|
||||||
|
url: 'https://www.pluto-development.de'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
swaggerDefinition,
|
||||||
|
// Paths to files containing OpenAPI definitions
|
||||||
|
apis: [
|
||||||
|
'./src/models/**/*.router.ts'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const swaggerSpec = swaggerJSDoc(options);
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
'/docs',
|
'/docs',
|
||||||
swaggerUi.serve,
|
swaggerUi.serve,
|
||||||
swaggerUi.setup(undefined, {
|
swaggerUi.setup(swaggerSpec)
|
||||||
swaggerOptions: {
|
|
||||||
url: '/swagger.json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add routers
|
// Add routers
|
||||||
|
@ -49,6 +73,7 @@ app.use('/partyplaner', partyPlanerRouter);
|
||||||
app.use('/raplachanges', dhbwRaPlaChangesRouter);
|
app.use('/raplachanges', dhbwRaPlaChangesRouter);
|
||||||
app.use('/rapla-middleware', raPlaMiddlewareRouter);
|
app.use('/rapla-middleware', raPlaMiddlewareRouter);
|
||||||
app.use('/betterzon', betterzonRouter);
|
app.use('/betterzon', betterzonRouter);
|
||||||
|
app.use('/crr', crrRouter);
|
||||||
|
|
||||||
// this is a simple route to make sure everything is working properly
|
// this is a simple route to make sure everything is working properly
|
||||||
app.get('/', (req: express.Request, res: express.Response) => {
|
app.get('/', (req: express.Request, res: express.Response) => {
|
||||||
|
|
8613
package-lock.json
generated
8613
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -15,6 +15,7 @@
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"app-root-path": "^3.0.0",
|
"app-root-path": "^3.0.0",
|
||||||
|
"axios": "^0.24.0",
|
||||||
"bcrypt": "^5.0.1",
|
"bcrypt": "^5.0.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"debug": "^4.3.1",
|
"debug": "^4.3.1",
|
||||||
|
@ -22,8 +23,9 @@
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"guid-typescript": "^1.0.9",
|
"guid-typescript": "^1.0.9",
|
||||||
"mariadb": "^2.5.3",
|
"mariadb": "^2.5.3",
|
||||||
|
"random-words": "^1.1.1",
|
||||||
|
"swagger-jsdoc": "^6.1.0",
|
||||||
"swagger-ui-express": "^4.3.0",
|
"swagger-ui-express": "^4.3.0",
|
||||||
"tsoa": "^3.14.1",
|
|
||||||
"winston": "^3.3.3"
|
"winston": "^3.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -31,6 +33,8 @@
|
||||||
"@types/bcrypt": "^3.0.1",
|
"@types/bcrypt": "^3.0.1",
|
||||||
"@types/debug": "^4.1.5",
|
"@types/debug": "^4.1.5",
|
||||||
"@types/express": "^4.17.11",
|
"@types/express": "^4.17.11",
|
||||||
|
"@types/random-words": "^1.1.2",
|
||||||
|
"@types/swagger-jsdoc": "^6.0.1",
|
||||||
"@types/swagger-ui-express": "^4.1.3",
|
"@types/swagger-ui-express": "^4.1.3",
|
||||||
"@types/winston": "^2.4.4",
|
"@types/winston": "^2.4.4",
|
||||||
"source-map-support": "^0.5.19",
|
"source-map-support": "^0.5.19",
|
||||||
|
|
19
src/models/climbing-route-rating/ClimbingRouteRating.db.ts
Normal file
19
src/models/climbing-route-rating/ClimbingRouteRating.db.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import * as dotenv from 'dotenv';
|
||||||
|
|
||||||
|
const mariadb = require('mariadb');
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
export namespace ClimbingRouteRatingDB {
|
||||||
|
const pool = mariadb.createPool({
|
||||||
|
host: process.env.DB_HOST,
|
||||||
|
user: process.env.DB_USER,
|
||||||
|
password: process.env.DB_PASSWORD,
|
||||||
|
database: process.env.CRR_DATABASE,
|
||||||
|
connectionLimit: 5
|
||||||
|
});
|
||||||
|
|
||||||
|
export function getConnection() {
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
/**
|
||||||
|
* Required External Modules and Interfaces
|
||||||
|
*/
|
||||||
|
import express, {Request, Response} from 'express';
|
||||||
|
import {Guid} from 'guid-typescript';
|
||||||
|
import logger from '../../middleware/logger';
|
||||||
|
import {climbingGymRouter} from './climbing_gyms/climbingGyms.router';
|
||||||
|
import {climbingRoutesRouter} from './climbing_routes/climbingRoutes.router';
|
||||||
|
import {routeCommentsRouter} from './route_comments/routeComments.router';
|
||||||
|
import {routeRatingsRouter} from './route_ratings/routeRatings.router';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Router Definition
|
||||||
|
*/
|
||||||
|
export const crrRouter = express.Router();
|
||||||
|
|
||||||
|
// Sub-Endpoints
|
||||||
|
crrRouter.use('/gyms', climbingGymRouter);
|
||||||
|
crrRouter.use('/routes', climbingRoutesRouter);
|
||||||
|
crrRouter.use('/comments', routeCommentsRouter);
|
||||||
|
crrRouter.use('/ratings', routeRatingsRouter);
|
||||||
|
|
||||||
|
crrRouter.get('/', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
res.status(200).send('Pluto Development Climbing Route Rating API Endpoint');
|
||||||
|
} catch (e) {
|
||||||
|
let errorGuid = Guid.create().toString();
|
||||||
|
logger.error('Error handling a request: ' + e.message, {reference: errorGuid});
|
||||||
|
res.status(500).send({
|
||||||
|
'status': 'PROCESSING_ERROR',
|
||||||
|
'message': 'Internal Server Error. Try again later.',
|
||||||
|
'reference': errorGuid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,6 @@
|
||||||
|
export interface ClimbingGym {
|
||||||
|
gym_id: number;
|
||||||
|
name: string;
|
||||||
|
city: string;
|
||||||
|
verified: boolean;
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
/**
|
||||||
|
* Required External Modules and Interfaces
|
||||||
|
*/
|
||||||
|
import express, {Request, Response} from 'express';
|
||||||
|
import {Guid} from 'guid-typescript';
|
||||||
|
import logger from '../../../middleware/logger';
|
||||||
|
import {ClimbingGym} from './ClimbingGym.interface';
|
||||||
|
import * as GymService from './climbingGyms.service';
|
||||||
|
import {verifyCaptcha} from '../common/VerifyCaptcha';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Router Definition
|
||||||
|
*/
|
||||||
|
export const climbingGymRouter = express.Router();
|
||||||
|
|
||||||
|
climbingGymRouter.get('/', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const gyms: ClimbingGym[] = await GymService.findAll();
|
||||||
|
|
||||||
|
res.status(200).send(gyms);
|
||||||
|
} catch (e) {
|
||||||
|
let errorGuid = Guid.create().toString();
|
||||||
|
logger.error('Error handling a request: ' + e.message, {reference: errorGuid});
|
||||||
|
res.status(500).send({
|
||||||
|
'status': 'PROCESSING_ERROR',
|
||||||
|
'message': 'Internal Server Error. Try again later.',
|
||||||
|
'reference': errorGuid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
climbingGymRouter.post('/', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
let name = req.query.name as string;
|
||||||
|
let city = req.query.city as string;
|
||||||
|
let captcha_token = req.query['h-captcha-response'] as string;
|
||||||
|
|
||||||
|
if (!name || !city || !captcha_token) {
|
||||||
|
res.status(400).send({'message': 'Missing parameters'});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify captcha
|
||||||
|
if (!await verifyCaptcha(captcha_token)) {
|
||||||
|
res.status(403).send({'message': 'Invalid Captcha. Please try again.'});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = await GymService.createGym(name, city);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
res.status(201).send({'gym_id': result});
|
||||||
|
} else {
|
||||||
|
res.status(500).send({});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
let errorGuid = Guid.create().toString();
|
||||||
|
logger.error('Error handling a request: ' + e.message, {reference: errorGuid});
|
||||||
|
res.status(500).send({
|
||||||
|
'status': 'PROCESSING_ERROR',
|
||||||
|
'message': 'Internal Server Error. Try again later.',
|
||||||
|
'reference': errorGuid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,33 @@
|
||||||
|
import {ClimbingRouteRatingDB} from '../ClimbingRouteRating.db';
|
||||||
|
import {ClimbingGym} from './ClimbingGym.interface';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches and returns all known climbing gyms
|
||||||
|
* @return Promise<ClimbingHall[]> The climbing halls
|
||||||
|
*/
|
||||||
|
export const findAll = async (): Promise<ClimbingGym[]> => {
|
||||||
|
let conn = ClimbingRouteRatingDB.getConnection();
|
||||||
|
try {
|
||||||
|
return await conn.query('SELECT gym_id, name, city, verified FROM climbing_gyms');
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a climbing gym and returns the id of the created gym
|
||||||
|
* @param name The name of the climbing hall
|
||||||
|
* @param city The city of the climbing hall
|
||||||
|
* @return number The id of the climbing hall
|
||||||
|
*/
|
||||||
|
export const createGym = async (name: string, city: string): Promise<number> => {
|
||||||
|
let conn = ClimbingRouteRatingDB.getConnection();
|
||||||
|
|
||||||
|
try {
|
||||||
|
let res = await conn.query('INSERT INTO climbing_gyms (name, city) VALUES (?, ?) RETURNING gym_id', [name, city]);
|
||||||
|
|
||||||
|
return res[0].hall_id;
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,7 @@
|
||||||
|
export interface ClimbingRoute {
|
||||||
|
route_id: string;
|
||||||
|
gym_id: number;
|
||||||
|
name: string;
|
||||||
|
difficulty: string;
|
||||||
|
route_setting_date: Date;
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
/**
|
||||||
|
* Required External Modules and Interfaces
|
||||||
|
*/
|
||||||
|
import express, {Request, Response} from 'express';
|
||||||
|
import {Guid} from 'guid-typescript';
|
||||||
|
import logger from '../../../middleware/logger';
|
||||||
|
import {ClimbingRoute} from './ClimbingRoute.interface';
|
||||||
|
import * as RouteService from './climbingRoutes.service';
|
||||||
|
import {verifyCaptcha} from '../common/VerifyCaptcha';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Router Definition
|
||||||
|
*/
|
||||||
|
export const climbingRoutesRouter = express.Router();
|
||||||
|
|
||||||
|
climbingRoutesRouter.get('/', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const routes: ClimbingRoute[] = await RouteService.findAll();
|
||||||
|
|
||||||
|
res.status(200).send(routes);
|
||||||
|
} catch (e) {
|
||||||
|
let errorGuid = Guid.create().toString();
|
||||||
|
logger.error('Error handling a request: ' + e.message, {reference: errorGuid});
|
||||||
|
res.status(500).send({
|
||||||
|
'status': 'PROCESSING_ERROR',
|
||||||
|
'message': 'Internal Server Error. Try again later.',
|
||||||
|
'reference': errorGuid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
climbingRoutesRouter.get('/:id', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
let route_id = req.params.id;
|
||||||
|
|
||||||
|
const route: ClimbingRoute = await RouteService.findById(route_id);
|
||||||
|
|
||||||
|
res.status(200).send(route);
|
||||||
|
} catch (e) {
|
||||||
|
let errorGuid = Guid.create().toString();
|
||||||
|
logger.error('Error handling a request: ' + e.message, {reference: errorGuid});
|
||||||
|
res.status(500).send({
|
||||||
|
'status': 'PROCESSING_ERROR',
|
||||||
|
'message': 'Internal Server Error. Try again later.',
|
||||||
|
'reference': errorGuid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
climbingRoutesRouter.post('/', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
let gym_id = Number(req.query.gym_id);
|
||||||
|
let name = req.query.name as string;
|
||||||
|
let difficulty = req.query.difficulty as string;
|
||||||
|
let captcha_token = req.query['h-captcha-response'] as string;
|
||||||
|
|
||||||
|
if (isNaN(gym_id) || !name || !difficulty || !captcha_token) {
|
||||||
|
res.status(400).send({'message': 'Missing parameters'});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify captcha
|
||||||
|
if (!await verifyCaptcha(captcha_token)) {
|
||||||
|
res.status(403).send({'message': 'Invalid Captcha. Please try again.'});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let route_id = await RouteService.createRoute(gym_id, name, difficulty);
|
||||||
|
|
||||||
|
res.status(201).send({'route_id': route_id});
|
||||||
|
} catch (e) {
|
||||||
|
let errorGuid = Guid.create().toString();
|
||||||
|
logger.error('Error handling a request: ' + e.message, {reference: errorGuid});
|
||||||
|
res.status(500).send({
|
||||||
|
'status': 'PROCESSING_ERROR',
|
||||||
|
'message': 'Internal Server Error. Try again later.',
|
||||||
|
'reference': errorGuid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,59 @@
|
||||||
|
import {ClimbingRouteRatingDB} from '../ClimbingRouteRating.db';
|
||||||
|
import {ClimbingRoute} from './ClimbingRoute.interface';
|
||||||
|
import random from 'random-words';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches and returns all known climbing routes
|
||||||
|
* @return Promise<ClimbingRoute[]> The climbing routes
|
||||||
|
*/
|
||||||
|
export const findAll = async (): Promise<ClimbingRoute[]> => {
|
||||||
|
let conn = ClimbingRouteRatingDB.getConnection();
|
||||||
|
try {
|
||||||
|
return await conn.query('SELECT route_id, gym_id, name, difficulty, route_setting_date FROM climbing_routes');
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches and returns information about the given route
|
||||||
|
* @param route_id The id of the route
|
||||||
|
* @return Promise<ClimbingRoute> The climbing route
|
||||||
|
*/
|
||||||
|
export const findById = async (route_id: string): Promise<ClimbingRoute> => {
|
||||||
|
let conn = ClimbingRouteRatingDB.getConnection();
|
||||||
|
try {
|
||||||
|
return await conn.query('SELECT route_id, gym_id, name, difficulty, route_setting_date FROM climbing_routes WHERE route_id = ?', route_id);
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new route and returns the id of the created route
|
||||||
|
* @param gym_id The id of the climbing gym that the route belongs to
|
||||||
|
* @param name The name of the climbing route
|
||||||
|
* @param difficulty The difficulty of the climbing route
|
||||||
|
* @return string The id of the created route
|
||||||
|
*/
|
||||||
|
export const createRoute = async (gym_id: number, name: string, difficulty: string): Promise<string> => {
|
||||||
|
let conn = ClimbingRouteRatingDB.getConnection();
|
||||||
|
|
||||||
|
// Generate route id
|
||||||
|
let route_id = '';
|
||||||
|
let randWords = random(3);
|
||||||
|
for (let i = 0; i <= 2; i++) {
|
||||||
|
route_id += randWords[i];
|
||||||
|
if (i < 2) {
|
||||||
|
route_id += '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await conn.query('INSERT INTO climbing_routes (route_id, gym_id, name, difficulty) VALUES (?, ?, ?, ?)', [route_id, gym_id, name, difficulty]);
|
||||||
|
|
||||||
|
return route_id;
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
22
src/models/climbing-route-rating/common/VerifyCaptcha.ts
Normal file
22
src/models/climbing-route-rating/common/VerifyCaptcha.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import * as dotenv from 'dotenv';
|
||||||
|
import * as querystring from 'querystring';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
export const verifyCaptcha = async (captcha_token: string): Promise<boolean> => {
|
||||||
|
var postData = querystring.stringify({
|
||||||
|
response: captcha_token,
|
||||||
|
secret: process.env.HCAPTCHA_SECRET
|
||||||
|
});
|
||||||
|
|
||||||
|
axios.post('https://hcaptcha.com/siteverify', postData)
|
||||||
|
.then(res => {
|
||||||
|
return res.data.success;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
throw(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
|
@ -0,0 +1,6 @@
|
||||||
|
export interface RouteComment {
|
||||||
|
comment_id: number;
|
||||||
|
route_id: string;
|
||||||
|
comment: string;
|
||||||
|
timestamp: Date;
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
import express, {Request, Response} from 'express';
|
||||||
|
import * as CommentService from './routeComments.service';
|
||||||
|
import {Guid} from 'guid-typescript';
|
||||||
|
import logger from '../../../middleware/logger';
|
||||||
|
import {RouteComment} from './RouteComment.interface';
|
||||||
|
import {verifyCaptcha} from '../common/VerifyCaptcha';
|
||||||
|
|
||||||
|
export const routeCommentsRouter = express.Router();
|
||||||
|
|
||||||
|
routeCommentsRouter.get('/by/route/:id', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
let route_id = req.params.id;
|
||||||
|
|
||||||
|
const comments: RouteComment[] = await CommentService.findByRoute(route_id);
|
||||||
|
|
||||||
|
res.status(200).send(comments);
|
||||||
|
} catch (e) {
|
||||||
|
let errorGuid = Guid.create().toString();
|
||||||
|
logger.error('Error handling a request: ' + e.message, {reference: errorGuid});
|
||||||
|
res.status(500).send({
|
||||||
|
'status': 'PROCESSING_ERROR',
|
||||||
|
'message': 'Internal Server Error. Try again later.',
|
||||||
|
'reference': errorGuid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
routeCommentsRouter.post('/', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
let route_id = req.query.route_id as string;
|
||||||
|
let comment = req.query.comment as string;
|
||||||
|
let captcha_token = req.query['h-captcha-response'] as string;
|
||||||
|
|
||||||
|
if (!route_id || !comment || !captcha_token) {
|
||||||
|
res.status(400).send({'message': 'Missing parameters'});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify captcha
|
||||||
|
if (!await verifyCaptcha(captcha_token)) {
|
||||||
|
res.status(403).send({'message': 'Invalid Captcha. Please try again.'});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = await CommentService.createComment(route_id, comment);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
res.status(201).send({'comment_id': result});
|
||||||
|
} else {
|
||||||
|
res.status(500).send({});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
let errorGuid = Guid.create().toString();
|
||||||
|
logger.error('Error handling a request: ' + e.message, {reference: errorGuid});
|
||||||
|
res.status(500).send({
|
||||||
|
'status': 'PROCESSING_ERROR',
|
||||||
|
'message': 'Internal Server Error. Try again later.',
|
||||||
|
'reference': errorGuid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,32 @@
|
||||||
|
import {ClimbingRouteRatingDB} from '../ClimbingRouteRating.db';
|
||||||
|
import {RouteComment} from './RouteComment.interface';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches and returns all comments that belong to the given route
|
||||||
|
* @return Promise<RouteComment[]> The comments
|
||||||
|
*/
|
||||||
|
export const findByRoute = async (route_id: string): Promise<RouteComment[]> => {
|
||||||
|
let conn = ClimbingRouteRatingDB.getConnection();
|
||||||
|
try {
|
||||||
|
return await conn.query('SELECT comment_id, route_id, comment, timestamp FROM route_comments WHERE route_id = ?', route_id);
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new comment and returns the id of the created comment
|
||||||
|
* @param route_id The id of the route to create the comment for
|
||||||
|
* @param comment The comment
|
||||||
|
* @return number The id of the comment
|
||||||
|
*/
|
||||||
|
export const createComment = async (route_id: string, comment: string): Promise<number> => {
|
||||||
|
let conn = ClimbingRouteRatingDB.getConnection();
|
||||||
|
try {
|
||||||
|
let res = await conn.query('INSERT INTO route_comments (route_id, comment) VALUES (?, ?) RETURNING comment_id', [route_id, comment]);
|
||||||
|
|
||||||
|
return res[0].comment_id;
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,6 @@
|
||||||
|
export interface RouteRating {
|
||||||
|
rating_id: number;
|
||||||
|
route_id: string;
|
||||||
|
stars: number;
|
||||||
|
timestamp: Date;
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
import express, {Request, Response} from 'express';
|
||||||
|
import * as RatingService from './routeRatings.service';
|
||||||
|
import {Guid} from 'guid-typescript';
|
||||||
|
import logger from '../../../middleware/logger';
|
||||||
|
import {verifyCaptcha} from '../common/VerifyCaptcha';
|
||||||
|
|
||||||
|
export const routeRatingsRouter = express.Router();
|
||||||
|
|
||||||
|
routeRatingsRouter.get('/by/route/:id', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
let route_id = req.params.id;
|
||||||
|
|
||||||
|
let rating = await RatingService.getStarsForRoute(route_id);
|
||||||
|
|
||||||
|
res.status(200).send({'rating': rating});
|
||||||
|
} catch (e) {
|
||||||
|
let errorGuid = Guid.create().toString();
|
||||||
|
logger.error('Error handling a request: ' + e.message, {reference: errorGuid});
|
||||||
|
res.status(500).send({
|
||||||
|
'status': 'PROCESSING_ERROR',
|
||||||
|
'message': 'Internal Server Error. Try again later.',
|
||||||
|
'reference': errorGuid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
routeRatingsRouter.post('/', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
let route_id = req.query.route_id as string;
|
||||||
|
let stars = Number(req.query.stars);
|
||||||
|
let captcha_token = req.query['h-captcha-response'] as string;
|
||||||
|
|
||||||
|
if (!route_id || isNaN(stars) || !captcha_token) {
|
||||||
|
res.status(400).send({'message': 'Missing parameters'});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify captcha
|
||||||
|
if (!await verifyCaptcha(captcha_token)) {
|
||||||
|
res.status(403).send({'message': 'Invalid Captcha. Please try again.'});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = await RatingService.createRating(route_id, stars);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
res.status(201).send({'rating_id': result});
|
||||||
|
} else {
|
||||||
|
res.status(500).send({});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
let errorGuid = Guid.create().toString();
|
||||||
|
logger.error('Error handling a request: ' + e.message, {reference: errorGuid});
|
||||||
|
res.status(500).send({
|
||||||
|
'status': 'PROCESSING_ERROR',
|
||||||
|
'message': 'Internal Server Error. Try again later.',
|
||||||
|
'reference': errorGuid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,52 @@
|
||||||
|
import {ClimbingRouteRatingDB} from '../ClimbingRouteRating.db';
|
||||||
|
import {RouteRating} from './RouteRating.interface';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches and returns all ratings for the given route
|
||||||
|
* @param route_id The id of the route to get the ratings for
|
||||||
|
* @return Promise<RouteRating[]> The ratings
|
||||||
|
*/
|
||||||
|
export const findByRoute = async (route_id: string): Promise<RouteRating[]> => {
|
||||||
|
let conn = ClimbingRouteRatingDB.getConnection();
|
||||||
|
try {
|
||||||
|
return await conn.query('SELECT rating_id, route_id, stars, timestamp FROM route_ratings WHERE route_id = ?', route_id);
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the median amount of stars the given route got from climbers
|
||||||
|
* @param route_id The id of the route to get the rating for
|
||||||
|
* @return number The median amount of stars with 1 fraction digit.
|
||||||
|
*/
|
||||||
|
export const getStarsForRoute = async (route_id: string): Promise<number> => {
|
||||||
|
let ratings = await findByRoute(route_id);
|
||||||
|
|
||||||
|
let starsSum = 0;
|
||||||
|
let starsAmount = 0;
|
||||||
|
|
||||||
|
for (let rating of ratings) {
|
||||||
|
starsSum += rating.stars;
|
||||||
|
starsAmount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number((starsSum / starsAmount).toFixed(1));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new rating and returns the id
|
||||||
|
* @param route_id The id of the route to be rated
|
||||||
|
* @param stars The amount of stars to be given
|
||||||
|
* @return number The id of the created rating
|
||||||
|
*/
|
||||||
|
export const createRating = async (route_id: string, stars: number): Promise<number> => {
|
||||||
|
let conn = ClimbingRouteRatingDB.getConnection();
|
||||||
|
try {
|
||||||
|
let res = await conn.query('INSERT INTO route_ratings (route_id, stars) VALUES (?, ?) RETURNING rating_id', [route_id, stars]);
|
||||||
|
|
||||||
|
return res[0].comment_id;
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
|
@ -11,6 +11,54 @@ import * as icalgenerator from './icalgenerator/icalgenerator.service';
|
||||||
*/
|
*/
|
||||||
export const raPlaMiddlewareRouter = express.Router();
|
export const raPlaMiddlewareRouter = express.Router();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /rapla-middleware:
|
||||||
|
* get:
|
||||||
|
* summary: Retrieve the adjusted RaPla .ics file
|
||||||
|
* description: Downloads the current .ics file from DHBW servers, removes unwanted events and returns the file.
|
||||||
|
* Required urls can be generated on https://rapla-middleware.p4ddy.com
|
||||||
|
* tags:
|
||||||
|
* - rapla-middleware
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: The .ics file
|
||||||
|
* 400:
|
||||||
|
* description: Wrong parameters, see response body for detailed information
|
||||||
|
* 500:
|
||||||
|
* description: A server error occured. Please try again. If this issue persists, contact the admin.
|
||||||
|
* parameters:
|
||||||
|
* - in: query
|
||||||
|
* name: user
|
||||||
|
* required: true
|
||||||
|
* description: The user from RaPla, can be taken directly from the RaPla link
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* - in: query
|
||||||
|
* name: file
|
||||||
|
* required: true
|
||||||
|
* description: The file from RaPla, can be taken directly from the RaPla link
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* - in: query
|
||||||
|
* name: blockers
|
||||||
|
* required: false
|
||||||
|
* description: Whether to remove blockers from the .ics file
|
||||||
|
* schema:
|
||||||
|
* type: boolean [0,1]
|
||||||
|
* - in: query
|
||||||
|
* name: wahl
|
||||||
|
* required: false
|
||||||
|
* description: The chosen elective module which is not to be filtered out
|
||||||
|
* schema:
|
||||||
|
* type: number
|
||||||
|
* - in: query
|
||||||
|
* name: pflicht
|
||||||
|
* required: false
|
||||||
|
* description: The chosen profile module which is not to be filtered out
|
||||||
|
* schema:
|
||||||
|
* type: number
|
||||||
|
*/
|
||||||
raPlaMiddlewareRouter.get('/', async (req: Request, res: Response) => {
|
raPlaMiddlewareRouter.get('/', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
let user = (req.query.user ?? '').toString();
|
let user = (req.query.user ?? '').toString();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user