merge
This commit is contained in:
commit
d1688242b0
151
.eslintrc.json
151
.eslintrc.json
@ -1,151 +0,0 @@
|
|||||||
{
|
|
||||||
"root": true,
|
|
||||||
"ignorePatterns": ["**/*"],
|
|
||||||
"plugins": ["@nx"],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
|
||||||
"rules": {
|
|
||||||
"@nx/enforce-module-boundaries": [
|
|
||||||
"warn",
|
|
||||||
{
|
|
||||||
"enforceBuildableLibDependency": true,
|
|
||||||
"allow": [],
|
|
||||||
"depConstraints": [
|
|
||||||
{
|
|
||||||
"sourceTag": "*",
|
|
||||||
"onlyDependOnLibsWithTags": ["*"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"@typescript-eslint/no-extra-semi": "error",
|
|
||||||
"no-extra-semi": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["*.ts", "*.tsx"],
|
|
||||||
"extends": ["plugin:@nx/typescript"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["*.js", "*.jsx"],
|
|
||||||
"extends": ["plugin:@nx/javascript"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["*.ts"],
|
|
||||||
"plugins": ["eslint-plugin-import", "@typescript-eslint"],
|
|
||||||
"extends": [
|
|
||||||
"plugin:@typescript-eslint/recommended-type-checked",
|
|
||||||
"plugin:@typescript-eslint/stylistic-type-checked"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/consistent-indexed-object-style": "off",
|
|
||||||
"@typescript-eslint/dot-notation": "off",
|
|
||||||
"@typescript-eslint/explicit-member-accessibility": [
|
|
||||||
"off",
|
|
||||||
{
|
|
||||||
"accessibility": "explicit"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"@typescript-eslint/member-ordering": "warn",
|
|
||||||
"@typescript-eslint/naming-convention": [
|
|
||||||
"off",
|
|
||||||
{
|
|
||||||
"selector": "default",
|
|
||||||
"format": ["camelCase"],
|
|
||||||
"leadingUnderscore": "allow",
|
|
||||||
"trailingUnderscore": "allow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"selector": ["variable", "classProperty", "typeProperty"],
|
|
||||||
"format": ["camelCase", "UPPER_CASE"],
|
|
||||||
"leadingUnderscore": "allow",
|
|
||||||
"trailingUnderscore": "allow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"selector": "objectLiteralProperty",
|
|
||||||
"format": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"selector": "enumMember",
|
|
||||||
"format": ["camelCase", "UPPER_CASE", "PascalCase"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"selector": "typeLike",
|
|
||||||
"format": ["PascalCase"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"@typescript-eslint/no-empty-interface": "warn",
|
|
||||||
"@typescript-eslint/no-inferrable-types": [
|
|
||||||
"warn",
|
|
||||||
{
|
|
||||||
"ignoreParameters": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"@typescript-eslint/no-non-null-assertion": "warn",
|
|
||||||
"@typescript-eslint/no-shadow": [
|
|
||||||
"warn",
|
|
||||||
{
|
|
||||||
"hoist": "all"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"@typescript-eslint/unified-signatures": "error",
|
|
||||||
"@typescript-eslint/no-loss-of-precision": "warn",
|
|
||||||
"@typescript-eslint/no-var-requires": "warn",
|
|
||||||
"@typescript-eslint/ban-types": "warn",
|
|
||||||
"arrow-body-style": "off",
|
|
||||||
"constructor-super": "error",
|
|
||||||
"eqeqeq": ["error", "smart"],
|
|
||||||
"guard-for-in": "warn",
|
|
||||||
"id-blacklist": "off",
|
|
||||||
"id-match": "off",
|
|
||||||
"import/no-deprecated": "warn",
|
|
||||||
"no-bitwise": "error",
|
|
||||||
"no-caller": "error",
|
|
||||||
"no-debugger": "error",
|
|
||||||
"no-empty": "off",
|
|
||||||
"no-eval": "error",
|
|
||||||
"no-fallthrough": "error",
|
|
||||||
"no-new-wrappers": "error",
|
|
||||||
"no-restricted-imports": ["error", "rxjs/Rx"],
|
|
||||||
"no-undef-init": "error",
|
|
||||||
"no-underscore-dangle": "off",
|
|
||||||
"no-var": "error",
|
|
||||||
"radix": "error",
|
|
||||||
"no-unsafe-optional-chaining": "warn",
|
|
||||||
"no-extra-boolean-cast": "warn",
|
|
||||||
"no-empty-pattern": "warn",
|
|
||||||
"no-useless-catch": "warn",
|
|
||||||
"no-unsafe-finally": "warn",
|
|
||||||
"no-prototype-builtins": "warn",
|
|
||||||
"no-async-promise-executor": "warn",
|
|
||||||
"no-constant-condition": "warn",
|
|
||||||
|
|
||||||
// The following rules are part of @typescript-eslint/recommended-type-checked
|
|
||||||
// and can be remove once solved
|
|
||||||
"@typescript-eslint/await-thenable": "warn",
|
|
||||||
"@typescript-eslint/ban-ts-comment": "warn",
|
|
||||||
"@typescript-eslint/no-base-to-string": "warn",
|
|
||||||
"@typescript-eslint/no-explicit-any": "warn",
|
|
||||||
"@typescript-eslint/no-floating-promises": "warn",
|
|
||||||
"@typescript-eslint/no-misused-promises": "warn",
|
|
||||||
"@typescript-eslint/no-redundant-type-constituents": "warn",
|
|
||||||
"@typescript-eslint/no-unnecessary-type-assertion": "warn",
|
|
||||||
"@typescript-eslint/no-unsafe-argument": "warn",
|
|
||||||
"@typescript-eslint/no-unsafe-assignment": "warn",
|
|
||||||
"@typescript-eslint/no-unsafe-enum-comparison": "warn",
|
|
||||||
"@typescript-eslint/no-unsafe-member-access": "warn",
|
|
||||||
"@typescript-eslint/no-unsafe-return": "warn",
|
|
||||||
"@typescript-eslint/no-unsafe-call": "warn",
|
|
||||||
"@typescript-eslint/require-await": "warn",
|
|
||||||
"@typescript-eslint/restrict-template-expressions": "warn",
|
|
||||||
"@typescript-eslint/unbound-method": "warn",
|
|
||||||
|
|
||||||
// The following rules are part of @typescript-eslint/stylistic-type-checked
|
|
||||||
// and can be remove once solved
|
|
||||||
"@typescript-eslint/prefer-nullish-coalescing": "warn" // TODO: Requires strictNullChecks: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"extends": ["plugin:storybook/recommended"]
|
|
||||||
}
|
|
16
CHANGELOG.md
16
CHANGELOG.md
@ -5,6 +5,22 @@ 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).
|
||||||
|
|
||||||
|
## 2.135.0 - 2025-01-19
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Moved the language localization for Polski (`pl`) from experimental to general availability
|
||||||
|
- Extended the _Financial Modeling Prep_ service
|
||||||
|
- Switched to _ESLint_’s flat config format
|
||||||
|
- Upgraded `chart.js` from version `4.2.0` to `4.4.7`
|
||||||
|
- Upgraded `chartjs-chart-treemap` from version `2.3.1` to `3.1.0`
|
||||||
|
- Upgraded `chartjs-plugin-annotation` from version `2.1.2` to `3.1.0`
|
||||||
|
- Upgraded `eslint` dependencies
|
||||||
|
- Upgraded `nestjs` from version `10.1.3` to `10.4.15`
|
||||||
|
- Upgraded `Nx` from version `20.3.0` to `20.3.2`
|
||||||
|
- Upgraded `reflect-metadata` from version `0.1.13` to `0.2.2`
|
||||||
|
- Upgraded `uuid` from version `11.0.2` to `11.0.5`
|
||||||
|
|
||||||
## 2.134.0 - 2025-01-15
|
## 2.134.0 - 2025-01-15
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../../.eslintrc.json",
|
|
||||||
"ignorePatterns": ["!**/*"],
|
|
||||||
"rules": {},
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
|
||||||
"parserOptions": {
|
|
||||||
"project": ["apps/api/tsconfig.*?.json"]
|
|
||||||
},
|
|
||||||
"rules": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["*.ts", "*.tsx"],
|
|
||||||
"rules": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["*.js", "*.jsx"],
|
|
||||||
"rules": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
31
apps/api/eslint.config.cjs
Normal file
31
apps/api/eslint.config.cjs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
const baseConfig = require('../../eslint.config.cjs');
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
ignores: ['**/dist']
|
||||||
|
},
|
||||||
|
...baseConfig,
|
||||||
|
{
|
||||||
|
rules: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||||
|
// Override or add rules here
|
||||||
|
rules: {},
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
project: ['apps/api/tsconfig.*?.json']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.ts', '**/*.tsx'],
|
||||||
|
// Override or add rules here
|
||||||
|
rules: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.js', '**/*.jsx'],
|
||||||
|
// Override or add rules here
|
||||||
|
rules: {}
|
||||||
|
}
|
||||||
|
];
|
@ -47,7 +47,7 @@ export class SymbolController {
|
|||||||
try {
|
try {
|
||||||
return this.symbolService.lookup({
|
return this.symbolService.lookup({
|
||||||
includeIndices,
|
includeIndices,
|
||||||
query: query.toLowerCase(),
|
query,
|
||||||
user: this.request.user
|
user: this.request.user
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -504,12 +504,10 @@
|
|||||||
<loc>https://ghostfol.io/pl/o-ghostfolio</loc>
|
<loc>https://ghostfol.io/pl/o-ghostfolio</loc>
|
||||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<!--
|
<url>
|
||||||
<url>
|
<loc>https://ghostfol.io/pl/open</loc>
|
||||||
<loc>https://ghostfol.io/pl/open</loc>
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
</url>
|
||||||
</url>
|
|
||||||
-->
|
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/pl/rynki</loc>
|
<loc>https://ghostfol.io/pl/rynki</loc>
|
||||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { DEFAULT_CURRENCY } from '@ghostfolio/common/config';
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
const cryptocurrencies = require('../../assets/cryptocurrencies/cryptocurrencies.json');
|
const cryptocurrencies = require('../../assets/cryptocurrencies/cryptocurrencies.json');
|
||||||
@ -9,7 +11,11 @@ export class CryptocurrencyService {
|
|||||||
|
|
||||||
public isCryptocurrency(aSymbol = '') {
|
public isCryptocurrency(aSymbol = '') {
|
||||||
const cryptocurrencySymbol = aSymbol.substring(0, aSymbol.length - 3);
|
const cryptocurrencySymbol = aSymbol.substring(0, aSymbol.length - 3);
|
||||||
return this.getCryptocurrencies().includes(cryptocurrencySymbol);
|
|
||||||
|
return (
|
||||||
|
aSymbol.endsWith(DEFAULT_CURRENCY) &&
|
||||||
|
this.getCryptocurrencies().includes(cryptocurrencySymbol)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCryptocurrencies() {
|
private getCryptocurrencies() {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
||||||
|
import { CryptocurrencyService } from '@ghostfolio/api/services/cryptocurrency/cryptocurrency.service';
|
||||||
import {
|
import {
|
||||||
DataProviderInterface,
|
DataProviderInterface,
|
||||||
GetDividendsParams,
|
GetDividendsParams,
|
||||||
@ -10,7 +11,6 @@ import {
|
|||||||
IDataProviderHistoricalResponse,
|
IDataProviderHistoricalResponse,
|
||||||
IDataProviderResponse
|
IDataProviderResponse
|
||||||
} from '@ghostfolio/api/services/interfaces/interfaces';
|
} from '@ghostfolio/api/services/interfaces/interfaces';
|
||||||
import { DEFAULT_CURRENCY } from '@ghostfolio/common/config';
|
|
||||||
import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper';
|
import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper';
|
||||||
import {
|
import {
|
||||||
DataProviderInfo,
|
DataProviderInfo,
|
||||||
@ -19,16 +19,31 @@ import {
|
|||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { DataSource, SymbolProfile } from '@prisma/client';
|
import {
|
||||||
import { format, isAfter, isBefore, isSameDay } from 'date-fns';
|
AssetClass,
|
||||||
|
AssetSubClass,
|
||||||
|
DataSource,
|
||||||
|
SymbolProfile
|
||||||
|
} from '@prisma/client';
|
||||||
|
import { isISIN } from 'class-validator';
|
||||||
|
import { countries } from 'countries-list';
|
||||||
|
import {
|
||||||
|
addDays,
|
||||||
|
format,
|
||||||
|
isAfter,
|
||||||
|
isBefore,
|
||||||
|
isSameDay,
|
||||||
|
parseISO
|
||||||
|
} from 'date-fns';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FinancialModelingPrepService implements DataProviderInterface {
|
export class FinancialModelingPrepService implements DataProviderInterface {
|
||||||
private apiKey: string;
|
private apiKey: string;
|
||||||
private readonly URL = 'https://financialmodelingprep.com/api/v3';
|
private readonly URL = this.getUrl({ version: 3 });
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly configurationService: ConfigurationService
|
private readonly configurationService: ConfigurationService,
|
||||||
|
private readonly cryptocurrencyService: CryptocurrencyService
|
||||||
) {
|
) {
|
||||||
this.apiKey = this.configurationService.get(
|
this.apiKey = this.configurationService.get(
|
||||||
'API_KEY_FINANCIAL_MODELING_PREP'
|
'API_KEY_FINANCIAL_MODELING_PREP'
|
||||||
@ -44,10 +59,152 @@ export class FinancialModelingPrepService implements DataProviderInterface {
|
|||||||
}: {
|
}: {
|
||||||
symbol: string;
|
symbol: string;
|
||||||
}): Promise<Partial<SymbolProfile>> {
|
}): Promise<Partial<SymbolProfile>> {
|
||||||
return {
|
const response: Partial<SymbolProfile> = {
|
||||||
symbol,
|
symbol,
|
||||||
dataSource: this.getName()
|
dataSource: this.getName()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (this.cryptocurrencyService.isCryptocurrency(symbol)) {
|
||||||
|
const [quote] = await fetch(
|
||||||
|
`${this.URL}/quote/${symbol}?apikey=${this.apiKey}`,
|
||||||
|
{
|
||||||
|
signal: AbortSignal.timeout(
|
||||||
|
this.configurationService.get('REQUEST_TIMEOUT')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
).then((res) => res.json());
|
||||||
|
|
||||||
|
response.assetClass = AssetClass.LIQUIDITY;
|
||||||
|
response.assetSubClass = AssetSubClass.CRYPTOCURRENCY;
|
||||||
|
response.currency = symbol.substring(symbol.length - 3);
|
||||||
|
response.name = quote.name;
|
||||||
|
} else {
|
||||||
|
const [assetProfile] = await fetch(
|
||||||
|
`${this.URL}/profile/${symbol}?apikey=${this.apiKey}`,
|
||||||
|
{
|
||||||
|
signal: AbortSignal.timeout(
|
||||||
|
this.configurationService.get('REQUEST_TIMEOUT')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
).then((res) => res.json());
|
||||||
|
|
||||||
|
const { assetClass, assetSubClass } =
|
||||||
|
this.parseAssetClass(assetProfile);
|
||||||
|
|
||||||
|
response.assetClass = assetClass;
|
||||||
|
response.assetSubClass = assetSubClass;
|
||||||
|
|
||||||
|
if (assetSubClass === AssetSubClass.ETF) {
|
||||||
|
const etfCountryWeightings = await fetch(
|
||||||
|
`${this.URL}/etf-country-weightings/${symbol}?apikey=${this.apiKey}`,
|
||||||
|
{
|
||||||
|
signal: AbortSignal.timeout(
|
||||||
|
this.configurationService.get('REQUEST_TIMEOUT')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
).then((res) => res.json());
|
||||||
|
|
||||||
|
response.countries = etfCountryWeightings.map(
|
||||||
|
({ country: countryName, weightPercentage }) => {
|
||||||
|
let countryCode: string;
|
||||||
|
|
||||||
|
for (const [code, country] of Object.entries(countries)) {
|
||||||
|
if (country.name === countryName) {
|
||||||
|
countryCode = code;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: countryCode,
|
||||||
|
weight: parseFloat(weightPercentage.slice(0, -1)) / 100
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const [portfolioDate] = await fetch(
|
||||||
|
`${this.getUrl({ version: 4 })}/etf-holdings/portfolio-date?symbol=${symbol}&apikey=${this.apiKey}`,
|
||||||
|
{
|
||||||
|
signal: AbortSignal.timeout(
|
||||||
|
this.configurationService.get('REQUEST_TIMEOUT')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
).then((res) => res.json());
|
||||||
|
|
||||||
|
if (portfolioDate) {
|
||||||
|
const etfHoldings = await fetch(
|
||||||
|
`${this.getUrl({ version: 4 })}/etf-holdings?date=${portfolioDate.date}&symbol=${symbol}&apikey=${this.apiKey}`,
|
||||||
|
{
|
||||||
|
signal: AbortSignal.timeout(
|
||||||
|
this.configurationService.get('REQUEST_TIMEOUT')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
).then((res) => res.json());
|
||||||
|
|
||||||
|
const sortedTopHoldings = etfHoldings
|
||||||
|
.sort((a, b) => {
|
||||||
|
return b.pctVal - a.pctVal;
|
||||||
|
})
|
||||||
|
.slice(0, 10);
|
||||||
|
|
||||||
|
response.holdings = sortedTopHoldings.map(({ name, pctVal }) => {
|
||||||
|
return { name, weight: pctVal / 100 };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const etfSectorWeightings = await fetch(
|
||||||
|
`${this.URL}/etf-sector-weightings/${symbol}?apikey=${this.apiKey}`,
|
||||||
|
{
|
||||||
|
signal: AbortSignal.timeout(
|
||||||
|
this.configurationService.get('REQUEST_TIMEOUT')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
).then((res) => res.json());
|
||||||
|
|
||||||
|
response.sectors = etfSectorWeightings.map(
|
||||||
|
({ sector, weightPercentage }) => {
|
||||||
|
return {
|
||||||
|
name: sector,
|
||||||
|
weight: parseFloat(weightPercentage.slice(0, -1)) / 100
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else if (assetSubClass === AssetSubClass.STOCK) {
|
||||||
|
if (assetProfile.country) {
|
||||||
|
response.countries = [{ code: assetProfile.country, weight: 1 }];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assetProfile.sector) {
|
||||||
|
response.sectors = [{ name: assetProfile.sector, weight: 1 }];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response.currency = assetProfile.currency;
|
||||||
|
|
||||||
|
if (assetProfile.isin) {
|
||||||
|
response.isin = assetProfile.isin;
|
||||||
|
}
|
||||||
|
|
||||||
|
response.name = assetProfile.companyName;
|
||||||
|
|
||||||
|
if (assetProfile.website) {
|
||||||
|
response.url = assetProfile.website;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
let message = error;
|
||||||
|
|
||||||
|
if (error?.name === 'AbortError') {
|
||||||
|
message = `RequestError: The operation to get the asset profile for ${symbol} was aborted because the request to the data provider took more than ${(
|
||||||
|
this.configurationService.get('REQUEST_TIMEOUT') / 1000
|
||||||
|
).toFixed(3)} seconds`;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.error(message, 'FinancialModelingPrepService');
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getDataProviderInfo(): DataProviderInfo {
|
public getDataProviderInfo(): DataProviderInfo {
|
||||||
@ -58,8 +215,54 @@ export class FinancialModelingPrepService implements DataProviderInterface {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getDividends({}: GetDividendsParams) {
|
public async getDividends({
|
||||||
return {};
|
from,
|
||||||
|
requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'),
|
||||||
|
symbol,
|
||||||
|
to
|
||||||
|
}: GetDividendsParams) {
|
||||||
|
if (isSameDay(from, to)) {
|
||||||
|
to = addDays(to, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response: {
|
||||||
|
[date: string]: IDataProviderHistoricalResponse;
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
const { historical } = await fetch(
|
||||||
|
`${this.URL}/historical-price-full/stock_dividend/${symbol}?apikey=${this.apiKey}`,
|
||||||
|
{
|
||||||
|
signal: AbortSignal.timeout(requestTimeout)
|
||||||
|
}
|
||||||
|
).then((res) => res.json());
|
||||||
|
|
||||||
|
historical
|
||||||
|
.filter(({ date }) => {
|
||||||
|
return (
|
||||||
|
(isSameDay(parseISO(date), from) ||
|
||||||
|
isAfter(parseISO(date), from)) &&
|
||||||
|
isBefore(parseISO(date), to)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.forEach(({ adjDividend, date }) => {
|
||||||
|
response[date] = {
|
||||||
|
marketPrice: adjDividend
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(
|
||||||
|
`Could not get dividends for ${symbol} (${this.getName()}) from ${format(
|
||||||
|
from,
|
||||||
|
DATE_FORMAT
|
||||||
|
)} to ${format(to, DATE_FORMAT)}: [${error.name}] ${error.message}`,
|
||||||
|
'FinancialModelingPrepService'
|
||||||
|
);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getHistorical({
|
public async getHistorical({
|
||||||
@ -84,14 +287,14 @@ export class FinancialModelingPrepService implements DataProviderInterface {
|
|||||||
[symbol]: {}
|
[symbol]: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const { close, date } of historical) {
|
for (const { adjClose, date } of historical) {
|
||||||
if (
|
if (
|
||||||
(isSameDay(parseDate(date), from) ||
|
(isSameDay(parseDate(date), from) ||
|
||||||
isAfter(parseDate(date), from)) &&
|
isAfter(parseDate(date), from)) &&
|
||||||
isBefore(parseDate(date), to)
|
isBefore(parseDate(date), to)
|
||||||
) {
|
) {
|
||||||
result[symbol][date] = {
|
result[symbol][date] = {
|
||||||
marketPrice: close
|
marketPrice: adjClose
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -130,8 +333,10 @@ export class FinancialModelingPrepService implements DataProviderInterface {
|
|||||||
).then((res) => res.json());
|
).then((res) => res.json());
|
||||||
|
|
||||||
for (const { price, symbol } of quotes) {
|
for (const { price, symbol } of quotes) {
|
||||||
|
const { currency } = await this.getAssetProfile({ symbol });
|
||||||
|
|
||||||
response[symbol] = {
|
response[symbol] = {
|
||||||
currency: DEFAULT_CURRENCY,
|
currency,
|
||||||
dataProviderInfo: this.getDataProviderInfo(),
|
dataProviderInfo: this.getDataProviderInfo(),
|
||||||
dataSource: DataSource.FINANCIAL_MODELING_PREP,
|
dataSource: DataSource.FINANCIAL_MODELING_PREP,
|
||||||
marketPrice: price,
|
marketPrice: price,
|
||||||
@ -161,25 +366,49 @@ export class FinancialModelingPrepService implements DataProviderInterface {
|
|||||||
let items: LookupItem[] = [];
|
let items: LookupItem[] = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await fetch(
|
if (isISIN(query)) {
|
||||||
`${this.URL}/search?query=${query}&apikey=${this.apiKey}`,
|
const result = await fetch(
|
||||||
{
|
`${this.getUrl({ version: 4 })}/search/isin?isin=${query}&apikey=${this.apiKey}`,
|
||||||
signal: AbortSignal.timeout(
|
{
|
||||||
this.configurationService.get('REQUEST_TIMEOUT')
|
signal: AbortSignal.timeout(
|
||||||
)
|
this.configurationService.get('REQUEST_TIMEOUT')
|
||||||
}
|
)
|
||||||
).then((res) => res.json());
|
}
|
||||||
|
).then((res) => res.json());
|
||||||
|
|
||||||
items = result.map(({ currency, name, symbol }) => {
|
items = result.map(({ companyName, currency, symbol }) => {
|
||||||
return {
|
return {
|
||||||
// TODO: Add assetClass
|
currency,
|
||||||
// TODO: Add assetSubClass
|
symbol,
|
||||||
currency,
|
assetClass: undefined, // TODO
|
||||||
name,
|
assetSubClass: undefined, // TODO
|
||||||
symbol,
|
dataProviderInfo: this.getDataProviderInfo(),
|
||||||
dataSource: this.getName()
|
dataSource: this.getName(),
|
||||||
};
|
name: companyName
|
||||||
});
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const result = await fetch(
|
||||||
|
`${this.URL}/search?query=${query}&apikey=${this.apiKey}`,
|
||||||
|
{
|
||||||
|
signal: AbortSignal.timeout(
|
||||||
|
this.configurationService.get('REQUEST_TIMEOUT')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
).then((res) => res.json());
|
||||||
|
|
||||||
|
items = result.map(({ currency, name, symbol }) => {
|
||||||
|
return {
|
||||||
|
currency,
|
||||||
|
name,
|
||||||
|
symbol,
|
||||||
|
assetClass: undefined, // TODO
|
||||||
|
assetSubClass: undefined, // TODO
|
||||||
|
dataProviderInfo: this.getDataProviderInfo(),
|
||||||
|
dataSource: this.getName()
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let message = error;
|
let message = error;
|
||||||
|
|
||||||
@ -194,4 +423,29 @@ export class FinancialModelingPrepService implements DataProviderInterface {
|
|||||||
|
|
||||||
return { items };
|
return { items };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getUrl({ version }: { version: number }) {
|
||||||
|
return `https://financialmodelingprep.com/api/v${version}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseAssetClass(profile: any): {
|
||||||
|
assetClass: AssetClass;
|
||||||
|
assetSubClass: AssetSubClass;
|
||||||
|
} {
|
||||||
|
let assetClass: AssetClass;
|
||||||
|
let assetSubClass: AssetSubClass;
|
||||||
|
|
||||||
|
if (profile.isEtf) {
|
||||||
|
assetClass = AssetClass.EQUITY;
|
||||||
|
assetSubClass = AssetSubClass.ETF;
|
||||||
|
} else if (profile.isFund) {
|
||||||
|
assetClass = AssetClass.EQUITY;
|
||||||
|
assetSubClass = AssetSubClass.MUTUALFUND;
|
||||||
|
} else {
|
||||||
|
assetClass = AssetClass.EQUITY;
|
||||||
|
assetSubClass = AssetSubClass.STOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { assetClass, assetSubClass };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": ["../../.eslintrc.json"],
|
|
||||||
"ignorePatterns": ["!**/*"],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
|
||||||
"parserOptions": {
|
|
||||||
"project": ["apps/client/tsconfig.*?.json"]
|
|
||||||
},
|
|
||||||
"rules": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["*.ts", "*.tsx"],
|
|
||||||
"rules": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["*.js", "*.jsx"],
|
|
||||||
"rules": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["*.ts"],
|
|
||||||
"rules": {
|
|
||||||
"@angular-eslint/prefer-standalone": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"plugins": ["@angular-eslint/eslint-plugin", "@typescript-eslint"],
|
|
||||||
"rules": {
|
|
||||||
"@angular-eslint/component-selector": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"type": "element",
|
|
||||||
"prefix": "gf",
|
|
||||||
"style": "kebab-case"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"@angular-eslint/directive-selector": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"type": "attribute",
|
|
||||||
"prefix": "gf",
|
|
||||||
"style": "camelCase"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
62
apps/client/eslint.config.cjs
Normal file
62
apps/client/eslint.config.cjs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
const baseConfig = require('../../eslint.config.cjs');
|
||||||
|
const angularEslintPlugin = require('@angular-eslint/eslint-plugin');
|
||||||
|
const typescriptEslintPlugin = require('@typescript-eslint/eslint-plugin');
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
ignores: ['**/dist']
|
||||||
|
},
|
||||||
|
...baseConfig,
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
'@angular-eslint': angularEslintPlugin,
|
||||||
|
'@typescript-eslint': typescriptEslintPlugin
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
'@angular-eslint/component-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
prefix: 'gf',
|
||||||
|
style: 'kebab-case'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@angular-eslint/directive-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'attribute',
|
||||||
|
prefix: 'gf',
|
||||||
|
style: 'camelCase'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||||
|
// Override or add rules here
|
||||||
|
rules: {},
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
project: ['apps/client/tsconfig.*?.json']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.ts', '**/*.tsx'],
|
||||||
|
// Override or add rules here
|
||||||
|
rules: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.js', '**/*.jsx'],
|
||||||
|
// Override or add rules here
|
||||||
|
rules: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.ts'],
|
||||||
|
rules: {
|
||||||
|
'@angular-eslint/prefer-standalone': 'off'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
@ -12,7 +12,9 @@
|
|||||||
[href]="pricingUrl"
|
[href]="pricingUrl"
|
||||||
>
|
>
|
||||||
@if (isGhostfolioApiKeyValid === false) {
|
@if (isGhostfolioApiKeyValid === false) {
|
||||||
<span class="badge badge-warning mr-1" i18n>NEW</span>
|
<span class="badge badge-warning mr-1" i18n
|
||||||
|
>Early Access</span
|
||||||
|
>
|
||||||
}
|
}
|
||||||
Ghostfolio Premium
|
Ghostfolio Premium
|
||||||
<gf-premium-indicator
|
<gf-premium-indicator
|
||||||
|
@ -100,12 +100,10 @@
|
|||||||
>Nederlands (<ng-container i18n>Community</ng-container
|
>Nederlands (<ng-container i18n>Community</ng-container
|
||||||
>)</mat-option
|
>)</mat-option
|
||||||
>
|
>
|
||||||
@if (user?.settings?.isExperimentalFeatures) {
|
<mat-option value="pl"
|
||||||
<mat-option value="pl"
|
>Polski (<ng-container i18n>Community</ng-container
|
||||||
>Polski (<ng-container i18n>Community</ng-container
|
>)</mat-option
|
||||||
>)</mat-option
|
>
|
||||||
>
|
|
||||||
}
|
|
||||||
<mat-option value="pt"
|
<mat-option value="pt"
|
||||||
>Português (<ng-container i18n>Community</ng-container
|
>Português (<ng-container i18n>Community</ng-container
|
||||||
>)</mat-option
|
>)</mat-option
|
||||||
|
@ -242,11 +242,10 @@
|
|||||||
<h4 i18n>Multi-Language</h4>
|
<h4 i18n>Multi-Language</h4>
|
||||||
<p class="m-0">
|
<p class="m-0">
|
||||||
Use Ghostfolio in multiple languages: English,
|
Use Ghostfolio in multiple languages: English,
|
||||||
<!--Català, -->
|
<!-- Català, -->
|
||||||
<!-- Chinese, -->
|
<!-- Chinese, -->
|
||||||
Dutch, French, German, Italian,
|
Dutch, French, German, Italian, Polish, Portuguese, Spanish
|
||||||
<!-- Polish, -->
|
and Turkish
|
||||||
Portuguese, Spanish and Turkish
|
|
||||||
<!-- and Ukrainian -->
|
<!-- and Ukrainian -->
|
||||||
are currently supported.
|
are currently supported.
|
||||||
</p>
|
</p>
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": ["plugin:cypress/recommended", "../../.eslintrc.json"],
|
|
||||||
"ignorePatterns": ["!**/*"],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
|
||||||
"parserOptions": {
|
|
||||||
"project": ["apps/ui-e2e/tsconfig.json"]
|
|
||||||
},
|
|
||||||
"rules": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["src/plugins/index.js"],
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/no-var-requires": "off",
|
|
||||||
"no-undef": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
33
apps/ui-e2e/eslint.config.cjs
Normal file
33
apps/ui-e2e/eslint.config.cjs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
const { FlatCompat } = require('@eslint/eslintrc');
|
||||||
|
const js = require('@eslint/js');
|
||||||
|
const baseConfig = require('../../eslint.config.cjs');
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: js.configs.recommended
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
ignores: ['**/dist']
|
||||||
|
},
|
||||||
|
...baseConfig,
|
||||||
|
...compat.extends('plugin:cypress/recommended'),
|
||||||
|
{
|
||||||
|
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||||
|
// Override or add rules here
|
||||||
|
rules: {},
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
project: ['apps/ui-e2e/tsconfig.json']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['src/plugins/index.js'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-var-requires': 'off',
|
||||||
|
'no-undef': 'off'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
196
eslint.config.cjs
Normal file
196
eslint.config.cjs
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
const { FlatCompat } = require('@eslint/eslintrc');
|
||||||
|
const js = require('@eslint/js');
|
||||||
|
const nxEslintPlugin = require('@nx/eslint-plugin');
|
||||||
|
const storybook = require('eslint-plugin-storybook');
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: js.configs.recommended
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
ignores: ['**/dist']
|
||||||
|
},
|
||||||
|
...storybook.configs['flat/recommended'],
|
||||||
|
{ plugins: { '@nx': nxEslintPlugin } },
|
||||||
|
{
|
||||||
|
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||||
|
rules: {
|
||||||
|
'@nx/enforce-module-boundaries': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
enforceBuildableLibDependency: true,
|
||||||
|
allow: [],
|
||||||
|
depConstraints: [
|
||||||
|
{
|
||||||
|
sourceTag: '*',
|
||||||
|
onlyDependOnLibsWithTags: ['*']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@typescript-eslint/no-extra-semi': 'error',
|
||||||
|
'no-extra-semi': 'off'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...compat
|
||||||
|
.config({
|
||||||
|
extends: ['plugin:@nx/typescript']
|
||||||
|
})
|
||||||
|
.map((config) => ({
|
||||||
|
...config,
|
||||||
|
files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'],
|
||||||
|
rules: {
|
||||||
|
...config.rules
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
...compat
|
||||||
|
.config({
|
||||||
|
extends: ['plugin:@nx/javascript']
|
||||||
|
})
|
||||||
|
.map((config) => ({
|
||||||
|
...config,
|
||||||
|
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
|
||||||
|
rules: {
|
||||||
|
...config.rules
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
...compat
|
||||||
|
.config({
|
||||||
|
plugins: ['eslint-plugin-import', '@typescript-eslint'],
|
||||||
|
extends: [
|
||||||
|
'plugin:@typescript-eslint/recommended-type-checked',
|
||||||
|
'plugin:@typescript-eslint/stylistic-type-checked'
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.map((config) => ({
|
||||||
|
...config,
|
||||||
|
files: ['**/*.ts'],
|
||||||
|
rules: {
|
||||||
|
...config.rules,
|
||||||
|
'@typescript-eslint/consistent-indexed-object-style': 'off',
|
||||||
|
'@typescript-eslint/dot-notation': 'off',
|
||||||
|
'@typescript-eslint/explicit-member-accessibility': [
|
||||||
|
'off',
|
||||||
|
{
|
||||||
|
accessibility: 'explicit'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@typescript-eslint/member-ordering': 'warn',
|
||||||
|
'@typescript-eslint/naming-convention': [
|
||||||
|
'off',
|
||||||
|
{
|
||||||
|
selector: 'default',
|
||||||
|
format: ['camelCase'],
|
||||||
|
leadingUnderscore: 'allow',
|
||||||
|
trailingUnderscore: 'allow'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: ['variable', 'classProperty', 'typeProperty'],
|
||||||
|
format: ['camelCase', 'UPPER_CASE'],
|
||||||
|
leadingUnderscore: 'allow',
|
||||||
|
trailingUnderscore: 'allow'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'objectLiteralProperty',
|
||||||
|
format: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'enumMember',
|
||||||
|
format: ['camelCase', 'UPPER_CASE', 'PascalCase']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'typeLike',
|
||||||
|
format: ['PascalCase']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@typescript-eslint/no-empty-interface': 'warn',
|
||||||
|
'@typescript-eslint/no-inferrable-types': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
ignoreParameters: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@typescript-eslint/no-non-null-assertion': 'warn',
|
||||||
|
'@typescript-eslint/no-shadow': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
hoist: 'all'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@typescript-eslint/unified-signatures': 'error',
|
||||||
|
'@typescript-eslint/no-loss-of-precision': 'warn',
|
||||||
|
'@typescript-eslint/no-var-requires': 'warn',
|
||||||
|
'arrow-body-style': 'off',
|
||||||
|
'constructor-super': 'error',
|
||||||
|
eqeqeq: ['error', 'smart'],
|
||||||
|
'guard-for-in': 'warn',
|
||||||
|
'id-blacklist': 'off',
|
||||||
|
'id-match': 'off',
|
||||||
|
'import/no-deprecated': 'warn',
|
||||||
|
'no-bitwise': 'error',
|
||||||
|
'no-caller': 'error',
|
||||||
|
'no-debugger': 'error',
|
||||||
|
'no-empty': 'off',
|
||||||
|
'no-eval': 'error',
|
||||||
|
'no-fallthrough': 'error',
|
||||||
|
'no-new-wrappers': 'error',
|
||||||
|
'no-restricted-imports': ['error', 'rxjs/Rx'],
|
||||||
|
'no-undef-init': 'error',
|
||||||
|
'no-underscore-dangle': 'off',
|
||||||
|
'no-var': 'error',
|
||||||
|
radix: 'error',
|
||||||
|
'no-unsafe-optional-chaining': 'warn',
|
||||||
|
'no-extra-boolean-cast': 'warn',
|
||||||
|
'no-empty-pattern': 'warn',
|
||||||
|
'no-useless-catch': 'warn',
|
||||||
|
'no-unsafe-finally': 'warn',
|
||||||
|
'no-prototype-builtins': 'warn',
|
||||||
|
'no-async-promise-executor': 'warn',
|
||||||
|
'no-constant-condition': 'warn',
|
||||||
|
|
||||||
|
// The following rules are part of eslint:recommended
|
||||||
|
// and can be remove once solved
|
||||||
|
'no-constant-binary-expression': 'warn',
|
||||||
|
'no-loss-of-precision': 'warn',
|
||||||
|
|
||||||
|
// The following rules are part of @typescript-eslint/recommended-type-checked
|
||||||
|
// and can be remove once solved
|
||||||
|
'@typescript-eslint/await-thenable': 'warn',
|
||||||
|
'@typescript-eslint/ban-ts-comment': 'warn',
|
||||||
|
'@typescript-eslint/no-base-to-string': 'warn',
|
||||||
|
'@typescript-eslint/no-empty-object-type': 'warn',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
|
'@typescript-eslint/no-floating-promises': 'warn',
|
||||||
|
'@typescript-eslint/no-misused-promises': 'warn',
|
||||||
|
'@typescript-eslint/no-redundant-type-constituents': 'warn',
|
||||||
|
'@typescript-eslint/no-require-imports': 'warn',
|
||||||
|
'@typescript-eslint/no-unnecessary-type-assertion': 'warn',
|
||||||
|
'@typescript-eslint/no-unsafe-argument': 'warn',
|
||||||
|
'@typescript-eslint/no-unsafe-assignment': 'warn',
|
||||||
|
'@typescript-eslint/no-unsafe-enum-comparison': 'warn',
|
||||||
|
'@typescript-eslint/no-unsafe-function-type': 'warn',
|
||||||
|
'@typescript-eslint/no-unsafe-member-access': 'warn',
|
||||||
|
'@typescript-eslint/no-unsafe-return': 'warn',
|
||||||
|
'@typescript-eslint/no-unsafe-call': 'warn',
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
caughtErrors: 'none'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@typescript-eslint/no-wrapper-object-types': 'warn',
|
||||||
|
'@typescript-eslint/only-throw-error': 'warn',
|
||||||
|
'@typescript-eslint/prefer-promise-reject-errors': 'warn',
|
||||||
|
'@typescript-eslint/require-await': 'warn',
|
||||||
|
'@typescript-eslint/restrict-template-expressions': 'warn',
|
||||||
|
'@typescript-eslint/unbound-method': 'warn',
|
||||||
|
|
||||||
|
// The following rules are part of @typescript-eslint/stylistic-type-checked
|
||||||
|
// and can be remove once solved
|
||||||
|
'@typescript-eslint/prefer-nullish-coalescing': 'warn', // TODO: Requires strictNullChecks: true
|
||||||
|
'@typescript-eslint/prefer-regexp-exec': 'warn'
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
];
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": ["../../.eslintrc.json"],
|
|
||||||
"ignorePatterns": ["!**/*"],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
|
||||||
"parserOptions": {
|
|
||||||
"project": ["libs/common/tsconfig.*?.json"]
|
|
||||||
},
|
|
||||||
"rules": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["*.ts", "*.tsx"],
|
|
||||||
"rules": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["*.js", "*.jsx"],
|
|
||||||
"rules": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
28
libs/common/eslint.config.cjs
Normal file
28
libs/common/eslint.config.cjs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
const baseConfig = require('../../eslint.config.cjs');
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
ignores: ['**/dist']
|
||||||
|
},
|
||||||
|
...baseConfig,
|
||||||
|
{
|
||||||
|
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||||
|
// Override or add rules here
|
||||||
|
rules: {},
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
project: ['libs/common/tsconfig.*?.json']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.ts', '**/*.tsx'],
|
||||||
|
// Override or add rules here
|
||||||
|
rules: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.js', '**/*.jsx'],
|
||||||
|
// Override or add rules here
|
||||||
|
rules: {}
|
||||||
|
}
|
||||||
|
];
|
@ -1,40 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": ["../../.eslintrc.json"],
|
|
||||||
"ignorePatterns": ["!**/*", "**/*.stories.ts"],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
|
||||||
"parserOptions": {
|
|
||||||
"project": ["libs/ui/tsconfig.*?.json"]
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"plugin:@nx/angular",
|
|
||||||
"plugin:@angular-eslint/template/process-inline-templates"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"@angular-eslint/directive-selector": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"type": "attribute",
|
|
||||||
"prefix": "gf",
|
|
||||||
"style": "camelCase"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"@angular-eslint/component-selector": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"type": "element",
|
|
||||||
"prefix": "gf",
|
|
||||||
"style": "kebab-case"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"@angular-eslint/prefer-standalone": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": ["*.html"],
|
|
||||||
"extends": ["plugin:@nx/angular-template"],
|
|
||||||
"rules": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
65
libs/ui/eslint.config.cjs
Normal file
65
libs/ui/eslint.config.cjs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
const { FlatCompat } = require('@eslint/eslintrc');
|
||||||
|
const js = require('@eslint/js');
|
||||||
|
const baseConfig = require('../../eslint.config.cjs');
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: js.configs.recommended
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
ignores: ['**/dist']
|
||||||
|
},
|
||||||
|
...baseConfig,
|
||||||
|
...compat
|
||||||
|
.config({
|
||||||
|
extends: [
|
||||||
|
'plugin:@nx/angular',
|
||||||
|
'plugin:@angular-eslint/template/process-inline-templates'
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.map((config) => ({
|
||||||
|
...config,
|
||||||
|
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||||
|
rules: {
|
||||||
|
...config.rules,
|
||||||
|
'@angular-eslint/directive-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'attribute',
|
||||||
|
prefix: 'gf',
|
||||||
|
style: 'camelCase'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@angular-eslint/component-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
prefix: 'gf',
|
||||||
|
style: 'kebab-case'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@angular-eslint/prefer-standalone': 'off'
|
||||||
|
},
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
project: ['libs/ui/tsconfig.*?.json']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
...compat
|
||||||
|
.config({
|
||||||
|
extends: ['plugin:@nx/angular-template']
|
||||||
|
})
|
||||||
|
.map((config) => ({
|
||||||
|
...config,
|
||||||
|
files: ['**/*.html'],
|
||||||
|
rules: {
|
||||||
|
...config.rules
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
ignores: ['**/*.stories.ts']
|
||||||
|
}
|
||||||
|
];
|
@ -77,7 +77,7 @@ export class GfPortfolioProportionChartComponent
|
|||||||
|
|
||||||
@ViewChild('chartCanvas') chartCanvas: ElementRef<HTMLCanvasElement>;
|
@ViewChild('chartCanvas') chartCanvas: ElementRef<HTMLCanvasElement>;
|
||||||
|
|
||||||
public chart: Chart<'pie'>;
|
public chart: Chart<'doughnut'>;
|
||||||
public isLoading = true;
|
public isLoading = true;
|
||||||
|
|
||||||
private readonly OTHER_KEY = 'OTHER';
|
private readonly OTHER_KEY = 'OTHER';
|
||||||
@ -257,7 +257,7 @@ export class GfPortfolioProportionChartComponent
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const datasets: ChartConfiguration['data']['datasets'] = [
|
const datasets: ChartConfiguration<'doughnut'>['data']['datasets'] = [
|
||||||
{
|
{
|
||||||
backgroundColor: chartDataSorted.map(([, item]) => {
|
backgroundColor: chartDataSorted.map(([, item]) => {
|
||||||
return item.color;
|
return item.color;
|
||||||
@ -295,7 +295,7 @@ export class GfPortfolioProportionChartComponent
|
|||||||
datasets[1].data[1] = Number.MAX_SAFE_INTEGER;
|
datasets[1].data[1] = Number.MAX_SAFE_INTEGER;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: ChartConfiguration['data'] = {
|
const data: ChartConfiguration<'doughnut'>['data'] = {
|
||||||
datasets,
|
datasets,
|
||||||
labels
|
labels
|
||||||
};
|
};
|
||||||
@ -308,7 +308,7 @@ export class GfPortfolioProportionChartComponent
|
|||||||
) as unknown;
|
) as unknown;
|
||||||
this.chart.update();
|
this.chart.update();
|
||||||
} else {
|
} else {
|
||||||
this.chart = new Chart(this.chartCanvas.nativeElement, {
|
this.chart = new Chart<'doughnut'>(this.chartCanvas.nativeElement, {
|
||||||
data,
|
data,
|
||||||
options: {
|
options: {
|
||||||
animation: false,
|
animation: false,
|
||||||
|
@ -196,7 +196,7 @@ export class GfTreemapChartComponent
|
|||||||
min: Math.min(...negativeNetPerformancePercents)
|
min: Math.min(...negativeNetPerformancePercents)
|
||||||
};
|
};
|
||||||
|
|
||||||
const data: ChartConfiguration['data'] = {
|
const data: ChartConfiguration<'treemap'>['data'] = {
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
backgroundColor: (ctx) => {
|
backgroundColor: (ctx) => {
|
||||||
|
1
nx.json
1
nx.json
@ -63,6 +63,7 @@
|
|||||||
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
|
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
|
||||||
"!{projectRoot}/**/*.stories.@(js|jsx|ts|tsx|mdx)",
|
"!{projectRoot}/**/*.stories.@(js|jsx|ts|tsx|mdx)",
|
||||||
"!{projectRoot}/.storybook/**/*",
|
"!{projectRoot}/.storybook/**/*",
|
||||||
|
"!{projectRoot}/eslint.config.cjs",
|
||||||
"!{projectRoot}/jest.config.[jt]s",
|
"!{projectRoot}/jest.config.[jt]s",
|
||||||
"!{projectRoot}/src/test-setup.[jt]s",
|
"!{projectRoot}/src/test-setup.[jt]s",
|
||||||
"!{projectRoot}/tsconfig.storybook.json",
|
"!{projectRoot}/tsconfig.storybook.json",
|
||||||
|
3209
package-lock.json
generated
3209
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
78
package.json
78
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ghostfolio",
|
"name": "ghostfolio",
|
||||||
"version": "2.134.0",
|
"version": "2.135.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",
|
||||||
@ -75,17 +75,17 @@
|
|||||||
"@dfinity/principal": "0.15.7",
|
"@dfinity/principal": "0.15.7",
|
||||||
"@dinero.js/currencies": "2.0.0-alpha.8",
|
"@dinero.js/currencies": "2.0.0-alpha.8",
|
||||||
"@internationalized/number": "3.6.0",
|
"@internationalized/number": "3.6.0",
|
||||||
"@nestjs/bull": "10.0.1",
|
"@nestjs/bull": "10.2.3",
|
||||||
"@nestjs/cache-manager": "2.2.2",
|
"@nestjs/cache-manager": "2.3.0",
|
||||||
"@nestjs/common": "10.1.3",
|
"@nestjs/common": "10.4.15",
|
||||||
"@nestjs/config": "3.0.0",
|
"@nestjs/config": "3.3.0",
|
||||||
"@nestjs/core": "10.1.3",
|
"@nestjs/core": "10.4.15",
|
||||||
"@nestjs/event-emitter": "2.0.4",
|
"@nestjs/event-emitter": "2.1.1",
|
||||||
"@nestjs/jwt": "10.1.0",
|
"@nestjs/jwt": "10.2.0",
|
||||||
"@nestjs/passport": "10.0.3",
|
"@nestjs/passport": "10.0.3",
|
||||||
"@nestjs/platform-express": "10.1.3",
|
"@nestjs/platform-express": "10.4.15",
|
||||||
"@nestjs/schedule": "3.0.2",
|
"@nestjs/schedule": "4.1.2",
|
||||||
"@nestjs/serve-static": "4.0.0",
|
"@nestjs/serve-static": "4.0.2",
|
||||||
"@prisma/client": "6.2.1",
|
"@prisma/client": "6.2.1",
|
||||||
"@simplewebauthn/browser": "9.0.1",
|
"@simplewebauthn/browser": "9.0.1",
|
||||||
"@simplewebauthn/server": "9.0.3",
|
"@simplewebauthn/server": "9.0.3",
|
||||||
@ -93,13 +93,13 @@
|
|||||||
"alphavantage": "2.2.0",
|
"alphavantage": "2.2.0",
|
||||||
"big.js": "6.2.2",
|
"big.js": "6.2.2",
|
||||||
"bootstrap": "4.6.0",
|
"bootstrap": "4.6.0",
|
||||||
"bull": "4.16.2",
|
"bull": "4.16.4",
|
||||||
"cache-manager": "5.7.6",
|
"cache-manager": "5.7.6",
|
||||||
"cache-manager-redis-yet": "5.1.4",
|
"cache-manager-redis-yet": "5.1.4",
|
||||||
"chart.js": "4.2.0",
|
"chart.js": "4.4.7",
|
||||||
"chartjs-adapter-date-fns": "3.0.0",
|
"chartjs-adapter-date-fns": "3.0.0",
|
||||||
"chartjs-chart-treemap": "2.3.1",
|
"chartjs-chart-treemap": "3.1.0",
|
||||||
"chartjs-plugin-annotation": "2.1.2",
|
"chartjs-plugin-annotation": "3.1.0",
|
||||||
"chartjs-plugin-datalabels": "2.2.0",
|
"chartjs-plugin-datalabels": "2.2.0",
|
||||||
"cheerio": "1.0.0",
|
"cheerio": "1.0.0",
|
||||||
"class-transformer": "0.5.1",
|
"class-transformer": "0.5.1",
|
||||||
@ -129,12 +129,12 @@
|
|||||||
"passport-google-oauth20": "2.0.0",
|
"passport-google-oauth20": "2.0.0",
|
||||||
"passport-headerapikey": "1.2.2",
|
"passport-headerapikey": "1.2.2",
|
||||||
"passport-jwt": "4.0.1",
|
"passport-jwt": "4.0.1",
|
||||||
"reflect-metadata": "0.1.13",
|
"reflect-metadata": "0.2.2",
|
||||||
"rxjs": "7.5.6",
|
"rxjs": "7.5.6",
|
||||||
"stripe": "17.3.0",
|
"stripe": "17.3.0",
|
||||||
"svgmap": "2.6.0",
|
"svgmap": "2.6.0",
|
||||||
"twitter-api-v2": "1.14.2",
|
"twitter-api-v2": "1.14.2",
|
||||||
"uuid": "11.0.2",
|
"uuid": "11.0.5",
|
||||||
"yahoo-finance2": "2.11.3",
|
"yahoo-finance2": "2.11.3",
|
||||||
"zone.js": "0.15.0"
|
"zone.js": "0.15.0"
|
||||||
},
|
},
|
||||||
@ -150,19 +150,21 @@
|
|||||||
"@angular/language-service": "19.0.5",
|
"@angular/language-service": "19.0.5",
|
||||||
"@angular/localize": "19.0.5",
|
"@angular/localize": "19.0.5",
|
||||||
"@angular/pwa": "19.0.6",
|
"@angular/pwa": "19.0.6",
|
||||||
"@nestjs/schematics": "10.0.1",
|
"@eslint/eslintrc": "3.2.0",
|
||||||
"@nestjs/testing": "10.1.3",
|
"@eslint/js": "9.18.0",
|
||||||
"@nx/angular": "20.3.0",
|
"@nestjs/schematics": "10.2.3",
|
||||||
"@nx/cypress": "20.3.0",
|
"@nestjs/testing": "10.4.15",
|
||||||
"@nx/eslint-plugin": "20.3.0",
|
"@nx/angular": "20.3.2",
|
||||||
"@nx/jest": "20.3.0",
|
"@nx/cypress": "20.3.2",
|
||||||
"@nx/js": "20.3.0",
|
"@nx/eslint-plugin": "20.3.2",
|
||||||
"@nx/module-federation": "20.3.0",
|
"@nx/jest": "20.3.2",
|
||||||
"@nx/nest": "20.3.0",
|
"@nx/js": "20.3.2",
|
||||||
"@nx/node": "20.3.0",
|
"@nx/module-federation": "20.3.2",
|
||||||
"@nx/storybook": "20.3.0",
|
"@nx/nest": "20.3.2",
|
||||||
"@nx/web": "20.3.0",
|
"@nx/node": "20.3.2",
|
||||||
"@nx/workspace": "20.3.0",
|
"@nx/storybook": "20.3.2",
|
||||||
|
"@nx/web": "20.3.2",
|
||||||
|
"@nx/workspace": "20.3.2",
|
||||||
"@schematics/angular": "19.0.6",
|
"@schematics/angular": "19.0.6",
|
||||||
"@simplewebauthn/types": "9.0.1",
|
"@simplewebauthn/types": "9.0.1",
|
||||||
"@storybook/addon-essentials": "8.4.7",
|
"@storybook/addon-essentials": "8.4.7",
|
||||||
@ -179,20 +181,20 @@
|
|||||||
"@types/node": "20.14.10",
|
"@types/node": "20.14.10",
|
||||||
"@types/papaparse": "5.3.7",
|
"@types/papaparse": "5.3.7",
|
||||||
"@types/passport-google-oauth20": "2.0.16",
|
"@types/passport-google-oauth20": "2.0.16",
|
||||||
"@typescript-eslint/eslint-plugin": "6.21.0",
|
"@typescript-eslint/eslint-plugin": "8.20.0",
|
||||||
"@typescript-eslint/parser": "6.21.0",
|
"@typescript-eslint/parser": "8.20.0",
|
||||||
"codelyzer": "6.0.1",
|
"codelyzer": "6.0.1",
|
||||||
"cypress": "6.2.1",
|
"cypress": "6.2.1",
|
||||||
"eslint": "8.57.0",
|
"eslint": "9.18.0",
|
||||||
"eslint-config-prettier": "9.1.0",
|
"eslint-config-prettier": "9.1.0",
|
||||||
"eslint-plugin-cypress": "2.15.1",
|
"eslint-plugin-cypress": "3.2.0",
|
||||||
"eslint-plugin-import": "2.29.1",
|
"eslint-plugin-import": "2.31.0",
|
||||||
"eslint-plugin-storybook": "0.6.15",
|
"eslint-plugin-storybook": "0.10.2",
|
||||||
"husky": "9.1.7",
|
"husky": "9.1.7",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"jest-environment-jsdom": "29.7.0",
|
"jest-environment-jsdom": "29.7.0",
|
||||||
"jest-preset-angular": "14.4.2",
|
"jest-preset-angular": "14.4.2",
|
||||||
"nx": "20.3.0",
|
"nx": "20.3.2",
|
||||||
"prettier": "3.4.2",
|
"prettier": "3.4.2",
|
||||||
"prettier-plugin-organize-attributes": "1.0.0",
|
"prettier-plugin-organize-attributes": "1.0.0",
|
||||||
"prisma": "6.2.1",
|
"prisma": "6.2.1",
|
||||||
@ -203,7 +205,7 @@
|
|||||||
"storybook": "8.4.7",
|
"storybook": "8.4.7",
|
||||||
"ts-jest": "29.1.0",
|
"ts-jest": "29.1.0",
|
||||||
"ts-node": "10.9.2",
|
"ts-node": "10.9.2",
|
||||||
"tslib": "2.6.0",
|
"tslib": "2.8.1",
|
||||||
"typescript": "5.6.3",
|
"typescript": "5.6.3",
|
||||||
"webpack-bundle-analyzer": "4.10.2"
|
"webpack-bundle-analyzer": "4.10.2"
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user