Browse Source

Feature/Rewrite HtmlTemplateMiddleware to use Dependency Injection (#4889)

* Rewrite HtmlTemplateMiddleware to use Dependency Injection

* Update changelog
release/2.171.0-beta.0
Kenrick Tandrian 3 weeks ago
committed by GitHub
parent
commit
076ac1a32f
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 13
      apps/api/src/app/app.module.ts
  3. 3
      apps/api/src/main.ts
  4. 158
      apps/api/src/middlewares/html-template.middleware.ts

1
CHANGELOG.md

@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Migrated the `HtmlTemplateMiddleware` to use `@Injectable()`
- Improved the language localization for French (`fr`) - Improved the language localization for French (`fr`)
- Improved the language localization for Polish (`pl`) - Improved the language localization for Polish (`pl`)

13
apps/api/src/app/app.module.ts

@ -1,8 +1,10 @@
import { EventsModule } from '@ghostfolio/api/events/events.module'; import { EventsModule } from '@ghostfolio/api/events/events.module';
import { HtmlTemplateMiddleware } from '@ghostfolio/api/middlewares/html-template.middleware';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { CronModule } from '@ghostfolio/api/services/cron/cron.module'; import { CronModule } from '@ghostfolio/api/services/cron/cron.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module'; import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { PropertyModule } from '@ghostfolio/api/services/property/property.module'; import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module'; import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
@ -13,7 +15,7 @@ import {
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { BullModule } from '@nestjs/bull'; import { BullModule } from '@nestjs/bull';
import { Module } from '@nestjs/common'; import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config'; import { ConfigModule } from '@nestjs/config';
import { EventEmitterModule } from '@nestjs/event-emitter'; import { EventEmitterModule } from '@nestjs/event-emitter';
import { ScheduleModule } from '@nestjs/schedule'; import { ScheduleModule } from '@nestjs/schedule';
@ -130,6 +132,11 @@ import { UserModule } from './user/user.module';
TagsModule, TagsModule,
UserModule, UserModule,
WatchlistModule WatchlistModule
] ],
providers: [I18nService]
}) })
export class AppModule {} export class AppModule implements NestModule {
public configure(consumer: MiddlewareConsumer) {
consumer.apply(HtmlTemplateMiddleware).forRoutes('*');
}
}

3
apps/api/src/main.ts

@ -18,7 +18,6 @@ import helmet from 'helmet';
import { AppModule } from './app/app.module'; import { AppModule } from './app/app.module';
import { environment } from './environments/environment'; import { environment } from './environments/environment';
import { HtmlTemplateMiddleware } from './middlewares/html-template.middleware';
async function bootstrap() { async function bootstrap() {
const configApp = await NestFactory.create(AppModule); const configApp = await NestFactory.create(AppModule);
@ -77,8 +76,6 @@ async function bootstrap() {
}); });
} }
app.use(HtmlTemplateMiddleware);
const HOST = configService.get<string>('HOST') || DEFAULT_HOST; const HOST = configService.get<string>('HOST') || DEFAULT_HOST;
const PORT = configService.get<number>('PORT') || DEFAULT_PORT; const PORT = configService.get<number>('PORT') || DEFAULT_PORT;

158
apps/api/src/middlewares/html-template.middleware.ts

@ -7,30 +7,14 @@ import {
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { DATE_FORMAT, interpolate } from '@ghostfolio/common/helper'; import { DATE_FORMAT, interpolate } from '@ghostfolio/common/helper';
import { Injectable, Logger, NestMiddleware } from '@nestjs/common';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { NextFunction, Request, Response } from 'express'; import { NextFunction, Request, Response } from 'express';
import * as fs from 'fs'; import * as fs from 'fs';
import { join } from 'path'; import { join } from 'path';
const i18nService = new I18nService();
let indexHtmlMap: { [languageCode: string]: string } = {};
const title = 'Ghostfolio'; const title = 'Ghostfolio';
try {
indexHtmlMap = SUPPORTED_LANGUAGE_CODES.reduce(
(map, languageCode) => ({
...map,
[languageCode]: fs.readFileSync(
join(__dirname, '..', 'client', languageCode, 'index.html'),
'utf8'
)
}),
{}
);
} catch {}
const locales = { const locales = {
'/de/blog/2023/01/ghostfolio-auf-sackgeld-vorgestellt': { '/de/blog/2023/01/ghostfolio-auf-sackgeld-vorgestellt': {
featureGraphicPath: 'assets/images/blog/ghostfolio-x-sackgeld.png', featureGraphicPath: 'assets/images/blog/ghostfolio-x-sackgeld.png',
@ -94,71 +78,93 @@ const locales = {
} }
}; };
const isFileRequest = (filename: string) => { @Injectable()
if (filename === '/assets/LICENSE') { export class HtmlTemplateMiddleware implements NestMiddleware {
return true; private indexHtmlMap: { [languageCode: string]: string } = {};
} else if (
filename.includes('auth/ey') ||
filename.includes(
'personal-finance-tools/open-source-alternative-to-de.fi'
) ||
filename.includes(
'personal-finance-tools/open-source-alternative-to-markets.sh'
)
) {
return false;
}
return filename.split('.').pop() !== filename; public constructor(private readonly i18nService: I18nService) {
}; try {
this.indexHtmlMap = SUPPORTED_LANGUAGE_CODES.reduce(
(map, languageCode) => ({
...map,
[languageCode]: fs.readFileSync(
join(__dirname, '..', 'client', languageCode, 'index.html'),
'utf8'
)
}),
{}
);
} catch (error) {
Logger.error(
'Failed to initialize index HTML map',
error,
'HTMLTemplateMiddleware'
);
}
}
export const HtmlTemplateMiddleware = async ( public use(request: Request, response: Response, next: NextFunction) {
request: Request, const path = request.originalUrl.replace(/\/$/, '');
response: Response, let languageCode = path.substr(1, 2);
next: NextFunction
) => {
const path = request.originalUrl.replace(/\/$/, '');
let languageCode = path.substr(1, 2);
if (!SUPPORTED_LANGUAGE_CODES.includes(languageCode)) { if (!SUPPORTED_LANGUAGE_CODES.includes(languageCode)) {
languageCode = DEFAULT_LANGUAGE_CODE; languageCode = DEFAULT_LANGUAGE_CODE;
} }
const currentDate = format(new Date(), DATE_FORMAT); const currentDate = format(new Date(), DATE_FORMAT);
const rootUrl = process.env.ROOT_URL || environment.rootUrl; const rootUrl = process.env.ROOT_URL || environment.rootUrl;
if ( if (
path.startsWith('/api/') || path.startsWith('/api/') ||
path.startsWith(STORYBOOK_PATH) || path.startsWith(STORYBOOK_PATH) ||
isFileRequest(path) || this.isFileRequest(path) ||
!environment.production !environment.production
) { ) {
// Skip // Skip
next(); next();
} else { } else {
const indexHtml = interpolate(indexHtmlMap[languageCode], { const indexHtml = interpolate(this.indexHtmlMap[languageCode], {
currentDate, currentDate,
languageCode,
path,
rootUrl,
description: i18nService.getTranslation({
languageCode, languageCode,
id: 'metaDescription' path,
}), rootUrl,
featureGraphicPath: description: this.i18nService.getTranslation({
locales[path]?.featureGraphicPath ?? 'assets/cover.png', languageCode,
keywords: i18nService.getTranslation({ id: 'metaDescription'
languageCode, }),
id: 'metaKeywords' featureGraphicPath:
}), locales[path]?.featureGraphicPath ?? 'assets/cover.png',
title: keywords: this.i18nService.getTranslation({
locales[path]?.title ??
`${title}${i18nService.getTranslation({
languageCode, languageCode,
id: 'slogan' id: 'metaKeywords'
})}` }),
}); title:
locales[path]?.title ??
`${title}${this.i18nService.getTranslation({
languageCode,
id: 'slogan'
})}`
});
return response.send(indexHtml); return response.send(indexHtml);
}
} }
};
private isFileRequest(filename: string) {
if (filename === '/assets/LICENSE') {
return true;
} else if (
filename.includes('auth/ey') ||
filename.includes(
'personal-finance-tools/open-source-alternative-to-de.fi'
) ||
filename.includes(
'personal-finance-tools/open-source-alternative-to-markets.sh'
)
) {
return false;
}
return filename.split('.').pop() !== filename;
}
}

Loading…
Cancel
Save