Browse Source

Merge branch 'main' into feature/extend-account-detail-dialog-by-performance

pull/8259/head
Thomas Kaul 3 days ago
committed by GitHub
parent
commit
12d9c875c9
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      CHANGELOG.md
  2. 16
      apps/api/src/app/__snapshots__/app.module.spec.ts.snap
  3. 8
      apps/api/src/app/app.module.ts
  4. 19
      apps/api/src/app/auth-device/auth-device.module.ts
  5. 4
      apps/api/src/app/auth/auth.module.ts
  6. 2
      apps/api/src/app/auth/web-auth.service.ts
  7. 10
      apps/api/src/app/endpoints/auth-devices/auth-devices.controller.ts
  8. 11
      apps/api/src/app/endpoints/auth-devices/auth-devices.module.ts
  9. 18
      apps/api/src/app/endpoints/exchange-rates/exchange-rates.controller.ts
  10. 13
      apps/api/src/app/endpoints/exchange-rates/exchange-rates.module.ts
  11. 2
      apps/api/src/app/endpoints/exchange-rates/exchange-rates.service.ts
  12. 14
      apps/api/src/app/exchange-rate/exchange-rate.module.ts
  13. 12
      apps/api/src/services/auth-device/auth-device.module.ts
  14. 0
      apps/api/src/services/auth-device/auth-device.service.ts
  15. 2
      apps/client/src/app/services/web-authn.service.ts
  16. 2
      libs/common/src/lib/interfaces/index.ts
  17. 3
      libs/common/src/lib/interfaces/responses/exchange-rate-response.interface.ts
  18. 13
      libs/ui/src/lib/services/data.service.ts
  19. 51
      package-lock.json
  20. 8
      package.json

5
CHANGELOG.md

@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Extended the account detail dialog to include the change and the performance
- Deprecated the `api/v1/auth-device` endpoint in favor of the `api/v1/auth-devices` endpoint
- Deprecated the `api/v1/exchange-rate` endpoint in favor of the `api/v1/exchange-rates` endpoint
- Upgraded `@modelcontextprotocol/core` and `@modelcontextprotocol/server` from version `2.0.0` to `2.3.1`
- Upgraded `@modelcontextprotocol/node` from version `2.0.0` to `2.1.1`
- Upgraded `@rekog/mcp-nest` from version `2.0.7` to `2.0.8`
## 3.89.0 - 2026-10-20

16
apps/api/src/app/__snapshots__/app.module.spec.ts.snap

@ -116,6 +116,15 @@ exports[`AppModule should keep the security of each route 1`] = `
"interceptors": [],
"permission": "deleteAuthDevice",
},
"DELETE /auth-devices/:id": {
"allowDuringImpersonation": true,
"guards": [
"AuthGuard('jwt')",
"HasPermissionGuard",
],
"interceptors": [],
"permission": "deleteAuthDevice",
},
"DELETE /benchmarks/:dataSource/:symbol": {
"allowDuringImpersonation": true,
"guards": [
@ -489,6 +498,13 @@ exports[`AppModule should keep the security of each route 1`] = `
],
"interceptors": [],
},
"GET /exchange-rates/:symbol/:dateString": {
"guards": [
"AuthGuard('jwt')",
"HasPermissionGuard",
],
"interceptors": [],
},
"GET /export": {
"guards": [
"AuthGuard('jwt')",

8
apps/api/src/app/app.module.ts

@ -41,15 +41,16 @@ import { ActivitiesModule } from './activities/activities.module';
import { AdminModule } from './admin/admin.module';
import { AppController } from './app.controller';
import { AssetModule } from './asset/asset.module';
import { AuthDeviceModule } from './auth-device/auth-device.module';
import { AuthModule } from './auth/auth.module';
import { CacheModule } from './cache/cache.module';
import { AiModule } from './endpoints/ai/ai.module';
import { ApiKeysModule } from './endpoints/api-keys/api-keys.module';
import { AssetProfilesModule } from './endpoints/asset-profiles/asset-profiles.module';
import { AssetsModule } from './endpoints/assets/assets.module';
import { AuthDevicesModule } from './endpoints/auth-devices/auth-devices.module';
import { BenchmarksModule } from './endpoints/benchmarks/benchmarks.module';
import { GhostfolioModule } from './endpoints/data-providers/ghostfolio/ghostfolio.module';
import { ExchangeRatesModule } from './endpoints/exchange-rates/exchange-rates.module';
import { MarketDataModule } from './endpoints/market-data/market-data.module';
import { McpModule } from './endpoints/mcp/mcp.module';
import { PlatformsModule } from './endpoints/platforms/platforms.module';
@ -57,7 +58,6 @@ import { PublicModule } from './endpoints/public/public.module';
import { SitemapModule } from './endpoints/sitemap/sitemap.module';
import { TagsModule } from './endpoints/tags/tags.module';
import { WatchlistModule } from './endpoints/watchlist/watchlist.module';
import { ExchangeRateModule } from './exchange-rate/exchange-rate.module';
import { ExportModule } from './export/export.module';
import { HealthModule } from './health/health.module';
import { ImportModule } from './import/import.module';
@ -80,7 +80,7 @@ import { UserModule } from './user/user.module';
AssetProfilesModule,
AssetModule,
AssetsModule,
AuthDeviceModule,
AuthDevicesModule,
AuthModule,
BenchmarksModule,
BullBoardModule.forRoot({
@ -119,8 +119,8 @@ import { UserModule } from './user/user.module';
DataProviderModule,
EventEmitterModule.forRoot(),
EventsModule,
ExchangeRateModule,
ExchangeRateDataModule,
ExchangeRatesModule,
ExportModule,
GhostfolioModule,
HealthModule,

19
apps/api/src/app/auth-device/auth-device.module.ts

@ -1,19 +0,0 @@
import { AuthDeviceController } from '@ghostfolio/api/app/auth-device/auth-device.controller';
import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.service';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
@Module({
controllers: [AuthDeviceController],
imports: [
JwtModule.register({
secret: process.env.JWT_SECRET_KEY,
signOptions: { expiresIn: '180 days' }
}),
PrismaModule
],
providers: [AuthDeviceService]
})
export class AuthDeviceModule {}

4
apps/api/src/app/auth/auth.module.ts

@ -1,9 +1,9 @@
import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.service';
import { WebAuthService } from '@ghostfolio/api/app/auth/web-auth.service';
import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.module';
import { UserModule } from '@ghostfolio/api/app/user/user.module';
import { ApiKeyService } from '@ghostfolio/api/services/api-key/api-key.service';
import { ApiModule } from '@ghostfolio/api/services/api/api.module';
import { AuthDeviceModule } from '@ghostfolio/api/services/auth-device/auth-device.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { FetchModule } from '@ghostfolio/api/services/fetch/fetch.module';
@ -29,6 +29,7 @@ import { OidcStrategy } from './oidc.strategy';
controllers: [AuthController],
imports: [
ApiModule,
AuthDeviceModule,
ConfigurationModule,
FetchModule,
JwtModule.register({
@ -45,7 +46,6 @@ import { OidcStrategy } from './oidc.strategy';
providers: [
ApiKeyService,
ApiKeyStrategy,
AuthDeviceService,
AuthService,
GoogleStrategy,
JwtStrategy,

2
apps/api/src/app/auth/web-auth.service.ts

@ -1,7 +1,7 @@
import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.service';
import { PortfolioSnapshotValue } from '@ghostfolio/api/app/portfolio/interfaces/snapshot-value.interface';
import { UserService } from '@ghostfolio/api/app/user/user.service';
import { ApiService } from '@ghostfolio/api/services/api/api.service';
import { AuthDeviceService } from '@ghostfolio/api/services/auth-device/auth-device.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';

10
apps/api/src/app/auth-device/auth-device.controller.ts → apps/api/src/app/endpoints/auth-devices/auth-devices.controller.ts

@ -1,7 +1,7 @@
import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.service';
import { AllowDuringImpersonation } from '@ghostfolio/api/decorators/allow-during-impersonation.decorator';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { AuthDeviceService } from '@ghostfolio/api/services/auth-device/auth-device.service';
import { permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
@ -18,8 +18,12 @@ import { AuthGuard } from '@nestjs/passport';
import { getReasonPhrase, StatusCodes } from 'http-status-codes';
@AllowDuringImpersonation()
@Controller('auth-device')
export class AuthDeviceController {
@Controller([
'auth-devices',
/** @deprecated */
'auth-device'
])
export class AuthDevicesController {
public constructor(
private readonly authDeviceService: AuthDeviceService,
@Inject(REQUEST) private readonly request: RequestWithUser

11
apps/api/src/app/endpoints/auth-devices/auth-devices.module.ts

@ -0,0 +1,11 @@
import { AuthDeviceModule } from '@ghostfolio/api/services/auth-device/auth-device.module';
import { Module } from '@nestjs/common';
import { AuthDevicesController } from './auth-devices.controller';
@Module({
controllers: [AuthDevicesController],
imports: [AuthDeviceModule]
})
export class AuthDevicesModule {}

18
apps/api/src/app/exchange-rate/exchange-rate.controller.ts → apps/api/src/app/endpoints/exchange-rates/exchange-rates.controller.ts

@ -1,5 +1,5 @@
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { DataProviderHistoricalResponse } from '@ghostfolio/common/interfaces';
import { ExchangeRateResponse } from '@ghostfolio/common/interfaces';
import {
Controller,
@ -12,12 +12,16 @@ import { AuthGuard } from '@nestjs/passport';
import { parseISO } from 'date-fns';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { ExchangeRateService } from './exchange-rate.service';
import { ExchangeRatesService } from './exchange-rates.service';
@Controller('exchange-rate')
export class ExchangeRateController {
@Controller([
'exchange-rates',
/** @deprecated */
'exchange-rate'
])
export class ExchangeRatesController {
public constructor(
private readonly exchangeRateService: ExchangeRateService
private readonly exchangeRatesService: ExchangeRatesService
) {}
@Get(':symbol/:dateString')
@ -25,10 +29,10 @@ export class ExchangeRateController {
public async getExchangeRate(
@Param('dateString') dateString: string,
@Param('symbol') symbol: string
): Promise<DataProviderHistoricalResponse> {
): Promise<ExchangeRateResponse> {
const date = parseISO(dateString);
const exchangeRate = await this.exchangeRateService.getExchangeRate({
const exchangeRate = await this.exchangeRatesService.getExchangeRate({
date,
symbol
});

13
apps/api/src/app/endpoints/exchange-rates/exchange-rates.module.ts

@ -0,0 +1,13 @@
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
import { Module } from '@nestjs/common';
import { ExchangeRatesController } from './exchange-rates.controller';
import { ExchangeRatesService } from './exchange-rates.service';
@Module({
controllers: [ExchangeRatesController],
imports: [ExchangeRateDataModule],
providers: [ExchangeRatesService]
})
export class ExchangeRatesModule {}

2
apps/api/src/app/exchange-rate/exchange-rate.service.ts → apps/api/src/app/endpoints/exchange-rates/exchange-rates.service.ts

@ -3,7 +3,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
import { Injectable } from '@nestjs/common';
@Injectable()
export class ExchangeRateService {
export class ExchangeRatesService {
public constructor(
private readonly exchangeRateDataService: ExchangeRateDataService
) {}

14
apps/api/src/app/exchange-rate/exchange-rate.module.ts

@ -1,14 +0,0 @@
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
import { Module } from '@nestjs/common';
import { ExchangeRateController } from './exchange-rate.controller';
import { ExchangeRateService } from './exchange-rate.service';
@Module({
controllers: [ExchangeRateController],
exports: [ExchangeRateService],
imports: [ExchangeRateDataModule],
providers: [ExchangeRateService]
})
export class ExchangeRateModule {}

12
apps/api/src/services/auth-device/auth-device.module.ts

@ -0,0 +1,12 @@
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { Module } from '@nestjs/common';
import { AuthDeviceService } from './auth-device.service';
@Module({
exports: [AuthDeviceService],
imports: [PrismaModule],
providers: [AuthDeviceService]
})
export class AuthDeviceModule {}

0
apps/api/src/app/auth-device/auth-device.service.ts → apps/api/src/services/auth-device/auth-device.service.ts

2
apps/client/src/app/services/web-authn.service.ts

@ -66,7 +66,7 @@ export class WebAuthnService {
const deviceId = this.getDeviceId();
return this.http
.delete<AuthDeviceDto>(`/api/v1/auth-device/${deviceId}`)
.delete<AuthDeviceDto>(`/api/v1/auth-devices/${deviceId}`)
.pipe(
catchError((error) => {
console.warn(`Could not deregister device ${deviceId}`, error);

2
libs/common/src/lib/interfaces/index.ts

@ -67,6 +67,7 @@ import type {
} from './responses/data-provider-response.interface';
import type { DividendsResponse } from './responses/dividends-response.interface';
import type { ResponseError } from './responses/errors.interface';
import type { ExchangeRateResponse } from './responses/exchange-rate-response.interface';
import type { ExportResponse } from './responses/export-response.interface';
import type { HistoricalResponse } from './responses/historical-response.interface';
import type { ImportResponse } from './responses/import-response.interface';
@ -150,6 +151,7 @@ export {
DataProviderResponse,
DividendsResponse,
EnhancedAssetProfile,
ExchangeRateResponse,
ExportResponse,
Filter,
FilterGroup,

3
libs/common/src/lib/interfaces/responses/exchange-rate-response.interface.ts

@ -0,0 +1,3 @@
export interface ExchangeRateResponse {
marketPrice: number;
}

13
libs/ui/src/lib/services/data.service.ts

@ -40,7 +40,6 @@ import {
CreateActivityResponse,
CreateStripeCheckoutSessionResponse,
DataProviderHealthResponse,
DataProviderHistoricalResponse,
ExportResponse,
Filter,
ImportResponse,
@ -309,18 +308,6 @@ export class DataService {
);
}
public fetchExchangeRateForDate({
date,
symbol
}: {
date: Date;
symbol: string;
}) {
return this.http.get<DataProviderHistoricalResponse>(
`/api/v1/exchange-rate/${encodeURIComponent(symbol)}/${format(date, DATE_FORMAT, { in: utc })}`
);
}
public deleteAccess(aId: string) {
return this.http.delete<void>(`/api/v1/access/${aId}`);
}

51
package-lock.json

@ -29,9 +29,9 @@
"@internationalized/number": "3.6.8",
"@ionic/angular": "8.8.12",
"@keyv/redis": "5.1.6",
"@modelcontextprotocol/core": "2.0.0",
"@modelcontextprotocol/node": "2.0.0",
"@modelcontextprotocol/server": "2.0.0",
"@modelcontextprotocol/core": "2.3.1",
"@modelcontextprotocol/node": "2.1.1",
"@modelcontextprotocol/server": "2.3.1",
"@nest-lab/throttler-storage-redis": "1.2.0",
"@nestjs/bull": "11.0.5",
"@nestjs/cache-manager": "3.1.3",
@ -49,7 +49,7 @@
"@openrouter/ai-sdk-provider": "3.1.0",
"@prisma/adapter-pg": "7.10.0",
"@prisma/client": "7.10.0",
"@rekog/mcp-nest": "2.0.7",
"@rekog/mcp-nest": "2.0.8",
"@simplewebauthn/browser": "14.0.0",
"@simplewebauthn/server": "14.0.3",
"ai": "7.0.127",
@ -9043,10 +9043,10 @@
}
},
"node_modules/@modelcontextprotocol/core": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.0.0.tgz",
"integrity": "sha512-pJCEwGG7Lfr/+PQp9ZTwKXNeO5wzbfKL7H3MYpCorM4oFBoQrdjnBgEoqG+RjhsvS1FKrDbKux+M1HhlnGWqcA==",
"license": "MIT",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.3.1.tgz",
"integrity": "sha512-laVmIhPGpWi7rG9q+0UigblRN0Hws/Yjlb6qLFtVqryXVLkc4h+yN8z731POD2LnAGQlFe7/tXhUm7Bi0YB91g==",
"license": "Apache-2.0",
"dependencies": {
"zod": "^4.2.0"
},
@ -9055,24 +9055,19 @@
}
},
"node_modules/@modelcontextprotocol/node": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/node/-/node-2.0.0.tgz",
"integrity": "sha512-Y4hAC2XdGDUdDOCbLDOCA4+aL3NUldjsOWlDL/YwpAxrPhRm1xHd7lZ+mLacvZ9t3PaH28wgNoaLQGrIk1P2pg==",
"license": "MIT",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/node/-/node-2.1.1.tgz",
"integrity": "sha512-EY92OAXN2xYL7tNS/MVsWJW1v8VHAPwl9B4Ca3pCrjmvqyC+S1C/6UHsMxF2GoFPraS5Yyg56vFz9brVpFojMA==",
"license": "Apache-2.0",
"dependencies": {
"@hono/node-server": "^1.19.9"
"@hono/node-server": "^1.19.9",
"hono": "^4.11.4"
},
"engines": {
"node": ">=20"
},
"peerDependencies": {
"@modelcontextprotocol/server": "^2.0.0",
"hono": "^4.11.4"
},
"peerDependenciesMeta": {
"hono": {
"optional": true
}
"@modelcontextprotocol/server": "^2.3.0"
}
},
"node_modules/@modelcontextprotocol/sdk": {
@ -9116,12 +9111,12 @@
}
},
"node_modules/@modelcontextprotocol/server": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.0.0.tgz",
"integrity": "sha512-YhHWdHfpFMQfd0prsEnxKeS3Qz3ytIGmsS0sth4KDjnacIT7hxk6hXHkJ9KysxlkvTM+WZAtQbbcUhdoP4Hvtw==",
"license": "MIT",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.3.1.tgz",
"integrity": "sha512-e59MfWuSssj6DoQ96JCTqdW1AXT5/XtnTl0WWd6MU1Ka434fFUGGosCLJbGNySDhHGvxQWoRWK137FXYFkYNWw==",
"license": "Apache-2.0",
"dependencies": {
"@modelcontextprotocol/core": "2.0.0",
"@modelcontextprotocol/core": "2.3.1",
"zod": "^4.2.0"
},
"engines": {
@ -12758,9 +12753,9 @@
}
},
"node_modules/@rekog/mcp-nest": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@rekog/mcp-nest/-/mcp-nest-2.0.7.tgz",
"integrity": "sha512-8DWrSapEA9Z/YJL9dFmj7sSbemmeQmf0VkGkW6Sn1itwKFf0n9hzTiwWt8XBtZvV5JVDNCtvRZKYYKgNjDcYPg==",
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/@rekog/mcp-nest/-/mcp-nest-2.0.8.tgz",
"integrity": "sha512-CDqzT1RBdvCNsRkDthcNPG3nxoy0GbRCrdrK+TjL+90ElL+j65lquBhUQcR37csGyeLa8uLnj/2unLqlKZRzoA==",
"license": "MIT",
"dependencies": {
"multer": "^2.3.0",

8
package.json

@ -73,9 +73,9 @@
"@internationalized/number": "3.6.8",
"@ionic/angular": "8.8.12",
"@keyv/redis": "5.1.6",
"@modelcontextprotocol/core": "2.0.0",
"@modelcontextprotocol/node": "2.0.0",
"@modelcontextprotocol/server": "2.0.0",
"@modelcontextprotocol/core": "2.3.1",
"@modelcontextprotocol/node": "2.1.1",
"@modelcontextprotocol/server": "2.3.1",
"@nest-lab/throttler-storage-redis": "1.2.0",
"@nestjs/bull": "11.0.5",
"@nestjs/cache-manager": "3.1.3",
@ -93,7 +93,7 @@
"@openrouter/ai-sdk-provider": "3.1.0",
"@prisma/adapter-pg": "7.10.0",
"@prisma/client": "7.10.0",
"@rekog/mcp-nest": "2.0.7",
"@rekog/mcp-nest": "2.0.8",
"@simplewebauthn/browser": "14.0.0",
"@simplewebauthn/server": "14.0.3",
"ai": "7.0.127",

Loading…
Cancel
Save