Skip to content

Commit c3ce253

Browse files
authored
Add Ghostfolio (#813)
* feat: Add Ghostfolio as Enhanced App * style: Fix phpcs errors * style: Fix phpcs error
1 parent 8c7952c commit c3ce253

File tree

5 files changed

+172
-0
lines changed

5 files changed

+172
-0
lines changed

Ghostfolio/Ghostfolio.php

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<?php
2+
3+
namespace App\SupportedApps\Ghostfolio;
4+
5+
use Exception;
6+
7+
class Ghostfolio extends \App\SupportedApps implements \App\EnhancedApps
8+
{
9+
public $config;
10+
11+
public function __construct()
12+
{
13+
}
14+
15+
public function test()
16+
{
17+
try {
18+
$this->auth();
19+
$perf = $this->getPortfolioPerformance();
20+
21+
echo "Successfully communicated with the API";
22+
} catch (Exception $err) {
23+
echo "Error connecting to Ghostfolio: " . $err->getMessage();
24+
}
25+
}
26+
27+
public function url($endpoint)
28+
{
29+
$api_url = parent::normaliseurl($this->config->url) . $endpoint;
30+
return $api_url;
31+
}
32+
33+
public function auth(): void
34+
{
35+
$body = json_encode(["accessToken" => $this->config->password]);
36+
37+
$vars = [
38+
"http_errors" => false,
39+
"timeout" => 5,
40+
"body" => $body,
41+
"headers" => [
42+
"Content-Type" => "application/json",
43+
],
44+
];
45+
46+
$result = parent::execute(
47+
$this->url("api/v1/auth/anonymous"),
48+
[],
49+
$vars,
50+
"POST"
51+
);
52+
53+
if ($result === null) {
54+
throw new Exception("Could not connect to Ghostfolio");
55+
}
56+
57+
$responseBody = $result->getBody()->getContents();
58+
$response = json_decode($responseBody, true);
59+
if (null === $response || $result->getStatusCode() !== 201 || !isset($response['authToken'])) {
60+
throw new Exception("Error logging in");
61+
}
62+
63+
$this->config->jwt = $response['authToken'];
64+
}
65+
66+
67+
public function livestats()
68+
{
69+
$this->auth();
70+
$status = 'inactive';
71+
72+
$data = $this->getPortfolioPerformance();
73+
foreach ($this->config->availablestats as $num => $key) {
74+
$stat = new \stdClass();
75+
$stat->title = self::getAvailableStats()[$key];
76+
$value = $data[$key] ?? null;
77+
$stat->value = is_numeric($value)
78+
? rtrim(
79+
rtrim(number_format($value, 1, decimal_separator: '.', thousands_separator: ''), characters: '0'),
80+
characters: '.'
81+
)
82+
: substr(string: $value, offset: 0, length: 1);
83+
$details["visiblestats"][] = $stat;
84+
}
85+
return parent::getLiveStats($status, $details);
86+
}
87+
88+
89+
90+
private function getPortfolioPerformance(): mixed
91+
{
92+
$attrs = [
93+
"headers" => [
94+
"Content-Type" => "application/json",
95+
"Authorization" => "Bearer " . $this->config->jwt,
96+
],
97+
"query" => [
98+
"range" => $this->config->selected_range,
99+
],
100+
];
101+
$result = parent::execute(
102+
$this->url("api/v2/portfolio/performance"),
103+
$attrs,
104+
);
105+
106+
if (null === $result || $result->getStatusCode() !== 200) {
107+
throw new Exception("Error retrieving performance");
108+
}
109+
$data = json_decode($result->getBody()->getContents(), true);
110+
return $data['performance'];
111+
}
112+
113+
public static function getAvailableStats()
114+
{
115+
return [
116+
"netPerformancePercentageWithCurrencyEffect" => "Net Perf (%)",
117+
"netPerformanceWithCurrencyEffect" => "Net Perf",
118+
"totalInvestment" => "Invested",
119+
"currentValueInBaseCurrency" => "Value",
120+
"currentNetWorth" => "Net Worth",
121+
];
122+
}
123+
}

Ghostfolio/app.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"appid": "636e86d7a1a80e47d1ec19e6782e3700227c7603",
3+
"name": "Ghostfolio",
4+
"website": "https://ghostfol.io",
5+
"license": "GNU Affero General Public License v3.0 only",
6+
"description": "test",
7+
"enhanced": true,
8+
"tile_background": "dark",
9+
"icon": "ghostfolio.png"
10+
}

Ghostfolio/config.blade.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<h2>{{ __('app.apps.config') }} ({{ __('app.optional') }}) @include('items.enable')</h2>
2+
3+
<div class="items" style="flex-direction:column">
4+
<div style="display:flex;flex-direction:row;">
5+
<div class="input">
6+
<label>{{ strtoupper(__('app.url')) }}</label>
7+
{!! Form::text('config[override_url]', isset($item) ? $item->getconfig()->override_url : null, ['placeholder' => __('app.apps.override'), 'id' => 'override_url', 'class' => 'form-control']) !!}
8+
</div>
9+
<div class="input">
10+
<label>{{ __('app.apps.password') }} (secret token)</label>
11+
{!! Form::input('password', 'config[password]', '', ['placeholder' => __('app.apps.password'), 'data-config' => 'password', 'class' => 'form-control config-item']) !!}
12+
</div>
13+
</div>
14+
<div style="display:flex;flex-direction:row;">
15+
<div class="input">
16+
<label>Time Range</label>
17+
{!! Form::select(
18+
'config[selected_range]',
19+
['1d' => '1 Day', 'wtd' => 'Week To Date', 'ytd' => 'Year To Date', 'max' => 'MAX'],
20+
isset($item) ? $item->getconfig()->selected_range : null,
21+
['data-config' => 'selected_range', 'class' => 'form-control config-item'],
22+
) !!}
23+
</div>
24+
<div class="input">
25+
<label>Stats to show</label>
26+
{!! Form::select('config[availablestats][]', App\SupportedApps\Ghostfolio\Ghostfolio::getAvailableStats(), isset($item) ? $item->getConfig()->availablestats ?? null : null, ['multiple' => 'multiple']) !!}
27+
</div>
28+
<dsclass="input">
29+
<button style="margin-top: 32px;" class="btn test" id="test_config">Test</button>
30+
</div>
31+
</div>

Ghostfolio/ghostfolio.png

1.02 KB
Loading

Ghostfolio/livestats.blade.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<ul class="livestats">
2+
@foreach ($visiblestats as $stat)
3+
<li>
4+
<span class="title">{!! $stat->title !!}</span>
5+
<strong>{!! $stat->value !!}</strong>
6+
</li>
7+
@endforeach
8+
</ul>

0 commit comments

Comments
 (0)