From ccaf06360ab87e45a21341ea073378185b7b020f Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Sun, 11 Feb 2024 10:06:07 +0100
Subject: [PATCH] Feature/introduce admin setting to disable data gathering
 (#2981)

* Introduce setting to disable data gathering

* Update changelog
---
 CHANGELOG.md                                  |  4 ++
 apps/api/src/app/app.module.ts                |  2 +
 apps/api/src/services/cron.service.ts         | 51 ++++++++++++-------
 .../admin-overview.component.ts               | 13 +++++
 .../admin-overview/admin-overview.html        | 11 ++++
 libs/common/src/lib/config.ts                 |  1 +
 .../src/lib/interfaces/info-item.interface.ts |  1 +
 7 files changed, 65 insertions(+), 18 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8b2c8d7d..646f5808 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## Unreleased
 
+### Added
+
+- Introduced a setting to disable the data gathering in the admin control
+
 ### Changed
 
 - Harmonized the environment variables of various API keys
diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts
index 03c6a4aa..9c17585a 100644
--- a/apps/api/src/app/app.module.ts
+++ b/apps/api/src/app/app.module.ts
@@ -6,6 +6,7 @@ import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering/dat
 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 { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
+import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
 import { TwitterBotModule } from '@ghostfolio/api/services/twitter-bot/twitter-bot.module';
 import {
   DEFAULT_LANGUAGE_CODE,
@@ -73,6 +74,7 @@ import { UserModule } from './user/user.module';
     PlatformModule,
     PortfolioModule,
     PrismaModule,
+    PropertyModule,
     RedisCacheModule,
     ScheduleModule.forRoot(),
     ServeStaticModule.forRoot({
diff --git a/apps/api/src/services/cron.service.ts b/apps/api/src/services/cron.service.ts
index e3597f04..b896c684 100644
--- a/apps/api/src/services/cron.service.ts
+++ b/apps/api/src/services/cron.service.ts
@@ -1,6 +1,7 @@
 import {
   GATHER_ASSET_PROFILE_PROCESS,
-  GATHER_ASSET_PROFILE_PROCESS_OPTIONS
+  GATHER_ASSET_PROFILE_PROCESS_OPTIONS,
+  PROPERTY_IS_DATA_GATHERING_ENABLED
 } from '@ghostfolio/common/config';
 import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
 import { Injectable } from '@nestjs/common';
@@ -8,6 +9,7 @@ import { Cron, CronExpression } from '@nestjs/schedule';
 
 import { DataGatheringService } from './data-gathering/data-gathering.service';
 import { ExchangeRateDataService } from './exchange-rate-data/exchange-rate-data.service';
+import { PropertyService } from './property/property.service';
 import { TwitterBotService } from './twitter-bot/twitter-bot.service';
 
 @Injectable()
@@ -17,12 +19,15 @@ export class CronService {
   public constructor(
     private readonly dataGatheringService: DataGatheringService,
     private readonly exchangeRateDataService: ExchangeRateDataService,
+    private readonly propertyService: PropertyService,
     private readonly twitterBotService: TwitterBotService
   ) {}
 
   @Cron(CronExpression.EVERY_HOUR)
   public async runEveryHour() {
-    await this.dataGatheringService.gather7Days();
+    if (await this.isDataGatheringEnabled()) {
+      await this.dataGatheringService.gather7Days();
+    }
   }
 
   @Cron(CronExpression.EVERY_12_HOURS)
@@ -37,22 +42,32 @@ export class CronService {
 
   @Cron(CronService.EVERY_SUNDAY_AT_LUNCH_TIME)
   public async runEverySundayAtTwelvePm() {
-    const uniqueAssets = await this.dataGatheringService.getUniqueAssets();
+    if (await this.isDataGatheringEnabled()) {
+      const uniqueAssets = await this.dataGatheringService.getUniqueAssets();
 
-    await this.dataGatheringService.addJobsToQueue(
-      uniqueAssets.map(({ dataSource, symbol }) => {
-        return {
-          data: {
-            dataSource,
-            symbol
-          },
-          name: GATHER_ASSET_PROFILE_PROCESS,
-          opts: {
-            ...GATHER_ASSET_PROFILE_PROCESS_OPTIONS,
-            jobId: getAssetProfileIdentifier({ dataSource, symbol })
-          }
-        };
-      })
-    );
+      await this.dataGatheringService.addJobsToQueue(
+        uniqueAssets.map(({ dataSource, symbol }) => {
+          return {
+            data: {
+              dataSource,
+              symbol
+            },
+            name: GATHER_ASSET_PROFILE_PROCESS,
+            opts: {
+              ...GATHER_ASSET_PROFILE_PROCESS_OPTIONS,
+              jobId: getAssetProfileIdentifier({ dataSource, symbol })
+            }
+          };
+        })
+      );
+    }
+  }
+
+  private async isDataGatheringEnabled() {
+    return (await this.propertyService.getByKey(
+      PROPERTY_IS_DATA_GATHERING_ENABLED
+    )) === false
+      ? false
+      : true;
   }
 }
diff --git a/apps/client/src/app/components/admin-overview/admin-overview.component.ts b/apps/client/src/app/components/admin-overview/admin-overview.component.ts
index 4b3e1f12..3c9ec929 100644
--- a/apps/client/src/app/components/admin-overview/admin-overview.component.ts
+++ b/apps/client/src/app/components/admin-overview/admin-overview.component.ts
@@ -7,6 +7,7 @@ import { UserService } from '@ghostfolio/client/services/user/user.service';
 import {
   PROPERTY_COUPONS,
   PROPERTY_CURRENCIES,
+  PROPERTY_IS_DATA_GATHERING_ENABLED,
   PROPERTY_IS_READ_ONLY_MODE,
   PROPERTY_IS_USER_SIGNUP_ENABLED,
   PROPERTY_SYSTEM_MESSAGE,
@@ -43,6 +44,7 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
   public hasPermissionForSystemMessage: boolean;
   public hasPermissionToToggleReadOnlyMode: boolean;
   public info: InfoItem;
+  public isDataGatheringEnabled: boolean;
   public permissions = permissions;
   public systemMessage: SystemMessage;
   public transactionCount: number;
@@ -168,6 +170,13 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
     }
   }
 
+  public onEnableDataGatheringChange(aEvent: MatSlideToggleChange) {
+    this.putAdminSetting({
+      key: PROPERTY_IS_DATA_GATHERING_ENABLED,
+      value: aEvent.checked ? undefined : false
+    });
+  }
+
   public onFlushCache() {
     const confirmation = confirm(
       $localize`Do you really want to flush the cache?`
@@ -233,6 +242,10 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
           this.coupons = (settings[PROPERTY_COUPONS] as Coupon[]) ?? [];
           this.customCurrencies = settings[PROPERTY_CURRENCIES] as string[];
           this.exchangeRates = exchangeRates;
+          this.isDataGatheringEnabled =
+            settings[PROPERTY_IS_DATA_GATHERING_ENABLED] === false
+              ? false
+              : true;
           this.systemMessage = settings[
             PROPERTY_SYSTEM_MESSAGE
           ] as SystemMessage;
diff --git a/apps/client/src/app/components/admin-overview/admin-overview.html b/apps/client/src/app/components/admin-overview/admin-overview.html
index bdf623ba..b1be5bd8 100644
--- a/apps/client/src/app/components/admin-overview/admin-overview.html
+++ b/apps/client/src/app/components/admin-overview/admin-overview.html
@@ -128,6 +128,17 @@
               ></mat-slide-toggle>
             </div>
           </div>
+          <div class="d-flex my-3">
+            <div class="w-50" i18n>Data Gathering</div>
+            <div class="w-50">
+              <mat-slide-toggle
+                color="primary"
+                hideIcon="true"
+                [checked]="isDataGatheringEnabled"
+                (change)="onEnableDataGatheringChange($event)"
+              ></mat-slide-toggle>
+            </div>
+          </div>
           <div *ngIf="hasPermissionForSystemMessage" class="d-flex my-3">
             <div class="w-50" i18n>System Message</div>
             <div class="w-50">
diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts
index b93d22f4..3bbe0ff8 100644
--- a/libs/common/src/lib/config.ts
+++ b/libs/common/src/lib/config.ts
@@ -97,6 +97,7 @@ export const PROPERTY_COUPONS = 'COUPONS';
 export const PROPERTY_CURRENCIES = 'CURRENCIES';
 export const PROPERTY_DATA_SOURCE_MAPPING = 'DATA_SOURCE_MAPPING';
 export const PROPERTY_DEMO_USER_ID = 'DEMO_USER_ID';
+export const PROPERTY_IS_DATA_GATHERING_ENABLED = 'IS_DATA_GATHERING_ENABLED';
 export const PROPERTY_IS_READ_ONLY_MODE = 'IS_READ_ONLY_MODE';
 export const PROPERTY_IS_USER_SIGNUP_ENABLED = 'IS_USER_SIGNUP_ENABLED';
 export const PROPERTY_SLACK_COMMUNITY_USERS = 'SLACK_COMMUNITY_USERS';
diff --git a/libs/common/src/lib/interfaces/info-item.interface.ts b/libs/common/src/lib/interfaces/info-item.interface.ts
index 1d3624e2..6b8fc665 100644
--- a/libs/common/src/lib/interfaces/info-item.interface.ts
+++ b/libs/common/src/lib/interfaces/info-item.interface.ts
@@ -12,6 +12,7 @@ export interface InfoItem {
   demoAuthToken: string;
   fearAndGreedDataSource?: string;
   globalPermissions: string[];
+  isDataGatheringEnabled?: string;
   isReadOnlyMode?: boolean;
   platforms: Platform[];
   statistics: Statistics;