Feature/add logo endpoint (#1506)

* Add logo endpoint

* Update changelog
This commit is contained in:
Thomas Kaul 2022-12-12 20:13:45 +01:00 committed by GitHub
parent 49dcade964
commit e87b93f19c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 148 additions and 8 deletions

View File

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Added the date of the first activity to the positions table - Added the date of the first activity to the positions table
- Added an endpoint to fetch the logo of an asset or a platform
### Changed ### Changed

View File

@ -27,6 +27,7 @@ import { ExportModule } from './export/export.module';
import { FrontendMiddleware } from './frontend.middleware'; import { FrontendMiddleware } from './frontend.middleware';
import { ImportModule } from './import/import.module'; import { ImportModule } from './import/import.module';
import { InfoModule } from './info/info.module'; import { InfoModule } from './info/info.module';
import { LogoModule } from './logo/logo.module';
import { OrderModule } from './order/order.module'; import { OrderModule } from './order/order.module';
import { PortfolioModule } from './portfolio/portfolio.module'; import { PortfolioModule } from './portfolio/portfolio.module';
import { SubscriptionModule } from './subscription/subscription.module'; import { SubscriptionModule } from './subscription/subscription.module';
@ -58,6 +59,7 @@ import { UserModule } from './user/user.module';
ExportModule, ExportModule,
ImportModule, ImportModule,
InfoModule, InfoModule,
LogoModule,
OrderModule, OrderModule,
PortfolioModule, PortfolioModule,
PrismaModule, PrismaModule,

View File

@ -30,8 +30,8 @@ export class BenchmarkController {
} }
@Get(':dataSource/:symbol/:startDateString') @Get(':dataSource/:symbol/:startDateString')
@UseInterceptors(TransformDataSourceInRequestInterceptor)
@UseGuards(AuthGuard('jwt')) @UseGuards(AuthGuard('jwt'))
@UseInterceptors(TransformDataSourceInRequestInterceptor)
public async getBenchmarkMarketDataBySymbol( public async getBenchmarkMarketDataBySymbol(
@Param('dataSource') dataSource: DataSource, @Param('dataSource') dataSource: DataSource,
@Param('startDateString') startDateString: string, @Param('startDateString') startDateString: string,

View File

@ -0,0 +1,54 @@
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor';
import {
Controller,
Get,
HttpStatus,
Param,
Query,
Res,
UseInterceptors
} from '@nestjs/common';
import { DataSource } from '@prisma/client';
import { Response } from 'express';
import { LogoService } from './logo.service';
@Controller('logo')
export class LogoController {
public constructor(private readonly logoService: LogoService) {}
@Get(':dataSource/:symbol')
@UseInterceptors(TransformDataSourceInRequestInterceptor)
public async getLogoByDataSourceAndSymbol(
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string,
@Res() response: Response
) {
try {
const buffer = await this.logoService.getLogoByDataSourceAndSymbol({
dataSource,
symbol
});
response.contentType('image/png');
response.send(buffer);
} catch {
response.status(HttpStatus.NOT_FOUND).send();
}
}
@Get()
public async getLogoByUrl(
@Query('url') url: string,
@Res() response: Response
) {
try {
const buffer = await this.logoService.getLogoByUrl(url);
response.contentType('image/png');
response.send(buffer);
} catch {
response.status(HttpStatus.NOT_FOUND).send();
}
}
}

View File

@ -0,0 +1,13 @@
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile.module';
import { Module } from '@nestjs/common';
import { LogoController } from './logo.controller';
import { LogoService } from './logo.service';
@Module({
controllers: [LogoController],
imports: [ConfigurationModule, SymbolProfileModule],
providers: [LogoService]
})
export class LogoModule {}

View File

@ -0,0 +1,55 @@
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile.service';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { HttpException, Injectable } from '@nestjs/common';
import { DataSource } from '@prisma/client';
import * as bent from 'bent';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
@Injectable()
export class LogoService {
public constructor(
private readonly symbolProfileService: SymbolProfileService
) {}
public async getLogoByDataSourceAndSymbol({
dataSource,
symbol
}: UniqueAsset) {
if (!DataSource[dataSource]) {
throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND),
StatusCodes.NOT_FOUND
);
}
const [assetProfile] = await this.symbolProfileService.getSymbolProfiles([
{ dataSource, symbol }
]);
if (!assetProfile) {
throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND),
StatusCodes.NOT_FOUND
);
}
return this.getBuffer(assetProfile.url);
}
public async getLogoByUrl(aUrl: string) {
return this.getBuffer(aUrl);
}
private getBuffer(aUrl: string) {
const get = bent(
`https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${aUrl}&size=64`,
'GET',
'buffer',
200,
{
'User-Agent': 'request'
}
);
return get();
}
}

View File

@ -373,6 +373,7 @@ export class PortfolioController {
} }
@Get('public/:accessId') @Get('public/:accessId')
@UseInterceptors(TransformDataSourceInResponseInterceptor)
public async getPublic( public async getPublic(
@Param('accessId') accessId @Param('accessId') accessId
): Promise<PortfolioPublicDetails> { ): Promise<PortfolioPublicDetails> {
@ -422,6 +423,7 @@ export class PortfolioController {
allocationCurrent: portfolioPosition.value / totalValue, allocationCurrent: portfolioPosition.value / totalValue,
countries: hasDetails ? portfolioPosition.countries : [], countries: hasDetails ? portfolioPosition.countries : [],
currency: hasDetails ? portfolioPosition.currency : undefined, currency: hasDetails ? portfolioPosition.currency : undefined,
dataSource: portfolioPosition.dataSource,
dateOfFirstActivity: portfolioPosition.dateOfFirstActivity, dateOfFirstActivity: portfolioPosition.dateOfFirstActivity,
markets: hasDetails ? portfolioPosition.markets : undefined, markets: hasDetails ? portfolioPosition.markets : undefined,
name: portfolioPosition.name, name: portfolioPosition.name,

View File

@ -10,9 +10,9 @@
<th *matHeaderCellDef class="px-1" mat-header-cell></th> <th *matHeaderCellDef class="px-1" mat-header-cell></th>
<td *matCellDef="let element" class="px-1 text-center" mat-cell> <td *matCellDef="let element" class="px-1 text-center" mat-cell>
<gf-symbol-icon <gf-symbol-icon
*ngIf="element.url" [dataSource]="element.dataSource"
[symbol]="element.symbol"
[tooltip]="element.name" [tooltip]="element.name"
[url]="element.url"
></gf-symbol-icon> ></gf-symbol-icon>
</td> </td>
</ng-container> </ng-container>

View File

@ -1,6 +1,7 @@
<img <img
*ngIf="url" *ngIf="src"
src="https://www.google.com/s2/favicons?domain={{ url }}&sz=64" onerror="this.style.display='none'"
[ngClass]="{ large: size === 'large' }" [ngClass]="{ large: size === 'large' }"
[src]="src"
[title]="tooltip ? tooltip : ''" [title]="tooltip ? tooltip : ''"
/> />

View File

@ -2,8 +2,9 @@ import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
Input, Input,
OnInit OnChanges
} from '@angular/core'; } from '@angular/core';
import { DataSource } from '@prisma/client';
@Component({ @Component({
selector: 'gf-symbol-icon', selector: 'gf-symbol-icon',
@ -11,12 +12,22 @@ import {
templateUrl: './symbol-icon.component.html', templateUrl: './symbol-icon.component.html',
styleUrls: ['./symbol-icon.component.scss'] styleUrls: ['./symbol-icon.component.scss']
}) })
export class SymbolIconComponent implements OnInit { export class SymbolIconComponent implements OnChanges {
@Input() dataSource: DataSource;
@Input() size: 'large'; @Input() size: 'large';
@Input() symbol: string;
@Input() tooltip: string; @Input() tooltip: string;
@Input() url: string; @Input() url: string;
public src: string;
public constructor() {} public constructor() {}
public ngOnInit() {} public ngOnChanges() {
if (this.dataSource && this.symbol) {
this.src = `../api/v1/logo/${this.dataSource}/${this.symbol}`;
} else if (this.url) {
this.src = `../api/v1/logo?url=${this.url}`;
}
}
} }

View File

@ -9,6 +9,7 @@ export interface PortfolioPublicDetails {
| 'allocationCurrent' | 'allocationCurrent'
| 'countries' | 'countries'
| 'currency' | 'currency'
| 'dataSource'
| 'dateOfFirstActivity' | 'dateOfFirstActivity'
| 'markets' | 'markets'
| 'name' | 'name'