Compare commits

...

3 Commits

Author SHA1 Message Date
Thomas Kaul 8b9c8e04f2
Task/upgrade jsonpath to version 1.3.0 (#6755) 2 days ago
Thomas Kaul ef7df25496
Task/refactor subscription types (#6735) 2 days ago
Thomas Kaul ccd81bde4b
Task/update OSS Friends 20260421 (#6754) 2 days ago
  1. 1
      CHANGELOG.md
  2. 5
      apps/api/src/app/access/access.controller.ts
  3. 3
      apps/api/src/app/endpoints/public/public.controller.ts
  4. 12
      apps/api/src/app/portfolio/portfolio.controller.ts
  5. 7
      apps/api/src/app/user/user.service.ts
  6. 7
      apps/api/src/services/data-provider/data-provider.service.ts
  7. 7
      apps/client/src/app/components/admin-overview/admin-overview.component.ts
  8. 5
      apps/client/src/app/pages/portfolio/fire/fire-page.component.ts
  9. 3
      apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts
  10. 7
      apps/client/src/assets/oss-friends.json
  11. 8
      package-lock.json
  12. 2
      package.json

1
CHANGELOG.md

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Upgraded `countup.js` from version `2.9.0` to `2.10.0` - Upgraded `countup.js` from version `2.9.0` to `2.10.0`
- Upgraded `jsonpath` from version `1.2.1` to `1.3.0`
## 2.255.0 - 2026-03-20 ## 2.255.0 - 2026-03-20

5
apps/api/src/app/access/access.controller.ts

@ -2,6 +2,7 @@ import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorat
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { CreateAccessDto, UpdateAccessDto } from '@ghostfolio/common/dtos'; import { CreateAccessDto, UpdateAccessDto } from '@ghostfolio/common/dtos';
import { SubscriptionType } from '@ghostfolio/common/enums';
import { Access } from '@ghostfolio/common/interfaces'; import { Access } from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions'; import { permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types'; import type { RequestWithUser } from '@ghostfolio/common/types';
@ -75,7 +76,7 @@ export class AccessController {
): Promise<AccessModel> { ): Promise<AccessModel> {
if ( if (
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
this.request.user.subscription.type === 'Basic' this.request.user.subscription.type === SubscriptionType.Basic
) { ) {
throw new HttpException( throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN), getReasonPhrase(StatusCodes.FORBIDDEN),
@ -130,7 +131,7 @@ export class AccessController {
): Promise<AccessModel> { ): Promise<AccessModel> {
if ( if (
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
this.request.user.subscription.type === 'Basic' this.request.user.subscription.type === SubscriptionType.Basic
) { ) {
throw new HttpException( throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN), getReasonPhrase(StatusCodes.FORBIDDEN),

3
apps/api/src/app/endpoints/public/public.controller.ts

@ -7,6 +7,7 @@ import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interc
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; import { DEFAULT_CURRENCY } from '@ghostfolio/common/config';
import { SubscriptionType } from '@ghostfolio/common/enums';
import { getSum } from '@ghostfolio/common/helper'; import { getSum } from '@ghostfolio/common/helper';
import { PublicPortfolioResponse } from '@ghostfolio/common/interfaces'; import { PublicPortfolioResponse } from '@ghostfolio/common/interfaces';
import type { RequestWithUser } from '@ghostfolio/common/types'; import type { RequestWithUser } from '@ghostfolio/common/types';
@ -58,7 +59,7 @@ export class PublicController {
}); });
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
hasDetails = user.subscription.type === 'Premium'; hasDetails = user.subscription.type === SubscriptionType.Premium;
} }
const [ const [

12
apps/api/src/app/portfolio/portfolio.controller.ts

@ -17,6 +17,7 @@ import {
HEADER_KEY_IMPERSONATION, HEADER_KEY_IMPERSONATION,
UNKNOWN_KEY UNKNOWN_KEY
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { SubscriptionType } from '@ghostfolio/common/enums';
import { import {
PortfolioDetails, PortfolioDetails,
PortfolioDividendsResponse, PortfolioDividendsResponse,
@ -92,7 +93,8 @@ export class PortfolioController {
let hasError = false; let hasError = false;
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
hasDetails = this.request.user.subscription.type === 'Premium'; hasDetails =
this.request.user.subscription.type === SubscriptionType.Premium;
} }
const filters = this.apiService.buildFiltersFromQueryParams({ const filters = this.apiService.buildFiltersFromQueryParams({
@ -356,7 +358,7 @@ export class PortfolioController {
if ( if (
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
this.request.user.subscription.type === 'Basic' this.request.user.subscription.type === SubscriptionType.Basic
) { ) {
dividends = dividends.map((item) => { dividends = dividends.map((item) => {
return nullifyValuesInObject(item, ['investment']); return nullifyValuesInObject(item, ['investment']);
@ -484,7 +486,7 @@ export class PortfolioController {
if ( if (
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
this.request.user.subscription.type === 'Basic' this.request.user.subscription.type === SubscriptionType.Basic
) { ) {
investments = investments.map((item) => { investments = investments.map((item) => {
return nullifyValuesInObject(item, ['investment']); return nullifyValuesInObject(item, ['investment']);
@ -596,7 +598,7 @@ export class PortfolioController {
if ( if (
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
this.request.user.subscription.type === 'Basic' this.request.user.subscription.type === SubscriptionType.Basic
) { ) {
performanceInformation.chart = performanceInformation.chart.map( performanceInformation.chart = performanceInformation.chart.map(
(item) => { (item) => {
@ -624,7 +626,7 @@ export class PortfolioController {
if ( if (
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
this.request.user.subscription.type === 'Basic' this.request.user.subscription.type === SubscriptionType.Basic
) { ) {
for (const category of report.xRay.categories) { for (const category of report.xRay.categories) {
category.rules = null; category.rules = null;

7
apps/api/src/app/user/user.service.ts

@ -32,6 +32,7 @@ import {
TAG_ID_EXCLUDE_FROM_ANALYSIS, TAG_ID_EXCLUDE_FROM_ANALYSIS,
locale as defaultLocale locale as defaultLocale
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { SubscriptionType } from '@ghostfolio/common/enums';
import { import {
User as IUser, User as IUser,
SystemMessage, SystemMessage,
@ -156,7 +157,7 @@ export class UserService {
if ( if (
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
subscription.type === 'Basic' subscription.type === SubscriptionType.Basic
) { ) {
tags = []; tags = [];
} }
@ -443,7 +444,7 @@ export class UserService {
createdAt: user.createdAt createdAt: user.createdAt
}); });
if (user.subscription?.type === 'Basic') { if (user.subscription?.type === SubscriptionType.Basic) {
const daysSinceRegistration = differenceInDays( const daysSinceRegistration = differenceInDays(
new Date(), new Date(),
user.createdAt user.createdAt
@ -485,7 +486,7 @@ export class UserService {
// Reset holdings view mode // Reset holdings view mode
user.settings.settings.holdingsViewMode = undefined; user.settings.settings.holdingsViewMode = undefined;
} else if (user.subscription?.type === 'Premium') { } else if (user.subscription?.type === SubscriptionType.Premium) {
if (!hasRole(user, Role.DEMO)) { if (!hasRole(user, Role.DEMO)) {
currentPermissions.push(permissions.createApiKey); currentPermissions.push(permissions.createApiKey);
currentPermissions.push(permissions.enableDataProviderGhostfolio); currentPermissions.push(permissions.enableDataProviderGhostfolio);

7
apps/api/src/services/data-provider/data-provider.service.ts

@ -12,6 +12,7 @@ import {
PROPERTY_DATA_SOURCE_MAPPING PROPERTY_DATA_SOURCE_MAPPING
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { CreateOrderDto } from '@ghostfolio/common/dtos'; import { CreateOrderDto } from '@ghostfolio/common/dtos';
import { SubscriptionType } from '@ghostfolio/common/enums';
import { import {
DATE_FORMAT, DATE_FORMAT,
getAssetProfileIdentifier, getAssetProfileIdentifier,
@ -227,7 +228,7 @@ export class DataProviderService implements OnModuleInit {
if ( if (
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
user.subscription.type === 'Basic' user.subscription.type === SubscriptionType.Basic
) { ) {
const dataProvider = this.getDataProvider(DataSource[dataSource]); const dataProvider = this.getDataProvider(DataSource[dataSource]);
@ -591,7 +592,7 @@ export class DataProviderService implements OnModuleInit {
} else if ( } else if (
dataProvider.getDataProviderInfo().isPremium && dataProvider.getDataProviderInfo().isPremium &&
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
user?.subscription.type === 'Basic' user?.subscription.type === SubscriptionType.Basic
) { ) {
// Skip symbols of Premium data providers for users without subscription // Skip symbols of Premium data providers for users without subscription
return false; return false;
@ -780,7 +781,7 @@ export class DataProviderService implements OnModuleInit {
}) })
.map((lookupItem) => { .map((lookupItem) => {
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
if (user.subscription.type === 'Premium') { if (user.subscription.type === SubscriptionType.Premium) {
lookupItem.dataProviderInfo.isPremium = false; lookupItem.dataProviderInfo.isPremium = false;
} }

7
apps/client/src/app/components/admin-overview/admin-overview.component.ts

@ -8,7 +8,10 @@ import {
PROPERTY_SYSTEM_MESSAGE, PROPERTY_SYSTEM_MESSAGE,
ghostfolioPrefix ghostfolioPrefix
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import {
ConfirmationDialogType,
SubscriptionType
} from '@ghostfolio/common/enums';
import { getDateFnsLocale } from '@ghostfolio/common/helper'; import { getDateFnsLocale } from '@ghostfolio/common/helper';
import { import {
Coupon, Coupon,
@ -255,7 +258,7 @@ export class GfAdminOverviewComponent implements OnInit {
this.systemMessage ?? this.systemMessage ??
({ ({
message: '⚒️ Scheduled maintenance in progress...', message: '⚒️ Scheduled maintenance in progress...',
targetGroups: ['Basic', 'Premium'] targetGroups: [SubscriptionType.Basic, SubscriptionType.Premium]
} as SystemMessage) } as SystemMessage)
) )
); );

5
apps/client/src/app/pages/portfolio/fire/fire-page.component.ts

@ -1,5 +1,6 @@
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { SubscriptionType } from '@ghostfolio/common/enums';
import { import {
FireCalculationCompleteEvent, FireCalculationCompleteEvent,
FireWealth, FireWealth,
@ -80,7 +81,7 @@ export class GfFirePageComponent implements OnInit {
: 0 : 0
} }
}; };
if (this.user.subscription?.type === 'Basic') { if (this.user.subscription?.type === SubscriptionType.Basic) {
this.fireWealth = { this.fireWealth = {
today: { today: {
valueInBaseCurrency: 10000 valueInBaseCurrency: 10000
@ -113,7 +114,7 @@ export class GfFirePageComponent implements OnInit {
this.user = state.user; this.user = state.user;
this.hasPermissionToUpdateUserSettings = this.hasPermissionToUpdateUserSettings =
this.user.subscription?.type === 'Basic' this.user.subscription?.type === SubscriptionType.Basic
? false ? false
: hasPermission( : hasPermission(
this.user.permissions, this.user.permissions,

3
apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts

@ -2,6 +2,7 @@ import { GfRulesComponent } from '@ghostfolio/client/components/rules/rules.comp
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { UpdateUserSettingDto } from '@ghostfolio/common/dtos'; import { UpdateUserSettingDto } from '@ghostfolio/common/dtos';
import { SubscriptionType } from '@ghostfolio/common/enums';
import { import {
PortfolioReportResponse, PortfolioReportResponse,
PortfolioReportRule PortfolioReportRule
@ -73,7 +74,7 @@ export class GfXRayPageComponent {
this.user = state.user; this.user = state.user;
this.hasPermissionToUpdateUserSettings = this.hasPermissionToUpdateUserSettings =
this.user.subscription?.type === 'Basic' this.user.subscription?.type === SubscriptionType.Basic
? false ? false
: hasPermission( : hasPermission(
this.user.permissions, this.user.permissions,

7
apps/client/src/assets/oss-friends.json

@ -1,5 +1,5 @@
{ {
"createdAt": "2025-12-08T00:00:00.000Z", "createdAt": "2026-04-21T00:00:00.000Z",
"data": [ "data": [
{ {
"name": "Activepieces", "name": "Activepieces",
@ -21,11 +21,6 @@
"description": "Fastest LLM gateway with adaptive load balancer, cluster mode, guardrails, 1000+ models support & <100 µs overhead at 5k RPS.", "description": "Fastest LLM gateway with adaptive load balancer, cluster mode, guardrails, 1000+ models support & <100 µs overhead at 5k RPS.",
"href": "https://www.getmaxim.ai/bifrost" "href": "https://www.getmaxim.ai/bifrost"
}, },
{
"name": "Cal.com",
"description": "Cal.com is a scheduling tool that helps you schedule meetings without the back-and-forth emails.",
"href": "https://cal.com"
},
{ {
"name": "Cap", "name": "Cap",
"description": "Cap is the open source alternative to Loom. Lightweight, powerful, and cross-platform. Record and share securely in seconds.", "description": "Cap is the open source alternative to Loom. Lightweight, powerful, and cross-platform. Record and share securely in seconds.",

8
package-lock.json

@ -72,7 +72,7 @@
"helmet": "7.0.0", "helmet": "7.0.0",
"http-status-codes": "2.3.0", "http-status-codes": "2.3.0",
"ionicons": "8.0.13", "ionicons": "8.0.13",
"jsonpath": "1.2.1", "jsonpath": "1.3.0",
"lodash": "4.18.1", "lodash": "4.18.1",
"marked": "17.0.2", "marked": "17.0.2",
"ms": "3.0.0-canary.1", "ms": "3.0.0-canary.1",
@ -28659,9 +28659,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/jsonpath": { "node_modules/jsonpath": {
"version": "1.2.1", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.2.1.tgz", "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.3.0.tgz",
"integrity": "sha512-Jl6Jhk0jG+kP3yk59SSeGq7LFPR4JQz1DU0K+kXTysUhMostbhU3qh5mjTuf0PqFcXpAT7kvmMt9WxV10NyIgQ==", "integrity": "sha512-0kjkYHJBkAy50Z5QzArZ7udmvxrJzkpKYW27fiF//BrMY7TQibYLl+FYIXN2BiYmwMIVzSfD8aDRj6IzgBX2/w==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"esprima": "1.2.5", "esprima": "1.2.5",

2
package.json

@ -117,7 +117,7 @@
"helmet": "7.0.0", "helmet": "7.0.0",
"http-status-codes": "2.3.0", "http-status-codes": "2.3.0",
"ionicons": "8.0.13", "ionicons": "8.0.13",
"jsonpath": "1.2.1", "jsonpath": "1.3.0",
"lodash": "4.18.1", "lodash": "4.18.1",
"marked": "17.0.2", "marked": "17.0.2",
"ms": "3.0.0-canary.1", "ms": "3.0.0-canary.1",

Loading…
Cancel
Save