diff --git a/.gitignore b/.gitignore index 307d7e9e2..1339a53b7 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ /.angular/cache .env .env.prod +.nx/cache /.sass-cache /connect.lock /coverage diff --git a/.prettierignore b/.prettierignore index 1188a3b2b..47a053eee 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,3 @@ +/.nx/cache /dist /test/import diff --git a/CHANGELOG.md b/CHANGELOG.md index 64f8439a4..d77b922ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,51 @@ 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 + +- Improved the language localization for German (`de`) + +### Fixed + +- Fixed an issue in the biometric authentication +- Fixed the alignment of the icons in various menus + +## 2.16.0 - 2023-10-29 + +### Changed + +- Improved the check for duplicates in the preview step of the activities import (allow different accounts) +- Improved the usability and validation in the cash balance transfer from one to another account +- Changed the checkboxes to slide toggles in the overview of the admin control panel +- Switched from the deprecated (`PUT`) to the new endpoint (`POST`) to manage historical market data in the asset profile details dialog of the admin control panel +- Improved the date parsing in the import historical market data of the admin control panel +- Improved the localized meta data (keywords) in `html` files +- Improved the language localization for German (`de`) +- Upgraded `prisma` from version `5.4.2` to `5.5.2` + +## 2.15.0 - 2023-10-26 + +### Added + +- Added support to edit the name, asset class and asset sub class of asset profiles with `MANUAL` data source in the asset profile details dialog of the admin control panel + +### Changed + +- Improved the style and wording of the position detail dialog +- Improved the validation in the activities import (expects positive values for `fee`, `quantity` and `unitPrice`) +- Improved the validation in the cash balance transfer from one to another account (expects a positive value) +- Changed the currency selector in the create or update account dialog to `@angular/material/autocomplete` +- Upgraded `Nx` from version `16.7.4` to `17.0.2` +- Upgraded `uuid` from version `9.0.0` to `9.0.1` +- Upgraded `yahoo-finance2` from version `2.8.0` to `2.8.1` + +### Fixed + +- Fixed the chart in the account detail dialog for accounts excluded from analysis +- Verified the current benchmark before loading it on the analysis page + ## 2.14.0 - 2023-10-21 ### Added diff --git a/apps/api/src/app/account/account.controller.ts b/apps/api/src/app/account/account.controller.ts index 4666e5084..e141dc11f 100644 --- a/apps/api/src/app/account/account.controller.ts +++ b/apps/api/src/app/account/account.controller.ts @@ -190,36 +190,46 @@ export class AccountController { this.request.user.id ); - const currentAccountIds = accountsOfUser.map(({ id }) => { - return id; + const accountFrom = accountsOfUser.find(({ id }) => { + return id === accountIdFrom; }); - if ( - ![accountIdFrom, accountIdTo].every((accountId) => { - return currentAccountIds.includes(accountId); - }) - ) { + const accountTo = accountsOfUser.find(({ id }) => { + return id === accountIdTo; + }); + + if (!accountFrom || !accountTo) { throw new HttpException( getReasonPhrase(StatusCodes.NOT_FOUND), StatusCodes.NOT_FOUND ); } - const { currency } = accountsOfUser.find(({ id }) => { - return id === accountIdFrom; - }); + if (accountFrom.id === accountTo.id) { + throw new HttpException( + getReasonPhrase(StatusCodes.BAD_REQUEST), + StatusCodes.BAD_REQUEST + ); + } + + if (accountFrom.balance < balance) { + throw new HttpException( + getReasonPhrase(StatusCodes.BAD_REQUEST), + StatusCodes.BAD_REQUEST + ); + } await this.accountService.updateAccountBalance({ - currency, - accountId: accountIdFrom, + accountId: accountFrom.id, amount: -balance, + currency: accountFrom.currency, userId: this.request.user.id }); await this.accountService.updateAccountBalance({ - currency, - accountId: accountIdTo, + accountId: accountTo.id, amount: balance, + currency: accountFrom.currency, userId: this.request.user.id }); } diff --git a/apps/api/src/app/account/transfer-balance.dto.ts b/apps/api/src/app/account/transfer-balance.dto.ts index fb602033e..93a25d7cc 100644 --- a/apps/api/src/app/account/transfer-balance.dto.ts +++ b/apps/api/src/app/account/transfer-balance.dto.ts @@ -1,4 +1,4 @@ -import { IsNumber, IsString } from 'class-validator'; +import { IsNumber, IsPositive, IsString } from 'class-validator'; export class TransferBalanceDto { @IsString() @@ -8,5 +8,6 @@ export class TransferBalanceDto { accountIdTo: string; @IsNumber() + @IsPositive() balance: number; } diff --git a/apps/api/src/app/admin/admin.controller.ts b/apps/api/src/app/admin/admin.controller.ts index a19b17d4a..e277e77e4 100644 --- a/apps/api/src/app/admin/admin.controller.ts +++ b/apps/api/src/app/admin/admin.controller.ts @@ -7,7 +7,10 @@ import { GATHER_ASSET_PROFILE_PROCESS, GATHER_ASSET_PROFILE_PROCESS_OPTIONS } from '@ghostfolio/common/config'; -import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; +import { + getAssetProfileIdentifier, + resetHours +} from '@ghostfolio/common/helper'; import { AdminData, AdminMarketData, @@ -331,9 +334,9 @@ export class AdminController { const dataBulkUpdate: Prisma.MarketDataUpdateInput[] = data.marketData.map( ({ date, marketPrice }) => ({ dataSource, - date, marketPrice, symbol, + date: resetHours(parseISO(date)), state: 'CLOSE' }) ); diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index 84ae5934c..a42723ba3 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -303,15 +303,21 @@ export class AdminService { } public async patchAssetProfileData({ + assetClass, + assetSubClass, comment, dataSource, + name, scraperConfiguration, symbol, symbolMapping }: Prisma.SymbolProfileUpdateInput & UniqueAsset) { await this.symbolProfileService.updateSymbolProfile({ + assetClass, + assetSubClass, comment, dataSource, + name, scraperConfiguration, symbol, symbolMapping diff --git a/apps/api/src/app/admin/update-asset-profile.dto.ts b/apps/api/src/app/admin/update-asset-profile.dto.ts index 54f2d8f25..a39f8db81 100644 --- a/apps/api/src/app/admin/update-asset-profile.dto.ts +++ b/apps/api/src/app/admin/update-asset-profile.dto.ts @@ -1,11 +1,23 @@ -import { Prisma } from '@prisma/client'; -import { IsObject, IsOptional, IsString } from 'class-validator'; +import { AssetClass, AssetSubClass, Prisma } from '@prisma/client'; +import { IsEnum, IsObject, IsOptional, IsString } from 'class-validator'; export class UpdateAssetProfileDto { + @IsEnum(AssetClass, { each: true }) + @IsOptional() + assetClass?: AssetClass; + + @IsEnum(AssetSubClass, { each: true }) + @IsOptional() + assetSubClass?: AssetSubClass; + @IsString() @IsOptional() comment?: string; + @IsString() + @IsOptional() + name?: string; + @IsObject() @IsOptional() scraperConfiguration?: Prisma.InputJsonObject; diff --git a/apps/api/src/app/admin/update-market-data.dto.ts b/apps/api/src/app/admin/update-market-data.dto.ts index c0463de31..c2a6de11e 100644 --- a/apps/api/src/app/admin/update-market-data.dto.ts +++ b/apps/api/src/app/admin/update-market-data.dto.ts @@ -1,9 +1,9 @@ -import { IsDate, IsNumber, IsOptional } from 'class-validator'; +import { IsISO8601, IsNumber, IsOptional } from 'class-validator'; export class UpdateMarketDataDto { - @IsDate() + @IsISO8601() @IsOptional() - date?: Date; + date?: string; @IsNumber() marketPrice: number; diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index 8fd35f8dd..96cceff54 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -83,6 +83,7 @@ export class ImportService { const isDuplicate = orders.some((activity) => { return ( + activity.accountId === Account?.id && activity.SymbolProfile.currency === assetProfile.currency && activity.SymbolProfile.dataSource === assetProfile.dataSource && isSameDay(activity.date, parseDate(dateString)) && @@ -482,6 +483,7 @@ export class ImportService { const date = parseISO((dateString)); const isDuplicate = existingActivities.some((activity) => { return ( + activity.accountId === accountId && activity.SymbolProfile.currency === currency && activity.SymbolProfile.dataSource === dataSource && isSameDay(activity.date, date) && diff --git a/apps/api/src/app/order/create-order.dto.ts b/apps/api/src/app/order/create-order.dto.ts index 49b193ca5..3eafa704f 100644 --- a/apps/api/src/app/order/create-order.dto.ts +++ b/apps/api/src/app/order/create-order.dto.ts @@ -13,7 +13,9 @@ import { IsISO8601, IsNumber, IsOptional, - IsString + IsPositive, + IsString, + Min } from 'class-validator'; import { isString } from 'lodash'; @@ -48,9 +50,11 @@ export class CreateOrderDto { date: string; @IsNumber() + @Min(0) fee: number; @IsNumber() + @IsPositive() quantity: number; @IsString() @@ -64,6 +68,7 @@ export class CreateOrderDto { type: Type; @IsNumber() + @IsPositive() unitPrice: number; @IsBoolean() diff --git a/apps/api/src/app/order/update-order.dto.ts b/apps/api/src/app/order/update-order.dto.ts index a8c33c40e..9d968aa86 100644 --- a/apps/api/src/app/order/update-order.dto.ts +++ b/apps/api/src/app/order/update-order.dto.ts @@ -13,7 +13,9 @@ import { IsISO8601, IsNumber, IsOptional, - IsString + IsPositive, + IsString, + Min } from 'class-validator'; import { isString } from 'lodash'; @@ -47,12 +49,14 @@ export class UpdateOrderDto { date: string; @IsNumber() + @Min(0) fee: number; @IsString() id: string; @IsNumber() + @IsPositive() quantity: number; @IsString() @@ -66,5 +70,6 @@ export class UpdateOrderDto { type: Type; @IsNumber() + @IsPositive() unitPrice: number; } diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index 7ec32d59c..65838eb98 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -358,7 +358,8 @@ export class PortfolioController { @Query('accounts') filterByAccounts?: string, @Query('assetClasses') filterByAssetClasses?: string, @Query('range') dateRange: DateRange = 'max', - @Query('tags') filterByTags?: string + @Query('tags') filterByTags?: string, + @Query('withExcludedAccounts') withExcludedAccounts = false ): Promise { const filters = this.apiService.buildFiltersFromQueryParams({ filterByAccounts, @@ -370,6 +371,7 @@ export class PortfolioController { dateRange, filters, impersonationId, + withExcludedAccounts, userId: this.request.user.id }); diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 322473644..f8fd6a7b5 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -372,20 +372,23 @@ export class PortfolioService { filters, impersonationId, userCurrency, - userId + userId, + withExcludedAccounts = false }: { dateRange?: DateRange; filters?: Filter[]; impersonationId: string; userCurrency: string; userId: string; + withExcludedAccounts?: boolean; }): Promise { userId = await this.getUserId(impersonationId, userId); const { portfolioOrders, transactionPoints } = await this.getTransactionPoints({ filters, - userId + userId, + withExcludedAccounts }); const portfolioCalculator = new PortfolioCalculator({ @@ -1199,12 +1202,14 @@ export class PortfolioService { dateRange = 'max', filters, impersonationId, - userId + userId, + withExcludedAccounts = false }: { dateRange?: DateRange; filters?: Filter[]; impersonationId: string; userId: string; + withExcludedAccounts?: boolean; }): Promise { userId = await this.getUserId(impersonationId, userId); const user = await this.userService.user({ id: userId }); @@ -1213,7 +1218,8 @@ export class PortfolioService { const { portfolioOrders, transactionPoints } = await this.getTransactionPoints({ filters, - userId + userId, + withExcludedAccounts }); const portfolioCalculator = new PortfolioCalculator({ @@ -1263,7 +1269,8 @@ export class PortfolioService { filters, impersonationId, userCurrency, - userId + userId, + withExcludedAccounts }); const itemOfToday = historicalDataContainer.items.find((item) => { @@ -1860,7 +1867,7 @@ export class PortfolioService { filters, includeDrafts = false, userId, - withExcludedAccounts + withExcludedAccounts = false }: { filters?: Filter[]; includeDrafts?: boolean; @@ -1948,7 +1955,7 @@ export class PortfolioService { portfolioItemsNow, userCurrency, userId, - withExcludedAccounts + withExcludedAccounts = false }: { filters?: Filter[]; orders: OrderWithAccount[]; diff --git a/apps/api/src/assets/sitemap.xml b/apps/api/src/assets/sitemap.xml index 2a3650752..2b34b50ec 100644 --- a/apps/api/src/assets/sitemap.xml +++ b/apps/api/src/assets/sitemap.xml @@ -58,6 +58,14 @@ https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-altoo ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-beanvest + ${currentDate}T00:00:00+00:00 + + + https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-capitally + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-capmon ${currentDate}T00:00:00+00:00 @@ -166,6 +174,10 @@ https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-utluna ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-wealthica + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/de/ressourcen/personal-finance-tools/open-source-alternative-zu-yeekatee ${currentDate}T00:00:00+00:00 @@ -312,6 +324,14 @@ https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-altoo ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-beanvest + ${currentDate}T00:00:00+00:00 + + + https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-capitally + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-capmon ${currentDate}T00:00:00+00:00 @@ -420,6 +440,10 @@ https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-utluna ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-wealthica + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-yeekatee ${currentDate}T00:00:00+00:00 @@ -595,7 +619,15 @@ ${currentDate}T00:00:00+00:00 - https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-campmon + https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-beanvest + ${currentDate}T00:00:00+00:00 + + + https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-capitally + ${currentDate}T00:00:00+00:00 + + + https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-capmon ${currentDate}T00:00:00+00:00 @@ -702,6 +734,10 @@ https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-utluna ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-wealthica + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/it/risorse/personal-finance-tools/alternativa-open-source-a-yeekatee ${currentDate}T00:00:00+00:00 @@ -722,6 +758,14 @@ https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-altoo ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-beanvest + ${currentDate}T00:00:00+00:00 + + + https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-capitally + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-capmon ${currentDate}T00:00:00+00:00 @@ -830,6 +874,10 @@ https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-utluna ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-wealthica + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/nl/bronnen/personal-finance-tools/open-source-alternatief-voor-yeekatee ${currentDate}T00:00:00+00:00 diff --git a/apps/api/src/middlewares/html-template.middleware.ts b/apps/api/src/middlewares/html-template.middleware.ts index f0358eca6..ba5112dac 100644 --- a/apps/api/src/middlewares/html-template.middleware.ts +++ b/apps/api/src/middlewares/html-template.middleware.ts @@ -12,13 +12,12 @@ import { DATE_FORMAT, interpolate } from '@ghostfolio/common/helper'; import { format } from 'date-fns'; import { NextFunction, Request, Response } from 'express'; -const title = 'Ghostfolio – Open Source Wealth Management Software'; -const titleShort = 'Ghostfolio'; - const i18nService = new I18nService(); let indexHtmlMap: { [languageCode: string]: string } = {}; +const title = 'Ghostfolio'; + try { indexHtmlMap = SUPPORTED_LANGUAGE_CODES.reduce( (map, languageCode) => ({ @@ -35,47 +34,47 @@ try { 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}` + title: `Ghostfolio auf Sackgeld.com vorgestellt - ${title}` }, '/en/blog/2022/08/500-stars-on-github': { featureGraphicPath: 'assets/images/blog/500-stars-on-github.jpg', - title: `500 Stars - ${titleShort}` + title: `500 Stars - ${title}` }, '/en/blog/2022/10/hacktoberfest-2022': { featureGraphicPath: 'assets/images/blog/hacktoberfest-2022.png', - title: `Hacktoberfest 2022 - ${titleShort}` + title: `Hacktoberfest 2022 - ${title}` }, '/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}` + title: `The importance of tracking your personal finances - ${title}` }, '/en/blog/2023/02/ghostfolio-meets-umbrel': { featureGraphicPath: 'assets/images/blog/ghostfolio-x-umbrel.png', - title: `Ghostfolio meets Umbrel - ${titleShort}` + title: `Ghostfolio meets Umbrel - ${title}` }, '/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}` + title: `Ghostfolio reaches 1’000 Stars on GitHub - ${title}` }, '/en/blog/2023/05/unlock-your-financial-potential-with-ghostfolio': { featureGraphicPath: 'assets/images/blog/20230520.jpg', - title: `Unlock your Financial Potential with Ghostfolio - ${titleShort}` + title: `Unlock your Financial Potential with Ghostfolio - ${title}` }, '/en/blog/2023/07/exploring-the-path-to-fire': { featureGraphicPath: 'assets/images/blog/20230701.jpg', - title: `Exploring the Path to FIRE - ${titleShort}` + title: `Exploring the Path to FIRE - ${title}` }, '/en/blog/2023/08/ghostfolio-joins-oss-friends': { featureGraphicPath: 'assets/images/blog/ghostfolio-joins-oss-friends.png', - title: `Ghostfolio joins OSS Friends - ${titleShort}` + title: `Ghostfolio joins OSS Friends - ${title}` }, '/en/blog/2023/09/ghostfolio-2': { featureGraphicPath: 'assets/images/blog/ghostfolio-2.jpg', - title: `Announcing Ghostfolio 2.0 - ${titleShort}` + title: `Announcing Ghostfolio 2.0 - ${title}` }, '/en/blog/2023/09/hacktoberfest-2023': { featureGraphicPath: 'assets/images/blog/hacktoberfest-2023.png', - title: `Hacktoberfest 2023 - ${titleShort}` + title: `Hacktoberfest 2023 - ${title}` } }; @@ -128,7 +127,16 @@ export const HtmlTemplateMiddleware = async ( }), featureGraphicPath: locales[path]?.featureGraphicPath ?? 'assets/cover.png', - title: locales[path]?.title ?? title + keywords: i18nService.getTranslation({ + languageCode, + id: 'metaKeywords' + }), + title: + locales[path]?.title ?? + `${title} – ${i18nService.getTranslation({ + languageCode, + id: 'slogan' + })}` }); return response.send(indexHtml); diff --git a/apps/api/src/services/symbol-profile/symbol-profile.service.ts b/apps/api/src/services/symbol-profile/symbol-profile.service.ts index b861ccf8f..46a6991cb 100644 --- a/apps/api/src/services/symbol-profile/symbol-profile.service.ts +++ b/apps/api/src/services/symbol-profile/symbol-profile.service.ts @@ -86,14 +86,24 @@ export class SymbolProfileService { } public updateSymbolProfile({ + assetClass, + assetSubClass, comment, dataSource, + name, scraperConfiguration, symbol, symbolMapping }: Prisma.SymbolProfileUpdateInput & UniqueAsset) { return this.prismaService.symbolProfile.update({ - data: { comment, scraperConfiguration, symbolMapping }, + data: { + assetClass, + assetSubClass, + comment, + name, + scraperConfiguration, + symbolMapping + }, where: { dataSource_symbol: { dataSource, symbol } } }); } diff --git a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts index 756df74cf..d232cb3df 100644 --- a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts +++ b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts @@ -116,7 +116,8 @@ export class AccountDetailDialog implements OnDestroy, OnInit { type: 'ACCOUNT' } ], - range: 'max' + range: 'max', + withExcludedAccounts: true }) .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(({ chart }) => { diff --git a/apps/client/src/app/components/accounts-table/accounts-table.component.html b/apps/client/src/app/components/accounts-table/accounts-table.component.html index bfe5a667a..b8c9ac4f7 100644 --- a/apps/client/src/app/components/accounts-table/accounts-table.component.html +++ b/apps/client/src/app/components/accounts-table/accounts-table.component.html @@ -2,6 +2,7 @@ diff --git a/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.component.ts b/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.component.ts index dddef0c8f..99fc1abe2 100644 --- a/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.component.ts @@ -57,10 +57,16 @@ export class MarketDataDetailDialog implements OnDestroy { public onUpdate() { this.adminService - .putMarketData({ + .postMarketData({ dataSource: this.data.dataSource, - date: this.data.date, - marketData: { marketPrice: this.data.marketPrice }, + marketData: { + marketData: [ + { + date: this.data.date.toISOString(), + marketPrice: this.data.marketPrice + } + ] + }, symbol: this.data.symbol }) .pipe(takeUntil(this.unsubscribeSubject)) diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.html b/apps/client/src/app/components/admin-market-data/admin-market-data.html index 80ba30a2a..e7e8733e0 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.html +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -143,12 +143,24 @@ + diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts index ccb6f3ccd..5e331ca91 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts @@ -6,19 +6,24 @@ import { OnDestroy, OnInit } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; +import { FormBuilder, FormControl, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto'; import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; -import { DATE_FORMAT } from '@ghostfolio/common/helper'; +import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper'; import { AdminMarketDataDetails, UniqueAsset } from '@ghostfolio/common/interfaces'; import { translate } from '@ghostfolio/ui/i18n'; -import { MarketData, SymbolProfile } from '@prisma/client'; -import { format, parseISO } from 'date-fns'; +import { + AssetClass, + AssetSubClass, + MarketData, + SymbolProfile +} from '@prisma/client'; +import { format } from 'date-fns'; import { parse as csvToJson } from 'papaparse'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -33,14 +38,23 @@ import { AssetProfileDialogParams } from './interfaces/interfaces'; styleUrls: ['./asset-profile-dialog.component.scss'] }) export class AssetProfileDialog implements OnDestroy, OnInit { - public assetClass: string; + public assetProfileClass: string; + public assetClasses = Object.keys(AssetClass).map((assetClass) => { + return { id: assetClass, label: translate(assetClass) }; + }); + public assetSubClasses = Object.keys(AssetSubClass).map((assetSubClass) => { + return { id: assetSubClass, label: translate(assetSubClass) }; + }); public assetProfile: AdminMarketDataDetails['assetProfile']; public assetProfileForm = this.formBuilder.group({ + assetClass: new FormControl(undefined), + assetSubClass: new FormControl(undefined), comment: '', + name: ['', Validators.required], scraperConfiguration: '', symbolMapping: '' }); - public assetSubClass: string; + public assetProfileSubClass: string; public benchmarks: Partial[]; public countries: { [code: string]: { name: string; value: number }; @@ -86,8 +100,8 @@ export class AssetProfileDialog implements OnDestroy, OnInit { .subscribe(({ assetProfile, marketData }) => { this.assetProfile = assetProfile; - this.assetClass = translate(this.assetProfile?.assetClass); - this.assetSubClass = translate(this.assetProfile?.assetSubClass); + this.assetProfileClass = translate(this.assetProfile?.assetClass); + this.assetProfileSubClass = translate(this.assetProfile?.assetSubClass); this.countries = {}; this.isBenchmark = this.benchmarks.some(({ id }) => { return id === this.assetProfile.id; @@ -114,6 +128,9 @@ export class AssetProfileDialog implements OnDestroy, OnInit { } this.assetProfileForm.setValue({ + name: this.assetProfile.name, + assetClass: this.assetProfile.assetClass, + assetSubClass: this.assetProfile.assetSubClass, comment: this.assetProfile?.comment ?? '', scraperConfiguration: JSON.stringify( this.assetProfile?.scraperConfiguration ?? {} @@ -157,7 +174,7 @@ export class AssetProfileDialog implements OnDestroy, OnInit { dataSource: this.data.dataSource, marketData: { marketData: marketData.map(({ date, marketPrice }) => { - return { marketPrice, date: parseISO(date) }; + return { marketPrice, date: parseDate(date).toISOString() }; }) }, symbol: this.data.symbol @@ -204,9 +221,12 @@ export class AssetProfileDialog implements OnDestroy, OnInit { } catch {} const assetProfileData: UpdateAssetProfileDto = { + assetClass: this.assetProfileForm.controls['assetClass'].value, + assetSubClass: this.assetProfileForm.controls['assetSubClass'].value, + comment: this.assetProfileForm.controls['comment'].value ?? null, + name: this.assetProfileForm.controls['name'].value, scraperConfiguration, - symbolMapping, - comment: this.assetProfileForm.controls['comment'].value ?? null + symbolMapping }; this.adminService diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html index 66d00e720..755768209 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -112,7 +112,11 @@ >
- Asset Class
@@ -120,8 +124,8 @@ Asset Sub Class @@ -174,6 +178,38 @@ +
+ + Name + + +
+
+ + Asset Class + + + {{ assetClass.label }} + + +
+
+ + Asset Sub Class + + + {{ assetSubClass.label }} + + +
User Signup
- + >
Read-only Mode
- + >
diff --git a/apps/client/src/app/components/admin-overview/admin-overview.module.ts b/apps/client/src/app/components/admin-overview/admin-overview.module.ts index 899fb09ae..fed4b84df 100644 --- a/apps/client/src/app/components/admin-overview/admin-overview.module.ts +++ b/apps/client/src/app/components/admin-overview/admin-overview.module.ts @@ -3,8 +3,8 @@ import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; -import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatSelectModule } from '@angular/material/select'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { CacheService } from '@ghostfolio/client/services/cache.service'; import { GfValueModule } from '@ghostfolio/ui/value'; @@ -18,9 +18,9 @@ import { AdminOverviewComponent } from './admin-overview.component'; FormsModule, GfValueModule, MatButtonModule, - MatCheckboxModule, MatCardModule, MatSelectModule, + MatSlideToggleModule, ReactiveFormsModule ], providers: [CacheService], diff --git a/apps/client/src/app/components/admin-platform/admin-platform.component.html b/apps/client/src/app/components/admin-platform/admin-platform.component.html index 63f4a2ffa..fd860d440 100644 --- a/apps/client/src/app/components/admin-platform/admin-platform.component.html +++ b/apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -86,12 +86,16 @@ diff --git a/apps/client/src/app/components/admin-tag/admin-tag.component.html b/apps/client/src/app/components/admin-tag/admin-tag.component.html index d21523321..b08ae96d4 100644 --- a/apps/client/src/app/components/admin-tag/admin-tag.component.html +++ b/apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -66,12 +66,16 @@ diff --git a/apps/client/src/app/components/admin-users/admin-users.html b/apps/client/src/app/components/admin-users/admin-users.html index b6e296b95..2071e058e 100644 --- a/apps/client/src/app/components/admin-users/admin-users.html +++ b/apps/client/src/app/components/admin-users/admin-users.html @@ -203,16 +203,20 @@ mat-menu-item (click)="onImpersonateUser(element.id)" > - - Impersonate User + + + Impersonate User + diff --git a/apps/client/src/app/components/dialog-footer/dialog-footer.component.html b/apps/client/src/app/components/dialog-footer/dialog-footer.component.html index 1c844c46d..c2153942a 100644 --- a/apps/client/src/app/components/dialog-footer/dialog-footer.component.html +++ b/apps/client/src/app/components/dialog-footer/dialog-footer.component.html @@ -1,6 +1,5 @@