API-37: Adding endpoints for climbing route rating app #16
							
								
								
									
										2
									
								
								app.ts
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								app.ts
									
									
									
									
									
								
							| 
						 | 
					@ -9,6 +9,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');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,6 +38,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) => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										3745
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3745
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
					@ -14,6 +14,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",
 | 
				
			||||||
| 
						 | 
					@ -21,6 +22,7 @@
 | 
				
			||||||
    "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",
 | 
				
			||||||
    "winston": "^3.3.3"
 | 
					    "winston": "^3.3.3"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
| 
						 | 
					@ -28,6 +30,7 @@
 | 
				
			||||||
    "@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/winston": "^2.4.4",
 | 
					    "@types/winston": "^2.4.4",
 | 
				
			||||||
    "source-map-support": "^0.5.19",
 | 
					    "source-map-support": "^0.5.19",
 | 
				
			||||||
    "tslint": "^6.1.3",
 | 
					    "tslint": "^6.1.3",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										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.captcha_token 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.captcha_token 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.captcha_token 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';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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.captcha_token as string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!route_id || isNaN(stars) || !captcha_token) {
 | 
				
			||||||
 | 
					            console.log(route_id, 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;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user