Compare commits

...

6 Commits

Author SHA1 Message Date
Thomas Kaul 842a564bb5
Release 2.175.0 (#5051) 3 days ago
Jack Ulf 559cac31bd
Feature/extend AI service by OpenRouter access (#5025) 3 days ago
Thomas Kaul 77d3121f0d
Feature/rename Account to account in AccountBalance database schema (#5049) 3 days ago
Thomas Kaul f936c69a7f
Feature/refactor scraper in manual service (#5048) 3 days ago
Thomas Kaul 3fcd611e29
Feature/extend selector handling of scraper for more use cases (#5047) 3 days ago
Thomas Kaul 4983b7ca12
Feature/simplify badge style in admin settings (#5046) 3 days ago
  1. 5
      CHANGELOG.md
  2. 12
      apps/api/src/app/account-balance/account-balance.service.ts
  3. 2
      apps/api/src/app/endpoints/ai/ai.module.ts
  4. 31
      apps/api/src/app/endpoints/ai/ai.service.ts
  5. 24
      apps/api/src/services/data-provider/manual/manual.service.ts
  6. 2
      apps/client/src/app/components/admin-settings/admin-settings.component.html
  7. 7
      apps/client/src/app/components/admin-settings/admin-settings.component.scss
  8. 2
      apps/client/src/app/pages/admin/admin-page.component.ts
  9. 2
      libs/common/src/lib/config.ts
  10. 232
      package-lock.json
  11. 4
      package.json
  12. 2
      prisma/schema.prisma

5
CHANGELOG.md

@ -5,7 +5,7 @@ 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
## 2.175.0 - 2025-06-28
### Added
@ -16,7 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Extended the selector handling of the scraper configuration for more use cases
- Extended the _AI_ service by an access to _OpenRouter_ (experimental)
- Changed `node main` to `exec node main` in the `entrypoint.sh` file to improve the container signal handling
- Renamed `Account` to `account` in the `AccountBalance` database schema
- Improved the language localization for Catalan (`ca`)
- Improved the language localization for Dutch (`nl`)
- Improved the language localization for Español (`es`)

12
apps/api/src/app/account-balance/account-balance.service.ts

@ -30,7 +30,7 @@ export class AccountBalanceService {
): Promise<AccountBalance | null> {
return this.prismaService.accountBalance.findFirst({
include: {
Account: true
account: true
},
where: accountBalanceWhereInput
});
@ -46,7 +46,7 @@ export class AccountBalanceService {
}): Promise<AccountBalance> {
const accountBalance = await this.prismaService.accountBalance.upsert({
create: {
Account: {
account: {
connect: {
id_userId: {
userId,
@ -154,7 +154,7 @@ export class AccountBalanceService {
}
if (withExcludedAccounts === false) {
where.Account = { isExcluded: false };
where.account = { isExcluded: false };
}
const balances = await this.prismaService.accountBalance.findMany({
@ -163,7 +163,7 @@ export class AccountBalanceService {
date: 'asc'
},
select: {
Account: true,
account: true,
date: true,
id: true,
value: true
@ -174,10 +174,10 @@ export class AccountBalanceService {
balances: balances.map((balance) => {
return {
...balance,
accountId: balance.Account.id,
accountId: balance.account.id,
valueInBaseCurrency: this.exchangeRateDataService.toCurrency(
balance.value,
balance.Account.currency,
balance.account.currency,
userCurrency
)
};

2
apps/api/src/app/endpoints/ai/ai.module.ts

@ -17,6 +17,7 @@ import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impe
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
import { PortfolioSnapshotQueueModule } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
@ -39,6 +40,7 @@ import { AiService } from './ai.service';
OrderModule,
PortfolioSnapshotQueueModule,
PrismaModule,
PropertyModule,
RedisCacheModule,
SymbolProfileModule,
UserModule

31
apps/api/src/app/endpoints/ai/ai.service.ts

@ -1,12 +1,41 @@
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import {
PROPERTY_API_KEY_OPENROUTER,
PROPERTY_OPENROUTER_MODEL
} from '@ghostfolio/common/config';
import { Filter } from '@ghostfolio/common/interfaces';
import type { AiPromptMode } from '@ghostfolio/common/types';
import { Injectable } from '@nestjs/common';
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
import { generateText } from 'ai';
@Injectable()
export class AiService {
public constructor(private readonly portfolioService: PortfolioService) {}
public constructor(
private readonly portfolioService: PortfolioService,
private readonly propertyService: PropertyService
) {}
public async generateText({ prompt }: { prompt: string }) {
const openRouterApiKey = (await this.propertyService.getByKey(
PROPERTY_API_KEY_OPENROUTER
)) as string;
const openRouterModel = (await this.propertyService.getByKey(
PROPERTY_OPENROUTER_MODEL
)) as string;
const openRouterService = createOpenRouter({
apiKey: openRouterApiKey
});
return generateText({
prompt,
model: openRouterService.chat(openRouterModel)
});
}
public async getPrompt({
filters,

24
apps/api/src/services/data-provider/manual/manual.service.ts

@ -286,14 +286,12 @@ export class ManualService implements DataProviderInterface {
)
});
let value: string;
if (response.headers.get('content-type')?.includes('application/json')) {
const data = await response.json();
const value = String(
jsonpath.query(data, scraperConfiguration.selector)[0]
);
return extractNumberFromString({ locale, value });
value = String(jsonpath.query(data, scraperConfiguration.selector)[0]);
} else {
const $ = cheerio.load(await response.text());
@ -303,10 +301,24 @@ export class ManualService implements DataProviderInterface {
} catch {}
}
value = $(scraperConfiguration.selector).first().text();
const lines = value?.split('\n') ?? [];
const lineWithDigits = lines.find((line) => {
return /\d/.test(line);
});
if (lineWithDigits) {
value = lineWithDigits;
}
return extractNumberFromString({
locale,
value: $(scraperConfiguration.selector).first().text()
value
});
}
return extractNumberFromString({ locale, value });
}
}

2
apps/client/src/app/components/admin-settings/admin-settings.component.html

@ -62,7 +62,7 @@
/>
@if (isGhostfolioApiKeyValid === false) {
<span
class="badge badge-light ml-2 new text-uppercase"
class="badge badge-pill badge-secondary ml-2 text-uppercase"
i18n
>new</span
>

7
apps/client/src/app/components/admin-settings/admin-settings.component.scss

@ -9,13 +9,6 @@
}
}
.badge {
&.new {
border: 1px solid var(--mat-table-row-item-outline-color);
padding-bottom: 0.05rem;
}
}
.mat-mdc-card {
--mdc-outlined-card-container-color: whitesmoke;

2
apps/client/src/app/pages/admin/admin-page.component.ts

@ -33,7 +33,7 @@ export class AdminPageComponent implements OnDestroy, OnInit {
iconName: 'settings-outline',
label:
internalRoutes.adminControl.subRoutes.settings.title +
'<span class="badge badge-secondary badge-pill ml-2 text-uppercase">' +
'<span class="badge badge-pill badge-secondary ml-2 text-uppercase">' +
$localize`new` +
'</span>',
routerLink: internalRoutes.adminControl.subRoutes.settings.routerLink

2
libs/common/src/lib/config.ts

@ -112,6 +112,7 @@ export const MAX_TOP_HOLDINGS = 50;
export const NUMERICAL_PRECISION_THRESHOLD = 100000;
export const PROPERTY_API_KEY_GHOSTFOLIO = 'API_KEY_GHOSTFOLIO';
export const PROPERTY_API_KEY_OPENROUTER = 'API_KEY_OPENROUTER';
export const PROPERTY_BENCHMARKS = 'BENCHMARKS';
export const PROPERTY_BETTER_UPTIME_MONITOR_ID = 'BETTER_UPTIME_MONITOR_ID';
export const PROPERTY_COUNTRIES_OF_SUBSCRIBERS = 'COUNTRIES_OF_SUBSCRIBERS';
@ -125,6 +126,7 @@ export const PROPERTY_DEMO_USER_ID = 'DEMO_USER_ID';
export const PROPERTY_IS_DATA_GATHERING_ENABLED = 'IS_DATA_GATHERING_ENABLED';
export const PROPERTY_IS_READ_ONLY_MODE = 'IS_READ_ONLY_MODE';
export const PROPERTY_IS_USER_SIGNUP_ENABLED = 'IS_USER_SIGNUP_ENABLED';
export const PROPERTY_OPENROUTER_MODEL = 'OPENROUTER_MODEL';
export const PROPERTY_SLACK_COMMUNITY_USERS = 'SLACK_COMMUNITY_USERS';
export const PROPERTY_STRIPE_CONFIG = 'STRIPE_CONFIG';
export const PROPERTY_SYSTEM_MESSAGE = 'SYSTEM_MESSAGE';

232
package-lock.json

@ -1,12 +1,12 @@
{
"name": "ghostfolio",
"version": "2.174.0",
"version": "2.175.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ghostfolio",
"version": "2.174.0",
"version": "2.175.0",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {
@ -42,10 +42,12 @@
"@nestjs/platform-express": "11.1.3",
"@nestjs/schedule": "6.0.0",
"@nestjs/serve-static": "5.0.3",
"@openrouter/ai-sdk-provider": "0.7.2",
"@prisma/client": "6.10.1",
"@simplewebauthn/browser": "13.1.0",
"@simplewebauthn/server": "13.1.1",
"@stripe/stripe-js": "7.3.1",
"ai": "4.3.16",
"alphavantage": "2.2.0",
"big.js": "7.0.1",
"bootstrap": "4.6.2",
@ -171,6 +173,88 @@
"dev": true,
"license": "MIT"
},
"node_modules/@ai-sdk/provider": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.3.tgz",
"integrity": "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==",
"license": "Apache-2.0",
"dependencies": {
"json-schema": "^0.4.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@ai-sdk/provider-utils": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.8.tgz",
"integrity": "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider": "1.1.3",
"nanoid": "^3.3.8",
"secure-json-parse": "^2.7.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"zod": "^3.23.8"
}
},
"node_modules/@ai-sdk/react": {
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.2.12.tgz",
"integrity": "sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider-utils": "2.2.8",
"@ai-sdk/ui-utils": "1.2.11",
"swr": "^2.2.5",
"throttleit": "2.1.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"react": "^18 || ^19 || ^19.0.0-rc",
"zod": "^3.23.8"
},
"peerDependenciesMeta": {
"zod": {
"optional": true
}
}
},
"node_modules/@ai-sdk/react/node_modules/throttleit": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz",
"integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@ai-sdk/ui-utils": {
"version": "1.2.11",
"resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.11.tgz",
"integrity": "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider": "1.1.3",
"@ai-sdk/provider-utils": "2.2.8",
"zod-to-json-schema": "^3.24.1"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"zod": "^3.23.8"
}
},
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
@ -9348,6 +9432,32 @@
"yargs-parser": "21.1.1"
}
},
"node_modules/@openrouter/ai-sdk-provider": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/@openrouter/ai-sdk-provider/-/ai-sdk-provider-0.7.2.tgz",
"integrity": "sha512-Fry2mV7uGGJRmP9JntTZRc8ElESIk7AJNTacLbF6Syoeb5k8d7HPGkcK9rTXDlqBb8HgU1hOKtz23HojesTmnw==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider": "1.1.3",
"@ai-sdk/provider-utils": "2.2.8"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"ai": "^4.3.16",
"zod": "^3.25.34"
}
},
"node_modules/@opentelemetry/api": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
"license": "Apache-2.0",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/@parcel/watcher": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
@ -12196,6 +12306,12 @@
"@types/d3-selection": "*"
}
},
"node_modules/@types/diff-match-patch": {
"version": "1.0.36",
"resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz",
"integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==",
"license": "MIT"
},
"node_modules/@types/eslint": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
@ -13686,6 +13802,32 @@
"node": ">= 14"
}
},
"node_modules/ai": {
"version": "4.3.16",
"resolved": "https://registry.npmjs.org/ai/-/ai-4.3.16.tgz",
"integrity": "sha512-KUDwlThJ5tr2Vw0A1ZkbDKNME3wzWhuVfAOwIvFUzl1TPVDFAXDFTXio3p+jaKneB+dKNCvFFlolYmmgHttG1g==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider": "1.1.3",
"@ai-sdk/provider-utils": "2.2.8",
"@ai-sdk/react": "1.2.12",
"@ai-sdk/ui-utils": "1.2.11",
"@opentelemetry/api": "1.9.0",
"jsondiffpatch": "0.6.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"react": "^18 || ^19 || ^19.0.0-rc",
"zod": "^3.23.8"
},
"peerDependenciesMeta": {
"react": {
"optional": true
}
}
},
"node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
@ -17874,7 +18016,6 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@ -17943,6 +18084,12 @@
"node": ">=0.3.1"
}
},
"node_modules/diff-match-patch": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
"integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==",
"license": "Apache-2.0"
},
"node_modules/diff-sequences": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
@ -24216,7 +24363,6 @@
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
"dev": true,
"license": "(AFL-2.1 OR BSD-3-Clause)"
},
"node_modules/json-schema-traverse": {
@ -24303,6 +24449,35 @@
"integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==",
"license": "MIT"
},
"node_modules/jsondiffpatch": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz",
"integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==",
"license": "MIT",
"dependencies": {
"@types/diff-match-patch": "^1.0.36",
"chalk": "^5.3.0",
"diff-match-patch": "^1.0.5"
},
"bin": {
"jsondiffpatch": "bin/jsondiffpatch.js"
},
"engines": {
"node": "^18.0.0 || >=20.0.0"
}
},
"node_modules/jsondiffpatch/node_modules/chalk": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
"license": "MIT",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
@ -25837,7 +26012,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
@ -29592,7 +29766,6 @@
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
@ -31113,6 +31286,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/secure-json-parse": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
"integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==",
"license": "BSD-3-Clause"
},
"node_modules/select": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
@ -32571,6 +32750,19 @@
"node": ">= 10"
}
},
"node_modules/swr": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/swr/-/swr-2.3.3.tgz",
"integrity": "sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==",
"license": "MIT",
"dependencies": {
"dequal": "^2.0.3",
"use-sync-external-store": "^1.4.0"
},
"peerDependencies": {
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/symbol-observable": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
@ -34142,6 +34334,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/use-sync-external-store": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/util": {
"version": "0.12.5",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
@ -35791,6 +35992,25 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zod": {
"version": "3.25.67",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz",
"integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zod-to-json-schema": {
"version": "3.24.6",
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz",
"integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==",
"license": "ISC",
"peerDependencies": {
"zod": "^3.24.1"
}
},
"node_modules/zone.js": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz",

4
package.json

@ -1,6 +1,6 @@
{
"name": "ghostfolio",
"version": "2.174.0",
"version": "2.175.0",
"homepage": "https://ghostfol.io",
"license": "AGPL-3.0",
"repository": "https://github.com/ghostfolio/ghostfolio",
@ -88,10 +88,12 @@
"@nestjs/platform-express": "11.1.3",
"@nestjs/schedule": "6.0.0",
"@nestjs/serve-static": "5.0.3",
"@openrouter/ai-sdk-provider": "0.7.2",
"@prisma/client": "6.10.1",
"@simplewebauthn/browser": "13.1.0",
"@simplewebauthn/server": "13.1.1",
"@stripe/stripe-js": "7.3.1",
"ai": "4.3.16",
"alphavantage": "2.2.0",
"big.js": "7.0.1",
"bootstrap": "4.6.2",

2
prisma/schema.prisma

@ -50,6 +50,7 @@ model Account {
}
model AccountBalance {
account Account @relation(fields: [accountId, userId], onDelete: Cascade, references: [id, userId])
accountId String
createdAt DateTime @default(now())
date DateTime @default(now())
@ -57,7 +58,6 @@ model AccountBalance {
updatedAt DateTime @updatedAt
userId String
value Float
Account Account @relation(fields: [accountId, userId], onDelete: Cascade, references: [id, userId])
@@unique([accountId, date])
@@index([accountId])

Loading…
Cancel
Save