mirror of https://github.com/ghostfolio/ghostfolio
116 changed files with 577 additions and 217 deletions
@ -1,24 +0,0 @@ |
|||||
import { Type } from 'class-transformer'; |
|
||||
import { |
|
||||
ArrayNotEmpty, |
|
||||
IsArray, |
|
||||
IsISO8601, |
|
||||
IsNumber, |
|
||||
IsOptional |
|
||||
} from 'class-validator'; |
|
||||
|
|
||||
export class UpdateBulkMarketDataDto { |
|
||||
@ArrayNotEmpty() |
|
||||
@IsArray() |
|
||||
@Type(() => UpdateMarketDataDto) |
|
||||
marketData: UpdateMarketDataDto[]; |
|
||||
} |
|
||||
|
|
||||
class UpdateMarketDataDto { |
|
||||
@IsISO8601() |
|
||||
@IsOptional() |
|
||||
date?: string; |
|
||||
|
|
||||
@IsNumber() |
|
||||
marketPrice: number; |
|
||||
} |
|
||||
@ -0,0 +1,11 @@ |
|||||
|
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; |
||||
|
|
||||
|
export class AssetProfileChangedEvent { |
||||
|
public constructor( |
||||
|
public readonly data: AssetProfileIdentifier & { currency: string } |
||||
|
) {} |
||||
|
|
||||
|
public static getName(): string { |
||||
|
return 'assetProfile.changed'; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
import { OrderService } from '@ghostfolio/api/app/order/order.service'; |
||||
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; |
||||
|
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; |
||||
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; |
||||
|
import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service'; |
||||
|
import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; |
||||
|
|
||||
|
import { Injectable, Logger } from '@nestjs/common'; |
||||
|
import { OnEvent } from '@nestjs/event-emitter'; |
||||
|
|
||||
|
import { AssetProfileChangedEvent } from './asset-profile-changed.event'; |
||||
|
|
||||
|
@Injectable() |
||||
|
export class AssetProfileChangedListener { |
||||
|
public constructor( |
||||
|
private readonly configurationService: ConfigurationService, |
||||
|
private readonly dataGatheringService: DataGatheringService, |
||||
|
private readonly dataProviderService: DataProviderService, |
||||
|
private readonly exchangeRateDataService: ExchangeRateDataService, |
||||
|
private readonly orderService: OrderService |
||||
|
) {} |
||||
|
|
||||
|
@OnEvent(AssetProfileChangedEvent.getName()) |
||||
|
public async handleAssetProfileChanged(event: AssetProfileChangedEvent) { |
||||
|
Logger.log( |
||||
|
`Asset profile of ${event.data.symbol} (${event.data.dataSource}) has changed`, |
||||
|
'AssetProfileChangedListener' |
||||
|
); |
||||
|
|
||||
|
if ( |
||||
|
this.configurationService.get( |
||||
|
'ENABLE_FEATURE_GATHER_NEW_EXCHANGE_RATES' |
||||
|
) === false || |
||||
|
event.data.currency === DEFAULT_CURRENCY |
||||
|
) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const existingCurrencies = this.exchangeRateDataService.getCurrencies(); |
||||
|
|
||||
|
if (!existingCurrencies.includes(event.data.currency)) { |
||||
|
Logger.log( |
||||
|
`New currency ${event.data.currency} has been detected`, |
||||
|
'AssetProfileChangedListener' |
||||
|
); |
||||
|
|
||||
|
await this.exchangeRateDataService.initialize(); |
||||
|
} |
||||
|
|
||||
|
const { dateOfFirstActivity } = |
||||
|
await this.orderService.getStatisticsByCurrency(event.data.currency); |
||||
|
|
||||
|
if (dateOfFirstActivity) { |
||||
|
await this.dataGatheringService.gatherSymbol({ |
||||
|
dataSource: this.dataProviderService.getDataSourceForExchangeRates(), |
||||
|
date: dateOfFirstActivity, |
||||
|
symbol: `${DEFAULT_CURRENCY}${event.data.currency}` |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,11 +1,24 @@ |
|||||
|
import { OrderModule } from '@ghostfolio/api/app/order/order.module'; |
||||
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; |
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; |
||||
|
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; |
||||
|
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; |
||||
|
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module'; |
||||
|
import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module'; |
||||
|
|
||||
import { Module } from '@nestjs/common'; |
import { Module } from '@nestjs/common'; |
||||
|
|
||||
|
import { AssetProfileChangedListener } from './asset-profile-changed.listener'; |
||||
import { PortfolioChangedListener } from './portfolio-changed.listener'; |
import { PortfolioChangedListener } from './portfolio-changed.listener'; |
||||
|
|
||||
@Module({ |
@Module({ |
||||
imports: [RedisCacheModule], |
imports: [ |
||||
providers: [PortfolioChangedListener] |
ConfigurationModule, |
||||
|
DataGatheringModule, |
||||
|
DataProviderModule, |
||||
|
ExchangeRateDataModule, |
||||
|
OrderModule, |
||||
|
RedisCacheModule |
||||
|
], |
||||
|
providers: [AssetProfileChangedListener, PortfolioChangedListener] |
||||
}) |
}) |
||||
export class EventsModule {} |
export class EventsModule {} |
||||
|
|||||
@ -0,0 +1,18 @@ |
|||||
|
import { publicRoutes } from '@ghostfolio/common/routes/routes'; |
||||
|
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; |
||||
|
|
||||
|
import { Component } from '@angular/core'; |
||||
|
import { MatButtonModule } from '@angular/material/button'; |
||||
|
import { RouterModule } from '@angular/router'; |
||||
|
|
||||
|
@Component({ |
||||
|
host: { class: 'page' }, |
||||
|
imports: [GfPremiumIndicatorComponent, MatButtonModule, RouterModule], |
||||
|
selector: 'gf-black-weeks-2025-page', |
||||
|
templateUrl: './black-weeks-2025-page.html' |
||||
|
}) |
||||
|
export class BlackWeeks2025PageComponent { |
||||
|
public routerLinkBlog = publicRoutes.blog.routerLink; |
||||
|
public routerLinkFeatures = publicRoutes.features.routerLink; |
||||
|
public routerLinkPricing = publicRoutes.pricing.routerLink; |
||||
|
} |
||||
@ -0,0 +1,159 @@ |
|||||
|
<div class="blog container"> |
||||
|
<div class="row"> |
||||
|
<div class="col-md-8 offset-md-2"> |
||||
|
<article> |
||||
|
<div class="mb-4 text-center"> |
||||
|
<h1 class="mb-1">Black Weeks 2025</h1> |
||||
|
<div class="mb-3 text-muted"><small>2025-11-16</small></div> |
||||
|
<img |
||||
|
alt="Black Week 2025 Teaser" |
||||
|
class="rounded w-100" |
||||
|
src="../assets/images/blog/black-weeks-2025.jpg" |
||||
|
title="Black Weeks 2025" |
||||
|
/> |
||||
|
</div> |
||||
|
<section class="mb-4"> |
||||
|
<p> |
||||
|
Save <strong>25%</strong> on the |
||||
|
<span class="align-items-center d-inline-flex" |
||||
|
>Ghostfolio Premium |
||||
|
<gf-premium-indicator |
||||
|
class="d-inline-block ml-1" |
||||
|
[enableLink]="false" |
||||
|
/> |
||||
|
</span> |
||||
|
annual plan and get <strong>3 extra months</strong> on top with our |
||||
|
exclusive <strong>Black Weeks</strong> offer. |
||||
|
</p> |
||||
|
</section> |
||||
|
<section class="mb-4"> |
||||
|
<p> |
||||
|
<a |
||||
|
href="https://ghostfol.io" |
||||
|
title="Open Source Wealth Management Software" |
||||
|
>Ghostfolio</a |
||||
|
> |
||||
|
unifies your finances in one place and gives you a clear overview of |
||||
|
your portfolio across stocks, ETFs, cryptocurrencies or other |
||||
|
assets. Real time analytics and smart evaluations help you |
||||
|
understand your financial situation quickly and make confident |
||||
|
decisions. |
||||
|
</p> |
||||
|
</section> |
||||
|
<section class="mb-4"> |
||||
|
<p> |
||||
|
Grab this limited Black Weeks deal to optimize your financial |
||||
|
future. |
||||
|
</p> |
||||
|
<p class="text-center"> |
||||
|
<a color="primary" mat-flat-button [routerLink]="routerLinkPricing" |
||||
|
>Get the offer</a |
||||
|
> |
||||
|
</p> |
||||
|
<p class="mt-5"> |
||||
|
More details are available on the |
||||
|
<a [routerLink]="routerLinkPricing">pricing page</a>. |
||||
|
</p> |
||||
|
</section> |
||||
|
<section class="mb-4"> |
||||
|
<ul class="list-inline"> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">2025</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Black Friday</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Black Weeks</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Cryptocurrency</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Deal</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Discount</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">ETF</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Finance</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Fintech</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Ghostfolio</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Ghostfolio Premium</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Investment</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Open Source</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">OSS</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Personal Finance</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Portfolio</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Portfolio Tracker</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Pricing</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Promotion</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">SaaS</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Sale</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Savings</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Software</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Stock</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Subscription</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Wealth</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Wealth Management</span> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</section> |
||||
|
<nav aria-label="breadcrumb"> |
||||
|
<ol class="breadcrumb"> |
||||
|
<li class="breadcrumb-item"> |
||||
|
<a i18n [routerLink]="routerLinkBlog">Blog</a> |
||||
|
</li> |
||||
|
<li |
||||
|
aria-current="page" |
||||
|
class="active breadcrumb-item text-truncate" |
||||
|
> |
||||
|
Black Weeks 2025 |
||||
|
</li> |
||||
|
</ol> |
||||
|
</nav> |
||||
|
</article> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
After Width: | Height: | Size: 304 KiB |
@ -1,8 +1,9 @@ |
|||||
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto'; |
|
||||
import { AccountBalance } from '@ghostfolio/common/interfaces'; |
import { AccountBalance } from '@ghostfolio/common/interfaces'; |
||||
|
|
||||
import { IsArray, IsOptional } from 'class-validator'; |
import { IsArray, IsOptional } from 'class-validator'; |
||||
|
|
||||
|
import { CreateAccountDto } from './create-account.dto'; |
||||
|
|
||||
export class CreateAccountWithBalancesDto extends CreateAccountDto { |
export class CreateAccountWithBalancesDto extends CreateAccountDto { |
||||
@IsArray() |
@IsArray() |
||||
@IsOptional() |
@IsOptional() |
||||
@ -1,4 +1,4 @@ |
|||||
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; |
import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code'; |
||||
|
|
||||
import { Transform, TransformFnParams } from 'class-transformer'; |
import { Transform, TransformFnParams } from 'class-transformer'; |
||||
import { |
import { |
||||
@ -1,4 +1,4 @@ |
|||||
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; |
import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code'; |
||||
|
|
||||
import { AssetClass, AssetSubClass, DataSource, Prisma } from '@prisma/client'; |
import { AssetClass, AssetSubClass, DataSource, Prisma } from '@prisma/client'; |
||||
import { |
import { |
||||
@ -1,5 +1,5 @@ |
|||||
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; |
|
||||
import { IsAfter1970Constraint } from '@ghostfolio/common/validator-constraints/is-after-1970'; |
import { IsAfter1970Constraint } from '@ghostfolio/common/validator-constraints/is-after-1970'; |
||||
|
import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code'; |
||||
|
|
||||
import { AssetClass, AssetSubClass, DataSource, Type } from '@prisma/client'; |
import { AssetClass, AssetSubClass, DataSource, Type } from '@prisma/client'; |
||||
import { Transform, TransformFnParams } from 'class-transformer'; |
import { Transform, TransformFnParams } from 'class-transformer'; |
||||
@ -0,0 +1,51 @@ |
|||||
|
import { AuthDeviceDto } from './auth-device.dto'; |
||||
|
import { CreateAccessDto } from './create-access.dto'; |
||||
|
import { CreateAccountBalanceDto } from './create-account-balance.dto'; |
||||
|
import { CreateAccountWithBalancesDto } from './create-account-with-balances.dto'; |
||||
|
import { CreateAccountDto } from './create-account.dto'; |
||||
|
import { CreateAssetProfileWithMarketDataDto } from './create-asset-profile-with-market-data.dto'; |
||||
|
import { CreateAssetProfileDto } from './create-asset-profile.dto'; |
||||
|
import { CreateOrderDto } from './create-order.dto'; |
||||
|
import { CreatePlatformDto } from './create-platform.dto'; |
||||
|
import { CreateTagDto } from './create-tag.dto'; |
||||
|
import { CreateWatchlistItemDto } from './create-watchlist-item.dto'; |
||||
|
import { DeleteOwnUserDto } from './delete-own-user.dto'; |
||||
|
import { TransferBalanceDto } from './transfer-balance.dto'; |
||||
|
import { UpdateAccessDto } from './update-access.dto'; |
||||
|
import { UpdateAccountDto } from './update-account.dto'; |
||||
|
import { UpdateAssetProfileDto } from './update-asset-profile.dto'; |
||||
|
import { UpdateBulkMarketDataDto } from './update-bulk-market-data.dto'; |
||||
|
import { UpdateMarketDataDto } from './update-market-data.dto'; |
||||
|
import { UpdateOrderDto } from './update-order.dto'; |
||||
|
import { UpdateOwnAccessTokenDto } from './update-own-access-token.dto'; |
||||
|
import { UpdatePlatformDto } from './update-platform.dto'; |
||||
|
import { UpdatePropertyDto } from './update-property.dto'; |
||||
|
import { UpdateTagDto } from './update-tag.dto'; |
||||
|
import { UpdateUserSettingDto } from './update-user-setting.dto'; |
||||
|
|
||||
|
export { |
||||
|
AuthDeviceDto, |
||||
|
CreateAccessDto, |
||||
|
CreateAccountBalanceDto, |
||||
|
CreateAccountDto, |
||||
|
CreateAccountWithBalancesDto, |
||||
|
CreateAssetProfileDto, |
||||
|
CreateAssetProfileWithMarketDataDto, |
||||
|
CreateOrderDto, |
||||
|
CreatePlatformDto, |
||||
|
CreateTagDto, |
||||
|
CreateWatchlistItemDto, |
||||
|
DeleteOwnUserDto, |
||||
|
TransferBalanceDto, |
||||
|
UpdateAccessDto, |
||||
|
UpdateAccountDto, |
||||
|
UpdateAssetProfileDto, |
||||
|
UpdateBulkMarketDataDto, |
||||
|
UpdateMarketDataDto, |
||||
|
UpdateOrderDto, |
||||
|
UpdateOwnAccessTokenDto, |
||||
|
UpdatePlatformDto, |
||||
|
UpdatePropertyDto, |
||||
|
UpdateTagDto, |
||||
|
UpdateUserSettingDto |
||||
|
}; |
||||
@ -1,4 +1,4 @@ |
|||||
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; |
import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code'; |
||||
|
|
||||
import { Transform, TransformFnParams } from 'class-transformer'; |
import { Transform, TransformFnParams } from 'class-transformer'; |
||||
import { |
import { |
||||
@ -1,4 +1,4 @@ |
|||||
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; |
import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code'; |
||||
|
|
||||
import { AssetClass, AssetSubClass, DataSource, Prisma } from '@prisma/client'; |
import { AssetClass, AssetSubClass, DataSource, Prisma } from '@prisma/client'; |
||||
import { |
import { |
||||
@ -1,8 +1,8 @@ |
|||||
|
import { UpdateMarketDataDto } from '@ghostfolio/common/dtos'; |
||||
|
|
||||
import { Type } from 'class-transformer'; |
import { Type } from 'class-transformer'; |
||||
import { ArrayNotEmpty, IsArray } from 'class-validator'; |
import { ArrayNotEmpty, IsArray } from 'class-validator'; |
||||
|
|
||||
import { UpdateMarketDataDto } from './update-market-data.dto'; |
|
||||
|
|
||||
export class UpdateBulkMarketDataDto { |
export class UpdateBulkMarketDataDto { |
||||
@ArrayNotEmpty() |
@ArrayNotEmpty() |
||||
@IsArray() |
@IsArray() |
||||
@ -1,5 +1,5 @@ |
|||||
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; |
|
||||
import { IsAfter1970Constraint } from '@ghostfolio/common/validator-constraints/is-after-1970'; |
import { IsAfter1970Constraint } from '@ghostfolio/common/validator-constraints/is-after-1970'; |
||||
|
import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code'; |
||||
|
|
||||
import { AssetClass, AssetSubClass, DataSource, Type } from '@prisma/client'; |
import { AssetClass, AssetSubClass, DataSource, Type } from '@prisma/client'; |
||||
import { Transform, TransformFnParams } from 'class-transformer'; |
import { Transform, TransformFnParams } from 'class-transformer'; |
||||
@ -1,6 +1,6 @@ |
|||||
import { IsOptional, IsString } from 'class-validator'; |
import { IsOptional, IsString } from 'class-validator'; |
||||
|
|
||||
export class PropertyDto { |
export class UpdatePropertyDto { |
||||
@IsOptional() |
@IsOptional() |
||||
@IsString() |
@IsString() |
||||
value: string; |
value: string; |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue