Browse Source

Check daily requests

pull/4016/head
Thomas Kaul 10 months ago
parent
commit
aa00895d3c
  1. 33
      apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts
  2. 4
      apps/api/src/app/user/user.service.ts
  3. 11
      apps/client/src/app/pages/api/api-page.component.ts
  4. 4
      apps/client/src/app/pages/api/api-page.html
  5. 2
      libs/common/src/lib/config.ts
  6. 2
      libs/common/src/lib/interfaces/index.ts
  7. 4
      libs/common/src/lib/interfaces/responses/data-provider-ghostfolio-status-response.interface.ts
  8. 1
      libs/common/src/lib/types/user-with-settings.type.ts

33
apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts

@ -2,6 +2,9 @@ import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.in
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import { PROPERTY_DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER_MAX_REQUESTS } from '@ghostfolio/common/config';
import { DataProviderGhostfolioStatusResponse } from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions'; import { permissions } from '@ghostfolio/common/permissions';
import { RequestWithUser } from '@ghostfolio/common/types'; import { RequestWithUser } from '@ghostfolio/common/types';
@ -24,6 +27,7 @@ export class GhostfolioController {
public constructor( public constructor(
private readonly ghostfolioService: GhostfolioService, private readonly ghostfolioService: GhostfolioService,
private readonly prismaService: PrismaService, private readonly prismaService: PrismaService,
private readonly propertyService: PropertyService,
@Inject(REQUEST) private readonly request: RequestWithUser @Inject(REQUEST) private readonly request: RequestWithUser
) {} ) {}
@ -35,6 +39,16 @@ export class GhostfolioController {
@Query('query') query = '' @Query('query') query = ''
): Promise<{ items: LookupItem[] }> { ): Promise<{ items: LookupItem[] }> {
const includeIndices = includeIndicesParam === 'true'; const includeIndices = includeIndicesParam === 'true';
const maxDailyRequests = await this.getMaxDailyRequests();
if (
this.request.user.dataProviderGhostfolioDailyRequests > maxDailyRequests
) {
throw new HttpException(
getReasonPhrase(StatusCodes.TOO_MANY_REQUESTS),
StatusCodes.TOO_MANY_REQUESTS
);
}
try { try {
const result = await this.ghostfolioService.lookup({ const result = await this.ghostfolioService.lookup({
@ -55,6 +69,25 @@ export class GhostfolioController {
} }
} }
@Get('status')
@HasPermission(permissions.enableDataProviderGhostfolio)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getStatus(): Promise<DataProviderGhostfolioStatusResponse> {
return {
dailyRequests: this.request.user.dataProviderGhostfolioDailyRequests,
dailyRequestsMax: await this.getMaxDailyRequests()
};
}
private async getMaxDailyRequests() {
return parseInt(
((await this.propertyService.getByKey(
PROPERTY_DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER_MAX_REQUESTS
)) as string) || '0',
10
);
}
private async incrementDailyRequests({ userId }: { userId: string }) { private async incrementDailyRequests({ userId }: { userId: string }) {
await this.prismaService.analytics.update({ await this.prismaService.analytics.update({
data: { data: {

4
apps/api/src/app/user/user.service.ts

@ -183,7 +183,9 @@ export class UserService {
Settings: Settings as UserWithSettings['Settings'], Settings: Settings as UserWithSettings['Settings'],
thirdPartyId, thirdPartyId,
updatedAt, updatedAt,
activityCount: Analytics?.activityCount activityCount: Analytics?.activityCount,
dataProviderGhostfolioDailyRequests:
Analytics?.dataProviderGhostfolioDailyRequests
}; };
if (user?.Settings) { if (user?.Settings) {

11
apps/client/src/app/pages/api/api-page.component.ts

@ -1,4 +1,5 @@
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
import { DataProviderGhostfolioStatusResponse } from '@ghostfolio/common/interfaces';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { HttpClient, HttpParams } from '@angular/common/http'; import { HttpClient, HttpParams } from '@angular/common/http';
@ -14,6 +15,7 @@ import { map, Observable, Subject, takeUntil } from 'rxjs';
templateUrl: './api-page.html' templateUrl: './api-page.html'
}) })
export class GfApiPageComponent implements OnInit { export class GfApiPageComponent implements OnInit {
public status$: Observable<DataProviderGhostfolioStatusResponse>;
public symbols$: Observable<LookupItem[]>; public symbols$: Observable<LookupItem[]>;
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
@ -21,6 +23,7 @@ export class GfApiPageComponent implements OnInit {
public constructor(private http: HttpClient) {} public constructor(private http: HttpClient) {}
public ngOnInit() { public ngOnInit() {
this.status$ = this.fetchStatus();
this.symbols$ = this.fetchSymbols({ query: 'apple' }); this.symbols$ = this.fetchSymbols({ query: 'apple' });
} }
@ -29,6 +32,14 @@ export class GfApiPageComponent implements OnInit {
this.unsubscribeSubject.complete(); this.unsubscribeSubject.complete();
} }
private fetchStatus() {
return this.http
.get<DataProviderGhostfolioStatusResponse>(
'/api/v1/data-providers/ghostfolio/status'
)
.pipe(takeUntil(this.unsubscribeSubject));
}
private fetchSymbols({ private fetchSymbols({
includeIndices = false, includeIndices = false,
query query

4
apps/client/src/app/pages/api/api-page.html

@ -1,4 +1,8 @@
<div class="container"> <div class="container">
<div class="mb-3">
<h2 class="text-center">Status</h2>
<div>{{ status$ | async | json }}</div>
</div>
<div> <div>
<h2 class="text-center">Lookup</h2> <h2 class="text-center">Lookup</h2>
<ul *ngIf="symbols$ | async as symbols"> <ul *ngIf="symbols$ | async as symbols">

2
libs/common/src/lib/config.ts

@ -117,6 +117,8 @@ export const PROPERTY_COUNTRIES_OF_SUBSCRIBERS = 'COUNTRIES_OF_SUBSCRIBERS';
export const PROPERTY_COUPONS = 'COUPONS'; export const PROPERTY_COUPONS = 'COUPONS';
export const PROPERTY_CURRENCIES = 'CURRENCIES'; export const PROPERTY_CURRENCIES = 'CURRENCIES';
export const PROPERTY_DATA_SOURCE_MAPPING = 'DATA_SOURCE_MAPPING'; export const PROPERTY_DATA_SOURCE_MAPPING = 'DATA_SOURCE_MAPPING';
export const PROPERTY_DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER_MAX_REQUESTS =
'DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER_MAX_REQUESTS';
export const PROPERTY_DEMO_USER_ID = 'DEMO_USER_ID'; export const PROPERTY_DEMO_USER_ID = 'DEMO_USER_ID';
export const PROPERTY_IS_DATA_GATHERING_ENABLED = 'IS_DATA_GATHERING_ENABLED'; 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_READ_ONLY_MODE = 'IS_READ_ONLY_MODE';

2
libs/common/src/lib/interfaces/index.ts

@ -40,6 +40,7 @@ import type { Position } from './position.interface';
import type { Product } from './product'; import type { Product } from './product';
import type { AccountBalancesResponse } from './responses/account-balances-response.interface'; import type { AccountBalancesResponse } from './responses/account-balances-response.interface';
import type { BenchmarkResponse } from './responses/benchmark-response.interface'; import type { BenchmarkResponse } from './responses/benchmark-response.interface';
import type { DataProviderGhostfolioStatusResponse } from './responses/data-provider-ghostfolio-status-response.interface';
import type { ResponseError } from './responses/errors.interface'; import type { ResponseError } from './responses/errors.interface';
import type { ImportResponse } from './responses/import-response.interface'; import type { ImportResponse } from './responses/import-response.interface';
import type { LookupResponse } from './responses/lookup-response.interface'; import type { LookupResponse } from './responses/lookup-response.interface';
@ -74,6 +75,7 @@ export {
BenchmarkProperty, BenchmarkProperty,
BenchmarkResponse, BenchmarkResponse,
Coupon, Coupon,
DataProviderGhostfolioStatusResponse,
DataProviderInfo, DataProviderInfo,
EnhancedSymbolProfile, EnhancedSymbolProfile,
Export, Export,

4
libs/common/src/lib/interfaces/responses/data-provider-ghostfolio-status-response.interface.ts

@ -0,0 +1,4 @@
export interface DataProviderGhostfolioStatusResponse {
dailyRequests: number;
dailyRequestsMax: number;
}

1
libs/common/src/lib/types/user-with-settings.type.ts

@ -9,6 +9,7 @@ export type UserWithSettings = User & {
Access: Access[]; Access: Access[];
Account: Account[]; Account: Account[];
activityCount: number; activityCount: number;
dataProviderGhostfolioDailyRequests: number;
permissions?: string[]; permissions?: string[];
Settings: Settings & { settings: UserSettings }; Settings: Settings & { settings: UserSettings };
subscription?: { subscription?: {

Loading…
Cancel
Save