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 { 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 { AssetProfileChangedListener } from './asset-profile-changed.listener'; |
|||
import { PortfolioChangedListener } from './portfolio-changed.listener'; |
|||
|
|||
@Module({ |
|||
imports: [RedisCacheModule], |
|||
providers: [PortfolioChangedListener] |
|||
imports: [ |
|||
ConfigurationModule, |
|||
DataGatheringModule, |
|||
DataProviderModule, |
|||
ExchangeRateDataModule, |
|||
OrderModule, |
|||
RedisCacheModule |
|||
], |
|||
providers: [AssetProfileChangedListener, PortfolioChangedListener] |
|||
}) |
|||
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 { IsArray, IsOptional } from 'class-validator'; |
|||
|
|||
import { CreateAccountDto } from './create-account.dto'; |
|||
|
|||
export class CreateAccountWithBalancesDto extends CreateAccountDto { |
|||
@IsArray() |
|||
@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 { |
|||
@ -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 { |
|||
@ -1,5 +1,5 @@ |
|||
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; |
|||
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 { 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 { |
|||
@ -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 { |
|||
@ -1,8 +1,8 @@ |
|||
import { UpdateMarketDataDto } from '@ghostfolio/common/dtos'; |
|||
|
|||
import { Type } from 'class-transformer'; |
|||
import { ArrayNotEmpty, IsArray } from 'class-validator'; |
|||
|
|||
import { UpdateMarketDataDto } from './update-market-data.dto'; |
|||
|
|||
export class UpdateBulkMarketDataDto { |
|||
@ArrayNotEmpty() |
|||
@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 { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code'; |
|||
|
|||
import { AssetClass, AssetSubClass, DataSource, Type } from '@prisma/client'; |
|||
import { Transform, TransformFnParams } from 'class-transformer'; |
|||
@ -1,6 +1,6 @@ |
|||
import { IsOptional, IsString } from 'class-validator'; |
|||
|
|||
export class PropertyDto { |
|||
export class UpdatePropertyDto { |
|||
@IsOptional() |
|||
@IsString() |
|||
value: string; |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue