diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bf25b404..1699c6c6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,48 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed + +- Refreshed the cryptocurrencies list +- Improved the _OSS Friends_ page + +## 1.302.0 - 2023-08-20 + +### Changed + +- Improved the language localization for German (`de`) +- Upgraded `angular` from version `16.1.8` to `16.2.1` +- Upgraded `Nx` from version `16.6.0` to `16.7.2` + +## 1.301.1 - 2023-08-19 + +### Added + +- Added the data export feature to the user account page +- Added a currencies preset to the historical market data table of the admin control panel +- Added the _OSS Friends_ page + +### Changed + +- Improved the localized meta data in `html` files + +### Fixed + +- Fixed the rows with cash positions in the holdings table +- Fixed an issue with the date parsing in the historical market data editor of the admin control panel + +## 1.300.0 - 2023-08-11 + +### Added + +- Added more durations in the coupon system + +### Changed + +- Migrated the remaining requests from `bent` to `got` + ## 1.299.1 - 2023-08-10 ### Changed @@ -601,7 +643,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Changed the slide toggles to checkboxes on the account page +- Changed the slide toggles to checkboxes on the user account page - Changed the slide toggles to checkboxes in the admin control panel - Increased the density of the theme - Migrated the style of various components to `@angular/material` `15` (mdc) @@ -1163,7 +1205,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Improved the language selector on the account page +- Improved the language selector on the user account page - Improved the wording in the _X-ray_ section (net worth instead of investment) - Extended the asset profile details dialog in the admin control panel - Updated the browserslist database @@ -1581,7 +1623,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Added a language selector to the account page +- Added a language selector to the user account page - Added support for translated labels in the value component ### Changed @@ -1910,7 +1952,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Added the user id to the account page +- Added the user id to the user account page - Added a new view with jobs of the queue to the admin control panel ### Changed @@ -3565,7 +3607,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Respected the cash balance on the analysis page -- Improved the settings selectors on the account page +- Improved the settings selectors on the user account page - Harmonized the slogan to "Open Source Wealth Management Software" ### Fixed @@ -4031,7 +4073,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added a gradient to the line charts -- Added a selector to set the base currency on the account page +- Added a selector to set the base currency on the user account page ## 0.81.0 - 06.04.2021 @@ -4345,7 +4387,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Added the membership status to the account page +- Added the membership status to the user account page ### Fixed diff --git a/apps/api/src/app/admin/admin.controller.ts b/apps/api/src/app/admin/admin.controller.ts index 8e3fb2871..67e106ff8 100644 --- a/apps/api/src/app/admin/admin.controller.ts +++ b/apps/api/src/app/admin/admin.controller.ts @@ -38,7 +38,7 @@ import { import { REQUEST } from '@nestjs/core'; import { AuthGuard } from '@nestjs/passport'; import { DataSource, MarketData, Prisma, SymbolProfile } from '@prisma/client'; -import { isDate } from 'date-fns'; +import { isDate, parseISO } from 'date-fns'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { AdminService } from './admin.service'; @@ -233,7 +233,7 @@ export class AdminController { ); } - const date = new Date(dateString); + const date = parseISO(dateString); if (!isDate(date)) { throw new HttpException( @@ -333,7 +333,7 @@ export class AdminController { ); } - const date = new Date(dateString); + const date = parseISO(dateString); return this.marketDataService.updateMarketData({ data: { marketPrice: data.marketPrice, state: 'CLOSE' }, diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index 996914c4e..7563909dd 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -6,14 +6,12 @@ import { MarketDataService } from '@ghostfolio/api/services/market-data/market-d import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; -import { - DEFAULT_PAGE_SIZE, - PROPERTY_CURRENCIES -} from '@ghostfolio/common/config'; +import { PROPERTY_CURRENCIES } from '@ghostfolio/common/config'; import { AdminData, AdminMarketData, AdminMarketDataDetails, + AdminMarketDataItem, Filter, UniqueAsset } from '@ghostfolio/common/interfaces'; @@ -121,7 +119,9 @@ export class AdminService { [{ symbol: 'asc' }]; const where: Prisma.SymbolProfileWhereInput = {}; - if ( + if (presetId === 'CURRENCIES') { + return this.getMarketDataForCurrencies(); + } else if ( presetId === 'ETF_WITHOUT_COUNTRIES' || presetId === 'ETF_WITHOUT_SECTORS' ) { @@ -313,6 +313,36 @@ export class AdminService { return response; } + private async getMarketDataForCurrencies(): Promise { + const marketDataItems = await this.prismaService.marketData.groupBy({ + _count: true, + by: ['dataSource', 'symbol'] + }); + + const marketData: AdminMarketDataItem[] = this.exchangeRateDataService + .getCurrencyPairs() + .map(({ dataSource, symbol }) => { + const marketDataItemCount = + marketDataItems.find((marketDataItem) => { + return ( + marketDataItem.dataSource === dataSource && + marketDataItem.symbol === symbol + ); + })?._count ?? 0; + + return { + dataSource, + marketDataItemCount, + symbol, + assetClass: 'CASH', + countriesCount: 0, + sectorsCount: 0 + }; + }); + + return { marketData, count: marketData.length }; + } + private async getUsersWithAnalytics(): Promise { let orderBy: any = { createdAt: 'desc' diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 4b78a1e7c..a521e7fa9 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -12,7 +12,7 @@ import { SUPPORTED_LANGUAGE_CODES } from '@ghostfolio/common/config'; import { BullModule } from '@nestjs/bull'; -import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common'; +import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { ScheduleModule } from '@nestjs/schedule'; import { ServeStaticModule } from '@nestjs/serve-static'; @@ -28,7 +28,6 @@ import { BenchmarkModule } from './benchmark/benchmark.module'; import { CacheModule } from './cache/cache.module'; import { ExchangeRateModule } from './exchange-rate/exchange-rate.module'; import { ExportModule } from './export/export.module'; -import { FrontendMiddleware } from './frontend.middleware'; import { HealthModule } from './health/health.module'; import { ImportModule } from './import/import.module'; import { InfoModule } from './info/info.module'; @@ -75,12 +74,6 @@ import { UserModule } from './user/user.module'; PrismaModule, RedisCacheModule, ScheduleModule.forRoot(), - ...SUPPORTED_LANGUAGE_CODES.map((languageCode) => { - return ServeStaticModule.forRoot({ - rootPath: join(__dirname, '..', 'client', languageCode), - serveRoot: `/${languageCode}` - }); - }), ServeStaticModule.forRoot({ exclude: ['/api*', '/sitemap.xml'], rootPath: join(__dirname, '..', 'client'), @@ -114,10 +107,4 @@ import { UserModule } from './user/user.module'; controllers: [AppController], providers: [CronService] }) -export class AppModule { - configure(consumer: MiddlewareConsumer) { - consumer - .apply(FrontendMiddleware) - .forRoutes({ path: '*', method: RequestMethod.ALL }); - } -} +export class AppModule {} diff --git a/apps/api/src/app/exchange-rate/exchange-rate.controller.ts b/apps/api/src/app/exchange-rate/exchange-rate.controller.ts index ca9b67ced..40ff9d3c8 100644 --- a/apps/api/src/app/exchange-rate/exchange-rate.controller.ts +++ b/apps/api/src/app/exchange-rate/exchange-rate.controller.ts @@ -10,6 +10,7 @@ import { AuthGuard } from '@nestjs/passport'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { ExchangeRateService } from './exchange-rate.service'; +import { parseISO } from 'date-fns'; @Controller('exchange-rate') export class ExchangeRateController { @@ -23,7 +24,7 @@ export class ExchangeRateController { @Param('dateString') dateString: string, @Param('symbol') symbol: string ): Promise { - const date = new Date(dateString); + const date = parseISO(dateString); const exchangeRate = await this.exchangeRateService.getExchangeRate({ date, diff --git a/apps/api/src/app/frontend.middleware.ts b/apps/api/src/app/frontend.middleware.ts deleted file mode 100644 index 9996445a5..000000000 --- a/apps/api/src/app/frontend.middleware.ts +++ /dev/null @@ -1,232 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; - -import { environment } from '@ghostfolio/api/environments/environment'; -import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; -import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config'; -import { DATE_FORMAT, interpolate } from '@ghostfolio/common/helper'; -import { Injectable, NestMiddleware } from '@nestjs/common'; -import { format } from 'date-fns'; -import { NextFunction, Request, Response } from 'express'; - -@Injectable() -export class FrontendMiddleware implements NestMiddleware { - public indexHtmlDe = ''; - public indexHtmlEn = ''; - public indexHtmlEs = ''; - public indexHtmlFr = ''; - public indexHtmlIt = ''; - public indexHtmlNl = ''; - public indexHtmlPt = ''; - - private static readonly DEFAULT_DESCRIPTION = - 'Ghostfolio is a personal finance dashboard to keep track of your assets like stocks, ETFs or cryptocurrencies across multiple platforms.'; - - public constructor( - private readonly configurationService: ConfigurationService - ) { - try { - this.indexHtmlDe = fs.readFileSync( - this.getPathOfIndexHtmlFile('de'), - 'utf8' - ); - this.indexHtmlEn = fs.readFileSync( - this.getPathOfIndexHtmlFile(DEFAULT_LANGUAGE_CODE), - 'utf8' - ); - this.indexHtmlEs = fs.readFileSync( - this.getPathOfIndexHtmlFile('es'), - 'utf8' - ); - this.indexHtmlFr = fs.readFileSync( - this.getPathOfIndexHtmlFile('fr'), - 'utf8' - ); - this.indexHtmlIt = fs.readFileSync( - this.getPathOfIndexHtmlFile('it'), - 'utf8' - ); - this.indexHtmlNl = fs.readFileSync( - this.getPathOfIndexHtmlFile('nl'), - 'utf8' - ); - this.indexHtmlPt = fs.readFileSync( - this.getPathOfIndexHtmlFile('pt'), - 'utf8' - ); - } catch {} - } - - public use(request: Request, response: Response, next: NextFunction) { - const currentDate = format(new Date(), DATE_FORMAT); - let featureGraphicPath = 'assets/cover.png'; - let title = 'Ghostfolio – Open Source Wealth Management Software'; - - if (request.path.startsWith('/en/blog/2022/08/500-stars-on-github')) { - featureGraphicPath = 'assets/images/blog/500-stars-on-github.jpg'; - title = `500 Stars - ${title}`; - } else if (request.path.startsWith('/en/blog/2022/10/hacktoberfest-2022')) { - featureGraphicPath = 'assets/images/blog/hacktoberfest-2022.png'; - title = `Hacktoberfest 2022 - ${title}`; - } else if (request.path.startsWith('/en/blog/2022/11/black-friday-2022')) { - featureGraphicPath = 'assets/images/blog/black-friday-2022.jpg'; - title = `Black Friday 2022 - ${title}`; - } else if ( - request.path.startsWith( - '/en/blog/2022/12/the-importance-of-tracking-your-personal-finances' - ) - ) { - featureGraphicPath = 'assets/images/blog/20221226.jpg'; - title = `The importance of tracking your personal finances - ${title}`; - } else if ( - request.path.startsWith( - '/de/blog/2023/01/ghostfolio-auf-sackgeld-vorgestellt' - ) - ) { - featureGraphicPath = 'assets/images/blog/ghostfolio-x-sackgeld.png'; - title = `Ghostfolio auf Sackgeld.com vorgestellt - ${title}`; - } else if ( - request.path.startsWith('/en/blog/2023/02/ghostfolio-meets-umbrel') - ) { - featureGraphicPath = 'assets/images/blog/ghostfolio-x-umbrel.png'; - title = `Ghostfolio meets Umbrel - ${title}`; - } else if ( - request.path.startsWith( - '/en/blog/2023/03/ghostfolio-reaches-1000-stars-on-github' - ) - ) { - featureGraphicPath = 'assets/images/blog/1000-stars-on-github.jpg'; - title = `Ghostfolio reaches 1’000 Stars on GitHub - ${title}`; - } else if ( - request.path.startsWith( - '/en/blog/2023/05/unlock-your-financial-potential-with-ghostfolio' - ) - ) { - featureGraphicPath = 'assets/images/blog/20230520.jpg'; - title = `Unlock your Financial Potential with Ghostfolio - ${title}`; - } else if ( - request.path.startsWith('/en/blog/2023/07/exploring-the-path-to-fire') - ) { - featureGraphicPath = 'assets/images/blog/20230701.jpg'; - title = `Exploring the Path to FIRE - ${title}`; - } - - if ( - request.path.startsWith('/api/') || - this.isFileRequest(request.url) || - !environment.production - ) { - // Skip - next(); - } else if (request.path === '/de' || request.path.startsWith('/de/')) { - response.send( - interpolate(this.indexHtmlDe, { - currentDate, - featureGraphicPath, - title, - description: - 'Mit dem Finanz-Dashboard Ghostfolio können Sie Ihr Vermögen in Form von Aktien, ETFs oder Kryptowährungen verteilt über mehrere Finanzinstitute überwachen.', - languageCode: 'de', - path: request.path, - rootUrl: this.configurationService.get('ROOT_URL') - }) - ); - } else if (request.path === '/es' || request.path.startsWith('/es/')) { - response.send( - interpolate(this.indexHtmlEs, { - currentDate, - featureGraphicPath, - title, - description: - 'Ghostfolio es un dashboard de finanzas personales para hacer un seguimiento de tus activos como acciones, ETFs o criptodivisas a través de múltiples plataformas.', - languageCode: 'es', - path: request.path, - rootUrl: this.configurationService.get('ROOT_URL') - }) - ); - } else if (request.path === '/fr' || request.path.startsWith('/fr/')) { - response.send( - interpolate(this.indexHtmlFr, { - currentDate, - featureGraphicPath, - title, - description: - 'Ghostfolio est un dashboard de finances personnelles qui permet de suivre vos actifs comme les actions, les ETF ou les crypto-monnaies sur plusieurs plateformes.', - languageCode: 'fr', - path: request.path, - rootUrl: this.configurationService.get('ROOT_URL') - }) - ); - } else if (request.path === '/it' || request.path.startsWith('/it/')) { - response.send( - interpolate(this.indexHtmlIt, { - currentDate, - featureGraphicPath, - title, - description: - 'Ghostfolio è un dashboard di finanza personale per tenere traccia delle vostre attività come azioni, ETF o criptovalute su più piattaforme.', - languageCode: 'it', - path: request.path, - rootUrl: this.configurationService.get('ROOT_URL') - }) - ); - } else if (request.path === '/nl' || request.path.startsWith('/nl/')) { - response.send( - interpolate(this.indexHtmlNl, { - currentDate, - featureGraphicPath, - title, - description: - 'Ghostfolio is een persoonlijk financieel dashboard om uw activa zoals aandelen, ETF’s of cryptocurrencies over meerdere platforms bij te houden.', - languageCode: 'nl', - path: request.path, - rootUrl: this.configurationService.get('ROOT_URL') - }) - ); - } else if (request.path === '/pt' || request.path.startsWith('/pt/')) { - response.send( - interpolate(this.indexHtmlPt, { - currentDate, - featureGraphicPath, - title, - description: - 'Ghostfolio é um dashboard de finanças pessoais para acompanhar os seus activos como acções, ETFs ou criptomoedas em múltiplas plataformas.', - languageCode: 'pt', - path: request.path, - rootUrl: this.configurationService.get('ROOT_URL') - }) - ); - } else { - response.send( - interpolate(this.indexHtmlEn, { - currentDate, - featureGraphicPath, - title, - description: FrontendMiddleware.DEFAULT_DESCRIPTION, - languageCode: DEFAULT_LANGUAGE_CODE, - path: request.path, - rootUrl: this.configurationService.get('ROOT_URL') - }) - ); - } - } - - private getPathOfIndexHtmlFile(aLocale: string) { - return path.join(__dirname, '..', 'client', aLocale, 'index.html'); - } - - 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-markets.sh' - ) - ) { - return false; - } - - return filename.split('.').pop() !== filename; - } -} diff --git a/apps/api/src/app/info/info.service.ts b/apps/api/src/app/info/info.service.ts index 187135a35..7eba432f5 100644 --- a/apps/api/src/app/info/info.service.ts +++ b/apps/api/src/app/info/info.service.ts @@ -30,9 +30,9 @@ import { permissions } from '@ghostfolio/common/permissions'; import { SubscriptionOffer } from '@ghostfolio/common/types'; import { Injectable, Logger } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; -import * as bent from 'bent'; import * as cheerio from 'cheerio'; import { format, subDays } from 'date-fns'; +import got from 'got'; @Injectable() export class InfoService { @@ -172,17 +172,13 @@ export class InfoService { private async countDockerHubPulls(): Promise { try { - const get = bent( + const { pull_count } = await got( `https://hub.docker.com/v2/repositories/ghostfolio/ghostfolio`, - 'GET', - 'json', - 200, { - 'User-Agent': 'request' + headers: { 'User-Agent': 'request' } } - ); + ).json(); - const { pull_count } = await get(); return pull_count; } catch (error) { Logger.error(error, 'InfoService'); @@ -193,16 +189,9 @@ export class InfoService { private async countGitHubContributors(): Promise { try { - const get = bent( - 'https://github.com/ghostfolio/ghostfolio', - 'GET', - 'string', - 200, - {} - ); + const { body } = await got('https://github.com/ghostfolio/ghostfolio'); - const html = await get(); - const $ = cheerio.load(html); + const $ = cheerio.load(body); return extractNumberFromString( $( @@ -218,17 +207,13 @@ export class InfoService { private async countGitHubStargazers(): Promise { try { - const get = bent( + const { stargazers_count } = await got( `https://api.github.com/repos/ghostfolio/ghostfolio`, - 'GET', - 'json', - 200, { - 'User-Agent': 'request' + headers: { 'User-Agent': 'request' } } - ); + ).json(); - const { stargazers_count } = await get(); return stargazers_count; } catch (error) { Logger.error(error, 'InfoService'); @@ -346,22 +331,21 @@ export class InfoService { PROPERTY_BETTER_UPTIME_MONITOR_ID )) as string; - const get = bent( + const { data } = await got( `https://betteruptime.com/api/v2/monitors/${monitorId}/sla?from=${format( subDays(new Date(), 90), DATE_FORMAT )}&to${format(new Date(), DATE_FORMAT)}`, - 'GET', - 'json', - 200, + { - Authorization: `Bearer ${this.configurationService.get( - 'BETTER_UPTIME_API_KEY' - )}` + headers: { + Authorization: `Bearer ${this.configurationService.get( + 'BETTER_UPTIME_API_KEY' + )}` + } } - ); + ).json(); - const { data } = await get(); return data.attributes.availability / 100; } catch (error) { Logger.error(error, 'InfoService'); diff --git a/apps/api/src/app/logo/logo.service.ts b/apps/api/src/app/logo/logo.service.ts index d2e377fc0..166143a75 100644 --- a/apps/api/src/app/logo/logo.service.ts +++ b/apps/api/src/app/logo/logo.service.ts @@ -2,7 +2,7 @@ import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/sy import { UniqueAsset } from '@ghostfolio/common/interfaces'; import { HttpException, Injectable } from '@nestjs/common'; import { DataSource } from '@prisma/client'; -import * as bent from 'bent'; +import got from 'got'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; @Injectable() @@ -41,15 +41,11 @@ export class LogoService { } private getBuffer(aUrl: string) { - const get = bent( + return got( `https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${aUrl}&size=64`, - 'GET', - 'buffer', - 200, { - 'User-Agent': 'request' + headers: { 'User-Agent': 'request' } } - ); - return get(); + ).buffer(); } } diff --git a/apps/api/src/app/symbol/symbol.controller.ts b/apps/api/src/app/symbol/symbol.controller.ts index da73382a6..e09b08f5b 100644 --- a/apps/api/src/app/symbol/symbol.controller.ts +++ b/apps/api/src/app/symbol/symbol.controller.ts @@ -21,6 +21,7 @@ import { isDate, isEmpty } from 'lodash'; import { LookupItem } from './interfaces/lookup-item.interface'; import { SymbolItem } from './interfaces/symbol-item.interface'; import { SymbolService } from './symbol.service'; +import { parseISO } from 'date-fns'; @Controller('symbol') export class SymbolController { @@ -93,7 +94,7 @@ export class SymbolController { @Param('dateString') dateString: string, @Param('symbol') symbol: string ): Promise { - const date = new Date(dateString); + const date = parseISO(dateString); if (!isDate(date)) { throw new HttpException( diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 3c1e42abf..bbb17b69c 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -4,7 +4,11 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration/con import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; import { TagService } from '@ghostfolio/api/services/tag/tag.service'; -import { PROPERTY_IS_READ_ONLY_MODE, locale } from '@ghostfolio/common/config'; +import { + DEFAULT_CURRENCY, + PROPERTY_IS_READ_ONLY_MODE, + locale +} from '@ghostfolio/common/config'; import { User as IUser, UserSettings } from '@ghostfolio/common/interfaces'; import { getPermissions, @@ -21,8 +25,6 @@ const crypto = require('crypto'); @Injectable() export class UserService { - public static DEFAULT_CURRENCY = 'USD'; - private baseCurrency: string; public constructor( @@ -145,8 +147,7 @@ export class UserService { // Set default value for base currency if (!(user.Settings.settings as UserSettings)?.baseCurrency) { - (user.Settings.settings as UserSettings).baseCurrency = - UserService.DEFAULT_CURRENCY; + (user.Settings.settings as UserSettings).baseCurrency = DEFAULT_CURRENCY; } // Set default value for date range @@ -186,6 +187,9 @@ export class UserService { if (Analytics?.activityCount % frequency === 1) { currentPermissions.push(permissions.enableSubscriptionInterstitial); } + + // Reset benchmark + user.Settings.settings.benchmark = undefined; } if (user.subscription?.type === 'Premium') { diff --git a/apps/api/src/assets/cryptocurrencies/cryptocurrencies.json b/apps/api/src/assets/cryptocurrencies/cryptocurrencies.json index 413425f68..390d2c68f 100644 --- a/apps/api/src/assets/cryptocurrencies/cryptocurrencies.json +++ b/apps/api/src/assets/cryptocurrencies/cryptocurrencies.json @@ -51,7 +51,9 @@ "3FT": "ThreeFold Token", "3ULL": "3ULL Coin", "3XD": "3DChain", + "420CHAN": "420chan", "4ART": "4ART Coin", + "4CHAN": "4Chan", "4JNET": "4JNET", "77G": "GraphenTech", "7E": "7ELEVEN", @@ -60,6 +62,7 @@ "8BT": "8 Circuit Studios", "8PAY": "8Pay", "8X8": "8X8 Protocol", + "9GAG": "9GAG", "A5T": "Alpha5", "AAA": "Moon Rabbit", "AAB": "AAX Token", @@ -101,6 +104,7 @@ "ACN": "AvonCoin", "ACOIN": "ACoin", "ACP": "Anarchists Prime", + "ACQ": "Acquire.Fi", "ACS": "Access Protocol", "ACT": "Achain", "ACTIN": "Actinium", @@ -180,7 +184,7 @@ "AGX": "Agricoin", "AHOO": "Ahoolee", "AHT": "AhaToken", - "AI": "Multiverse", + "AI": "AiDoge", "AIB": "AdvancedInternetBlock", "AIBB": "AiBB", "AIBK": "AIB Utility Token", @@ -213,6 +217,7 @@ "AKA": "Akroma", "AKITA": "Akita Inu", "AKN": "Akoin", + "AKNC": "Aave KNC v1", "AKRO": "Akropolis", "AKT": "Akash Network", "AKTIO": "AKTIO Coin", @@ -237,12 +242,14 @@ "ALIC": "AliCoin", "ALICE": "My Neighbor Alice", "ALIEN": "AlienCoin", + "ALINK": "Aave LINK v1", "ALIS": "ALISmedia", "ALITA": "Alita Network", "ALIX": "AlinX", "ALKI": "Alkimi", "ALLBI": "ALL BEST ICO", "ALLEY": "NFT Alley", + "ALLIN": "All in", "ALN": "Aluna", "ALOHA": "Aloha", "ALP": "Alphacon", @@ -410,12 +417,14 @@ "ARIX": "Arix", "ARK": "ARK", "ARKER": "Arker", + "ARKM": "Arkham", "ARKN": "Ark Rivals", "ARM": "Armory Coin", "ARMOR": "ARMOR", "ARMR": "ARMR", "ARMS": "2Acoin", "ARNA": "ARNA Panacea", + "ARNM": "Arenum", "ARNO": "ARNO", "ARNX": "Aeron", "ARNXM": "Armor NXM", @@ -472,6 +481,7 @@ "ASTO": "Altered State Token", "ASTON": "Aston", "ASTR": "Astar", + "ASTRAFER": "Astrafer", "ASTRAL": "Astral", "ASTRO": "AstroSwap", "ASTROC": "Astroport Classic", @@ -531,6 +541,7 @@ "AURY": "Aurory", "AUSCM": "Auric Network", "AUSD": "Appeal dollar", + "AUSDC": "Aave USDC v1", "AUT": "Autoria", "AUTHORSHIP": "Authorship", "AUTO": "Auto", @@ -612,6 +623,7 @@ "BACK": "DollarBack", "BACOIN": "BACoin", "BACON": "BaconDAO (BACON)", + "BAD": "Bad Idea AI", "BADGER": "Badger DAO", "BAG": "BondAppetit", "BAGS": "Basis Gold Share", @@ -662,6 +674,7 @@ "BBCT": "TraDove B2BCoin", "BBDT": "BBD Token", "BBF": "Bubblefong", + "BBFT": "Block Busters Tech Token", "BBG": "BigBang", "BBGC": "BigBang Game", "BBI": "BelugaPay", @@ -725,6 +738,7 @@ "BDX": "Beldex", "BDY": "Buddy DAO", "BEACH": "BeachCoin", + "BEAI": "BeNFT Solutions", "BEAM": "Beam", "BEAN": "BeanCash", "BEAST": "CryptoBeast", @@ -806,6 +820,7 @@ "BIDR": "Binance IDR Stable Coin", "BIFI": "Beefy.Finance", "BIFIF": "BiFi", + "BIG": "Big Eyes", "BIGHAN": "BighanCoin", "BIGSB": "BigShortBets", "BIGUP": "BigUp", @@ -1090,6 +1105,7 @@ "BRNK": "Brank", "BRNX": "Bronix", "BRO": "Bitradio", + "BROCK": "Bitrock", "BRONZ": "BitBronze", "BRT": "Bikerush", "BRTR": "Barter", @@ -1226,7 +1242,7 @@ "BULL": "Bullieverse", "BULLC": "BuySell", "BULLION": "BullionFX", - "BULLS": "BullshitCoin", + "BULLS": "Bull Coin", "BULLSH": "Bullshit Inu", "BUMN": "BUMooN", "BUMP": "Bumper", @@ -1319,8 +1335,10 @@ "CAP": "BottleCaps", "CAPD": "Capdax", "CAPP": "Cappasity", + "CAPRICOIN": "CapriCoin", "CAPS": "Ternoa", "CAPT": "Bitcoin Captain", + "CAPTAINPLANET": "Captain Planet", "CAR": "CarBlock", "CARAT": "Carats Token", "CARBON": "Carboncoin", @@ -1478,6 +1496,7 @@ "CHECKR": "CheckerChain", "CHECOIN": "CheCoin", "CHEDDA": "Chedda", + "CHEEL": "Cheelee", "CHEESE": "CHEESE", "CHEESUS": "Cheesus", "CHEQ": "CHEQD Network", @@ -1520,7 +1539,8 @@ "CHX": "Own", "CHY": "Concern Poverty Chain", "CHZ": "Chiliz", - "CIC": "CIChain", + "CIC": "Crazy Internet Coin", + "CICHAIN": "CIChain", "CIF": "Crypto Improvement Fund", "CIM": "COINCOME", "CIN": "CinderCoin", @@ -1630,7 +1650,6 @@ "COB": "Cobinhood", "COC": "Coin of the champions", "COCK": "Shibacock", - "COCOS": "COCOS BCX", "CODEO": "Codeo Token", "CODEX": "CODEX Finance", "CODI": "Codi Finance", @@ -1659,7 +1678,7 @@ "COLX": "ColossusCoinXT", "COM": "Coliseum", "COMB": "Combo", - "COMBO": "Furucombo", + "COMBO": "COMBO", "COMFI": "CompliFi", "COMM": "Community Coin", "COMMUNITYCOIN": "Community Coin", @@ -1672,7 +1691,6 @@ "CONI": "CoinBene", "CONS": "ConSpiracy Coin", "CONSENTIUM": "Consentium", - "CONT": "Contentos", "CONUN": "CONUN", "CONV": "Convergence", "COOK": "Cook", @@ -1683,17 +1701,19 @@ "COPS": "Cops Finance", "COR": "Corion", "CORAL": "CoralPay", - "CORE": "Coreum", + "CORE": "Core", "COREDAO": "coreDAO", "COREG": "Core Group Asset", + "COREUM": "Coreum", "CORGI": "Corgi Inu", "CORN": "CORN", "CORX": "CorionX", - "COS": "COS", + "COS": "Contentos", "COSHI": "CoShi Inu", "COSM": "CosmoChain", "COSMIC": "CosmicSwap", "COSP": "Cosplay Token", + "COSS": "COS", "COSX": "Cosmecoin", "COT": "CoTrader", "COTI": "COTI", @@ -1729,7 +1749,7 @@ "CPOOL": "Clearpool", "CPROP": "CPROP", "CPRX": "Crypto Perx", - "CPS": "CapriCoin", + "CPS": "Cryptostone", "CPT": "Cryptaur", "CPU": "CPUcoin", "CPX": "Apex Token", @@ -1796,6 +1816,7 @@ "CRTS": "Cratos", "CRU": "Crust Network", "CRV": "Curve DAO Token", + "CRVUSD": "crvUSD", "CRW": "Crown Coin", "CRWD": "CRWD Network", "CRWNY": "Crowny Token", @@ -1843,7 +1864,7 @@ "CTLX": "Cash Telex", "CTN": "Continuum Finance", "CTO": "Crypto", - "CTP": "Captain Planet", + "CTP": "Ctomorrow Platform", "CTPL": "Cultiplan", "CTPT": "Contents Protocol", "CTR": "Creator Platform", @@ -2007,6 +2028,7 @@ "DBC": "DeepBrain Chain", "DBCCOIN": "Datablockchain", "DBD": "Day By Day", + "DBEAR": "DBear Coin", "DBET": "Decent.bet", "DBIC": "DubaiCoin", "DBIX": "DubaiCoin", @@ -2058,6 +2080,7 @@ "DEEP": "DeepCloud AI", "DEEPG": "Deep Gold", "DEEX": "DEEX", + "DEEZ": "DEEZ NUTS", "DEFI": "Defi", "DEFI5": "DEFI Top 5 Tokens Index", "DEFIL": "DeFIL", @@ -2162,11 +2185,12 @@ "DIEM": "Facebook Diem", "DIESEL": "Diesel", "DIFX": "Digital Financial Exchange", - "DIG": "Dignity", + "DIG": "DIEGO", "DIGG": "DIGG", "DIGIC": "DigiCube", "DIGIF": "DigiFel", "DIGITAL": "Digital Reserve Currency", + "DIGNITY": "Dignity", "DIGS": "Diggits", "DIKO": "Arkadiko", "DILI": "D Community", @@ -2246,6 +2270,7 @@ "DOGBOSS": "Dog Boss", "DOGDEFI": "DogDeFiCoin", "DOGE": "Dogecoin", + "DOGE20": "Doge 2.0", "DOGEBNB": "DogeBNB", "DOGEC": "DogeCash", "DOGECEO": "Doge CEO", @@ -2559,6 +2584,7 @@ "EMC2": "Einsteinium", "EMD": "Emerald", "EMIGR": "EmiratesGoldCoin", + "EML": "EML Protocol", "EMN.CUR": "Eastman Chemical", "EMON": "Ethermon", "EMOT": "Sentigraph.io", @@ -2692,6 +2718,7 @@ "ETHD": "Ethereum Dark", "ETHER": "Etherparty", "ETHERDELTA": "EtherDelta", + "ETHERKING": "Ether Kingdoms Token", "ETHERNITY": "Ethernity Chain", "ETHF": "EthereumFair", "ETHIX": "EthicHub", @@ -2709,6 +2736,7 @@ "ETHSHIB": "Eth Shiba", "ETHV": "Ethverse", "ETHW": "Ethereum PoW", + "ETHX": "Stader ETHx", "ETHY": "Ethereum Yield", "ETI": "EtherInc", "ETK": "Energi Token", @@ -2722,7 +2750,7 @@ "ETR": "Electric Token", "ETRNT": "Eternal Trusts", "ETS": "ETH Share", - "ETSC": "​Ether star blockchain", + "ETSC": "Ether star blockchain", "ETT": "EncryptoTel", "ETY": "Ethereum Cloud", "ETZ": "EtherZero", @@ -2773,6 +2801,7 @@ "EXB": "ExaByte (EXB)", "EXC": "Eximchain", "EXCC": "ExchangeCoin", + "EXCHANGEN": "ExchangeN", "EXCL": "Exclusive Coin", "EXE": "ExeCoin", "EXFI": "Flare Finance", @@ -2781,7 +2810,7 @@ "EXLT": "ExtraLovers", "EXM": "EXMO Coin", "EXMR": "EXMR FDN", - "EXN": "ExchangeN", + "EXN": "Exeno", "EXO": "Exosis", "EXP": "Expanse", "EXRD": "Radix", @@ -2814,6 +2843,7 @@ "FAIR": "FairCoin", "FAIRC": "Faireum Token", "FAIRG": "FairGame", + "FAKE": "FAKE COIN", "FAKT": "Medifakt", "FALCONS": "Falcon Swaps", "FAME": "Fame MMA", @@ -2870,6 +2900,7 @@ "FEN": "First Ever NFT", "FENOMY": "Fenomy", "FER": "Ferro", + "FERC": "FairERC20", "FERMA": "Ferma", "FESS": "Fesschain", "FET": "Fetch.AI", @@ -2931,7 +2962,7 @@ "FLASH": "Flashstake", "FLASHC": "FLASH coin", "FLC": "FlowChainCoin", - "FLD": "FLUID", + "FLD": "FluidAI", "FLDC": "Folding Coin", "FLDT": "FairyLand", "FLETA": "FLETA", @@ -3091,6 +3122,7 @@ "FUEL": "Jetfuel Finance", "FUJIN": "Fujinto", "FUKU": "Furukuru", + "FUMO": "Alien Milady Fumo", "FUN": "FUN Token", "FUNC": "FunCoin", "FUND": "Unification", @@ -3101,6 +3133,7 @@ "FUNDZ": "FundFantasy", "FUNK": "Cypherfunks Coin", "FUR": "Furio", + "FURU": "Furucombo", "FURY": "Engines of Fury", "FUS": "Fus", "FUSE": "Fuse Network Token", @@ -3118,6 +3151,7 @@ "FXP": "FXPay", "FXS": "Frax Share", "FXT": "FuzeX", + "FXY": "Floxypay", "FYN": "Affyn", "FYP": "FlypMe", "FYZ": "Fyooz", @@ -3172,6 +3206,7 @@ "GAT": "GATCOIN", "GATE": "GATENet", "GATEWAY": "Gateway Protocol", + "GAYPEPE": "Gay Pepe", "GAZE": "GazeTV", "GB": "GoldBlocks", "GBA": "Geeba", @@ -3222,6 +3257,7 @@ "GEMZ": "Gemz Social", "GEN": "DAOstack", "GENE": "Genopets", + "GENIE": "The Genie", "GENIX": "Genix", "GENS": "Genshiro", "GENSTAKE": "Genstake", @@ -3261,6 +3297,7 @@ "GHCOLD": "Galaxy Heroes Coin", "GHD": "Giftedhands", "GHNY": "Grizzly Honey", + "GHO": "GHO", "GHOST": "GhostbyMcAfee", "GHOSTCOIN": "GhostCoin", "GHOSTM": "GhostMarket", @@ -3274,6 +3311,7 @@ "GIFT": "GiftNet", "GIG": "GigaCoin", "GIGA": "GigaSwap", + "GIGX": "GigXCoin", "GIM": "Gimli", "GIMMER": "Gimmer", "GIN": "GINcoin", @@ -3385,6 +3423,7 @@ "GOVT": "The Government Network", "GOZ": "Göztepe S.K. Fan Token", "GP": "Wizards And Dragons", + "GPBP": "Genius Playboy Billionaire Philanthropist", "GPKR": "Gold Poker", "GPL": "Gold Pressed Latinum", "GPPT": "Pluto Project Coin", @@ -3501,7 +3540,8 @@ "HALF": "0.5X Long Bitcoin Token", "HALFSHIT": "0.5X Long Shitcoin Index Token", "HALLO": "Halloween Coin", - "HALO": "Halo Platform", + "HALO": "Halo Coin", + "HALOPLATFORM": "Halo Platform", "HAM": "Hamster", "HAMS": "HamsterCoin", "HANA": "Hanacoin", @@ -3598,6 +3638,7 @@ "HILL": "President Clinton", "HINA": "Hina Inu", "HINT": "Hintchain", + "HIPPO": "HIPPO", "HIRE": "HireMatch", "HIT": "HitChain", "HITBTC": "HitBTC Token", @@ -3634,6 +3675,7 @@ "HNTR": "Hunter", "HNY": "Honey", "HNZO": "Hanzo Inu", + "HOBO": "HOBO THE BEAR", "HOD": "HoDooi.com", "HODL": "HOdlcoin", "HOGE": "Hoge Finance", @@ -3839,7 +3881,7 @@ "IMPCN": "Brain Space", "IMPER": "Impermax", "IMPS": "Impulse Coin", - "IMPT": "Ether Kingdoms Token", + "IMPT": "IMPT", "IMPULSE": "IMPULSE by FDR", "IMS": "Independent Money System", "IMST": "Imsmart", @@ -4001,6 +4043,7 @@ "JAM": "Tune.Fm", "JANE": "JaneCoin", "JAR": "Jarvis+", + "JARED": "Jared From Subway", "JASMY": "JasmyCoin", "JBS": "JumBucks Coin", "JBX": "Juicebox", @@ -4163,9 +4206,10 @@ "KIN": "Kin", "KIND": "Kind Ads", "KINE": "Kine Protocol", - "KING": "King Finance", + "KING": "KING", "KING93": "King93", "KINGDOMQUEST": "Kingdom Quest", + "KINGF": "King Finance", "KINGSHIB": "King Shiba", "KINGSWAP": "KingSwap", "KINT": "Kintsugi", @@ -4175,6 +4219,7 @@ "KISC": "Kaiser", "KISHIMOTO": "Kishimoto Inu", "KISHU": "Kishu Inu", + "KITA": "KITA INU", "KITSU": "Kitsune Inu", "KITTY": "Kitty Inu", "KKO": "Kineko", @@ -4267,10 +4312,12 @@ "KUBO": "KUBO", "KUBOS": "KubosCoin", "KUE": "Kuende", + "KUJI": "Kujira", "KUMA": "Kuma Inu", "KUNCI": "Kunci Coin", "KUR": "Kuro", "KURT": "Kurrent", + "KUSA": "Kusa Inu", "KUSD": "Kowala", "KUSH": "KushCoin", "KUV": "Kuverit", @@ -4280,6 +4327,7 @@ "KVT": "Kinesis Velocity Token", "KWATT": "4New", "KWD": "KIWI DEFI", + "KWENTA": "Kwenta", "KWH": "KWHCoin", "KWIK": "KwikSwap", "KWS": "Knight War Spirits", @@ -4299,7 +4347,9 @@ "LABX": "Stakinglab", "LACCOIN": "LocalAgro", "LACE": "Lovelace World", + "LADYS": "Milady Meme Coin", "LAEEB": "LaEeb", + "LAELAPS": "Laelaps", "LAIKA": "Laika Protocol", "LALA": "LaLa World", "LAMB": "Lambda", @@ -4455,13 +4505,14 @@ "LLAND": "Lyfe Land", "LLG": "Loligo", "LLION": "Lydian Lion", - "LM": "LM Token", + "LM": "LeisureMeta", "LMAO": "LMAO Finance", "LMC": "LomoCoin", "LMCH": "Latamcash", "LMCSWAP": "LimoCoin SWAP", "LMR": "Lumerin", "LMT": "Lympo Market Token", + "LMTOKEN": "LM Token", "LMXC": "LimonX", "LMY": "Lunch Money", "LN": "LINK", @@ -4530,6 +4581,7 @@ "LRG": "Largo Coin", "LRN": "Loopring [NEO]", "LSD": "LightSpeedCoin", + "LSETH": "Liquid Staked ETH", "LSK": "Lisk", "LSP": "Lumenswap", "LSS": "Lossless", @@ -4626,6 +4678,7 @@ "MAEP": "Maester Protocol", "MAG": "Magnet", "MAGIC": "Magic", + "MAGICF": "MagicFox", "MAHA": "MahaDAO", "MAI": "Mindsync", "MAID": "MaidSafe Coin", @@ -4639,6 +4692,7 @@ "MANDOX": "MandoX", "MANGA": "Manga Token", "MANNA": "Manna", + "MANTLE": "Mantle", "MAP": "MAP Protocol", "MAPC": "MapCoin", "MAPE": "Mecha Morphing", @@ -4672,6 +4726,7 @@ "MATIC": "Polygon", "MATPAD": "MaticPad", "MATTER": "AntiMatter", + "MAV": "Maverick Protocol", "MAX": "MaxCoin", "MAXR": "Max Revive", "MAY": "Theresa May Coin", @@ -4776,6 +4831,7 @@ "MESA": "MetaVisa", "MESG": "MESG", "MESH": "MeshBox", + "MESSI": "MESSI COIN", "MET": "Metronome", "META": "Metadium", "METAC": "Metacoin", @@ -4881,6 +4937,7 @@ "MIODIO": "MIODIOCOIN", "MIOTA": "IOTA", "MIR": "Mirror Protocol", + "MIRACLE": "MIRACLE", "MIRC": "MIR COIN", "MIS": "Mithril Share", "MISA": "Sangkara", @@ -4938,7 +4995,6 @@ "MNRB": "MoneyRebel", "MNS": "Monnos", "MNST": "MoonStarter", - "MNT": "microNFT", "MNTC": "Manet Coin", "MNTG": "Monetas", "MNTL": "AssetMantle", @@ -4967,6 +5023,7 @@ "MOF": "Molecular Future (TRC20)", "MOFI": "MobiFi", "MOFOLD": "Molecular Future (ERC20)", + "MOG": "Mog Coin", "MOGU": "Mogu", "MOGX": "Mogu", "MOI": "MyOwnItem", @@ -4989,9 +5046,11 @@ "MONEYIMT": "MoneyToken", "MONF": "Monfter", "MONG": "MongCoin", + "MONG20": "Mongoose 2.0", "MONI": "Monsta Infinite", "MONK": "Monkey Project", "MONKEY": "Monkey", + "MONKEYS": "Monkeys Token", "MONO": "MonoX", "MONONOKEINU": "Mononoke Inu", "MONS": "Monsters Clan", @@ -5011,11 +5070,13 @@ "MOONSHOT": "Moonshot", "MOOO": "Hashtagger", "MOOV": "dotmoovs", + "MOOX": "Moox Protocol", "MOPS": "Mops", "MORA": "Meliora", "MORE": "More Coin", "MOS": "MOS Coin", "MOT": "Olympus Labs", + "MOTG": "MetaOctagon", "MOTI": "Motion", "MOTO": "Motocoin", "MOV": "MovieCoin", @@ -5076,6 +5137,7 @@ "MSWAP": "MoneySwap", "MT": "MyToken", "MTA": "Meta", + "MTB": "MetaBridge", "MTBC": "Metabolic", "MTC": "MEDICAL TOKEN CURRENCY", "MTCMN": "MTC Mesh", @@ -5108,6 +5170,7 @@ "MUE": "MonetaryUnit", "MULTI": "Multichain", "MULTIBOT": "Multibot", + "MULTIV": "Multiverse", "MUN": "MUNcoin", "MUNCH": "Munch Token", "MUSD": "mStable USD", @@ -5648,6 +5711,7 @@ "OZP": "OZAPHYRE", "P202": "Project 202", "P2PS": "P2P Solutions Foundation", + "PAAL": "PAAL AI", "PAC": "PAC Protocol", "PACOCA": "Pacoca", "PAD": "NearPad", @@ -5736,6 +5800,7 @@ "PEARL": "Pearl Finance", "PEC": "PeaceCoin", "PEEL": "Meta Apes", + "PEEPA": "Peepa", "PEEPS": "The People’s Coin", "PEG": "PegNet", "PEGS": "PegShares", @@ -5748,6 +5813,7 @@ "PEOPLE": "ConstitutionDAO", "PEOS": "pEOS", "PEPE": "Pepe", + "PEPE20": "Pepe 2.0", "PEPECASH": "Pepe Cash", "PEPPER": "Pepper Token", "PEPS": "PEPS Coin", @@ -5822,6 +5888,7 @@ "PINK": "PinkCoin", "PINKX": "PantherCoin", "PINMO": "Pinmo", + "PINO": "Pinocchu", "PINU": "Piccolo Inu", "PIO": "Pioneershares", "PIPI": "Pippi Finance", @@ -5885,6 +5952,7 @@ "PLS": "Pulsechain", "PLSD": "PulseDogecoin", "PLSPAD": "PulsePad", + "PLSX": "PulseX", "PLT": "Poollotto.finance", "PLTC": "PlatonCoin", "PLTX": "PlutusX", @@ -5911,7 +5979,6 @@ "PNK": "Kleros", "PNL": "True PNL", "PNODE": "Pinknode", - "PNP": "LogisticsX", "PNT": "pNetwork Token", "PNX": "PhantomX", "PNY": "Peony Coin", @@ -5927,6 +5994,7 @@ "POINTS": "Cryptsy Points", "POK": "Pokmonsters", "POKEM": "Pokemonio", + "POKEMON": "Pokemon", "POKER": "PokerCoin", "POKT": "Pocket Network", "POL": "Pool-X", @@ -6010,6 +6078,7 @@ "PRIME": "Echelon Prime", "PRIMECHAIN": "PrimeChain", "PRINT": "Printer.Finance", + "PRINTERIUM": "Printerium", "PRINTS": "FingerprintsDAO", "PRISM": "Prism", "PRIX": "Privatix", @@ -6033,7 +6102,7 @@ "PROTON": "Proton", "PROUD": "PROUD Money", "PROXI": "PROXI", - "PRP": "Papyrus", + "PRP": "Pepe Prime", "PRPS": "Purpose", "PRPT": "Purple Token", "PRQ": "PARSIQ", @@ -6042,7 +6111,7 @@ "PRTG": "Pre-Retogeum", "PRV": "PrivacySwap", "PRVS": "Previse", - "PRX": "Printerium", + "PRX": "Parex", "PRXY": "Proxy", "PRY": "PRIMARY", "PSB": "Planet Sandbox", @@ -6120,6 +6189,7 @@ "PYRAM": "Pyram Token", "PYRK": "Pyrk", "PYT": "Payther", + "PYUSD": "PayPal USD", "PZM": "Prizm", "Q1S": "Quantum1Net", "Q2C": "QubitCoin", @@ -6178,6 +6248,7 @@ "QUA": "Quantum Tech", "QUACK": "Rich Quack", "QUAM": "Quam Network", + "QUANT": "Quant Finance", "QUARASHI": "Quarashi Network", "QUARTZ": "Sandclock", "QUASA": "Quasacoin", @@ -6201,7 +6272,7 @@ "RAC": "RAcoin", "RACA": "Radio Caca", "RACEFI": "RaceFi", - "RAD": "Radicle", + "RAD": "Radworks", "RADAR": "DappRadar", "RADI": "RadicalCoin", "RADIO": "RadioShack", @@ -6220,7 +6291,7 @@ "RAM": "Ramifi Protocol", "RAMP": "RAMP", "RANKER": "RankerDao", - "RAP": "Rapture", + "RAP": "Philosoraptor", "RAPDOGE": "RapDoge", "RARE": "SuperRare", "RARI": "Rarible", @@ -6277,6 +6348,7 @@ "REA": "Realisto", "REAL": "RealLink", "REALM": "Realm", + "REALMS": "Realms of Ethernity", "REALPLATFORM": "REAL", "REALY": "Realy Metaverse", "REAP": "ReapChain", @@ -6287,6 +6359,7 @@ "RED": "RED TOKEN", "REDC": "RedCab", "REDCO": "Redcoin", + "REDDIT": "Reddit", "REDI": "REDi", "REDLANG": "RED", "REDLC": "Redlight Chain", @@ -6324,7 +6397,7 @@ "REST": "Restore", "RET": "RealTract", "RETAIL": "Retail.Global", - "RETH": "Realms of Ethernity", + "RETH": "Rocket Pool ETH", "RETH2": "rETH2", "RETIRE": "Retire Token", "REU": "REUCOIN", @@ -6351,6 +6424,7 @@ "RGP": "Rigel Protocol", "RGT": "Rari Governance Token", "RHEA": "Rhea", + "RHINO": "RHINO", "RHOC": "RChain", "RHP": "Rhypton Club", "RIC": "Riecoin", @@ -6490,6 +6564,7 @@ "RWE": "Real-World Evidence", "RWN": "Rowan Token", "RWS": "Robonomics Web Services", + "RXD": "Radiant", "RXT": "RIMAUNANGIS", "RYC": "RoyalCoin", "RYCN": "RoyalCoin 2.0", @@ -6564,6 +6639,7 @@ "SBTC": "Super Bitcoin", "SC": "Siacoin", "SCA": "SiaClassic", + "SCAM": "Scam Coin", "SCAP": "SafeCapital", "SCAR": "Velhalla", "SCASH": "SpaceCash", @@ -6624,6 +6700,7 @@ "SEER": "SEER", "SEI": "Sei", "SEL": "SelenCoin", + "SELF": "SELFCrypto", "SEM": "Semux", "SEN": "Sentaro", "SENATE": "SENATE", @@ -6665,6 +6742,7 @@ "SGE": "Society of Galactic Exploration", "SGLY": "Singularity", "SGN": "Signals Network", + "SGO": "SafuuGO", "SGOLD": "SpaceGold", "SGP": "SGPay", "SGR": "Sogur Currency", @@ -6684,6 +6762,7 @@ "SHEESH": "Sheesh it is bussin bussin", "SHEESHA": "Sheesha Finance", "SHELL": "Shell Token", + "SHERA": "Shera Tokens", "SHFL": "SHUFFLE!", "SHFT": "Shyft Network", "SHI": "Shirtum", @@ -6719,6 +6798,8 @@ "SHR": "ShareToken", "SHREK": "ShrekCoin", "SHROOM": "Shroom.Finance", + "SHROOMFOX": "Magic Shroom", + "SHS": "SHEESH", "SHX": "Stronghold Token", "SI": "Siren", "SIB": "SibCoin", @@ -7018,9 +7099,11 @@ "STEN": "Steneum Coin", "STEP": "Step Finance", "STEPH": "Step Hero", + "STEPR": "Step", "STEPS": "Steps", "STERLINGCOIN": "SterlingCoin", "STETH": "Staked Ether", + "STEWIE": "Stewie Coin", "STEX": "STEX", "STF": "Structure Finance", "STFX": "STFX", @@ -7055,7 +7138,7 @@ "STR": "Sourceless", "STRAKS": "Straks", "STRAX": "Stratis", - "STRAY": "Animal Token", + "STRAY": "Stray Dog", "STREAM": "STREAMIT COIN", "STRIP": "Stripto", "STRK": "Strike", @@ -7361,6 +7444,7 @@ "TOM": "TOM Finance", "TOMAHAWKCOIN": "Tomahawkcoin", "TOMB": "Tomb", + "TOMI": "tomiNet", "TOMO": "TomoChain", "TOMOE": "TomoChain ERC20", "TOMS": "TomTomCoin", @@ -7385,6 +7469,7 @@ "TOTM": "Totem", "TOWER": "Tower", "TOWN": "Town Star", + "TOX": "INTOverse", "TOZ": "Tozex", "TP": "Token Swap", "TPAD": "TrustPad", @@ -7600,6 +7685,7 @@ "UNITY": "SuperNET", "UNIVRS": "Universe", "UNIX": "UniX", + "UNLEASH": "UnleashClub", "UNN": "UNION Protocol Governance Token", "UNO": "Unobtanium", "UNORE": "UnoRe", @@ -7673,6 +7759,7 @@ "UTT": "United Traders Token", "UTU": "UTU Protocol", "UUU": "U Network", + "UWU": "uwu", "UZUMAKI": "Uzumaki Inu", "VAB": "Vabble", "VADER": "Vader Protocol", @@ -7695,6 +7782,7 @@ "VCF": "Valencia CF Fan Token", "VCG": "VCGamers", "VCK": "28VCK", + "VCORE": "VCORE", "VDG": "VeriDocGlobal", "VDL": "Vidulum", "VDO": "VidioCoin", @@ -7710,6 +7798,7 @@ "VEIL": "VEIL", "VELA": "Vela Token", "VELO": "Velo", + "VELOD": "Velodrome Finance", "VELOX": "Velox", "VELOXPROJECT": "Velox", "VEMP": "vEmpire DDAO", @@ -7782,6 +7871,7 @@ "VNT": "VNT Chain", "VNTW": "Value Network Token", "VNX": "VisionX", + "VNXAU": "VNX Gold", "VNXLU": "VNX Exchange", "VOCO": "Provoco", "VODKA": "Vodka Token", @@ -7902,7 +7992,8 @@ "WEC": "Whole Earth Coin", "WEGEN": "WeGen Platform", "WELD": "Weld", - "WELL": "Well", + "WELL": "Moonwell", + "WELLTOKEN": "Well", "WELT": "Fabwelt", "WELUPS": "Welups Blockchain", "WEMIX": "WEMIX", @@ -7958,6 +8049,7 @@ "WIX": "Wixlar", "WIZ": "WIZ Protocol", "WKD": "Wakanda Inu", + "WLD": "Worldcoin", "WLF": "Wolfs Group", "WLITI": "wLITI", "WLK": "Wolk", @@ -7983,6 +8075,7 @@ "WNZ": "Winerz", "WOA": "Wrapped Origin Axie", "WOD": "World of Defish", + "WOID": "WORLD ID", "WOJ": "Wojak Finance", "WOLF": "Insanity Coin", "WOLFILAND": "Wolfiland", @@ -8000,6 +8093,7 @@ "WOOFY": "Woofy", "WOOL": "Wolf Game Wool", "WOONK": "Woonkly", + "WOOO": "wooonen", "WOOP": "Woonkly Power", "WOP": "WorldPay", "WORLD": "World Token", @@ -8010,6 +8104,7 @@ "WOZX": "Efforce", "WPC": "WePiggy Coin", "WPE": "OPES (Wrapped PE)", + "WPLS": "Wrapped Pulse", "WPP": "Green Energy Token", "WPR": "WePower", "WQT": "Work Quest", @@ -8049,6 +8144,7 @@ "WZEC": "Wrapped Zcash", "WZENIQ": "Wrapped Zeniq (ETH)", "WZRD": "Wizardia", + "X": "AI-X", "X2": "X2Coin", "X2Y2": "X2Y2", "X42": "X42 Protocol", @@ -8096,7 +8192,7 @@ "XCI": "Cannabis Industry Coin", "XCLR": "ClearCoin", "XCM": "CoinMetro", - "XCN": "Chain", + "XCN": "Onyxcoin", "XCO": "XCoin", "XCONSOL": "X-Consoles", "XCP": "CounterParty", @@ -8365,6 +8461,7 @@ "YUANG": "Yuang Coin", "YUCJ": "Yu Coin", "YUCT": "Yucreat", + "YUDI": "Yudi", "YUM": "Yumerium", "YUMMY": "Yummy", "YUP": "Crowdholding", diff --git a/apps/api/src/assets/sitemap.xml b/apps/api/src/assets/sitemap.xml index cd4c9f3fb..10f20ca46 100644 --- a/apps/api/src/assets/sitemap.xml +++ b/apps/api/src/assets/sitemap.xml @@ -66,6 +66,10 @@ https://ghostfol.io/de/ueber-uns/lizenz ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/de/ueber-uns/oss-friends + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/en ${currentDate}T00:00:00+00:00 @@ -82,6 +86,10 @@ https://ghostfol.io/en/about/license ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/en/about/oss-friends + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/en/blog ${currentDate}T00:00:00+00:00 @@ -314,6 +322,10 @@ https://ghostfol.io/es/sobre/licencia ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/es/sobre/oss-friends + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/es/sobre/politica-de-privacidad ${currentDate}T00:00:00+00:00 @@ -334,6 +346,10 @@ https://ghostfol.io/fr/a-propos/licence ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/fr/a-propos/oss-friends + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/fr/a-propos/politique-de-confidentialite ${currentDate}T00:00:00+00:00 @@ -388,12 +404,16 @@ https://ghostfol.io/it/informazioni-su/changelog ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/it/informazioni-su/informativa-sulla-privacy + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/it/informazioni-su/licenza ${currentDate}T00:00:00+00:00 - https://ghostfol.io/it/informazioni-su/informativa-sulla-privacy + https://ghostfol.io/it/informazioni-su/oss-friends ${currentDate}T00:00:00+00:00 @@ -452,6 +472,10 @@ https://ghostfol.io/nl/over/licentie ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/nl/over/oss-friends + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/nl/over/privacybeleid ${currentDate}T00:00:00+00:00 @@ -512,6 +536,10 @@ https://ghostfol.io/pt/sobre/licenca ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/pt/sobre/oss-friends + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/pt/sobre/politica-de-privacidade ${currentDate}T00:00:00+00:00 diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index 5d76776a1..e0e7daf2f 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -7,6 +7,7 @@ import helmet from 'helmet'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; +import { HtmlTemplateMiddleware } from './middlewares/html-template.middleware'; async function bootstrap() { const configApp = await NestFactory.create(AppModule); @@ -52,6 +53,8 @@ async function bootstrap() { ); } + app.use(HtmlTemplateMiddleware); + const BASE_CURRENCY = configService.get('BASE_CURRENCY'); const HOST = configService.get('HOST') || '0.0.0.0'; const PORT = configService.get('PORT') || 3333; diff --git a/apps/api/src/middlewares/html-template.middleware.ts b/apps/api/src/middlewares/html-template.middleware.ts new file mode 100644 index 000000000..efbd60fa7 --- /dev/null +++ b/apps/api/src/middlewares/html-template.middleware.ts @@ -0,0 +1,128 @@ +import * as fs from 'fs'; +import { join } from 'path'; + +import { environment } from '@ghostfolio/api/environments/environment'; +import { + DEFAULT_LANGUAGE_CODE, + DEFAULT_ROOT_URL, + SUPPORTED_LANGUAGE_CODES +} from '@ghostfolio/common/config'; +import { DATE_FORMAT, interpolate } from '@ghostfolio/common/helper'; +import { format } from 'date-fns'; +import { NextFunction, Request, Response } from 'express'; + +const descriptions = { + de: 'Mit dem Finanz-Dashboard Ghostfolio können Sie Ihr Vermögen in Form von Aktien, ETFs oder Kryptowährungen verteilt über mehrere Finanzinstitute überwachen.', + en: 'Ghostfolio is a personal finance dashboard to keep track of your assets like stocks, ETFs or cryptocurrencies across multiple platforms.', + es: 'Ghostfolio es un dashboard de finanzas personales para hacer un seguimiento de tus activos como acciones, ETFs o criptodivisas a través de múltiples plataformas.', + fr: 'Ghostfolio est un dashboard de finances personnelles qui permet de suivre vos actifs comme les actions, les ETF ou les crypto-monnaies sur plusieurs plateformes.', + it: 'Ghostfolio è un dashboard di finanza personale per tenere traccia delle vostre attività come azioni, ETF o criptovalute su più piattaforme.', + nl: 'Ghostfolio is een persoonlijk financieel dashboard om uw activa zoals aandelen, ETF’s of cryptocurrencies over meerdere platforms bij te houden.', + pt: 'Ghostfolio é um dashboard de finanças pessoais para acompanhar os seus activos como acções, ETFs ou criptomoedas em múltiplas plataformas.' +}; + +const title = 'Ghostfolio – Open Source Wealth Management Software'; +const titleShort = 'Ghostfolio'; + +let indexHtmlMap: { [languageCode: string]: string } = {}; + +try { + indexHtmlMap = SUPPORTED_LANGUAGE_CODES.reduce( + (map, languageCode) => ({ + ...map, + [languageCode]: fs.readFileSync( + join(__dirname, '..', 'client', languageCode, 'index.html'), + 'utf8' + ) + }), + {} + ); +} catch {} + +const locales = { + '/de/blog/2023/01/ghostfolio-auf-sackgeld-vorgestellt': { + featureGraphicPath: 'assets/images/blog/ghostfolio-x-sackgeld.png', + title: `Ghostfolio auf Sackgeld.com vorgestellt - ${titleShort}` + }, + '/en/blog/2022/08/500-stars-on-github': { + featureGraphicPath: 'assets/images/blog/500-stars-on-github.jpg', + title: `500 Stars - ${titleShort}` + }, + '/en/blog/2022/10/hacktoberfest-2022': { + featureGraphicPath: 'assets/images/blog/hacktoberfest-2022.png', + title: `Hacktoberfest 2022 - ${titleShort}` + }, + '/en/blog/2022/12/the-importance-of-tracking-your-personal-finances': { + featureGraphicPath: 'assets/images/blog/20221226.jpg', + title: `The importance of tracking your personal finances - ${titleShort}` + }, + '/en/blog/2023/02/ghostfolio-meets-umbrel': { + featureGraphicPath: 'assets/images/blog/ghostfolio-x-umbrel.png', + title: `Ghostfolio meets Umbrel - ${titleShort}` + }, + '/en/blog/2023/03/ghostfolio-reaches-1000-stars-on-github': { + featureGraphicPath: 'assets/images/blog/1000-stars-on-github.jpg', + title: `Ghostfolio reaches 1’000 Stars on GitHub - ${titleShort}` + }, + '/en/blog/2023/05/unlock-your-financial-potential-with-ghostfolio': { + featureGraphicPath: 'assets/images/blog/20230520.jpg', + title: `Unlock your Financial Potential with Ghostfolio - ${titleShort}` + }, + '/en/blog/2023/07/exploring-the-path-to-fire': { + featureGraphicPath: 'assets/images/blog/20230701.jpg', + title: `Exploring the Path to FIRE - ${titleShort}` + } +}; + +const isFileRequest = (filename: string) => { + if (filename === '/assets/LICENSE') { + return true; + } else if ( + filename.includes('auth/ey') || + filename.includes( + 'personal-finance-tools/open-source-alternative-to-markets.sh' + ) + ) { + return false; + } + + return filename.split('.').pop() !== filename; +}; + +export const HtmlTemplateMiddleware = async ( + request: Request, + response: Response, + next: NextFunction +) => { + const path = request.originalUrl.replace(/\/$/, ''); + let languageCode = path.substr(1, 2); + + if (!SUPPORTED_LANGUAGE_CODES.includes(languageCode)) { + languageCode = DEFAULT_LANGUAGE_CODE; + } + + const currentDate = format(new Date(), DATE_FORMAT); + const rootUrl = process.env.ROOT_URL || DEFAULT_ROOT_URL; + + if ( + path.startsWith('/api/') || + isFileRequest(path) || + !environment.production + ) { + // Skip + next(); + } else { + const indexHtml = interpolate(indexHtmlMap[languageCode], { + currentDate, + languageCode, + path, + rootUrl, + description: descriptions[languageCode], + featureGraphicPath: + locales[path]?.featureGraphicPath ?? 'assets/cover.png', + title: locales[path]?.title ?? title + }); + + return response.send(indexHtml); + } +}; diff --git a/apps/api/src/services/configuration/configuration.service.ts b/apps/api/src/services/configuration/configuration.service.ts index e522aeccd..54eedaa4d 100644 --- a/apps/api/src/services/configuration/configuration.service.ts +++ b/apps/api/src/services/configuration/configuration.service.ts @@ -1,4 +1,5 @@ import { Environment } from '@ghostfolio/api/services/interfaces/environment.interface'; +import { DEFAULT_CURRENCY, DEFAULT_ROOT_URL } from '@ghostfolio/common/config'; import { Injectable } from '@nestjs/common'; import { DataSource } from '@prisma/client'; import { bool, cleanEnv, host, json, num, port, str } from 'envalid'; @@ -13,7 +14,7 @@ export class ConfigurationService { ALPHA_VANTAGE_API_KEY: str({ default: '' }), BASE_CURRENCY: str({ choices: ['AUD', 'CAD', 'CNY', 'EUR', 'GBP', 'JPY', 'RUB', 'USD'], - default: 'USD' + default: DEFAULT_CURRENCY }), BETTER_UPTIME_API_KEY: str({ default: '' }), CACHE_QUOTES_TTL: num({ default: 1 }), @@ -46,7 +47,7 @@ export class ConfigurationService { REDIS_HOST: str({ default: 'localhost' }), REDIS_PASSWORD: str({ default: '' }), REDIS_PORT: port({ default: 6379 }), - ROOT_URL: str({ default: 'http://localhost:4200' }), + ROOT_URL: str({ default: DEFAULT_ROOT_URL }), STRIPE_PUBLIC_KEY: str({ default: '' }), STRIPE_SECRET_KEY: str({ default: '' }), TWITTER_ACCESS_TOKEN: str({ default: 'dummyAccessToken' }), diff --git a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts index 35083e810..492664393 100644 --- a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts +++ b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts @@ -15,8 +15,8 @@ import { DataSource, SymbolProfile } from '@prisma/client'; -import bent from 'bent'; import { format, fromUnixTime, getUnixTime } from 'date-fns'; +import got from 'got'; @Injectable() export class CoinGeckoService implements DataProviderInterface { @@ -45,8 +45,7 @@ export class CoinGeckoService implements DataProviderInterface { }; try { - const get = bent(`${this.URL}/coins/${aSymbol}`, 'GET', 'json', 200); - const { name } = await get(); + const { name } = await got(`${this.URL}/coins/${aSymbol}`).json(); response.name = name; } catch (error) { @@ -79,17 +78,13 @@ export class CoinGeckoService implements DataProviderInterface { [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; }> { try { - const get = bent( + const { prices } = await got( `${ this.URL }/coins/${aSymbol}/market_chart/range?vs_currency=${this.baseCurrency.toLowerCase()}&from=${getUnixTime( from - )}&to=${getUnixTime(to)}`, - 'GET', - 'json', - 200 - ); - const { prices } = await get(); + )}&to=${getUnixTime(to)}` + ).json(); const result: { [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; @@ -132,15 +127,11 @@ export class CoinGeckoService implements DataProviderInterface { } try { - const get = bent( + const response = await got( `${this.URL}/simple/price?ids=${aSymbols.join( ',' - )}&vs_currencies=${this.baseCurrency.toLowerCase()}`, - 'GET', - 'json', - 200 - ); - const response = await get(); + )}&vs_currencies=${this.baseCurrency.toLowerCase()}` + ).json(); for (const symbol in response) { if (Object.prototype.hasOwnProperty.call(response, symbol)) { @@ -174,8 +165,9 @@ export class CoinGeckoService implements DataProviderInterface { let items: LookupItem[] = []; try { - const get = bent(`${this.URL}/search?query=${query}`, 'GET', 'json', 200); - const { coins } = await get(); + const { coins } = await got( + `${this.URL}/search?query=${query}` + ).json(); items = coins.map(({ id: symbol, name }) => { return { diff --git a/apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts b/apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts index ec68dd2eb..a18d82449 100644 --- a/apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts +++ b/apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts @@ -3,9 +3,7 @@ import { Country } from '@ghostfolio/common/interfaces/country.interface'; import { Sector } from '@ghostfolio/common/interfaces/sector.interface'; import { Injectable } from '@nestjs/common'; import { SymbolProfile } from '@prisma/client'; -import bent from 'bent'; - -const getJSON = bent('json'); +import got from 'got'; @Injectable() export class TrackinsightDataEnhancerService implements DataEnhancerInterface { @@ -34,11 +32,13 @@ export class TrackinsightDataEnhancerService implements DataEnhancerInterface { return response; } - const profile = await getJSON( + const profile = await got( `${TrackinsightDataEnhancerService.baseUrl}/data-api/funds/${symbol}.json` - ).catch(() => { - return {}; - }); + ) + .json() + .catch(() => { + return {}; + }); const isin = profile.isin?.split(';')?.[0]; @@ -46,15 +46,17 @@ export class TrackinsightDataEnhancerService implements DataEnhancerInterface { response.isin = isin; } - const holdings = await getJSON( + const holdings = await got( `${TrackinsightDataEnhancerService.baseUrl}/holdings/${symbol}.json` - ).catch(() => { - return getJSON( - `${TrackinsightDataEnhancerService.baseUrl}/holdings/${ - symbol.split('.')?.[0] - }.json` - ); - }); + ) + .json() + .catch(() => { + return got( + `${TrackinsightDataEnhancerService.baseUrl}/holdings/${ + symbol.split('.')?.[0] + }.json` + ); + }); if (holdings?.weight < 0.95) { // Skip if data is inaccurate diff --git a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts index ed65c3f65..3d7ba65f4 100644 --- a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts +++ b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts @@ -10,8 +10,8 @@ import { DataProviderInfo } from '@ghostfolio/common/interfaces'; import { Granularity } from '@ghostfolio/common/types'; import { Injectable, Logger } from '@nestjs/common'; import { DataSource, SymbolProfile } from '@prisma/client'; -import bent from 'bent'; import { format, isAfter, isBefore, isSameDay } from 'date-fns'; +import got from 'got'; @Injectable() export class FinancialModelingPrepService implements DataProviderInterface { @@ -64,13 +64,9 @@ export class FinancialModelingPrepService implements DataProviderInterface { [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; }> { try { - const get = bent( - `${this.URL}/historical-price-full/${aSymbol}?apikey=${this.apiKey}`, - 'GET', - 'json', - 200 - ); - const { historical } = await get(); + const { historical } = await got( + `${this.URL}/historical-price-full/${aSymbol}?apikey=${this.apiKey}` + ).json(); const result: { [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; @@ -115,13 +111,9 @@ export class FinancialModelingPrepService implements DataProviderInterface { } try { - const get = bent( - `${this.URL}/quote/${aSymbols.join(',')}?apikey=${this.apiKey}`, - 'GET', - 'json', - 200 - ); - const response = await get(); + const response = await got( + `${this.URL}/quote/${aSymbols.join(',')}?apikey=${this.apiKey}` + ).json(); for (const { price, symbol } of response) { results[symbol] = { @@ -153,13 +145,9 @@ export class FinancialModelingPrepService implements DataProviderInterface { let items: LookupItem[] = []; try { - const get = bent( - `${this.URL}/search?query=${query}&apikey=${this.apiKey}`, - 'GET', - 'json', - 200 - ); - const result = await get(); + const result = await got( + `${this.URL}/search?query=${query}&apikey=${this.apiKey}` + ).json(); items = result.map(({ currency, name, symbol }) => { return { diff --git a/apps/api/src/services/data-provider/manual/manual.service.ts b/apps/api/src/services/data-provider/manual/manual.service.ts index 7b3933532..adf14e43f 100644 --- a/apps/api/src/services/data-provider/manual/manual.service.ts +++ b/apps/api/src/services/data-provider/manual/manual.service.ts @@ -14,10 +14,10 @@ import { import { Granularity } from '@ghostfolio/common/types'; import { Injectable, Logger } from '@nestjs/common'; import { DataSource, SymbolProfile } from '@prisma/client'; -import bent from 'bent'; import * as cheerio from 'cheerio'; import { isUUID } from 'class-validator'; import { addDays, format, isBefore } from 'date-fns'; +import got from 'got'; @Injectable() export class ManualService implements DataProviderInterface { @@ -95,10 +95,9 @@ export class ManualService implements DataProviderInterface { return {}; } - const get = bent(url, 'GET', 'string', 200, headers); + const { body } = await got(url, { headers }); - const html = await get(); - const $ = cheerio.load(html); + const $ = cheerio.load(body); const value = extractNumberFromString($(selector).text()); diff --git a/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts b/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts index 053391a8a..307855aaf 100644 --- a/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts +++ b/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts @@ -10,8 +10,8 @@ import { DATE_FORMAT, getYesterday } from '@ghostfolio/common/helper'; import { Granularity } from '@ghostfolio/common/types'; import { Injectable, Logger } from '@nestjs/common'; import { DataSource, SymbolProfile } from '@prisma/client'; -import bent from 'bent'; import { format } from 'date-fns'; +import got from 'got'; @Injectable() export class RapidApiService implements DataProviderInterface { @@ -135,19 +135,17 @@ export class RapidApiService implements DataProviderInterface { oneYearAgo: { value: number; valueText: string }; }> { try { - const get = bent( + const { fgi } = await got( `https://fear-and-greed-index.p.rapidapi.com/v1/fgi`, - 'GET', - 'json', - 200, { - useQueryString: true, - 'x-rapidapi-host': 'fear-and-greed-index.p.rapidapi.com', - 'x-rapidapi-key': this.configurationService.get('RAPID_API_API_KEY') + headers: { + useQueryString: 'true', + 'x-rapidapi-host': 'fear-and-greed-index.p.rapidapi.com', + 'x-rapidapi-key': this.configurationService.get('RAPID_API_API_KEY') + } } - ); + ).json(); - const { fgi } = await get(); return fgi; } catch (error) { Logger.error(error, 'RapidApiService'); diff --git a/apps/client/src/app/app-routing.module.ts b/apps/client/src/app/app-routing.module.ts index e9eed48a2..f82bad864 100644 --- a/apps/client/src/app/app-routing.module.ts +++ b/apps/client/src/app/app-routing.module.ts @@ -4,25 +4,29 @@ import { PageTitleStrategy } from '@ghostfolio/client/services/page-title.strate import { ModulePreloadService } from './core/module-preload.service'; +export const paths = { + about: $localize`about`, + faq: $localize`faq`, + features: $localize`features`, + license: $localize`license`, + markets: $localize`markets`, + pricing: $localize`pricing`, + privacyPolicy: $localize`privacy-policy`, + register: $localize`register`, + resources: $localize`resources` +}; + const routes: Routes = [ - ...[ - 'about', - ///// - 'a-propos', - 'informazioni-su', - 'over', - 'sobre', - 'ueber-uns' - ].map((path) => ({ - path, + { + path: paths.about, loadChildren: () => import('./pages/about/about-page.module').then((m) => m.AboutPageModule) - })), + }, { path: 'account', loadChildren: () => - import('./pages/account/account-page.module').then( - (m) => m.AccountPageModule + import('./pages/user-account/user-account-page.module').then( + (m) => m.UserAccountPageModule ) }, { @@ -42,64 +46,40 @@ const routes: Routes = [ loadChildren: () => import('./pages/auth/auth-page.module').then((m) => m.AuthPageModule) }, - ...['blog'].map((path) => ({ - path, + { + path: 'blog', loadChildren: () => import('./pages/blog/blog-page.module').then((m) => m.BlogPageModule) - })), + }, { path: 'demo', loadChildren: () => import('./pages/demo/demo-page.module').then((m) => m.DemoPageModule) }, - ...[ - 'faq', - ///// - 'domande-piu-frequenti', - 'foire-aux-questions', - 'haeufig-gestellte-fragen', - 'perguntas-mais-frequentes', - 'preguntas-mas-frecuentes', - 'vaak-gestelde-vragen' - ].map((path) => ({ - path, + { + path: paths.faq, loadChildren: () => import('./pages/faq/faq-page.module').then((m) => m.FaqPageModule) - })), - ...[ - 'features', - ///// - 'fonctionnalites', - 'funcionalidades', - 'funzionalita', - 'kenmerken' - ].map((path) => ({ - path, + }, + { + path: paths.features, loadChildren: () => import('./pages/features/features-page.module').then( (m) => m.FeaturesPageModule ) - })), + }, { path: 'home', loadChildren: () => import('./pages/home/home-page.module').then((m) => m.HomePageModule) }, - ...[ - 'markets', - ///// - 'maerkte', - 'marches', - 'markten', - 'mercados', - 'mercati' - ].map((path) => ({ - path, + { + path: paths.markets, loadChildren: () => import('./pages/markets/markets-page.module').then( (m) => m.MarketsPageModule ) - })), + }, { path: 'open', loadChildren: () => @@ -119,53 +99,27 @@ const routes: Routes = [ (m) => m.PortfolioPageModule ) }, - ...[ - 'pricing', - ///// - 'precios', - 'precos', - 'preise', - 'prezzi', - 'prijzen', - 'prix' - ].map((path) => ({ - path, + { + path: paths.pricing, loadChildren: () => import('./pages/pricing/pricing-page.module').then( (m) => m.PricingPageModule ) - })), - ...[ - 'register', - ///// - 'enregistrement', - 'iscrizione', - 'registo', - 'registratie', - 'registrierung', - 'registro' - ].map((path) => ({ - path, + }, + { + path: paths.register, loadChildren: () => import('./pages/register/register-page.module').then( (m) => m.RegisterPageModule ) - })), - ...[ - 'resources', - ///// - 'bronnen', - 'recursos', - 'ressourcen', - 'ressources', - 'risorse' - ].map((path) => ({ - path, + }, + { + path: paths.resources, loadChildren: () => import('./pages/resources/resources-page.module').then( (m) => m.ResourcesPageModule ) - })), + }, { path: 'start', loadChildren: () => diff --git a/apps/client/src/app/app.component.html b/apps/client/src/app/app.component.html index af40ec4fb..c9457cfc0 100644 --- a/apps/client/src/app/app.component.html +++ b/apps/client/src/app/app.component.html @@ -19,7 +19,7 @@
-