Browse Source

Merge remote-tracking branch 'origin/main' into bugfix/portfolio-calculator-precision-issues

pull/5509/head
KenTandrian 3 days ago
parent
commit
a5ee33d079
  1. 17
      CHANGELOG.md
  2. 29
      apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts
  3. 324
      apps/api/src/assets/cryptocurrencies/cryptocurrencies.json
  4. 12
      apps/client/src/app/pages/api/api-page.component.ts
  5. 4
      apps/client/src/app/pages/api/api-page.html
  6. 12
      apps/client/src/app/pages/register/register-page.component.ts
  7. 34
      apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.component.ts
  8. 34
      apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.module.ts
  9. 2
      libs/common/src/lib/permissions.ts
  10. 1
      libs/ui/src/lib/activities-table/activities-table.component.html
  11. 76
      package-lock.json
  12. 6
      package.json
  13. 17
      prisma/migrations/20250915163323_added_asset_profile_resolution/migration.sql
  14. 14
      prisma/schema.prisma

17
CHANGELOG.md

@ -5,6 +5,23 @@ 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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Changed
- Refreshed the cryptocurrencies list
## 2.200.0 - 2025-09-17
### Changed
- Refactored the show access token dialog component to standalone
- Upgraded `prisma` from version `6.15.0` to `6.16.1`
### Fixed
- Removed a temporary element from the activities table component
## 2.199.0 - 2025-09-14 ## 2.199.0 - 2025-09-14
### Added ### Added

29
apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts

@ -56,11 +56,36 @@ export class GhostfolioService {
requestTimeout, requestTimeout,
symbol symbol
}) })
.then((assetProfile) => { .then(async (assetProfile) => {
const dataSourceOrigin = DataSource.GHOSTFOLIO;
if (assetProfile) {
await this.prismaService.assetProfileResolution.upsert({
create: {
dataSourceOrigin,
currency: assetProfile.currency,
dataSourceTarget: assetProfile.dataSource,
symbolOrigin: symbol,
symbolTarget: assetProfile.symbol
},
update: {
requestCount: {
increment: 1
}
},
where: {
dataSourceOrigin_symbolOrigin: {
dataSourceOrigin,
symbolOrigin: symbol
}
}
});
}
result = { result = {
...result, ...result,
...assetProfile, ...assetProfile,
dataSource: DataSource.GHOSTFOLIO dataSource: dataSourceOrigin
}; };
return assetProfile; return assetProfile;

324
apps/api/src/assets/cryptocurrencies/cryptocurrencies.json

File diff suppressed because it is too large

12
apps/client/src/app/pages/api/api-page.component.ts

@ -4,6 +4,7 @@ import {
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { DATE_FORMAT } from '@ghostfolio/common/helper';
import { import {
DataProviderGhostfolioAssetProfileResponse,
DataProviderGhostfolioStatusResponse, DataProviderGhostfolioStatusResponse,
DividendsResponse, DividendsResponse,
HistoricalResponse, HistoricalResponse,
@ -25,6 +26,7 @@ import { map, Observable, Subject, takeUntil } from 'rxjs';
templateUrl: './api-page.html' templateUrl: './api-page.html'
}) })
export class GfApiPageComponent implements OnInit { export class GfApiPageComponent implements OnInit {
public assetProfile$: Observable<DataProviderGhostfolioAssetProfileResponse>;
public dividends$: Observable<DividendsResponse['dividends']>; public dividends$: Observable<DividendsResponse['dividends']>;
public historicalData$: Observable<HistoricalResponse['historicalData']>; public historicalData$: Observable<HistoricalResponse['historicalData']>;
public isinLookupItems$: Observable<LookupResponse['items']>; public isinLookupItems$: Observable<LookupResponse['items']>;
@ -40,6 +42,7 @@ export class GfApiPageComponent implements OnInit {
public ngOnInit() { public ngOnInit() {
this.apiKey = prompt($localize`Please enter your Ghostfolio API key:`); this.apiKey = prompt($localize`Please enter your Ghostfolio API key:`);
this.assetProfile$ = this.fetchAssetProfile({ symbol: 'AAPL' });
this.dividends$ = this.fetchDividends({ symbol: 'KO' }); this.dividends$ = this.fetchDividends({ symbol: 'KO' });
this.historicalData$ = this.fetchHistoricalData({ symbol: 'AAPL' }); this.historicalData$ = this.fetchHistoricalData({ symbol: 'AAPL' });
this.isinLookupItems$ = this.fetchLookupItems({ query: 'US0378331005' }); this.isinLookupItems$ = this.fetchLookupItems({ query: 'US0378331005' });
@ -53,6 +56,15 @@ export class GfApiPageComponent implements OnInit {
this.unsubscribeSubject.complete(); this.unsubscribeSubject.complete();
} }
private fetchAssetProfile({ symbol }: { symbol: string }) {
return this.http
.get<DataProviderGhostfolioAssetProfileResponse>(
`/api/v1/data-providers/ghostfolio/asset-profile/${symbol}`,
{ headers: this.getHeaders() }
)
.pipe(takeUntil(this.unsubscribeSubject));
}
private fetchDividends({ symbol }: { symbol: string }) { private fetchDividends({ symbol }: { symbol: string }) {
const params = new HttpParams() const params = new HttpParams()
.set('from', format(startOfYear(new Date()), DATE_FORMAT)) .set('from', format(startOfYear(new Date()), DATE_FORMAT))

4
apps/client/src/app/pages/api/api-page.html

@ -3,6 +3,10 @@
<h2 class="text-center">Status</h2> <h2 class="text-center">Status</h2>
<div>{{ status$ | async | json }}</div> <div>{{ status$ | async | json }}</div>
</div> </div>
<div class="mb-3">
<h2 class="text-center">Asset Profile</h2>
<div>{{ assetProfile$ | async | json }}</div>
</div>
<div> <div>
<h2 class="text-center">Lookup</h2> <h2 class="text-center">Lookup</h2>
@if (lookupItems$) { @if (lookupItems$) {

12
apps/client/src/app/pages/register/register-page.component.ts

@ -19,17 +19,11 @@ import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { ShowAccessTokenDialogParams } from './show-access-token-dialog/interfaces/interfaces'; import { ShowAccessTokenDialogParams } from './show-access-token-dialog/interfaces/interfaces';
import { ShowAccessTokenDialog } from './show-access-token-dialog/show-access-token-dialog.component'; import { GfShowAccessTokenDialogComponent } from './show-access-token-dialog/show-access-token-dialog.component';
import { ShowAccessTokenDialogModule } from './show-access-token-dialog/show-access-token-dialog.module';
@Component({ @Component({
host: { class: 'page' }, host: { class: 'page' },
imports: [ imports: [GfLogoComponent, MatButtonModule, RouterModule],
GfLogoComponent,
MatButtonModule,
RouterModule,
ShowAccessTokenDialogModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-register-page', selector: 'gf-register-page',
styleUrls: ['./register-page.scss'], styleUrls: ['./register-page.scss'],
@ -90,7 +84,7 @@ export class GfRegisterPageComponent implements OnDestroy, OnInit {
} }
public openShowAccessTokenDialog() { public openShowAccessTokenDialog() {
const dialogRef = this.dialog.open(ShowAccessTokenDialog, { const dialogRef = this.dialog.open(GfShowAccessTokenDialogComponent, {
data: { data: {
deviceType: this.deviceType, deviceType: this.deviceType,
needsToAcceptTermsOfService: this.hasPermissionForSubscription needsToAcceptTermsOfService: this.hasPermissionForSubscription

34
apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.component.ts

@ -1,15 +1,26 @@
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { publicRoutes } from '@ghostfolio/common/routes/routes';
import { ClipboardModule } from '@angular/cdk/clipboard';
import { TextFieldModule } from '@angular/cdk/text-field';
import { CommonModule } from '@angular/common';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
CUSTOM_ELEMENTS_SCHEMA,
Inject, Inject,
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material/dialog'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatStepper } from '@angular/material/stepper'; import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatStepper, MatStepperModule } from '@angular/material/stepper';
import { RouterModule } from '@angular/router';
import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons'; import { addIcons } from 'ionicons';
import { import {
arrowForwardOutline, arrowForwardOutline,
@ -23,12 +34,27 @@ import { ShowAccessTokenDialogParams } from './interfaces/interfaces';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
ClipboardModule,
CommonModule,
FormsModule,
IonIcon,
MatButtonModule,
MatCheckboxModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
MatStepperModule,
ReactiveFormsModule,
RouterModule,
TextFieldModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-show-access-token-dialog', selector: 'gf-show-access-token-dialog',
standalone: false,
styleUrls: ['./show-access-token-dialog.scss'], styleUrls: ['./show-access-token-dialog.scss'],
templateUrl: 'show-access-token-dialog.html' templateUrl: 'show-access-token-dialog.html'
}) })
export class ShowAccessTokenDialog { export class GfShowAccessTokenDialogComponent {
@ViewChild(MatStepper) stepper!: MatStepper; @ViewChild(MatStepper) stepper!: MatStepper;
public accessToken: string; public accessToken: string;

34
apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.module.ts

@ -1,34 +0,0 @@
import { ClipboardModule } from '@angular/cdk/clipboard';
import { TextFieldModule } from '@angular/cdk/text-field';
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatStepperModule } from '@angular/material/stepper';
import { RouterModule } from '@angular/router';
import { ShowAccessTokenDialog } from './show-access-token-dialog.component';
@NgModule({
declarations: [ShowAccessTokenDialog],
imports: [
ClipboardModule,
CommonModule,
FormsModule,
MatButtonModule,
MatCheckboxModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
MatStepperModule,
ReactiveFormsModule,
RouterModule,
TextFieldModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class ShowAccessTokenDialogModule {}

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

@ -197,5 +197,5 @@ export function hasRole(aUser: UserWithSettings, aRole: Role) {
} }
export function isRestrictedView(aUser: UserWithSettings) { export function isRestrictedView(aUser: UserWithSettings) {
return aUser.settings.settings.isRestrictedView ?? false; return aUser?.settings?.settings?.isRestrictedView ?? false;
} }

1
libs/ui/src/lib/activities-table/activities-table.component.html

@ -129,7 +129,6 @@
[symbol]="element.SymbolProfile?.symbol" [symbol]="element.SymbolProfile?.symbol"
[tooltip]="element.SymbolProfile?.name" [tooltip]="element.SymbolProfile?.name"
/> />
<div>{{ element.dataSource }}</div>
</td> </td>
</ng-container> </ng-container>

76
package-lock.json

@ -1,12 +1,12 @@
{ {
"name": "ghostfolio", "name": "ghostfolio",
"version": "2.199.0", "version": "2.200.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ghostfolio", "name": "ghostfolio",
"version": "2.199.0", "version": "2.200.0",
"hasInstallScript": true, "hasInstallScript": true,
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
@ -44,7 +44,7 @@
"@nestjs/schedule": "6.0.0", "@nestjs/schedule": "6.0.0",
"@nestjs/serve-static": "5.0.3", "@nestjs/serve-static": "5.0.3",
"@openrouter/ai-sdk-provider": "0.7.2", "@openrouter/ai-sdk-provider": "0.7.2",
"@prisma/client": "6.15.0", "@prisma/client": "6.16.1",
"@simplewebauthn/browser": "13.1.0", "@simplewebauthn/browser": "13.1.0",
"@simplewebauthn/server": "13.1.1", "@simplewebauthn/server": "13.1.1",
"@stripe/stripe-js": "7.9.0", "@stripe/stripe-js": "7.9.0",
@ -149,7 +149,7 @@
"nx": "21.5.1", "nx": "21.5.1",
"prettier": "3.6.2", "prettier": "3.6.2",
"prettier-plugin-organize-attributes": "1.0.0", "prettier-plugin-organize-attributes": "1.0.0",
"prisma": "6.15.0", "prisma": "6.16.1",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"replace-in-file": "8.3.0", "replace-in-file": "8.3.0",
@ -11960,9 +11960,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@prisma/client": { "node_modules/@prisma/client": {
"version": "6.15.0", "version": "6.16.1",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.15.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.16.1.tgz",
"integrity": "sha512-wR2LXUbOH4cL/WToatI/Y2c7uzni76oNFND7+23ypLllBmIS8e3ZHhO+nud9iXSXKFt1SoM3fTZvHawg63emZw==", "integrity": "sha512-QaBCOY29lLAxEFFJgBPyW3WInCW52fJeQTmWx/h6YsP5u0bwuqP51aP0uhqFvhK9DaZPwvai/M4tSDYLVE9vRg==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
@ -11982,9 +11982,9 @@
} }
}, },
"node_modules/@prisma/config": { "node_modules/@prisma/config": {
"version": "6.15.0", "version": "6.16.1",
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.15.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.16.1.tgz",
"integrity": "sha512-KMEoec9b2u6zX0EbSEx/dRpx1oNLjqJEBZYyK0S3TTIbZ7GEGoVyGyFRk4C72+A38cuPLbfQGQvgOD+gBErKlA==", "integrity": "sha512-sz3uxRPNL62QrJ0EYiujCFkIGZ3hg+9hgC1Ae1HjoYuj0BxCqHua4JNijYvYCrh9LlofZDZcRBX3tHBfLvAngA==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@ -11995,53 +11995,53 @@
} }
}, },
"node_modules/@prisma/debug": { "node_modules/@prisma/debug": {
"version": "6.15.0", "version": "6.16.1",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.15.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.16.1.tgz",
"integrity": "sha512-y7cSeLuQmyt+A3hstAs6tsuAiVXSnw9T55ra77z0nbNkA8Lcq9rNcQg6PI00by/+WnE/aMRJ/W7sZWn2cgIy1g==", "integrity": "sha512-RWv/VisW5vJE4cDRTuAHeVedtGoItXTnhuLHsSlJ9202QKz60uiXWywBlVcqXVq8bFeIZoCoWH+R1duZJPwqLw==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@prisma/engines": { "node_modules/@prisma/engines": {
"version": "6.15.0", "version": "6.16.1",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.15.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.16.1.tgz",
"integrity": "sha512-opITiR5ddFJ1N2iqa7mkRlohCZqVSsHhRcc29QXeldMljOf4FSellLT0J5goVb64EzRTKcIDeIsJBgmilNcKxA==", "integrity": "sha512-EOnEM5HlosPudBqbI+jipmaW/vQEaF0bKBo4gVkGabasINHR6RpC6h44fKZEqx4GD8CvH+einD2+b49DQrwrAg==",
"devOptional": true, "devOptional": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "6.15.0", "@prisma/debug": "6.16.1",
"@prisma/engines-version": "6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb", "@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43",
"@prisma/fetch-engine": "6.15.0", "@prisma/fetch-engine": "6.16.1",
"@prisma/get-platform": "6.15.0" "@prisma/get-platform": "6.16.1"
} }
}, },
"node_modules/@prisma/engines-version": { "node_modules/@prisma/engines-version": {
"version": "6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb", "version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb.tgz", "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43.tgz",
"integrity": "sha512-a/46aK5j6L3ePwilZYEgYDPrhBQ/n4gYjLxT5YncUTJJNRnTCVjPF86QdzUOLRdYjCLfhtZp9aum90W0J+trrg==", "integrity": "sha512-ThvlDaKIVrnrv97ujNFDYiQbeMQpLa0O86HFA2mNoip4mtFqM7U5GSz2ie1i2xByZtvPztJlNRgPsXGeM/kqAA==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@prisma/fetch-engine": { "node_modules/@prisma/fetch-engine": {
"version": "6.15.0", "version": "6.16.1",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.15.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.16.1.tgz",
"integrity": "sha512-xcT5f6b+OWBq6vTUnRCc7qL+Im570CtwvgSj+0MTSGA1o9UDSKZ/WANvwtiRXdbYWECpyC3CukoG3A04VTAPHw==", "integrity": "sha512-fl/PKQ8da5YTayw86WD3O9OmKJEM43gD3vANy2hS5S1CnfW2oPXk+Q03+gUWqcKK306QqhjjIHRFuTZ31WaosQ==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "6.15.0", "@prisma/debug": "6.16.1",
"@prisma/engines-version": "6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb", "@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43",
"@prisma/get-platform": "6.15.0" "@prisma/get-platform": "6.16.1"
} }
}, },
"node_modules/@prisma/get-platform": { "node_modules/@prisma/get-platform": {
"version": "6.15.0", "version": "6.16.1",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.15.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.16.1.tgz",
"integrity": "sha512-Jbb+Xbxyp05NSR1x2epabetHiXvpO8tdN2YNoWoA/ZsbYyxxu/CO/ROBauIFuMXs3Ti+W7N7SJtWsHGaWte9Rg==", "integrity": "sha512-kUfg4vagBG7dnaGRcGd1c0ytQFcDj2SUABiuveIpL3bthFdTLI6PJeLEia6Q8Dgh+WhPdo0N2q0Fzjk63XTyaA==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "6.15.0" "@prisma/debug": "6.16.1"
} }
}, },
"node_modules/@redis/client": { "node_modules/@redis/client": {
@ -35747,15 +35747,15 @@
} }
}, },
"node_modules/prisma": { "node_modules/prisma": {
"version": "6.15.0", "version": "6.16.1",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.15.0.tgz", "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.16.1.tgz",
"integrity": "sha512-E6RCgOt+kUVtjtZgLQDBJ6md2tDItLJNExwI0XJeBc1FKL+Vwb+ovxXxuok9r8oBgsOXBA33fGDuE/0qDdCWqQ==", "integrity": "sha512-MFkMU0eaDDKAT4R/By2IA9oQmwLTxokqv2wegAErr9Rf+oIe7W2sYpE/Uxq0H2DliIR7vnV63PkC1bEwUtl98w==",
"devOptional": true, "devOptional": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/config": "6.15.0", "@prisma/config": "6.16.1",
"@prisma/engines": "6.15.0" "@prisma/engines": "6.16.1"
}, },
"bin": { "bin": {
"prisma": "build/index.js" "prisma": "build/index.js"

6
package.json

@ -1,6 +1,6 @@
{ {
"name": "ghostfolio", "name": "ghostfolio",
"version": "2.199.0", "version": "2.200.0",
"homepage": "https://ghostfol.io", "homepage": "https://ghostfol.io",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"repository": "https://github.com/ghostfolio/ghostfolio", "repository": "https://github.com/ghostfolio/ghostfolio",
@ -90,7 +90,7 @@
"@nestjs/schedule": "6.0.0", "@nestjs/schedule": "6.0.0",
"@nestjs/serve-static": "5.0.3", "@nestjs/serve-static": "5.0.3",
"@openrouter/ai-sdk-provider": "0.7.2", "@openrouter/ai-sdk-provider": "0.7.2",
"@prisma/client": "6.15.0", "@prisma/client": "6.16.1",
"@simplewebauthn/browser": "13.1.0", "@simplewebauthn/browser": "13.1.0",
"@simplewebauthn/server": "13.1.1", "@simplewebauthn/server": "13.1.1",
"@stripe/stripe-js": "7.9.0", "@stripe/stripe-js": "7.9.0",
@ -195,7 +195,7 @@
"nx": "21.5.1", "nx": "21.5.1",
"prettier": "3.6.2", "prettier": "3.6.2",
"prettier-plugin-organize-attributes": "1.0.0", "prettier-plugin-organize-attributes": "1.0.0",
"prisma": "6.15.0", "prisma": "6.16.1",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"replace-in-file": "8.3.0", "replace-in-file": "8.3.0",

17
prisma/migrations/20250915163323_added_asset_profile_resolution/migration.sql

@ -0,0 +1,17 @@
-- CreateTable
CREATE TABLE "public"."AssetProfileResolution" (
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"currency" TEXT NOT NULL,
"dataSourceOrigin" "public"."DataSource" NOT NULL,
"dataSourceTarget" "public"."DataSource" NOT NULL,
"id" TEXT NOT NULL,
"requestCount" INTEGER NOT NULL DEFAULT 1,
"symbolOrigin" TEXT NOT NULL,
"symbolTarget" TEXT NOT NULL,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "AssetProfileResolution_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "AssetProfileResolution_dataSourceOrigin_symbolOrigin_key" ON "public"."AssetProfileResolution"("dataSourceOrigin", "symbolOrigin");

14
prisma/schema.prisma

@ -88,6 +88,20 @@ model ApiKey {
@@index([userId]) @@index([userId])
} }
model AssetProfileResolution {
createdAt DateTime @default(now())
currency String
dataSourceOrigin DataSource
dataSourceTarget DataSource
id String @id @default(uuid())
requestCount Int @default(1)
symbolOrigin String
symbolTarget String
updatedAt DateTime @updatedAt
@@unique([dataSourceOrigin, symbolOrigin])
}
model AuthDevice { model AuthDevice {
createdAt DateTime @default(now()) createdAt DateTime @default(now())
credentialId Bytes credentialId Bytes

Loading…
Cancel
Save