Refactor code to match the tslint rules. Add env variables to the build step

parent 8025b02d
stages:
- build
- install
- test
- build
- deploy
build:
stage: build
only:
- master
- fixPipeline
cache:
paths:
- node_modules
npm_install:
stage: install
tags:
- nodejs
script:
- npm install
- npm run build:PROD
artifacts:
paths:
- dist
test:
ts_lint:
stage: test
tags:
- nodejs
script:
- node_modules/tslint/bin/tslint -c tslint.json -p tsconfig.json
npm_test:
stage: test
only:
- master
- fixPipeline
tags:
- nodejs
script:
- npm install
- npm test
build:
stage: build
only:
- master
tags:
- nodejs
script:
- export NODE_ENV='production'
- export ARSNOVA_CLICK_BACKEND_PORT_INTERNAL='3000'
- export ARSNOVA_CLICK_BACKEND_ROUTE_PREFIX='/backend'
- npm run build:PROD
artifacts:
paths:
- dist
deploy:
stage: deploy
only:
- master
- fixPipeline
tags:
- rsync
dependencies:
......
......@@ -3,7 +3,7 @@ import * as compress from 'compression';
import * as busboy from 'connect-busboy';
import * as cors from 'cors';
import * as express from 'express';
import { NextFunction, Request, Response, Router } from 'express';
import { Request, Response, Router } from 'express';
import * as i18n from 'i18n';
import * as logger from 'morgan';
import * as path from 'path';
......@@ -56,12 +56,12 @@ i18n.configure({
logDebugFn: require('debug')('i18n:debug'),
// setting of log level WARN - default to require('debug')('i18n:warn')
logWarnFn: function (msg) {
logWarnFn: msg => {
console.log('warn', msg);
},
// setting of log level ERROR - default to require('debug')('i18n:error')
logErrorFn: function (msg) {
logErrorFn: msg => {
console.log('error', msg);
},
......@@ -108,10 +108,10 @@ class App {
// Configure API endpoints.
private routes(): void {
const router: Router = express.Router();
router.get(`/`, (req: Request, res: Response, next: NextFunction) => {
router.get(`/`, (req: Request, res: Response) => {
res.send(Object.assign({}, staticStatistics, dynamicStatistics()));
});
router.get(`/err`, (req: Request, res: Response, next: NextFunction) => {
router.get(`/err`, () => {
throw new Error('testerror');
});
this._express.use(`${staticStatistics.routePrefix}/`, router);
......@@ -125,7 +125,7 @@ class App {
this._express.use(`${staticStatistics.routePrefix}/api/v1/themes`, themesRouter);
this._express.use(`${staticStatistics.routePrefix}/api/v1/plugin/i18nator`, i18nApiRouter);
this._express.use(function (err, req, res, next) {
this._express.use((err, req, res, next) => {
global.createDump(err);
next();
});
......
declare function require(name: string);
declare function require(name: string): any;
import * as fs from 'fs';
import * as path from 'path';
......@@ -6,18 +6,18 @@ import { staticStatistics } from './statistics';
const homedir = require('os').homedir();
function createPath(basePath, pathRelativeToBase) {
function createPath(basePath, pathRelativeToBase): void {
const exists = fs.existsSync(path.join(basePath, pathRelativeToBase));
if (!exists) {
fs.mkdir(path.join(basePath, pathRelativeToBase), (err) => console.log('app_bootstrap:createPathError', err));
}
}
function createAssetPath(pathRelativeToBase) {
function createAssetPath(pathRelativeToBase): void {
createPath(staticStatistics.pathToAssets, pathRelativeToBase);
}
function createCachePath(pathRelativeToBase) {
function createCachePath(pathRelativeToBase): void {
createPath(staticStatistics.pathToCache, pathRelativeToBase);
}
......
import * as sha256 from 'crypto-js/sha256';
import { IAnswerOption } from 'arsnova-click-v2-types/src/answeroptions/interfaces';
import { IQuestion } from 'arsnova-click-v2-types/src/questions/interfaces';
import * as Hex from 'crypto-js/enc-hex';
import * as sha256 from 'crypto-js/sha256';
import * as fs from 'fs';
import * as request from 'request';
import {DatabaseTypes, DbDAO} from '../db/DbDAO';
import {IQuestion} from 'arsnova-click-v2-types/src/questions/interfaces';
import {staticStatistics} from '../statistics';
import {IAnswerOption} from 'arsnova-click-v2-types/src/answeroptions/interfaces';
import { DatabaseTypes, DbDAO } from '../db/DbDAO';
import { staticStatistics } from '../statistics';
export const assetsUrlRegex = /[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/gi;
export function MatchTextToAssetsDb(value: string) {
export function MatchTextToAssetsDb(value: string): void {
const acceptedFileTypes = [/image\/*/];
const matchedValue = value.match(assetsUrlRegex);
if (matchedValue) {
......@@ -17,7 +17,7 @@ export function MatchTextToAssetsDb(value: string) {
const digest = Hex.stringify(sha256(matchedValueElement));
const cachePath = `${staticStatistics.pathToCache}/${digest}`;
if (fs.existsSync(cachePath)) {
DbDAO.create(DatabaseTypes.assets, {url: matchedValueElement, digest, path: cachePath}, matchedValueElement.replace(/\./g, '_'));
DbDAO.create(DatabaseTypes.assets, { url: matchedValueElement, digest, path: cachePath }, matchedValueElement.replace(/\./g, '_'));
return;
}
if (!matchedValueElement.startsWith('http')) {
......@@ -28,7 +28,9 @@ export function MatchTextToAssetsDb(value: string) {
const contentType = response.headers['content-type'];
const hasContentTypeMatched = acceptedFileTypes.some((contentTypeRegex) => contentType.match(contentTypeRegex));
if (hasContentTypeMatched) {
DbDAO.create(DatabaseTypes.assets, {url: matchedValueElement, digest, path: cachePath}, matchedValueElement.replace(/\./g, '_'));
DbDAO.create(DatabaseTypes.assets, {
url: matchedValueElement, digest, path: cachePath,
}, matchedValueElement.replace(/\./g, '_'));
} else {
req.abort();
fs.exists(cachePath, (exists: boolean) => {
......@@ -45,7 +47,7 @@ export function MatchTextToAssetsDb(value: string) {
}
}
export function parseCachedAssetQuiz(cacheAwareQuestions: Array<IQuestion>) {
export function parseCachedAssetQuiz(cacheAwareQuestions: Array<IQuestion>): void {
const assetsCache = DbDAO.read(DatabaseTypes.assets);
const assetsBasePath = `${staticStatistics.rewriteAssetCacheUrl}/lib/cache/quiz/assets`;
cacheAwareQuestions.forEach((question: IQuestion) => {
......
......@@ -5,7 +5,7 @@ const options: cors.CorsOptions = {
credentials: true,
methods: 'GET,HEAD,OPTIONS,PUT,PATCH,POST,DELETE',
origin: true,
preflightContinue: false
preflightContinue: false,
};
export default options;
......@@ -4,7 +4,7 @@ export class CasDAO {
private static readonly casData = {};
public static add(ticket: string, data: ICasData) {
public static add(ticket: string, data: ICasData): void {
this.casData[ticket] = data;
}
......@@ -12,7 +12,7 @@ export class CasDAO {
return this.casData[ticket];
}
public static remove(ticket: string) {
public static remove(ticket: string): void {
delete this.casData[ticket];
}
......
declare function require(name: string);
declare function require(name: string): any;
import * as fs from 'fs';
import * as lowdb from 'lowdb';
......@@ -7,8 +7,7 @@ import * as path from 'path';
import { createHomePath } from '../app_bootstrap';
export enum DatabaseTypes {
quiz = 'quiz',
assets = 'assets'
quiz = 'quiz', assets = 'assets'
}
const homedir = require('os').homedir();
......@@ -90,7 +89,7 @@ export class DbDAO {
return DbDAO.instance;
}
private static initDb(type: DatabaseTypes, initialValue: any) {
private static initDb(type: DatabaseTypes, initialValue: any): void {
DbDAO.db.set(type, initialValue).write();
}
}
......
......@@ -11,7 +11,7 @@ export class I18nDAO {
return I18nDAO.cache;
}
public static reloadCache() {
public static reloadCache(): void {
Object.keys(I18nDAO.cache).forEach(projectName => {
console.log(``);
console.log(`------- Building cache for '${projectName}' -------`);
......@@ -34,9 +34,7 @@ export class I18nDAO {
console.log(`* Fetching unused keys`);
const unusedKeysStart = new Date().getTime();
I18nDAO.cache[projectName].unused = I18nDAO.getUnusedKeys({
params: {},
projectAppLocation: projectAppLocation[projectName],
i18nFileBaseLocation: i18nFileBaseLocation[projectName],
params: {}, projectAppLocation: projectAppLocation[projectName], i18nFileBaseLocation: i18nFileBaseLocation[projectName],
});
const unusedKeysEnd = new Date().getTime();
console.log(`-- Done. Took ${unusedKeysEnd - unusedKeysStart}ms`);
......@@ -54,7 +52,7 @@ export class I18nDAO {
console.log(`Cache built successfully`);
}
public static buildKeys({ root, dataNode, langRef, langData }) {
public static buildKeys({ root, dataNode, langRef, langData }): void {
if (!dataNode) {
return;
......@@ -80,7 +78,7 @@ export class I18nDAO {
}
}
public static getUnusedKeys(req) {
public static getUnusedKeys(req): object {
const result = {};
const fileNames = I18nDAO.fromDir(req.projectAppLocation, /\.(ts|html|js)$/);
const langRefs = req.params.langRef ? [req.params.langRef] : availableLangs;
......@@ -90,31 +88,33 @@ export class I18nDAO {
const i18nFileContent = JSON.parse(fs.readFileSync(path.join(req.i18nFileBaseLocation, `${langRefs[i]}.json`)).toString('UTF-8'));
const objectPaths = I18nDAO.objectPath(i18nFileContent);
objectPaths.forEach((i18nPath => {
let matched = false;
fileNames.forEach(filename => {
if (matched) {
return;
objectPaths.forEach((
i18nPath => {
let matched = false;
fileNames.forEach(filename => {
if (matched) {
return;
}
const fileContent = fs.readFileSync(filename).toString('UTF-8');
matched = fileContent.indexOf(i18nPath) > -1;
});
if (!matched) {
result[langRefs[i]].push(i18nPath);
}
const fileContent = fs.readFileSync(filename).toString('UTF-8');
matched = fileContent.indexOf(i18nPath) > -1;
});
if (!matched) {
result[langRefs[i]].push(i18nPath);
}
}));
));
}
return result;
}
public static getBranch(req) {
public static getBranch(req): string {
const command = `git branch 2> /dev/null | sed -e '/^[^*]/d' -e "s/* \\(.*\\)/\\1/"`;
const child = spawnSync('/bin/sh', [`-c`, command], { cwd: req.projectGitLocation });
return child.stdout.toString().replace('\n', '');
}
public static createObjectFromKeys({ data, result }) {
public static createObjectFromKeys({ data, result }): void {
for (const langRef in result) {
if (result.hasOwnProperty(langRef)) {
......@@ -139,7 +139,7 @@ export class I18nDAO {
}
}
private static fromDir(startPath, filter) {
private static fromDir(startPath, filter): Array<string> {
if (!fs.existsSync(startPath)) {
console.log('no dir ', startPath);
return;
......@@ -160,7 +160,7 @@ export class I18nDAO {
return result;
}
private static objectPath(obj, currentPath = '') {
private static objectPath(obj, currentPath = ''): Array<string> {
let localCurrentPath = currentPath;
let result = [];
......@@ -179,7 +179,7 @@ export class I18nDAO {
return result;
}
private static isString(data) {
private static isString(data): boolean {
return typeof data === 'string';
}
}
export class MathjaxDAO {
private static readonly mathjaxCache: Object = {};
private static readonly mathjaxCache: object = {};
public static createDump(): {} {
return MathjaxDAO.mathjaxCache;
}
public static getAllPreviouslyRenderedData(plainData: string): Object {
public static getAllPreviouslyRenderedData(plainData: string): object {
return MathjaxDAO.mathjaxCache[plainData];
}
......
......@@ -48,7 +48,9 @@ export class QuizManagerDAO {
public static initActiveQuiz(quiz: IQuestionGroup): IActiveQuiz {
const name: string = QuizManagerDAO.normalizeQuizName(quiz.hashtag);
if (!this.activeQuizzes[name] || !(this.activeQuizzes[name] instanceof ActiveQuizItemPlaceholder)) {
if (!this.activeQuizzes[name] || !(
this.activeQuizzes[name] instanceof ActiveQuizItemPlaceholder
)) {
console.log('trying to init an active quiz which is not inactive');
return;
}
......@@ -128,12 +130,14 @@ export class QuizManagerDAO {
public static getLastPersistedNumberForData(data): number {
let maxNumber = 0;
data.forEach((demoQuizName => {
const currentNumber = parseInt(demoQuizName.substring(demoQuizName.lastIndexOf(' '), demoQuizName.length), 10);
if (currentNumber > maxNumber) {
maxNumber = currentNumber;
data.forEach((
demoQuizName => {
const currentNumber = parseInt(demoQuizName.substring(demoQuizName.lastIndexOf(' '), demoQuizName.length), 10);
if (currentNumber > maxNumber) {
maxNumber = currentNumber;
}
}
}));
));
return maxNumber;
}
......@@ -162,8 +166,8 @@ export class QuizManagerDAO {
public static getAllPersistedAbcdQuizzesByLength(length: number): String[] {
return Object.keys(this.activeQuizzes).filter((value: string) => {
const name: string = QuizManagerDAO.normalizeQuizName(value);
return QuizManagerDAO.checkABCDOrdering(this.activeQuizzes[name].name) &&
this.activeQuizzes[name].originalObject.questionList[0].answerOptionList.length === length;
return QuizManagerDAO.checkABCDOrdering(this.activeQuizzes[name].name)
&& this.activeQuizzes[name].originalObject.questionList[0].answerOptionList.length === length;
});
}
......@@ -177,15 +181,13 @@ export class QuizManagerDAO {
lobby: legacyQuiz.configuration.music.lobbyTitle,
countdownRunning: legacyQuiz.configuration.music.countdownRunningTitle,
countdownEnd: legacyQuiz.configuration.music.countdownEndTitle,
},
volumeConfig: {
}, volumeConfig: {
global: legacyQuiz.configuration.music.lobbyVolume,
lobby: legacyQuiz.configuration.music.lobbyVolume,
countdownRunning: legacyQuiz.configuration.music.countdownRunningVolume,
countdownEnd: legacyQuiz.configuration.music.countdownEndVolume,
useGlobalVolume: legacyQuiz.configuration.music.isUsingGlobalVolume,
},
enabled: {
}, enabled: {
lobby: legacyQuiz.configuration.music.lobbyEnabled,
countdownRunning: legacyQuiz.configuration.music.countdownRunningEnabled,
countdownEnd: legacyQuiz.configuration.music.countdownEndEnabled,
......
import { IActiveQuiz } from 'arsnova-click-v2-types/src/common';
import { IExcelWorkbook, IExcelWorksheet } from 'arsnova-click-v2-types/src/excel.interfaces';
import * as xlsx from 'excel4node';
import { Response } from 'express';
import * as MessageFormat from 'messageformat';
import { MultipleChoiceExcelWorksheet } from './excel-worksheet-choice-multiple';
import { SingleChoiceExcelWorksheet } from './excel-worksheet-choice-single';
import { FreeTextExcelWorksheet } from './excel-worksheet-freetext';
import { RangedExcelWorksheet } from './excel-worksheet-ranged';
import { SummaryExcelWorksheet } from './excel-worksheet-summary';
import { SurveyExcelWorksheet } from './excel-worksheet-survey';
import {ExcelTheme} from './lib/excel_default_styles';
import {IActiveQuiz} from 'arsnova-click-v2-types/src/common';
import {IExcelWorkbook, IExcelWorksheet} from 'arsnova-click-v2-types/src/excel.interfaces';
import {SummaryExcelWorksheet} from './excel-worksheet-summary';
import {SingleChoiceExcelWorksheet} from './excel-worksheet-choice-single';
import {MultipleChoiceExcelWorksheet} from './excel-worksheet-choice-multiple';
import {RangedExcelWorksheet} from './excel-worksheet-ranged';
import {SurveyExcelWorksheet} from './excel-worksheet-survey';
import {FreeTextExcelWorksheet} from './excel-worksheet-freetext';
import {Response} from 'express';
import { ExcelTheme } from './lib/excel_default_styles';
export class ExcelWorkbook implements IExcelWorkbook {
get theme(): ExcelTheme {
return this._theme;
}
protected _worksheets: Array<IExcelWorksheet> = [];
private readonly _wb: xlsx.Workbook;
private readonly _theme: ExcelTheme;
private readonly _translation: string;
private readonly _mf: MessageFormat;
private readonly _quiz: IActiveQuiz;
protected _worksheets: Array<IExcelWorksheet> = [];
constructor(
{themeName, quiz, translation, mf}: {themeName: string, quiz: IActiveQuiz, translation: string, mf: MessageFormat}
) {
constructor({ themeName, quiz, translation, mf }: { themeName: string, quiz: IActiveQuiz, translation: string, mf: MessageFormat }) {
this._wb = new xlsx.Workbook({
jszip: {
compression: 'DEFLATE'
},
defaultFont: {
size: 12,
name: 'Calibri',
color: 'FF000000'
},
dateFormat: 'd.m.yyyy'
compression: 'DEFLATE',
}, defaultFont: {
size: 12, name: 'Calibri', color: 'FF000000',
}, dateFormat: 'd.m.yyyy',
});
this._theme = new ExcelTheme(themeName);
this._translation = translation;
......@@ -47,13 +40,17 @@ export class ExcelWorkbook implements IExcelWorkbook {
this.generateSheets();
}
public write(name: string, handler?: Response | Function): void {
this._wb.write(name, handler);
}
public writeToBuffer(): any {
return this._wb.writeToBuffer();
}
private generateSheets(): void {
const worksheetOptions: any = {
wb: this._wb,
theme: this._theme,
translation: this._translation,
quiz: this._quiz,
mf: this._mf
wb: this._wb, theme: this._theme, translation: this._translation, quiz: this._quiz, mf: this._mf,
};
this._worksheets.push(new SummaryExcelWorksheet(worksheetOptions));
......@@ -84,12 +81,4 @@ export class ExcelWorkbook implements IExcelWorkbook {
}
}
}
public write(name: string, handler?: Response | Function): void {
this._wb.write(name, handler);
}
public writeToBuffer(): any {
return this._wb.writeToBuffer();
}
}
import {INickname, IQuizResponse} from 'arsnova-click-v2-types/src/common';
import {IQuestion} from 'arsnova-click-v2-types/src/questions/interfaces';
import {calculateNumberOfAnswers} from './lib/excel_function_library';
import {IAnswerOption} from 'arsnova-click-v2-types/src/answeroptions/interfaces';
import {IExcelWorksheet} from 'arsnova-click-v2-types/src/excel.interfaces';
import {ExcelWorksheet} from './excel-worksheet';
import { IAnswerOption } from 'arsnova-click-v2-types/src/answeroptions/interfaces';
import { INickname, IQuizResponse } from 'arsnova-click-v2-types/src/common';
import { IExcelWorksheet } from 'arsnova-click-v2-types/src/excel.interfaces';
import { IQuestion } from 'arsnova-click-v2-types/src/questions/interfaces';
import { ExcelWorksheet } from './excel-worksheet';
import { calculateNumberOfAnswers } from './lib/excel_function_library';
export class MultipleChoiceExcelWorksheet extends ExcelWorksheet implements IExcelWorksheet {
private _isCasRequired = this.quiz.originalObject.sessionConfig.nicks.restrictToCasLogin;
private _question: IQuestion;
private _questionIndex: number;
private readonly _questionIndex: number;
constructor({ wb, theme, translation, quiz, mf, questionIndex }) {
super({ theme, translation, quiz, mf, questionIndex });
this._ws = wb.addWorksheet(`${mf('export.question')} ${questionIndex + 1}`, this._options);
this._questionIndex = questionIndex;
this._question = this.quiz.originalObject.questionList[questionIndex];
this.formatSheet();
this.addSheetData();
}
public formatSheet(): void {
const defaultStyles = this._theme.getStyles();
......@@ -23,13 +32,10 @@ export class MultipleChoiceExcelWorksheet extends ExcelWorksheet implements IExc
const columnsToFormat: number = answerList.length + 1 < minColums ? minColums : answerList.length + 1;
const answerCellStyle: Object = {
alignment: {
wrapText: true,
horizontal: 'center',
vertical: 'center'
wrapText: true, horizontal: 'center', vertical: 'center',
}, font: {
color: 'FFFFFFFF',
},
font: {
color: 'FFFFFFFF'
}
};
this.ws.row(1).setHeight(20);
......@@ -42,8 +48,8 @@ export class MultipleChoiceExcelWorksheet extends ExcelWorksheet implements IExc
this.ws.cell(2, 1, 2, columnsToFormat).style(defaultStyles.exportedAtRowStyle);
this.ws.cell(2, 2, 2, columnsToFormat).style({
alignment: {
horizontal: 'center'
}
horizontal: 'center',
},
});
this.ws.cell(4, 1).style(defaultStyles.questionCellStyle);
......@@ -52,49 +58,45 @@ export class MultipleChoiceExcelWorksheet extends ExcelWorksheet implements IExc
this.ws.cell(4, targetColumn).style(Object.assign({}, answerCellStyle, {
border: {
right: {
style: (targetColumn <= answerList.length) ? 'thin' : 'none',
color: 'black'
}
style: (
targetColumn <= answerList.length
) ? 'thin' : 'none', color: 'black',
},
}, fill: {
type: 'pattern', patternType: 'solid', fgColor: answerList[j].isCorrect ? 'FF008000' : 'FFB22222',
},
fill: {
type: 'pattern',
patternType: 'solid',
fgColor: answerList[j].isCorrect ? 'FF008000' : 'FFB22222'
}
}));
}
this.ws.cell(6, 1, this.responsesWithConfidenceValue.length > 0 ? 8 : 7, columnsToFormat).style(defaultStyles.statisticsRowStyle);
this.ws.cell(6, 2, this.responsesWithConfidenceValue.length > 0 ? 8 : 7, columnsToFormat).style({
alignment: {
horizontal: 'center'
}
horizontal: 'center',
},