Browse Source

Merge branch 'main' into feature/2820-grant-private-access

pull/2822/head
Thomas Kaul 2 years ago
committed by GitHub
parent
commit
435c0241cf
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      .prettierignore
  2. 9
      CHANGELOG.md
  3. 2
      README.md
  4. 4
      apps/api/src/app/admin/admin.controller.ts
  5. 43
      apps/api/src/services/data-provider/manual/manual.service.ts
  6. 2
      apps/client/src/app/components/header/header.component.scss
  7. 10
      apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html
  8. 5
      docker/docker-compose.build.yml
  9. 3
      docker/docker-compose.dev.yml
  10. 5
      docker/docker-compose.yml
  11. 2
      libs/ui/src/lib/assistant/assistant.html
  12. 1
      package.json
  13. 42
      yarn.lock

4
.prettierignore

@ -1,3 +1,7 @@
/.nx/cache
# Issue: https://github.com/prettier/prettier/issues/15650
/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html
/dist
/test/import

9
CHANGELOG.md

@ -9,8 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Improved the user interface of the access table to share the portfolio
- Added support to grant private access
- Added a hint for _Time-Weighted Rate of Return_ (TWR) to the portfolio summary tab on the home page
- Added support for REST APIs (`JSON`) via the scraper configuration
- Enabled the _Redis_ authentication in the `docker-compose` files
### Changed
- Improved the user interface of the access table to share the portfolio
- Improved the style of the assistant (experimental)
## 2.34.0 - 2024-01-02

2
README.md

@ -49,7 +49,7 @@ Ghostfolio is for you if you are...
- ✅ Create, update and delete transactions
- ✅ Multi account management
- ✅ Portfolio performance for `Today`, `YTD`, `1Y`, `5Y`, `Max`
- ✅ Portfolio performance: Time-weighted rate of return (TWR) for `Today`, `YTD`, `1Y`, `5Y`, `Max`
- ✅ Various charts
- ✅ Static analysis to identify potential risks in your portfolio
- ✅ Import and export transactions

4
apps/api/src/app/admin/admin.controller.ts

@ -227,8 +227,8 @@ export class AdminController {
@Param('symbol') symbol: string
): Promise<{ price: number }> {
try {
const { headers, selector, url } = JSON.parse(data.scraperConfiguration);
const price = await this.manualService.test({ headers, selector, url });
const scraperConfiguration = JSON.parse(data.scraperConfiguration);
const price = await this.manualService.test(scraperConfiguration);
if (price) {
return { price };

43
apps/api/src/services/data-provider/manual/manual.service.ts

@ -12,6 +12,7 @@ import {
extractNumberFromString,
getYesterday
} from '@ghostfolio/common/helper';
import { ScraperConfiguration } from '@ghostfolio/common/interfaces';
import { Granularity } from '@ghostfolio/common/types';
import { Injectable, Logger } from '@nestjs/common';
import { DataSource, SymbolProfile } from '@prisma/client';
@ -19,6 +20,7 @@ import * as cheerio from 'cheerio';
import { isUUID } from 'class-validator';
import { addDays, format, isBefore } from 'date-fns';
import got, { Headers } from 'got';
import jsonpath from 'jsonpath';
@Injectable()
export class ManualService implements DataProviderInterface {
@ -97,7 +99,7 @@ export class ManualService implements DataProviderInterface {
return {};
}
const value = await this.scrape({ headers, selector, url });
const value = await this.scrape(symbolProfile.scraperConfiguration);
return {
[symbol]: {
@ -220,23 +222,13 @@ export class ManualService implements DataProviderInterface {
return { items };
}
public async test(params: any) {
return this.scrape({
headers: params.headers,
selector: params.selector,
url: params.url
});
public async test(scraperConfiguration: ScraperConfiguration) {
return this.scrape(scraperConfiguration);
}
private async scrape({
headers = {},
selector,
url
}: {
headers?: Headers;
selector: string;
url: string;
}): Promise<number> {
private async scrape(
scraperConfiguration: ScraperConfiguration
): Promise<number> {
try {
const abortController = new AbortController();
@ -244,15 +236,26 @@ export class ManualService implements DataProviderInterface {
abortController.abort();
}, this.configurationService.get('REQUEST_TIMEOUT'));
const { body } = await got(url, {
headers,
const { body, headers } = await got(scraperConfiguration.url, {
headers: scraperConfiguration.headers as Headers,
// @ts-ignore
signal: abortController.signal
});
const $ = cheerio.load(body);
if (headers['content-type'] === 'application/json') {
const data = JSON.parse(body);
const value = String(
jsonpath.query(data, scraperConfiguration.selector)[0]
);
return extractNumberFromString($(selector).first().text());
return extractNumberFromString(value);
} else {
const $ = cheerio.load(body);
return extractNumberFromString(
$(scraperConfiguration.selector).first().text()
);
}
} catch (error) {
throw error;
}

2
apps/client/src/app/components/header/header.component.scss

@ -34,7 +34,7 @@
font-size: 1.5rem;
&.rotate-90 {
transform: rotate(-90deg);
transform: rotate(90deg);
}
}
}

10
apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html

@ -69,7 +69,10 @@
</div>
</div>
<div class="flex-nowrap px-3 py-1 row">
<div class="flex-grow-1 ml-3 text-truncate" i18n>Gross Performance</div>
<div class="align-items-center d-flex flex-grow-1 ml-3 text-truncate">
<ng-container i18n>Gross Performance</ng-container>
<abbr class="initialism ml-2 text-muted" title="Time-Weighted Rate of Return">(TWR)</abbr>
</div>
<div class="flex-column flex-wrap justify-content-end">
<gf-value
class="justify-content-end"
@ -112,7 +115,10 @@
</div>
</div>
<div class="flex-nowrap px-3 py-1 row">
<div class="flex-grow-1 text-truncate ml-3" i18n>Net Performance</div>
<div class="flex-grow-1 text-truncate ml-3">
<ng-container i18n>Net Performance</ng-container>
<abbr class="initialism ml-2 text-muted" title="Time-Weighted Rate of Return">(TWR)</abbr>
</div>
<div class="flex-column flex-wrap justify-content-end">
<gf-value
class="justify-content-end"

5
docker/docker-compose.build.yml

@ -29,8 +29,11 @@ services:
- postgres:/var/lib/postgresql/data
redis:
image: redis:alpine
env_file:
- ../.env
command: ['redis-server', '--requirepass', $REDIS_PASSWORD]
healthcheck:
test: ['CMD-SHELL', 'redis-cli ping | grep PONG']
test: ['CMD-SHELL', 'redis-cli --pass $REDIS_PASSWORD ping | grep PONG']
interval: 10s
timeout: 5s
retries: 5

3
docker/docker-compose.dev.yml

@ -14,6 +14,9 @@ services:
image: redis:alpine
container_name: redis
restart: unless-stopped
env_file:
- ../.env
command: ['redis-server', '--requirepass', $REDIS_PASSWORD]
ports:
- ${REDIS_PORT:-6379}:6379

5
docker/docker-compose.yml

@ -29,8 +29,11 @@ services:
- postgres:/var/lib/postgresql/data
redis:
image: redis:alpine
env_file:
- ../.env
command: ['redis-server', '--requirepass', $REDIS_PASSWORD]
healthcheck:
test: ['CMD-SHELL', 'redis-cli ping | grep PONG']
test: ['CMD-SHELL', 'redis-cli --pass $REDIS_PASSWORD ping | grep PONG']
interval: 10s
timeout: 5s
retries: 5

2
libs/ui/src/lib/assistant/assistant.html

@ -96,7 +96,7 @@
>Date Range</span
></ng-template
>
<div class="d-flex justify-content-center p-3">
<div class="d-flex justify-content-center py-3">
<gf-toggle
[defaultValue]="user?.settings?.dateRange"
[options]="dateRangeOptions"

1
package.json

@ -110,6 +110,7 @@
"helmet": "7.0.0",
"http-status-codes": "2.3.0",
"ionicons": "7.1.0",
"jsonpath": "1.1.1",
"lodash": "4.17.21",
"marked": "9.1.6",
"ms": "3.0.0-canary.1",

42
yarn.lock

@ -10959,6 +10959,18 @@ escape-string-regexp@^5.0.0:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
escodegen@^1.8.1:
version "1.14.3"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503"
integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==
dependencies:
esprima "^4.0.1"
estraverse "^4.2.0"
esutils "^2.0.2"
optionator "^0.8.1"
optionalDependencies:
source-map "~0.6.1"
escodegen@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17"
@ -11187,6 +11199,11 @@ espree@^9.0.0, espree@^9.4.0:
acorn-jsx "^5.3.2"
eslint-visitor-keys "^3.4.1"
esprima@1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-1.2.2.tgz#76a0fd66fcfe154fd292667dc264019750b1657b"
integrity sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==
esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
@ -11206,7 +11223,7 @@ esrecurse@^4.1.0, esrecurse@^4.3.0:
dependencies:
estraverse "^5.2.0"
estraverse@^4.1.1:
estraverse@^4.1.1, estraverse@^4.2.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
@ -14245,6 +14262,15 @@ jsonparse@^1.3.1:
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==
jsonpath@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/jsonpath/-/jsonpath-1.1.1.tgz#0ca1ed8fb65bb3309248cc9d5466d12d5b0b9901"
integrity sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==
dependencies:
esprima "1.2.2"
static-eval "2.0.2"
underscore "1.12.1"
jsonwebtoken@9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz#d0faf9ba1cc3a56255fe49c0961a67e520c1926d"
@ -16051,7 +16077,7 @@ opn@5.3.0:
dependencies:
is-wsl "^1.1.0"
optionator@^0.8.2:
optionator@^0.8.1, optionator@^0.8.2:
version "0.8.3"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==
@ -18446,6 +18472,13 @@ standard-as-callback@^2.1.0:
resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45"
integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==
static-eval@2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/static-eval/-/static-eval-2.0.2.tgz#2d1759306b1befa688938454c546b7871f806a42"
integrity sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==
dependencies:
escodegen "^1.8.1"
static-extend@^0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
@ -19380,6 +19413,11 @@ unbox-primitive@^1.0.2:
has-symbols "^1.0.3"
which-boxed-primitive "^1.0.2"
underscore@1.12.1:
version "1.12.1"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.12.1.tgz#7bb8cc9b3d397e201cf8553336d262544ead829e"
integrity sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==
undici-types@~5.26.4:
version "5.26.5"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"

Loading…
Cancel
Save