Skip to content

Commit

Permalink
Merge pull request #80 from bahnzumberg/uat
Browse files Browse the repository at this point in the history
First Fal PR
  • Loading branch information
martinheppner authored Jan 13, 2024
2 parents 80a088e + 9eb439c commit bb60b57
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 30 deletions.
9 changes: 4 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
"body-parser": "^1.17.1",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"cron": "^1.8.2",
"crypto": "^1.0.1",
"csurf": "^1.11.0",
"es6-promise-pool": "^2.5.0",
Expand All @@ -57,15 +56,15 @@
"mysql": "^2.18.1",
"mysql2": "^3.6.2",
"node-schedule": "^2.1.0",
"p-limit": "^3.1.0",
"p-limit": "^5.0.0",
"path": "^0.12.7",
"pg": "^8.7.3",

"puppeteer": "^16.0.0",
"puppeteer-core": "^16.0.0",
"puppeteer": "^21.7.0",
"puppeteer-core": "^21.7.0",
"request": "^2.88.2",
"request-promise": "^4.2.6",
"sharp": "^0.30.7",
"sharp": "^0.33.1",
"xml-js": "^1.6.11",
"xmlbuilder2": "^3.0.2"
}
Expand Down
5 changes: 5 additions & 0 deletions src/jobs/sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export async function fixTours(){


const deleteFilesOlder30days = (dirPath) => {
!!dirPath && console.log("L76 dirPath = ", dirPath)
// if the directory does not exist, create it
if (!fs.existsSync(dirPath)){
fs.mkdirSync(dirPath);
Expand Down Expand Up @@ -120,6 +121,10 @@ export async function writeKPIs(){

await knex.raw(`DELETE FROM kpi WHERE kpi.name='total_provider';`);
await knex.raw(`INSERT INTO kpi SELECT 'total_provider', COUNT(DISTINCT provider) FROM tour;`);


// Unrelated to the KPIs, but the old disposable links have to be deleted as well
await knex.raw(`DELETE FROM disposible WHERE calendar_date < now() - INTERVAL '10 DAY';`);
}


Expand Down
21 changes: 20 additions & 1 deletion src/routes/tours.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@ import moment from "moment";
import {tourPdf} from "../utils/pdf/tourPdf";
import {getHost, replaceFilePath, round, get_domain_country, get_country_lanuage_from_domain, getAllLanguages } from "../utils/utils";
import { convertDifficulty } from '../utils/dataConversion';
import logger from '../utils/logger';
// import logger from '../utils/logger';
import logger, { create_api_log } from '../utils/logger';

const fs = require('fs');
const path = require('path');

//create log file
create_api_log();


router.get('/', (req, res) => listWrapper(req, res));
router.get('/filter', (req, res) => filterWrapper(req, res));
router.get('/provider/:provider', (req, res) => providerWrapper(req, res));
Expand Down Expand Up @@ -1311,6 +1316,20 @@ const tourPdfWrapper = async (req, res) => {
const tour = await knex('tour').select().where({id: id}).first();
let connection, connectionReturn, connectionReturns = null;

if (!tour){
logger("L1320 : tour not found")
res.status(404).json({success: false});
return;
}else{
if(process.env.NODE_ENV != "production"){
logger("-----------------------------------------------")
logger(`L1324 : tour with id ${id} found`)
logger("-----------------------------------------------")
console.log("-----------------------------------------------")
console.log(`L1324 : tour with id ${id} found`)
console.log("-----------------------------------------------")
}
}
if(!!connectionId){
connection = await knex('fahrplan').select().where({id: connectionId}).first();
}
Expand Down
54 changes: 36 additions & 18 deletions src/utils/logger.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,43 @@
import * as fs from "fs";
const path = require('path');
import * as fs from 'fs';
import path from 'path';

export default function(text) {
let proddevPath = "../";
if(process.env.NODE_ENV != "production"){
proddevPath = "../../";
let lineCount = 0;
const maxAllowed = 100;

export default function (text) {
// const proddevPath = process.env.NODE_ENV !== 'production' ? '../../' : '../';
const proddevPath = process.env.NODE_ENV !== 'production' ? '../../' : '../../';
const filePath = path.join(__dirname, proddevPath, 'logs/api.log');

// Ensure the directory exists
if (!fs.existsSync(path.dirname(filePath))) {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
}
const filePath = path.join(__dirname, proddevPath, "logs/api.log");

fs.appendFileSync(filePath, text + "\n", function (err) {
if (err) throw err;
});
};
if (lineCount < maxAllowed) {
// add log entry
fs.appendFileSync(filePath, text + '\n');
lineCount++;
} else {
// this will erase the current file and add a new "text" line
fs.writeFileSync(filePath, text + '\n');
lineCount = 1;
}
}

// call this function whenever the server starts// for now test it on tours.js
export function create_api_log() {
let proddevPath = "../";
if(process.env.NODE_ENV != "production"){
proddevPath = "../../";
// const proddevPath = process.env.NODE_ENV !== 'production' ? '../../' : '../';
const proddevPath = process.env.NODE_ENV !== 'production' ? '../../' : '../../';
const filePath = path.join(__dirname, proddevPath, 'logs/api.log');

// Make sure directory exists
if (!fs.existsSync(path.dirname(filePath))) {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
}
if (!fs.existsSync(path.join(__dirname, proddevPath, "logs"))){
fs.mkdirSync(path.join(__dirname, proddevPath, "logs"));
fs.createWriteStream(path.join(__dirname, proddevPath, "logs/api.log"));

// Create file when not existent yet
if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, '');
}
}
}
38 changes: 35 additions & 3 deletions src/utils/pdf/tourPdf.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,44 @@ export const createReturnEntries = (entries, connection) => {
}

}
// console.log("L158 toReturn: " + JSON.stringify(toReturn));
return toReturn;
}
//example of variable "entries"
// [
// "08:02 Wien Meidling",
// " | 01:03 Std mit Zug REX9 nach",
// "09:05 Payerbach-Reichenau Bahnhof",
// " = 00:09 Std Umstiegszeit",
// "09:14 Payerbach-Reichenau Bahnhof",
// " | 00:22 Std mit Bus 341 nach",
// "09:36 Höllental Abzw. Weichtalhaus",
// " > 00:02 Std Zustiegsdauer zum Touren-Ausgangspunkt",
// ""];
// Below is sample output from : "L158 toReturn"
// [
// {"time":"08:14","text":" Baden Bahnhof","firstEntry":true},
// {"time":"","image":"http://localhost:8080/public/icons/ic_transport_train.svg","text":" 00:51 Std mit Zug REX9 nach","middleEntry":true,"detailEntry":true},
// {"time":"09:05","text":" Payerbach-Reichenau Bahnhof","middleEntry":true},
// {"time":"","image":"http://localhost:8080/public/icons/ic_shuffle_black.svg","text":" 00:09 Std Umstiegszeit","middleEntry":true,"detailEntry":true},
// {"time":"09:14","text":" Payerbach-Reichenau Bahnhof","middleEntry":true},
// {"time":"","image":"http://localhost:8080/public/icons/ic_transport_bus.svg","text":" 00:22 Std mit Bus 341 nach","middleEntry":true,"detailEntry":true},
// {"time":"09:36","text":" Höllental Abzw. Weichtalhaus","middleEntry":true},
// {"time":"","image":"http://localhost:8080/public/icons/ic_transport_walk.svg","text":" 00:02 Std Zustiegsdauer zum Touren-Ausgangspunkt","middleEntry":true,"detailEntry":true},
// {"time":"00:02","text":" Ankunft bei Tourstart","lastEntry":true}
// ]

// using the new JSON method described in :
// https://github.com/bahnzumberg/hermes/wiki/Connection-and-Return-Description ,
// check the file /zuugle-api/test_fahrplan.json for a sample of data
// from jsonb column 'connection_description_json' inside table 'fahrplan'


export const createConnectionEntries = (entries, connection) => {
let toReturn = [];
if(!!entries && entries.length > 0){
let _entries = entries.filter(e => !!e && e.length > 0);
toReturn.push(getDepartureEntry(_entries[0]));
toReturn.push(getDepartureEntry(_entries[0])); // e.g. "08:02 Wien Meidling" result: ['08:02', 'Wien Meidling']
for(let i=1; i<_entries.length; i++){
let entry = _entries[i];
if((i-1)%2 == 0){
Expand All @@ -184,10 +214,12 @@ export const createConnectionEntries = (entries, connection) => {
}
toReturn.push(getArrivalEntry(`${newStart} Ankunft bei Tourstart`));
}
// console.log("L197 toReturn: " + JSON.stringify(toReturn));

return toReturn;
}

const getDepartureEntry = (entry) => {
const getDepartureEntry = (entry) => { // e.g. entry = "08:02 Wien Meidling"
return {
time: getTimeFromConnectionDescriptionEntry(entry),
text: getTextFromConnectionDescriptionEntry(entry),
Expand Down Expand Up @@ -221,7 +253,7 @@ const getStationEntry = (entry) => {
}
}

export const getTimeFromConnectionDescriptionEntry = (entry) => {
export const getTimeFromConnectionDescriptionEntry = (entry) => { // e.g. entry = "08:02 Wien Meidling"
let _entry = !!entry ? entry.trim() : null;
if(!!_entry && _entry.length > 5){
return _entry.substring(0,5);
Expand Down
6 changes: 3 additions & 3 deletions src/utils/pdf/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ const readFile = (name, contentType = null) => {
}
};

export const getLogoBase64 = () => {
return "data:image/png;base64,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";
}
export const getLogoBase64 = () =>
"data:image/png;base64,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";


// **************
// description 1:
Expand Down

0 comments on commit bb60b57

Please sign in to comment.