From 66c955ad6c4f0798c9eacb89ce2f54933fff93ec Mon Sep 17 00:00:00 2001 From: Thomas <4159106+dtslvr@users.noreply.github.com> Date: Thu, 17 Jun 2021 22:59:48 +0200 Subject: [PATCH] Feature/ghostfolio in numbers (#175) * Add Ghostfolio in numbers section * Update changelog --- CHANGELOG.md | 6 ++ apps/api/src/app/info/info.service.ts | 72 ++++++++++++++++++- .../api/src/services/configuration.service.ts | 1 + .../interfaces/environment.interface.ts | 1 + .../app/pages/about/about-page.component.ts | 19 +++++ .../src/app/pages/about/about-page.html | 34 +++++++++ .../src/lib/interfaces/info-item.interface.ts | 3 + .../lib/interfaces/statistics.interface.ts | 5 ++ libs/common/src/lib/permissions.ts | 1 + 9 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 libs/common/src/lib/interfaces/statistics.interface.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 83f51d2a5..d31dbab08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Added + +- Added a _Ghostfolio in Numbers_ section to the about page + ## 1.18.0 - 16.06.2021 ### Changed diff --git a/apps/api/src/app/info/info.service.ts b/apps/api/src/app/info/info.service.ts index 13fad7c27..580d39f35 100644 --- a/apps/api/src/app/info/info.service.ts +++ b/apps/api/src/app/info/info.service.ts @@ -5,6 +5,8 @@ import { permissions } from '@ghostfolio/common/permissions'; import { Injectable } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { Currency } from '@prisma/client'; +import * as bent from 'bent'; +import { subDays } from 'date-fns'; @Injectable() export class InfoService { @@ -28,6 +30,10 @@ export class InfoService { globalPermissions.push(permissions.enableSocialLogin); } + if (this.configurationService.get('ENABLE_FEATURE_STATISTICS')) { + globalPermissions.push(permissions.enableStatistics); + } + if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { globalPermissions.push(permissions.enableSubscription); } @@ -37,7 +43,8 @@ export class InfoService { platforms, currencies: Object.values(Currency), demoAuthToken: this.getDemoAuthToken(), - lastDataGathering: await this.getLastDataGathering() + lastDataGathering: await this.getLastDataGathering(), + statistics: await this.getStatistics() }; } @@ -54,4 +61,67 @@ export class InfoService { return lastDataGathering?.value ? new Date(lastDataGathering.value) : null; } + + private async getStatistics() { + if (!this.configurationService.get('ENABLE_FEATURE_STATISTICS')) { + return undefined; + } + + const activeUsers1d = await this.countActiveUsers(1); + const activeUsers30d = await this.countActiveUsers(30); + const gitHubStargazers = await this.countGitHubStargazers(); + + return { + activeUsers1d, + activeUsers30d, + gitHubStargazers + }; + } + + private async countActiveUsers(aDays: number) { + return await this.prisma.user.count({ + orderBy: { + Analytics: { + updatedAt: 'desc' + } + }, + where: { + AND: [ + { + NOT: { + Analytics: null + } + }, + { + Analytics: { + updatedAt: { + gt: subDays(new Date(), aDays) + } + } + } + ] + } + }); + } + + private async countGitHubStargazers(): Promise { + try { + const get = bent( + `https://api.github.com/repos/ghostfolio/ghostfolio`, + 'GET', + 'json', + 200, + { + 'User-Agent': 'request' + } + ); + + const { stargazers_count } = await get(); + return stargazers_count; + } catch (error) { + console.error(error); + + return undefined; + } + } } diff --git a/apps/api/src/services/configuration.service.ts b/apps/api/src/services/configuration.service.ts index 9a7a294e1..af17e9989 100644 --- a/apps/api/src/services/configuration.service.ts +++ b/apps/api/src/services/configuration.service.ts @@ -17,6 +17,7 @@ export class ConfigurationService { ENABLE_FEATURE_CUSTOM_SYMBOLS: bool({ default: false }), ENABLE_FEATURE_FEAR_AND_GREED_INDEX: bool({ default: false }), ENABLE_FEATURE_SOCIAL_LOGIN: bool({ default: false }), + ENABLE_FEATURE_STATISTICS: bool({ default: false }), ENABLE_FEATURE_SUBSCRIPTION: bool({ default: false }), GOOGLE_CLIENT_ID: str({ default: 'dummyClientId' }), GOOGLE_SECRET: str({ default: 'dummySecret' }), diff --git a/apps/api/src/services/interfaces/environment.interface.ts b/apps/api/src/services/interfaces/environment.interface.ts index fd67d34d7..8d344c60f 100644 --- a/apps/api/src/services/interfaces/environment.interface.ts +++ b/apps/api/src/services/interfaces/environment.interface.ts @@ -8,6 +8,7 @@ export interface Environment extends CleanedEnvAccessors { ENABLE_FEATURE_CUSTOM_SYMBOLS: boolean; ENABLE_FEATURE_FEAR_AND_GREED_INDEX: boolean; ENABLE_FEATURE_SOCIAL_LOGIN: boolean; + ENABLE_FEATURE_STATISTICS: boolean; ENABLE_FEATURE_SUBSCRIPTION: boolean; GOOGLE_CLIENT_ID: string; GOOGLE_SECRET: string; diff --git a/apps/client/src/app/pages/about/about-page.component.ts b/apps/client/src/app/pages/about/about-page.component.ts index 03d684d46..25f41a1e3 100644 --- a/apps/client/src/app/pages/about/about-page.component.ts +++ b/apps/client/src/app/pages/about/about-page.component.ts @@ -1,7 +1,10 @@ import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; +import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { baseCurrency } from '@ghostfolio/common/config'; import { User } from '@ghostfolio/common/interfaces'; +import { Statistics } from '@ghostfolio/common/interfaces/statistics.interface'; +import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -14,8 +17,10 @@ import { environment } from '../../../environments/environment'; }) export class AboutPageComponent implements OnInit { public baseCurrency = baseCurrency; + public hasPermissionForStatistics: boolean; public isLoggedIn: boolean; public lastPublish = environment.lastPublish; + public statistics: Statistics; public user: User; public version = environment.version; @@ -26,6 +31,7 @@ export class AboutPageComponent implements OnInit { */ public constructor( private changeDetectorRef: ChangeDetectorRef, + private dataService: DataService, private userService: UserService ) {} @@ -33,6 +39,19 @@ export class AboutPageComponent implements OnInit { * Initializes the controller */ public ngOnInit() { + this.dataService + .fetchInfo() + .subscribe(({ globalPermissions, statistics }) => { + this.hasPermissionForStatistics = hasPermission( + globalPermissions, + permissions.enableStatistics + ); + + this.statistics = statistics; + + this.changeDetectorRef.markForCheck(); + }); + this.userService.stateChanged .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((state) => { diff --git a/apps/client/src/app/pages/about/about-page.html b/apps/client/src/app/pages/about/about-page.html index bbfe62d16..c56c439fc 100644 --- a/apps/client/src/app/pages/about/about-page.html +++ b/apps/client/src/app/pages/about/about-page.html @@ -60,6 +60,40 @@ +
+
+

Ghostfolio in Numbers

+ + +
+
+

+ {{ statistics?.activeUsers1d ?? '-' }} +

+
+ Active Users (Last 24 hours) +
+
+
+

+ {{ statistics?.activeUsers30d ?? '-' }} +

+
+ Active Users (Last 30 days) +
+
+
+

+ {{ statistics?.gitHubStargazers ?? '-' }} +

+
Stars on GitHub
+
+
+
+
+
+
+

Changelog

diff --git a/libs/common/src/lib/interfaces/info-item.interface.ts b/libs/common/src/lib/interfaces/info-item.interface.ts index 58712e297..cef64e5f6 100644 --- a/libs/common/src/lib/interfaces/info-item.interface.ts +++ b/libs/common/src/lib/interfaces/info-item.interface.ts @@ -1,5 +1,7 @@ import { Currency } from '@prisma/client'; +import { Statistics } from './statistics.interface'; + export interface InfoItem { currencies: Currency[]; demoAuthToken: string; @@ -10,4 +12,5 @@ export interface InfoItem { type: string; }; platforms: { id: string; name: string }[]; + statistics: Statistics; } diff --git a/libs/common/src/lib/interfaces/statistics.interface.ts b/libs/common/src/lib/interfaces/statistics.interface.ts new file mode 100644 index 000000000..dcb2729e5 --- /dev/null +++ b/libs/common/src/lib/interfaces/statistics.interface.ts @@ -0,0 +1,5 @@ +export interface Statistics { + activeUsers1d: number; + activeUsers30d: number; + gitHubStargazers: number; +} diff --git a/libs/common/src/lib/permissions.ts b/libs/common/src/lib/permissions.ts index bbe0806a7..4d8f3a719 100644 --- a/libs/common/src/lib/permissions.ts +++ b/libs/common/src/lib/permissions.ts @@ -15,6 +15,7 @@ export const permissions = { deleteOrder: 'deleteOrder', deleteUser: 'deleteUser', enableSocialLogin: 'enableSocialLogin', + enableStatistics: 'enableStatistics', enableSubscription: 'enableSubscription', readForeignPortfolio: 'readForeignPortfolio', updateAccount: 'updateAccount',