diff --git a/api/src/index.ts b/api/src/index.ts index b466c42..45acba6 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -4,7 +4,7 @@ import { existsSync } from 'fs'; // eslint-disable-next-line import/no-duplicates import * as express from 'express'; // eslint-disable-next-line import/no-duplicates -import { Request, Response, NextFunction } from 'express'; +import { type Request, type Response, type NextFunction } from 'express'; import * as bodyParser from 'body-parser'; import * as morgan from 'morgan'; @@ -64,9 +64,9 @@ app.get('/sitemap.xml', async (req, res) => { // frontend if (existsSync(join(__dirname, '../frontend'))) { // eslint-disable-next-line @typescript-eslint/no-var-requires - const assetsManifest: { [originalFilePath: string]: string } = require(join(__dirname, '../frontend', 'assets-manifest.json')); + const assetsManifest: Record = require(join(__dirname, '../frontend', 'assets-manifest.json')); - const cacheHeaders: { [path: string]: string|undefined } = {}; + const cacheHeaders: Record = {}; for (const originalFilePath in assetsManifest) { if (Object.prototype.hasOwnProperty.call(assetsManifest, originalFilePath)) { diff --git a/api/src/models/container.model.ts b/api/src/models/container.model.ts index 1a282a6..202b6ab 100644 --- a/api/src/models/container.model.ts +++ b/api/src/models/container.model.ts @@ -40,17 +40,17 @@ export default class Container extends Model { @Default(null) @AllowNull @Column(DataType.TEXT) - public headerColor: string|null; + public headerColor: string | null; @Default(null) @AllowNull @Column(DataType.TEXT) - public headerImage: string|null; + public headerImage: string | null; @Default(null) @AllowNull @Column(DataType.TEXT) - public pinnedImage: string|null; + public pinnedImage: string | null; @Default(0) @Column(DataType.INTEGER) @@ -67,13 +67,13 @@ export default class Container extends Model { @AfterUpdate @AfterCreate @AfterDestroy - static updatePageUpdatedAt(instance: Container): Promise { - return (Page.findByPk(instance.pageSlug) as Promise).then((page): Promise => { - if (page === null) return Promise.resolve(); + static async updatePageUpdatedAt (instance: Container): Promise { + await (Page.findByPk(instance.pageSlug)).then(async (page): Promise => { + if (page === null) { await Promise.resolve(); return; } page.changed('updatedAt', true); - return page.save(); + return await page.save(); }); } } diff --git a/api/src/utils/EventsService.ts b/api/src/utils/EventsService.ts index 1f5462b..f246c30 100644 --- a/api/src/utils/EventsService.ts +++ b/api/src/utils/EventsService.ts @@ -2,10 +2,10 @@ import * as equals from 'fast-deep-equal'; import fetch from 'node-fetch'; export interface ArmaEvent { - date: Date; - title: string; - url: string; - attendance: [number, number]; + date: Date + title: string + url: string + attendance: [number, number] } export class ArmaEventsService { @@ -13,21 +13,21 @@ export class ArmaEventsService { private static readonly FORUM_URI = 'https://forum.gruppe-adler.de'; private static readonly TOPIC_TITLE_REGEX = /^\d{4}-\d{2}-\d{2}\s*,(\s*\w+\s*,)?\s*/i; - private static instance: ArmaEventsService|null = null; + private static instance: ArmaEventsService | null = null; private cachedEvents: ArmaEvent[] = []; private lastModified: Date = new Date(0); // this constructor is actually important to make sure it is private (singleton pattern) // eslint-disable-next-line no-useless-constructor, @typescript-eslint/no-empty-function - private constructor() { + private constructor () { this.cacheEvents(); // fetch new events every half an hour setInterval(this.cacheEvents.bind(this), 1000 * 60 * 30); } - public static getInstance(): ArmaEventsService { + public static getInstance (): ArmaEventsService { if (this.instance === null) { this.instance = new ArmaEventsService(); } @@ -35,11 +35,11 @@ export class ArmaEventsService { return this.instance; } - public async getEvents(): Promise<{ events: ArmaEvent[], lastModified: Date }> { + public async getEvents (): Promise<{ events: ArmaEvent[], lastModified: Date }> { return { events: this.cachedEvents, lastModified: this.lastModified }; } - public async cacheEvents(): Promise { + public async cacheEvents (): Promise { const events = await this.fetchEvents(); if (!equals(this.cachedEvents, events)) { @@ -48,12 +48,12 @@ export class ArmaEventsService { } } - private async fetchEvents(): Promise { + private async fetchEvents (): Promise { const response = await fetch(`${ArmaEventsService.FORUM_URI}/api/events/cid-3`); if (!response.ok) { throw new Error(`Error while trying to fetch events. Forum API responded with status code ${response.status}.`); } - const { topics } = await response.json() as { topics: Array<{ slug: string; titleRaw: string; tid: number; deleted: 1|0; timestamp: number }> }; + const { topics } = await response.json() as { topics: Array<{ slug: string, titleRaw: string, tid: number, deleted: 1 | 0, timestamp: number }> }; const rawEvents: Array & { tid: number }> = []; for (const topic of topics) { @@ -83,9 +83,9 @@ export class ArmaEventsService { const firstFutureEvent = sortedEvents.findIndex(e => ArmaEventsService.isInFuture(e.date)) + 1; const filteredEvents = sortedEvents.slice(0, firstFutureEvent > 0 ? firstFutureEvent : sortedEvents.length).reverse().slice(0, 10); - const promises = filteredEvents.map(({ tid, ...rest }) => ArmaEventsService.fetchAttendance(tid).then((attendance): ArmaEvent => ({ ...rest, attendance }))); + const promises = filteredEvents.map(async ({ tid, ...rest }) => await ArmaEventsService.fetchAttendance(tid).then((attendance): ArmaEvent => ({ ...rest, attendance }))); - return Promise.all(promises); + return await Promise.all(promises); } /** @@ -118,7 +118,7 @@ export class ArmaEventsService { if (!response.ok) { throw new Error(`Error while trying to fetch attendance for topic ${tid}. Forum API responded with status code ${response.status}.`); } - const { attendants } = await response.json() as { attendants: Array<{ probability: 0|0.5|1 }> }; + const { attendants } = await response.json() as { attendants: Array<{ probability: 0 | 0.5 | 1 }> }; let firm = 0; let maybe = 0; diff --git a/api/src/utils/ResponseError.ts b/api/src/utils/ResponseError.ts index 3e229e4..2a03813 100644 --- a/api/src/utils/ResponseError.ts +++ b/api/src/utils/ResponseError.ts @@ -3,7 +3,7 @@ export default class ReponseError extends Error { public status: number; - constructor(status: number, msg?: string) { + constructor (status: number, msg?: string) { super(msg); this.status = status; } diff --git a/api/src/utils/UploadService.ts b/api/src/utils/UploadService.ts index 7c5fa49..ba91d25 100644 --- a/api/src/utils/UploadService.ts +++ b/api/src/utils/UploadService.ts @@ -1,7 +1,7 @@ import { existsSync, mkdirSync, writeFileSync, unlinkSync } from 'fs'; export class UploadService { - private static instance: UploadService|null = null; + private static instance: UploadService | null = null; public static readonly UPLOADS_BASE_PATH = 'data/uploads'; private static readonly MINE_TYPES = new Map([ @@ -13,18 +13,18 @@ export class UploadService { ['image/bmp', 'bmp'] ]); - public static get ALLOWED_MIME_TYPES(): string[] { + public static get ALLOWED_MIME_TYPES (): string[] { return Array.from(this.MINE_TYPES.keys()); } - private constructor() { + private constructor () { // create directory if it doesn't exist already if (!existsSync(UploadService.UPLOADS_BASE_PATH)) { mkdirSync(UploadService.UPLOADS_BASE_PATH); } } - public static getInstance(): UploadService { + public static getInstance (): UploadService { if (this.instance === null) { this.instance = new UploadService(); } @@ -32,7 +32,7 @@ export class UploadService { return this.instance; } - public saveFile(buffer: Buffer, mimeType: string): string { + public saveFile (buffer: Buffer, mimeType: string): string { if (!UploadService.MINE_TYPES.has(mimeType.toLowerCase())) throw new Error(`MIME type '${mimeType}' is not allowed`); const fileEnding: string = UploadService.MINE_TYPES.get(mimeType.toLowerCase()); @@ -46,13 +46,13 @@ export class UploadService { return `${name}.${fileEnding}`; } - public removeFile(name: string): void { + public removeFile (name: string): void { if (!existsSync(`${UploadService.UPLOADS_BASE_PATH}/${name}`)) return; unlinkSync(`${UploadService.UPLOADS_BASE_PATH}/${name}`); } - private static randomName(): string { + private static randomName (): string { return (Math.random().toString(36).substr(2)) + (Math.random().toString(36).substr(2)); } } diff --git a/api/src/utils/express.ts b/api/src/utils/express.ts index fb91f89..72b2c1a 100644 --- a/api/src/utils/express.ts +++ b/api/src/utils/express.ts @@ -1,4 +1,4 @@ -import { Request, Response, NextFunction, RequestHandler } from 'express'; +import { type Request, type Response, type NextFunction, type RequestHandler } from 'express'; import { validationResult } from 'express-validator'; import ResponseError from './ResponseError'; @@ -15,7 +15,7 @@ export const globalErrorHandler = (err: unknown, req: Request, res: Response, ne } let status = 500; - let msg: undefined|string; + let msg: undefined | string; if (err instanceof ResponseError) { status = err.status; @@ -33,7 +33,7 @@ export const globalErrorHandler = (err: unknown, req: Request, res: Response, ne } }; -export function return422(req: Request, res: Response, next: NextFunction): void { +export function return422 (req: Request, res: Response, next: NextFunction): void { const errors = validationResult(req); if (!errors.isEmpty()) { res.status(422).json({ errors: errors.array() }); diff --git a/api/src/utils/sitemap.ts b/api/src/utils/sitemap.ts index 1d638f2..c12f37a 100644 --- a/api/src/utils/sitemap.ts +++ b/api/src/utils/sitemap.ts @@ -3,7 +3,7 @@ import { SitemapStream, streamToPromise } from 'sitemap'; import { createGzip } from 'zlib'; import { Page } from '../models'; -let cachedSitemap: Buffer|null = null; +let cachedSitemap: Buffer | null = null; const BUILD_TIME_PATH = '/build-date.txt'; let buildTime = new Date(0); @@ -22,11 +22,11 @@ Page.addHook('afterCreate', cacheSitemap); Page.addHook('afterDestroy', cacheSitemap); Page.addHook('afterUpdate', cacheSitemap); -async function cacheSitemap(): Promise { +async function cacheSitemap (): Promise { cachedSitemap = await generateSitemap(); } -async function generateSitemap(): Promise { +async function generateSitemap (): Promise { const smStream = new SitemapStream({ hostname: 'https://gruppe-adler.de/' }); const pipeline = smStream.pipe(createGzip()); @@ -43,10 +43,10 @@ async function generateSitemap(): Promise { const prom = streamToPromise(pipeline); smStream.end(); - return prom; + return await prom; } -export async function getSitemap(): Promise { +export async function getSitemap (): Promise { await initialCachePromise; return cachedSitemap; } diff --git a/api/src/utils/sso.ts b/api/src/utils/sso.ts index d54f85c..8d4d279 100644 --- a/api/src/utils/sso.ts +++ b/api/src/utils/sso.ts @@ -1,4 +1,4 @@ -import { Request, Response, NextFunction } from 'express/index'; +import { type Request, type Response, type NextFunction } from 'express/index'; import fetch from 'node-fetch'; import { globalErrorHandler } from './express'; @@ -8,11 +8,11 @@ import ReponseError from './ResponseError'; const config = require('../../config/config.json'); interface SSOUser { - admin: boolean; - groups: { tag: string; }[]; + admin: boolean + groups: Array<{ tag: string }> } -async function fetchUser(token: string): Promise { +async function fetchUser (token: string): Promise { const res = await fetch(`${config.sso.domain}/api/v1/graphql`, { method: 'POST', headers: { @@ -37,7 +37,7 @@ async function fetchUser(token: string): Promise { if (!res.ok) throw new ReponseError(401); - const json = await res.json() as { data: { authenticate: SSOUser|null } }; + const json = await res.json() as { data: { authenticate: SSOUser | null } }; return json.data.authenticate; } @@ -58,7 +58,7 @@ async function validateToken (token: string) { if (!admin && !isInGroup) throw new ReponseError(403); } -function extractToken(req: Request): string { +function extractToken (req: Request): string { if (req.cookies[config.sso.cookiename]) { return req.cookies[config.sso.cookiename]; } @@ -73,7 +73,7 @@ function extractToken(req: Request): string { throw new Error('Couldn\'t find token to extract'); } -export async function ssoCheckAuthorized(req: Request, res: Response, next: NextFunction): Promise { +export async function ssoCheckAuthorized (req: Request, res: Response, next: NextFunction): Promise { let token: string; try { token = extractToken(req); diff --git a/api/src/v1/routes/container.router.ts b/api/src/v1/routes/container.router.ts index f936a49..7b9dc5b 100644 --- a/api/src/v1/routes/container.router.ts +++ b/api/src/v1/routes/container.router.ts @@ -15,7 +15,7 @@ const defaultContainerRules = [ body('index').optional().isInt().toInt() ]; -type OptionalContainerFields = Partial>; +type OptionalContainerFields = Partial>; const containerRouter = Router(); @@ -41,7 +41,7 @@ containerRouter.put('/:id', [ ], wrapAsync(async (req, res) => { const { id, ...updateData } = matchedData(req) as OptionalContainerFields & Partial> & Pick; - const container: Container|null = await Container.findByPk(id); + const container: Container | null = await Container.findByPk(id); if (container === null) { throw new ReponseError(404, `Container with id '${id}' not found.`); @@ -59,7 +59,7 @@ containerRouter.delete('/:id', [ ], wrapAsync(async (req, res) => { const { id } = matchedData(req) as { id: number }; - const container: Container|null = await Container.findByPk(id); + const container: Container | null = await Container.findByPk(id); if (container === null) { throw new ReponseError(404, `Container with id '${id}' not found.`); diff --git a/api/src/v1/routes/page.router.ts b/api/src/v1/routes/page.router.ts index c82694a..0726e77 100644 --- a/api/src/v1/routes/page.router.ts +++ b/api/src/v1/routes/page.router.ts @@ -10,7 +10,7 @@ const pageRouter = Router(); pageRouter.get('/*', wrapAsync(async (req, res) => { const slug = req.path; - const page = await Page.findByPk(slug) as Page|null; + const page = await Page.findByPk(slug); if (page === null) { throw new ReponseError(404, `Page with slug '${slug}' not found.`); @@ -31,9 +31,9 @@ pageRouter.put('/*', [ ], wrapAsync(async (req, res) => { const slug = req.path; - const updateData = matchedData(req) as Partial>; + const updateData = matchedData(req) as Partial>; - const page: Page|null = await Page.findByPk(slug); + const page: Page | null = await Page.findByPk(slug); if (page === null) { throw new ReponseError(404, `Page with slug '${slug}' not found.`); @@ -53,7 +53,7 @@ pageRouter.post('/', [ body('priority').optional().isFloat({ min: 0, max: 1 }).toFloat(), return422 ], wrapAsync(async (req, res) => { - const data = matchedData(req) as Partial> & Pick; + const data = matchedData(req) as Partial> & Pick; const page = await Page.create(data); @@ -65,7 +65,7 @@ pageRouter.delete('/*', [ ], wrapAsync(async (req, res) => { const slug = req.path; - const page: Page|null = await Page.findByPk(slug); + const page: Page | null = await Page.findByPk(slug); if (page === null) { throw new ReponseError(404, `Container with slug '${slug}' not found.`); diff --git a/api/tsconfig.json b/api/tsconfig.json index 9976c17..aa9cb57 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -7,6 +7,7 @@ "target": "es6", "sourceMap": true, "outDir": "./build", + "strictNullChecks": true }, "exclude": [ "node_modules"