diff --git a/CHANGELOG.md b/CHANGELOG.md index 13a7b85d0..c942fc9cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added support for data decimation in the line chart component +### 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 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/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/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/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/client/src/app/components/accounts-table/accounts-table.component.html b/apps/client/src/app/components/accounts-table/accounts-table.component.html index bfe5a667a..991ab7454 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 @@