diff --git a/CHANGELOG.md b/CHANGELOG.md index 71d612833..0940aad29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added tabs with routing to the admin control panel +- Added a new tab to manage historical data to the admin control panel ### Changed diff --git a/apps/api/src/app/admin/admin.controller.ts b/apps/api/src/app/admin/admin.controller.ts index 2a2f94988..3ace41263 100644 --- a/apps/api/src/app/admin/admin.controller.ts +++ b/apps/api/src/app/admin/admin.controller.ts @@ -1,5 +1,9 @@ import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service'; -import { AdminData } from '@ghostfolio/common/interfaces'; +import { + AdminData, + AdminMarketData, + AdminMarketDataDetails +} from '@ghostfolio/common/interfaces'; import { getPermissions, hasPermission, @@ -11,6 +15,7 @@ import { Get, HttpException, Inject, + Param, Post, UseGuards } from '@nestjs/common'; @@ -86,4 +91,42 @@ export class AdminController { return; } + + @Get('market-data') + @UseGuards(AuthGuard('jwt')) + public async getMarketData(): Promise { + if ( + !hasPermission( + getPermissions(this.request.user.role), + permissions.accessAdminControl + ) + ) { + throw new HttpException( + getReasonPhrase(StatusCodes.FORBIDDEN), + StatusCodes.FORBIDDEN + ); + } + + return this.adminService.getMarketData(); + } + + @Get('market-data/:symbol') + @UseGuards(AuthGuard('jwt')) + public async getMarketDataBySymbol( + @Param('symbol') symbol + ): Promise { + if ( + !hasPermission( + getPermissions(this.request.user.role), + permissions.accessAdminControl + ) + ) { + throw new HttpException( + getReasonPhrase(StatusCodes.FORBIDDEN), + StatusCodes.FORBIDDEN + ); + } + + return this.adminService.getMarketDataBySymbol(symbol); + } } diff --git a/apps/api/src/app/admin/admin.module.ts b/apps/api/src/app/admin/admin.module.ts index 4257f3fc5..4184ff2fd 100644 --- a/apps/api/src/app/admin/admin.module.ts +++ b/apps/api/src/app/admin/admin.module.ts @@ -3,6 +3,7 @@ import { ConfigurationModule } from '@ghostfolio/api/services/configuration.modu import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.module'; import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data.module'; +import { MarketDataModule } from '@ghostfolio/api/services/market-data.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma.module'; import { Module } from '@nestjs/common'; @@ -15,6 +16,7 @@ import { AdminService } from './admin.service'; DataGatheringModule, DataProviderModule, ExchangeRateDataModule, + MarketDataModule, PrismaModule, SubscriptionModule ], diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index b5c2c2d91..d4c21a29e 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -2,9 +2,14 @@ import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscripti import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service'; +import { MarketDataService } from '@ghostfolio/api/services/market-data.service'; import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { baseCurrency } from '@ghostfolio/common/config'; -import { AdminData } from '@ghostfolio/common/interfaces'; +import { + AdminData, + AdminMarketData, + AdminMarketDataDetails +} from '@ghostfolio/common/interfaces'; import { Injectable } from '@nestjs/common'; import { differenceInDays } from 'date-fns'; @@ -14,6 +19,7 @@ export class AdminService { private readonly configurationService: ConfigurationService, private readonly dataGatheringService: DataGatheringService, private readonly exchangeRateDataService: ExchangeRateDataService, + private readonly marketDataService: MarketDataService, private readonly prismaService: PrismaService, private readonly subscriptionService: SubscriptionService ) {} @@ -45,6 +51,31 @@ export class AdminService { }; } + public async getMarketData(): Promise { + return { + marketData: await ( + await this.dataGatheringService.getSymbolsMax() + ).map((symbol) => { + return symbol; + }) + }; + } + + public async getMarketDataBySymbol( + aSymbol: string + ): Promise { + return { + marketData: await this.marketDataService.marketDataItems({ + orderBy: { + date: 'asc' + }, + where: { + symbol: aSymbol + } + }) + }; + } + private async getLastDataGathering() { const lastDataGathering = await this.dataGatheringService.getLastDataGathering(); diff --git a/apps/api/src/app/portfolio/current-rate.service.spec.ts b/apps/api/src/app/portfolio/current-rate.service.spec.ts index 1f3f89309..f474a10f0 100644 --- a/apps/api/src/app/portfolio/current-rate.service.spec.ts +++ b/apps/api/src/app/portfolio/current-rate.service.spec.ts @@ -1,11 +1,11 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service'; +import { MarketDataService } from '@ghostfolio/api/services/market-data.service'; import { DataSource, MarketData } from '@prisma/client'; import { CurrentRateService } from './current-rate.service'; -import { MarketDataService } from './market-data.service'; -jest.mock('./market-data.service', () => { +jest.mock('@ghostfolio/api/services/market-data.service', () => { return { MarketDataService: jest.fn().mockImplementation(() => { return { diff --git a/apps/api/src/app/portfolio/current-rate.service.ts b/apps/api/src/app/portfolio/current-rate.service.ts index c5dff75f9..3c7a6bde5 100644 --- a/apps/api/src/app/portfolio/current-rate.service.ts +++ b/apps/api/src/app/portfolio/current-rate.service.ts @@ -1,5 +1,6 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service'; +import { MarketDataService } from '@ghostfolio/api/services/market-data.service'; import { resetHours } from '@ghostfolio/common/helper'; import { Injectable } from '@nestjs/common'; import { isBefore, isToday } from 'date-fns'; @@ -8,7 +9,6 @@ import { flatten } from 'lodash'; import { GetValueObject } from './interfaces/get-value-object.interface'; import { GetValueParams } from './interfaces/get-value-params.interface'; import { GetValuesParams } from './interfaces/get-values-params.interface'; -import { MarketDataService } from './market-data.service'; @Injectable() export class CurrentRateService { diff --git a/apps/api/src/app/portfolio/portfolio.module.ts b/apps/api/src/app/portfolio/portfolio.module.ts index 34aaeb99b..515330516 100644 --- a/apps/api/src/app/portfolio/portfolio.module.ts +++ b/apps/api/src/app/portfolio/portfolio.module.ts @@ -7,12 +7,12 @@ import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.mod import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data.module'; import { ImpersonationModule } from '@ghostfolio/api/services/impersonation.module'; +import { MarketDataModule } from '@ghostfolio/api/services/market-data.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma.module'; import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile.module'; import { Module } from '@nestjs/common'; import { CurrentRateService } from './current-rate.service'; -import { MarketDataService } from './market-data.service'; import { PortfolioController } from './portfolio.controller'; import { PortfolioService } from './portfolio.service'; import { RulesService } from './rules.service'; @@ -26,6 +26,7 @@ import { RulesService } from './rules.service'; DataProviderModule, ExchangeRateDataModule, ImpersonationModule, + MarketDataModule, OrderModule, PrismaModule, SymbolProfileModule, @@ -35,7 +36,6 @@ import { RulesService } from './rules.service'; providers: [ AccountService, CurrentRateService, - MarketDataService, PortfolioService, RulesService ] diff --git a/apps/api/src/services/data-gathering.service.ts b/apps/api/src/services/data-gathering.service.ts index 9541fce4a..a6272d297 100644 --- a/apps/api/src/services/data-gathering.service.ts +++ b/apps/api/src/services/data-gathering.service.ts @@ -313,6 +313,52 @@ export class DataGatheringService { return undefined; } + public async getSymbolsMax(): Promise { + const startDate = + ( + await this.prismaService.order.findFirst({ + orderBy: [{ date: 'asc' }] + }) + )?.date ?? new Date(); + + const currencyPairsToGather = this.exchangeRateDataService + .getCurrencyPairs() + .map(({ dataSource, symbol }) => { + return { + dataSource, + symbol, + date: startDate + }; + }); + + const symbolProfilesToGather = ( + await this.prismaService.symbolProfile.findMany({ + orderBy: [{ symbol: 'asc' }], + select: { + dataSource: true, + Order: { + orderBy: [{ date: 'asc' }], + select: { date: true }, + take: 1 + }, + scraperConfiguration: true, + symbol: true + } + }) + ).map((symbolProfile) => { + return { + ...symbolProfile, + date: symbolProfile.Order?.[0]?.date ?? startDate + }; + }); + + return [ + ...this.getBenchmarksToGather(startDate), + ...currencyPairsToGather, + ...symbolProfilesToGather + ]; + } + public async reset() { Logger.log('Data gathering has been reset.'); @@ -379,52 +425,6 @@ export class DataGatheringService { ]; } - private async getSymbolsMax(): Promise { - const startDate = - ( - await this.prismaService.order.findFirst({ - orderBy: [{ date: 'asc' }] - }) - )?.date ?? new Date(); - - const currencyPairsToGather = this.exchangeRateDataService - .getCurrencyPairs() - .map(({ dataSource, symbol }) => { - return { - dataSource, - symbol, - date: startDate - }; - }); - - const symbolProfilesToGather = ( - await this.prismaService.symbolProfile.findMany({ - orderBy: [{ symbol: 'asc' }], - select: { - dataSource: true, - Order: { - orderBy: [{ date: 'asc' }], - select: { date: true }, - take: 1 - }, - scraperConfiguration: true, - symbol: true - } - }) - ).map((symbolProfile) => { - return { - ...symbolProfile, - date: symbolProfile.Order?.[0]?.date ?? startDate - }; - }); - - return [ - ...this.getBenchmarksToGather(startDate), - ...currencyPairsToGather, - ...symbolProfilesToGather - ]; - } - private async getSymbolsProfileData(): Promise { const startDate = subDays(resetHours(new Date()), 7); diff --git a/apps/api/src/services/market-data.module.ts b/apps/api/src/services/market-data.module.ts new file mode 100644 index 000000000..b1a09fa91 --- /dev/null +++ b/apps/api/src/services/market-data.module.ts @@ -0,0 +1,11 @@ +import { PrismaModule } from '@ghostfolio/api/services/prisma.module'; +import { Module } from '@nestjs/common'; + +import { MarketDataService } from './market-data.service'; + +@Module({ + exports: [MarketDataService], + imports: [PrismaModule], + providers: [MarketDataService] +}) +export class MarketDataModule {} diff --git a/apps/api/src/app/portfolio/market-data.service.ts b/apps/api/src/services/market-data.service.ts similarity index 62% rename from apps/api/src/app/portfolio/market-data.service.ts rename to apps/api/src/services/market-data.service.ts index 0061da64f..4ba6c966f 100644 --- a/apps/api/src/app/portfolio/market-data.service.ts +++ b/apps/api/src/services/market-data.service.ts @@ -1,9 +1,8 @@ +import { DateQuery } from '@ghostfolio/api/app/portfolio/interfaces/date-query.interface'; import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { resetHours } from '@ghostfolio/common/helper'; import { Injectable } from '@nestjs/common'; -import { MarketData } from '@prisma/client'; - -import { DateQuery } from './interfaces/date-query.interface'; +import { MarketData, Prisma } from '@prisma/client'; @Injectable() export class MarketDataService { @@ -48,4 +47,22 @@ export class MarketDataService { } }); } + + public async marketDataItems(params: { + skip?: number; + take?: number; + cursor?: Prisma.MarketDataWhereUniqueInput; + where?: Prisma.MarketDataWhereInput; + orderBy?: Prisma.MarketDataOrderByInput; + }): Promise { + const { skip, take, cursor, where, orderBy } = params; + + return this.prismaService.marketData.findMany({ + cursor, + orderBy, + skip, + take, + where + }); + } } diff --git a/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.html b/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.html new file mode 100644 index 000000000..433cb0df0 --- /dev/null +++ b/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.html @@ -0,0 +1,18 @@ +
+
+
{{ itemByMonth.key }}
+
+
+
+
+
diff --git a/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.scss b/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.scss new file mode 100644 index 000000000..12c57e206 --- /dev/null +++ b/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.scss @@ -0,0 +1,16 @@ +@import '~apps/client/src/styles/ghostfolio-style'; + +:host { + display: block; + + .day { + background-color: var(--danger); + height: 0.5rem; + margin-right: 0.25rem; + width: 0.5rem; + + &.available { + background-color: var(--success); + } + } +} diff --git a/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.ts b/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.ts new file mode 100644 index 000000000..ed1b0874c --- /dev/null +++ b/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.ts @@ -0,0 +1,48 @@ +import { + ChangeDetectionStrategy, + Component, + Input, + OnChanges, + OnInit +} from '@angular/core'; +import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config'; +import { MarketData } from '@prisma/client'; +import { format } from 'date-fns'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + selector: 'gf-admin-market-data-detail', + styleUrls: ['./admin-market-data-detail.component.scss'], + templateUrl: './admin-market-data-detail.component.html' +}) +export class AdminMarketDataDetailComponent implements OnChanges, OnInit { + @Input() marketData: MarketData[]; + + public days = Array(31); + public defaultDateFormat = DEFAULT_DATE_FORMAT; + public marketDataByMonth: { + [yearMonth: string]: { [day: string]: MarketData & { day: number } }; + } = {}; + + public constructor() {} + + public ngOnInit() {} + + public ngOnChanges() { + this.marketDataByMonth = {}; + + for (const marketDataItem of this.marketData) { + const currentDay = parseInt(format(marketDataItem.date, 'd'), 10); + const key = format(marketDataItem.date, 'yyyy-MM'); + + if (!this.marketDataByMonth[key]) { + this.marketDataByMonth[key] = {}; + } + + this.marketDataByMonth[key][currentDay] = { + ...marketDataItem, + day: currentDay + }; + } + } +} diff --git a/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.module.ts b/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.module.ts new file mode 100644 index 000000000..b5b3310f1 --- /dev/null +++ b/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.module.ts @@ -0,0 +1,13 @@ +import { CommonModule } from '@angular/common'; +import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; + +import { AdminMarketDataDetailComponent } from './admin-market-data-detail.component'; + +@NgModule({ + declarations: [AdminMarketDataDetailComponent], + exports: [AdminMarketDataDetailComponent], + imports: [CommonModule], + providers: [], + schemas: [CUSTOM_ELEMENTS_SCHEMA] +}) +export class GfAdminMarketDataDetailModule {} diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts new file mode 100644 index 000000000..2492d88e4 --- /dev/null +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts @@ -0,0 +1,82 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + OnDestroy, + OnInit +} from '@angular/core'; +import { DataService } from '@ghostfolio/client/services/data.service'; +import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config'; +import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface'; +import { MarketData } from '@prisma/client'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + selector: 'gf-admin-market-data', + styleUrls: ['./admin-market-data.scss'], + templateUrl: './admin-market-data.html' +}) +export class AdminMarketDataComponent implements OnDestroy, OnInit { + public currentSymbol: string; + public defaultDateFormat = DEFAULT_DATE_FORMAT; + public marketData: AdminMarketDataItem[] = []; + public marketDataDetails: MarketData[] = []; + + private unsubscribeSubject = new Subject(); + + /** + * @constructor + */ + public constructor( + private changeDetectorRef: ChangeDetectorRef, + private dataService: DataService + ) {} + + /** + * Initializes the controller + */ + public ngOnInit() { + this.fetchAdminMarketData(); + } + + public setCurrentSymbol(aSymbol: string) { + this.marketDataDetails = []; + + if (this.currentSymbol === aSymbol) { + this.currentSymbol = ''; + } else { + this.currentSymbol = aSymbol; + + this.fetchAdminMarketDataBySymbol(this.currentSymbol); + } + } + + public ngOnDestroy() { + this.unsubscribeSubject.next(); + this.unsubscribeSubject.complete(); + } + + private fetchAdminMarketData() { + this.dataService + .fetchAdminMarketData() + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(({ marketData }) => { + this.marketData = marketData; + + this.changeDetectorRef.markForCheck(); + }); + } + + private fetchAdminMarketDataBySymbol(aSymbol: string) { + this.dataService + .fetchAdminMarketDataBySymbol(aSymbol) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(({ marketData }) => { + this.marketDataDetails = marketData; + + this.changeDetectorRef.markForCheck(); + }); + } +} diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.html b/apps/client/src/app/components/admin-market-data/admin-market-data.html new file mode 100644 index 000000000..2ad50f2a8 --- /dev/null +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -0,0 +1,37 @@ +
+
+
+ + + + + + + + + + + + + + + + + + + + + +
#SymbolFirst transaction
{{ i + 1 }}{{ item.symbol }} + {{ (item.date | date: defaultDateFormat) ?? '' }} +
+ +
+
+
+
diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.module.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.module.ts new file mode 100644 index 000000000..b3996d701 --- /dev/null +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.module.ts @@ -0,0 +1,12 @@ +import { CommonModule } from '@angular/common'; +import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; +import { GfAdminMarketDataDetailModule } from '@ghostfolio/client/components/admin-market-data-detail/admin-market-data-detail.module'; + +import { AdminMarketDataComponent } from './admin-market-data.component'; + +@NgModule({ + declarations: [AdminMarketDataComponent], + imports: [CommonModule, GfAdminMarketDataDetailModule], + schemas: [CUSTOM_ELEMENTS_SCHEMA] +}) +export class GfAdminMarketDataModule {} diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.scss b/apps/client/src/app/components/admin-market-data/admin-market-data.scss new file mode 100644 index 000000000..b97d286cc --- /dev/null +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.scss @@ -0,0 +1,5 @@ +@import '~apps/client/src/styles/ghostfolio-style'; + +:host { + display: block; +} diff --git a/apps/client/src/app/pages/admin/admin-page-routing.module.ts b/apps/client/src/app/pages/admin/admin-page-routing.module.ts index a483fefe7..64a421458 100644 --- a/apps/client/src/app/pages/admin/admin-page-routing.module.ts +++ b/apps/client/src/app/pages/admin/admin-page-routing.module.ts @@ -1,5 +1,6 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { AdminMarketDataComponent } from '@ghostfolio/client/components/admin-market-data/admin-market-data.component'; import { AdminOverviewComponent } from '@ghostfolio/client/components/admin-overview/admin-overview.component'; import { AdminUsersComponent } from '@ghostfolio/client/components/admin-users/admin-users.component'; import { AuthGuard } from '@ghostfolio/client/core/auth.guard'; @@ -13,6 +14,7 @@ const routes: Routes = [ canActivate: [AuthGuard], children: [ { path: '', redirectTo: 'overview', pathMatch: 'full' }, + { path: 'market-data', component: AdminMarketDataComponent }, { path: 'overview', component: AdminOverviewComponent }, { path: 'users', component: AdminUsersComponent } ] diff --git a/apps/client/src/app/pages/admin/admin-page.html b/apps/client/src/app/pages/admin/admin-page.html index 544d0cc1a..fe63f5d46 100644 --- a/apps/client/src/app/pages/admin/admin-page.html +++ b/apps/client/src/app/pages/admin/admin-page.html @@ -4,7 +4,8 @@ ('/api/admin'); } + public fetchAdminMarketData() { + return this.http.get('/api/admin/market-data'); + } + + public fetchAdminMarketDataBySymbol( + aSymbol: string + ): Observable { + return this.http.get(`/api/admin/market-data/${aSymbol}`).pipe( + map((data) => { + for (const item of data.marketData) { + item.date = parseISO(item.date); + } + return data; + }) + ); + } + public deleteAccess(aId: string) { return this.http.delete(`/api/access/${aId}`); } diff --git a/apps/client/src/styles.scss b/apps/client/src/styles.scss index 1f976a8d7..6093c451f 100644 --- a/apps/client/src/styles.scss +++ b/apps/client/src/styles.scss @@ -134,6 +134,10 @@ ngx-skeleton-loader { } } +.cursor-pointer { + cursor: pointer; +} + .gf-table { @include gf-table; } diff --git a/libs/common/src/lib/interfaces/admin-market-data-details.interface.ts b/libs/common/src/lib/interfaces/admin-market-data-details.interface.ts new file mode 100644 index 000000000..87e92f3ae --- /dev/null +++ b/libs/common/src/lib/interfaces/admin-market-data-details.interface.ts @@ -0,0 +1,5 @@ +import { MarketData } from '@prisma/client'; + +export interface AdminMarketDataDetails { + marketData: MarketData[]; +} diff --git a/libs/common/src/lib/interfaces/admin-market-data.interface.ts b/libs/common/src/lib/interfaces/admin-market-data.interface.ts new file mode 100644 index 000000000..c46c3e37c --- /dev/null +++ b/libs/common/src/lib/interfaces/admin-market-data.interface.ts @@ -0,0 +1,7 @@ +export interface AdminMarketData { + marketData: AdminMarketDataItem[]; +} + +export interface AdminMarketDataItem { + symbol: string; +} diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index af065936b..3192ece9f 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -1,6 +1,8 @@ import { Access } from './access.interface'; import { Accounts } from './accounts.interface'; import { AdminData } from './admin-data.interface'; +import { AdminMarketDataDetails } from './admin-market-data-details.interface'; +import { AdminMarketData } from './admin-market-data.interface'; import { Export } from './export.interface'; import { InfoItem } from './info-item.interface'; import { PortfolioChart } from './portfolio-chart.interface'; @@ -23,6 +25,8 @@ export { Access, Accounts, AdminData, + AdminMarketData, + AdminMarketDataDetails, Export, InfoItem, PortfolioChart,