diff --git a/CHANGELOG.md b/CHANGELOG.md index 80f83111..83e0bec1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added support for filtering in the _Copy AI prompt to clipboard_ actions on the analysis page (experimental) +- Added an endpoint to localize the `site.webmanifest` - Added the _Storybook_ path to the `sitemap.xml` file ### Changed diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 2a515bf4..99080e1e 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -32,6 +32,7 @@ import { AuthModule } from './auth/auth.module'; import { CacheModule } from './cache/cache.module'; import { AiModule } from './endpoints/ai/ai.module'; import { ApiKeysModule } from './endpoints/api-keys/api-keys.module'; +import { AssetsModule } from './endpoints/assets/assets.module'; import { BenchmarksModule } from './endpoints/benchmarks/benchmarks.module'; import { GhostfolioModule } from './endpoints/data-providers/ghostfolio/ghostfolio.module'; import { MarketDataModule } from './endpoints/market-data/market-data.module'; @@ -61,6 +62,7 @@ import { UserModule } from './user/user.module'; AiModule, ApiKeysModule, AssetModule, + AssetsModule, AuthDeviceModule, AuthModule, BenchmarksModule, diff --git a/apps/api/src/app/endpoints/assets/assets.controller.ts b/apps/api/src/app/endpoints/assets/assets.controller.ts new file mode 100644 index 00000000..1735cc59 --- /dev/null +++ b/apps/api/src/app/endpoints/assets/assets.controller.ts @@ -0,0 +1,46 @@ +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; +import { interpolate } from '@ghostfolio/common/helper'; + +import { + Controller, + Get, + Param, + Res, + Version, + VERSION_NEUTRAL +} from '@nestjs/common'; +import { Response } from 'express'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +@Controller('assets') +export class AssetsController { + private webManifest = ''; + + public constructor( + public readonly configurationService: ConfigurationService + ) { + try { + this.webManifest = readFileSync( + join(__dirname, 'assets', 'site.webmanifest'), + 'utf8' + ); + } catch {} + } + + @Get('/:languageCode/site.webmanifest') + @Version(VERSION_NEUTRAL) + public getWebManifest( + @Param('languageCode') languageCode: string, + @Res() response: Response + ): void { + const rootUrl = this.configurationService.get('ROOT_URL'); + const webManifest = interpolate(this.webManifest, { + languageCode, + rootUrl + }); + + response.setHeader('Content-Type', 'application/json'); + response.send(webManifest); + } +} diff --git a/apps/api/src/app/endpoints/assets/assets.module.ts b/apps/api/src/app/endpoints/assets/assets.module.ts new file mode 100644 index 00000000..51d330e5 --- /dev/null +++ b/apps/api/src/app/endpoints/assets/assets.module.ts @@ -0,0 +1,11 @@ +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; + +import { Module } from '@nestjs/common'; + +import { AssetsController } from './assets.controller'; + +@Module({ + controllers: [AssetsController], + providers: [ConfigurationService] +}) +export class AssetsModule {} diff --git a/apps/api/src/app/sitemap/sitemap.controller.ts b/apps/api/src/app/sitemap/sitemap.controller.ts index ea21906e..aad5e39a 100644 --- a/apps/api/src/app/sitemap/sitemap.controller.ts +++ b/apps/api/src/app/sitemap/sitemap.controller.ts @@ -9,8 +9,8 @@ import { personalFinanceTools } from '@ghostfolio/common/personal-finance-tools' import { Controller, Get, Res, VERSION_NEUTRAL, Version } from '@nestjs/common'; import { format } from 'date-fns'; import { Response } from 'express'; -import * as fs from 'fs'; -import * as path from 'path'; +import { readFileSync } from 'fs'; +import { join } from 'path'; @Controller('sitemap.xml') export class SitemapController { @@ -20,8 +20,8 @@ export class SitemapController { private readonly configurationService: ConfigurationService ) { try { - this.sitemapXml = fs.readFileSync( - path.join(__dirname, 'assets', 'sitemap.xml'), + this.sitemapXml = readFileSync( + join(__dirname, 'assets', 'sitemap.xml'), 'utf8' ); } catch {} diff --git a/apps/client/src/assets/site.webmanifest b/apps/api/src/assets/site.webmanifest similarity index 92% rename from apps/client/src/assets/site.webmanifest rename to apps/api/src/assets/site.webmanifest index 8f1eceef..a2871962 100644 --- a/apps/client/src/assets/site.webmanifest +++ b/apps/api/src/assets/site.webmanifest @@ -25,7 +25,7 @@ "name": "Ghostfolio", "orientation": "portrait", "short_name": "Ghostfolio", - "start_url": "/en/", + "start_url": "/${languageCode}/", "theme_color": "#FFFFFF", - "url": "https://ghostfol.io" + "url": "${rootUrl}" } diff --git a/apps/api/src/environments/environment.prod.ts b/apps/api/src/environments/environment.prod.ts index 81b32496..6d4cbb4b 100644 --- a/apps/api/src/environments/environment.prod.ts +++ b/apps/api/src/environments/environment.prod.ts @@ -1,4 +1,7 @@ +import { DEFAULT_HOST, DEFAULT_PORT } from '@ghostfolio/common/config'; + export const environment = { production: true, + rootUrl: `http://${DEFAULT_HOST}:${DEFAULT_PORT}`, version: `${require('../../../../package.json').version}` }; diff --git a/apps/api/src/environments/environment.ts b/apps/api/src/environments/environment.ts index c0ae2e7e..05476646 100644 --- a/apps/api/src/environments/environment.ts +++ b/apps/api/src/environments/environment.ts @@ -1,4 +1,7 @@ +import { DEFAULT_HOST } from '@ghostfolio/common/config'; + export const environment = { production: false, + rootUrl: `https://${DEFAULT_HOST}:4200`, version: 'dev' }; diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index 7cd5953b..73502525 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -1,4 +1,8 @@ -import { STORYBOOK_PATH } from '@ghostfolio/common/config'; +import { + DEFAULT_HOST, + DEFAULT_PORT, + STORYBOOK_PATH +} from '@ghostfolio/common/config'; import { Logger, @@ -75,8 +79,8 @@ async function bootstrap() { app.use(HtmlTemplateMiddleware); - const HOST = configService.get('HOST') || '0.0.0.0'; - const PORT = configService.get('PORT') || 3333; + const HOST = configService.get('HOST') || DEFAULT_HOST; + const PORT = configService.get('PORT') || DEFAULT_PORT; await app.listen(PORT, HOST, () => { logLogo(); diff --git a/apps/api/src/middlewares/html-template.middleware.ts b/apps/api/src/middlewares/html-template.middleware.ts index 25687695..403b0961 100644 --- a/apps/api/src/middlewares/html-template.middleware.ts +++ b/apps/api/src/middlewares/html-template.middleware.ts @@ -2,7 +2,6 @@ import { environment } from '@ghostfolio/api/environments/environment'; import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service'; import { DEFAULT_LANGUAGE_CODE, - DEFAULT_ROOT_URL, STORYBOOK_PATH, SUPPORTED_LANGUAGE_CODES } from '@ghostfolio/common/config'; @@ -126,7 +125,7 @@ export const HtmlTemplateMiddleware = async ( } const currentDate = format(new Date(), DATE_FORMAT); - const rootUrl = process.env.ROOT_URL || DEFAULT_ROOT_URL; + const rootUrl = process.env.ROOT_URL || environment.rootUrl; if ( path.startsWith('/api/') || diff --git a/apps/api/src/services/configuration/configuration.service.ts b/apps/api/src/services/configuration/configuration.service.ts index 3dfe5d5c..473d909e 100644 --- a/apps/api/src/services/configuration/configuration.service.ts +++ b/apps/api/src/services/configuration/configuration.service.ts @@ -1,11 +1,13 @@ +import { environment } from '@ghostfolio/api/environments/environment'; import { Environment } from '@ghostfolio/api/services/interfaces/environment.interface'; import { CACHE_TTL_NO_CACHE, + DEFAULT_HOST, + DEFAULT_PORT, DEFAULT_PROCESSOR_GATHER_ASSET_PROFILE_CONCURRENCY, DEFAULT_PROCESSOR_GATHER_HISTORICAL_MARKET_DATA_CONCURRENCY, DEFAULT_PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_CONCURRENCY, - DEFAULT_PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_TIMEOUT, - DEFAULT_ROOT_URL + DEFAULT_PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_TIMEOUT } from '@ghostfolio/common/config'; import { Injectable } from '@nestjs/common'; @@ -49,11 +51,11 @@ export class ConfigurationService { GOOGLE_SHEETS_ACCOUNT: str({ default: '' }), GOOGLE_SHEETS_ID: str({ default: '' }), GOOGLE_SHEETS_PRIVATE_KEY: str({ default: '' }), - HOST: host({ default: '0.0.0.0' }), + HOST: host({ default: DEFAULT_HOST }), JWT_SECRET_KEY: str({}), MAX_ACTIVITIES_TO_IMPORT: num({ default: Number.MAX_SAFE_INTEGER }), MAX_CHART_ITEMS: num({ default: 365 }), - PORT: port({ default: 3333 }), + PORT: port({ default: DEFAULT_PORT }), PROCESSOR_GATHER_ASSET_PROFILE_CONCURRENCY: num({ default: DEFAULT_PROCESSOR_GATHER_ASSET_PROFILE_CONCURRENCY }), @@ -71,7 +73,9 @@ export class ConfigurationService { REDIS_PASSWORD: str({ default: '' }), REDIS_PORT: port({ default: 6379 }), REQUEST_TIMEOUT: num({ default: ms('3 seconds') }), - ROOT_URL: url({ default: DEFAULT_ROOT_URL }), + ROOT_URL: url({ + default: environment.rootUrl + }), STRIPE_PUBLIC_KEY: str({ default: '' }), STRIPE_SECRET_KEY: str({ default: '' }), TWITTER_ACCESS_TOKEN: str({ default: 'dummyAccessToken' }), diff --git a/apps/client/ngsw-config.json b/apps/client/ngsw-config.json index c0f03a13..56e1cfd6 100644 --- a/apps/client/ngsw-config.json +++ b/apps/client/ngsw-config.json @@ -6,13 +6,7 @@ "name": "app", "installMode": "prefetch", "resources": { - "files": [ - "/favicon.ico", - "/index.html", - "/assets/site.webmanifest", - "/*.css", - "/*.js" - ] + "files": ["/favicon.ico", "/index.html", "/*.css", "/*.js"] } }, { diff --git a/apps/client/project.json b/apps/client/project.json index 160a27ea..b2144d7b 100644 --- a/apps/client/project.json +++ b/apps/client/project.json @@ -146,9 +146,6 @@ { "command": "shx cp apps/client/src/assets/robots.txt dist/apps/client" }, - { - "command": "shx cp apps/client/src/assets/site.webmanifest dist/apps/client" - }, { "command": "shx cp node_modules/ionicons/dist/index.js dist/apps/client" }, diff --git a/apps/client/src/index.html b/apps/client/src/index.html index 47f2c3d1..e11bd157 100644 --- a/apps/client/src/index.html +++ b/apps/client/src/index.html @@ -45,7 +45,10 @@ sizes="16x16" type="image/png" /> - + diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index 696ca86d..b8588fd0 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -48,13 +48,14 @@ export const PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_LOW = export const DEFAULT_CURRENCY = 'USD'; export const DEFAULT_DATE_FORMAT_MONTH_YEAR = 'MMM yyyy'; +export const DEFAULT_HOST = '0.0.0.0'; export const DEFAULT_LANGUAGE_CODE = 'en'; export const DEFAULT_PAGE_SIZE = 50; +export const DEFAULT_PORT = 3333; export const DEFAULT_PROCESSOR_GATHER_ASSET_PROFILE_CONCURRENCY = 1; export const DEFAULT_PROCESSOR_GATHER_HISTORICAL_MARKET_DATA_CONCURRENCY = 1; export const DEFAULT_PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_CONCURRENCY = 1; export const DEFAULT_PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_TIMEOUT = 30000; -export const DEFAULT_ROOT_URL = 'https://localhost:4200'; // USX is handled separately export const DERIVED_CURRENCIES = [