diff --git a/.github/workflows/build-code.yml b/.github/workflows/build-code.yml index 262729f29..c1e950ba7 100644 --- a/.github/workflows/build-code.yml +++ b/.github/workflows/build-code.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: node_version: - - 18 + - 16 steps: - name: Checkout code uses: actions/checkout@v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 52f95a3a5..b0d906f4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Simplified the database seeding - Upgraded `ngx-skeleton-loader` from version `5.0.0` to `7.0.0` +### Fixed + +- Downgraded `Node.js` from version `18` to `16` (Dockerfile) to resolve `SIGSEGV` (segmentation fault) during the `prisma` database migrations (see https://github.com/prisma/prisma/issues/10649) + ## 1.241.0 - 2023-03-01 ### Changed diff --git a/Dockerfile b/Dockerfile index cdebe275d..99385d7e0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM node:18-slim as builder +FROM --platform=$BUILDPLATFORM node:16-slim as builder # Build application and add additional files WORKDIR /ghostfolio @@ -50,7 +50,7 @@ COPY package.json /ghostfolio/dist/apps/api RUN yarn database:generate-typings # Image to run, copy everything needed from builder -FROM node:18-slim +FROM node:16-slim RUN apt update && apt install -y \ openssl \ && rm -rf /var/lib/apt/lists/* diff --git a/README.md b/README.md index aebc7129f..37058d6c2 100644 --- a/README.md +++ b/README.md @@ -126,13 +126,10 @@ docker-compose --env-file ./.env -f docker/docker-compose.build.yml build docker-compose --env-file ./.env -f docker/docker-compose.build.yml up -d ``` -#### Fetch Historical Data - -Open http://localhost:3333 in your browser and accomplish these steps: +#### Setup +1. Open http://localhost:3333 in your browser 1. Create a new user via _Get Started_ (this first user will get the role `ADMIN`) -1. Go to the _Market Data_ tab in the _Admin Control Panel_ and click _Gather All Data_ to fetch historical data -1. Click _Sign out_ and check out the _Live Demo_ #### Upgrade Version @@ -159,11 +156,10 @@ Please follow the instructions of the Ghostfolio [Unraid Community App](https:// 1. Run `yarn install` 1. Run `yarn build:dev` to build the source code including the assets 1. Run `docker-compose --env-file ./.env -f docker/docker-compose.dev.yml up -d` to start [PostgreSQL](https://www.postgresql.org) and [Redis](https://redis.io) -1. Run `yarn database:setup` to initialize the database schema and populate your database with (example) data +1. Run `yarn database:setup` to initialize the database schema 1. Start the server and the client (see [_Development_](#Development)) +1. Open http://localhost:4200/en in your browser 1. Create a new user via _Get Started_ (this first user will get the role `ADMIN`) -1. Go to the _Market Data_ tab in the _Admin Control Panel_ and click _Gather All Data_ to fetch historical data -1. Click _Sign out_ and check out the _Live Demo_ ### Start Server diff --git a/apps/api/src/app/account/account.controller.ts b/apps/api/src/app/account/account.controller.ts index d91e8c5c7..4f458bcfa 100644 --- a/apps/api/src/app/account/account.controller.ts +++ b/apps/api/src/app/account/account.controller.ts @@ -1,6 +1,7 @@ import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response.interceptor'; import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service'; +import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config'; import { Accounts } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import type { @@ -83,7 +84,7 @@ export class AccountController { @UseGuards(AuthGuard('jwt')) @UseInterceptors(RedactValuesInResponseInterceptor) public async getAllAccounts( - @Headers('impersonation-id') impersonationId + @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId ): Promise { const impersonationUserId = await this.impersonationService.validateImpersonationId( @@ -101,7 +102,7 @@ export class AccountController { @UseGuards(AuthGuard('jwt')) @UseInterceptors(RedactValuesInResponseInterceptor) public async getAccountById( - @Headers('impersonation-id') impersonationId, + @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId, @Param('id') id: string ): Promise { const impersonationUserId = diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index ad1620a23..f2fe18fc9 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -231,12 +231,27 @@ export class AdminService { } private async getUsersWithAnalytics(): Promise { - const usersWithAnalytics = await this.prismaService.user.findMany({ - orderBy: { + let orderBy: any = { + createdAt: 'desc' + }; + let where; + + if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { + orderBy = { Analytics: { updatedAt: 'desc' } - }, + }; + where = { + NOT: { + Analytics: null + } + }; + } + + const usersWithAnalytics = await this.prismaService.user.findMany({ + orderBy, + where, select: { _count: { select: { Account: true, Order: true } @@ -252,19 +267,16 @@ export class AdminService { id: true, Subscription: true }, - take: 30, - where: { - NOT: { - Analytics: null - } - } + take: 30 }); return usersWithAnalytics.map( ({ _count, Analytics, createdAt, id, Subscription }) => { const daysSinceRegistration = differenceInDays(new Date(), createdAt) + 1; - const engagement = Analytics.activityCount / daysSinceRegistration; + const engagement = Analytics + ? Analytics.activityCount / daysSinceRegistration + : undefined; const subscription = this.configurationService.get( 'ENABLE_FEATURE_SUBSCRIPTION' @@ -278,8 +290,8 @@ export class AdminService { id, subscription, accountCount: _count.Account || 0, - country: Analytics.country, - lastActivity: Analytics.updatedAt, + country: Analytics?.country, + lastActivity: Analytics?.updatedAt, transactionCount: _count.Order || 0 }; } diff --git a/apps/api/src/app/auth/jwt.strategy.ts b/apps/api/src/app/auth/jwt.strategy.ts index ee50e3b72..af21ecc0e 100644 --- a/apps/api/src/app/auth/jwt.strategy.ts +++ b/apps/api/src/app/auth/jwt.strategy.ts @@ -1,33 +1,46 @@ import { UserService } from '@ghostfolio/api/app/user/user.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; import { PrismaService } from '@ghostfolio/api/services/prisma.service'; +import { HEADER_KEY_TIMEZONE } from '@ghostfolio/common/config'; import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; +import * as countriesAndTimezones from 'countries-and-timezones'; import { ExtractJwt, Strategy } from 'passport-jwt'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { public constructor( - readonly configurationService: ConfigurationService, + private readonly configurationService: ConfigurationService, private readonly prismaService: PrismaService, private readonly userService: UserService ) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + passReqToCallback: true, secretOrKey: configurationService.get('JWT_SECRET_KEY') }); } - public async validate({ id }: { id: string }) { + public async validate(request: Request, { id }: { id: string }) { try { + const timezone = request.headers[HEADER_KEY_TIMEZONE.toLowerCase()]; const user = await this.userService.user({ id }); if (user) { - await this.prismaService.analytics.upsert({ - create: { User: { connect: { id: user.id } } }, - update: { activityCount: { increment: 1 }, updatedAt: new Date() }, - where: { userId: user.id } - }); + if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { + const country = + countriesAndTimezones.getCountryForTimezone(timezone)?.id; + + await this.prismaService.analytics.upsert({ + create: { country, User: { connect: { id: user.id } } }, + update: { + country, + activityCount: { increment: 1 }, + updatedAt: new Date() + }, + where: { userId: user.id } + }); + } return user; } else { diff --git a/apps/api/src/app/info/info.service.ts b/apps/api/src/app/info/info.service.ts index a13e3395f..695920660 100644 --- a/apps/api/src/app/info/info.service.ts +++ b/apps/api/src/app/info/info.service.ts @@ -6,12 +6,12 @@ import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; import { TagService } from '@ghostfolio/api/services/tag/tag.service'; import { - DEMO_USER_ID, PROPERTY_COUNTRIES_OF_SUBSCRIBERS, PROPERTY_IS_READ_ONLY_MODE, PROPERTY_SLACK_COMMUNITY_USERS, PROPERTY_STRIPE_CONFIG, PROPERTY_SYSTEM_MESSAGE, + PROPERTY_DEMO_USER_ID, ghostfolioFearAndGreedIndexDataSource } from '@ghostfolio/common/config'; import { @@ -59,9 +59,7 @@ export class InfoService { } if (this.configurationService.get('ENABLE_FEATURE_FEAR_AND_GREED_INDEX')) { - if ( - this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') === true - ) { + if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { info.fearAndGreedDataSource = encodeDataSource( ghostfolioFearAndGreedIndexDataSource ); @@ -120,7 +118,7 @@ export class InfoService { baseCurrency: this.configurationService.get('BASE_CURRENCY'), benchmarks: await this.benchmarkService.getBenchmarkAssetProfiles(), currencies: this.exchangeRateDataService.getCurrencies(), - demoAuthToken: this.getDemoAuthToken(), + demoAuthToken: await this.getDemoAuthToken(), statistics: await this.getStatistics(), subscriptions: await this.getSubscriptions(), tags: await this.tagService.get() @@ -248,10 +246,18 @@ export class InfoService { )) as string; } - private getDemoAuthToken() { - return this.jwtService.sign({ - id: DEMO_USER_ID - }); + private async getDemoAuthToken() { + const demoUserId = (await this.propertyService.getByKey( + PROPERTY_DEMO_USER_ID + )) as string; + + if (demoUserId) { + return this.jwtService.sign({ + id: demoUserId + }); + } + + return undefined; } private async getStatistics() { diff --git a/apps/api/src/app/order/order.controller.ts b/apps/api/src/app/order/order.controller.ts index 5a46f0866..5e5da4fde 100644 --- a/apps/api/src/app/order/order.controller.ts +++ b/apps/api/src/app/order/order.controller.ts @@ -3,6 +3,7 @@ import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interce import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor'; import { ApiService } from '@ghostfolio/api/services/api/api.service'; import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service'; +import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import type { RequestWithUser } from '@ghostfolio/common/types'; import { @@ -66,7 +67,7 @@ export class OrderController { @UseInterceptors(RedactValuesInResponseInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor) public async getAllOrders( - @Headers('impersonation-id') impersonationId, + @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId, @Query('accounts') filterByAccounts?: string, @Query('assetClasses') filterByAssetClasses?: string, @Query('tags') filterByTags?: string diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index 795362195..dd6eb0ca6 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -10,6 +10,7 @@ import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interc import { ApiService } from '@ghostfolio/api/services/api/api.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service'; +import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config'; import { PortfolioDetails, PortfolioDividends, @@ -65,7 +66,7 @@ export class PortfolioController { @UseInterceptors(RedactValuesInResponseInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor) public async getDetails( - @Headers('impersonation-id') impersonationId: string, + @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Query('accounts') filterByAccounts?: string, @Query('assetClasses') filterByAssetClasses?: string, @Query('range') dateRange: DateRange = 'max', @@ -189,7 +190,7 @@ export class PortfolioController { @Get('dividends') @UseGuards(AuthGuard('jwt')) public async getDividends( - @Headers('impersonation-id') impersonationId: string, + @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Query('accounts') filterByAccounts?: string, @Query('assetClasses') filterByAssetClasses?: string, @Query('groupBy') groupBy?: GroupBy, @@ -239,7 +240,7 @@ export class PortfolioController { @Get('investments') @UseGuards(AuthGuard('jwt')) public async getInvestments( - @Headers('impersonation-id') impersonationId: string, + @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Query('accounts') filterByAccounts?: string, @Query('assetClasses') filterByAssetClasses?: string, @Query('groupBy') groupBy?: GroupBy, @@ -291,7 +292,7 @@ export class PortfolioController { @UseInterceptors(TransformDataSourceInResponseInterceptor) @Version('2') public async getPerformanceV2( - @Headers('impersonation-id') impersonationId: string, + @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Query('accounts') filterByAccounts?: string, @Query('assetClasses') filterByAssetClasses?: string, @Query('range') dateRange: DateRange = 'max', @@ -360,7 +361,7 @@ export class PortfolioController { @UseInterceptors(RedactValuesInResponseInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor) public async getPositions( - @Headers('impersonation-id') impersonationId: string, + @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Query('accounts') filterByAccounts?: string, @Query('assetClasses') filterByAssetClasses?: string, @Query('range') dateRange: DateRange = 'max', @@ -451,7 +452,7 @@ export class PortfolioController { @UseInterceptors(TransformDataSourceInResponseInterceptor) @UseGuards(AuthGuard('jwt')) public async getPosition( - @Headers('impersonation-id') impersonationId: string, + @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Param('dataSource') dataSource, @Param('symbol') symbol ): Promise { @@ -474,7 +475,7 @@ export class PortfolioController { @Get('report') @UseGuards(AuthGuard('jwt')) public async getReport( - @Headers('impersonation-id') impersonationId: string + @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string ): Promise { const report = await this.portfolioService.getReport(impersonationId); diff --git a/apps/api/src/app/user/create-user.dto.ts b/apps/api/src/app/user/create-user.dto.ts deleted file mode 100644 index 7751f75fa..000000000 --- a/apps/api/src/app/user/create-user.dto.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { IsOptional, IsString } from 'class-validator'; - -export class CreateUserDto { - @IsString() - @IsOptional() - country?: string; -} diff --git a/apps/api/src/app/user/user.controller.ts b/apps/api/src/app/user/user.controller.ts index 0eb37fb60..44d21e9c9 100644 --- a/apps/api/src/app/user/user.controller.ts +++ b/apps/api/src/app/user/user.controller.ts @@ -22,7 +22,6 @@ import { User as UserModel } from '@prisma/client'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { size } from 'lodash'; -import { CreateUserDto } from './create-user.dto'; import { UserItem } from './interfaces/user-item.interface'; import { UpdateUserSettingDto } from './update-user-setting.dto'; import { UserService } from './user.service'; @@ -66,7 +65,7 @@ export class UserController { } @Post() - public async signupUser(@Body() data: CreateUserDto): Promise { + public async signupUser(): Promise { const isUserSignupEnabled = await this.propertyService.isUserSignupEnabled(); @@ -80,7 +79,6 @@ export class UserController { const hasAdmin = await this.userService.hasAdmin(); const { accessToken, id, role } = await this.userService.createUser({ - country: data.country, data: { role: hasAdmin ? 'USER' : 'ADMIN' } }); diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index b45d1849d..1630a74aa 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -18,8 +18,6 @@ import { Injectable } from '@nestjs/common'; import { Prisma, Role, User } from '@prisma/client'; import { sortBy } from 'lodash'; -import { CreateUserDto } from './create-user.dto'; - const crypto = require('crypto'); @Injectable() @@ -234,9 +232,10 @@ export class UserService { } public async createUser({ - country, data - }: CreateUserDto & { data: Prisma.UserCreateInput }): Promise { + }: { + data: Prisma.UserCreateInput; + }): Promise { if (!data?.provider) { data.provider = 'ANONYMOUS'; } @@ -264,7 +263,6 @@ export class UserService { if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { await this.prismaService.analytics.create({ data: { - country, User: { connect: { id: user.id } } } }); diff --git a/apps/api/src/interceptors/redact-values-in-response.interceptor.ts b/apps/api/src/interceptors/redact-values-in-response.interceptor.ts index 0cc7b2168..6b10a4ebb 100644 --- a/apps/api/src/interceptors/redact-values-in-response.interceptor.ts +++ b/apps/api/src/interceptors/redact-values-in-response.interceptor.ts @@ -1,5 +1,6 @@ import { UserService } from '@ghostfolio/api/app/user/user.service'; import { redactAttributes } from '@ghostfolio/api/helper/object.helper'; +import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config'; import { CallHandler, ExecutionContext, @@ -22,7 +23,8 @@ export class RedactValuesInResponseInterceptor return next.handle().pipe( map((data: any) => { const request = context.switchToHttp().getRequest(); - const hasImpersonationId = !!request.headers?.['impersonation-id']; + const hasImpersonationId = + !!request.headers?.[HEADER_KEY_IMPERSONATION.toLowerCase()]; if ( hasImpersonationId || diff --git a/apps/api/src/interceptors/transform-data-source-in-request.interceptor.ts b/apps/api/src/interceptors/transform-data-source-in-request.interceptor.ts index aa9952473..ecef35bb6 100644 --- a/apps/api/src/interceptors/transform-data-source-in-request.interceptor.ts +++ b/apps/api/src/interceptors/transform-data-source-in-request.interceptor.ts @@ -24,7 +24,7 @@ export class TransformDataSourceInRequestInterceptor const http = context.switchToHttp(); const request = http.getRequest(); - if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') === true) { + if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { if (request.body.dataSource) { request.body.dataSource = decodeDataSource(request.body.dataSource); } diff --git a/apps/api/src/interceptors/transform-data-source-in-response.interceptor.ts b/apps/api/src/interceptors/transform-data-source-in-response.interceptor.ts index d02c26fca..0d8b8f264 100644 --- a/apps/api/src/interceptors/transform-data-source-in-response.interceptor.ts +++ b/apps/api/src/interceptors/transform-data-source-in-response.interceptor.ts @@ -26,9 +26,7 @@ export class TransformDataSourceInResponseInterceptor ): Observable { return next.handle().pipe( map((data: any) => { - if ( - this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') === true - ) { + if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { data = redactAttributes({ options: [ { diff --git a/apps/client/src/app/components/admin-users/admin-users.html b/apps/client/src/app/components/admin-users/admin-users.html index 317086c90..15127802e 100644 --- a/apps/client/src/app/components/admin-users/admin-users.html +++ b/apps/client/src/app/components/admin-users/admin-users.html @@ -28,7 +28,13 @@ > Engagement per Day - Last Request + + Last Request + @@ -86,7 +92,10 @@ [value]="userItem.engagement" > - + {{ formatDistanceToNow(userItem.lastActivity) }} diff --git a/apps/client/src/app/core/auth.interceptor.ts b/apps/client/src/app/core/auth.interceptor.ts index 481f83b71..5cbbbf868 100644 --- a/apps/client/src/app/core/auth.interceptor.ts +++ b/apps/client/src/app/core/auth.interceptor.ts @@ -5,14 +5,16 @@ import { HttpRequest } from '@angular/common/http'; import { Injectable } from '@angular/core'; +import { + HEADER_KEY_IMPERSONATION, + HEADER_KEY_TIMEZONE, + HEADER_KEY_TOKEN +} from '@ghostfolio/common/config'; import { Observable } from 'rxjs'; import { ImpersonationStorageService } from '../services/impersonation-storage.service'; import { TokenStorageService } from '../services/token-storage.service'; -const IMPERSONATION_KEY = 'Impersonation-Id'; -const TOKEN_HEADER_KEY = 'Authorization'; - @Injectable() export class AuthInterceptor implements HttpInterceptor { public constructor( @@ -24,21 +26,27 @@ export class AuthInterceptor implements HttpInterceptor { req: HttpRequest, next: HttpHandler ): Observable> { - let authReq = req; + let request = req; + let headers = request.headers.set( + HEADER_KEY_TIMEZONE, + Intl?.DateTimeFormat().resolvedOptions().timeZone + ); + const token = this.tokenStorageService.getToken(); - const impersonationId = this.impersonationStorageService.getId(); if (token !== null) { - let headers = req.headers.set(TOKEN_HEADER_KEY, `Bearer ${token}`); + headers = headers.set(HEADER_KEY_TOKEN, `Bearer ${token}`); + + const impersonationId = this.impersonationStorageService.getId(); if (impersonationId !== null) { - headers = headers.set(IMPERSONATION_KEY, impersonationId); + headers = headers.set(HEADER_KEY_IMPERSONATION, impersonationId); } - - authReq = req.clone({ headers }); } - return next.handle(authReq); + request = request.clone({ headers }); + + return next.handle(request); } } diff --git a/apps/client/src/app/pages/landing/landing-page.component.ts b/apps/client/src/app/pages/landing/landing-page.component.ts index e2ad8a592..18f2110ef 100644 --- a/apps/client/src/app/pages/landing/landing-page.component.ts +++ b/apps/client/src/app/pages/landing/landing-page.component.ts @@ -17,8 +17,8 @@ export class LandingPageComponent implements OnDestroy, OnInit { [code: string]: { value: number }; } = {}; public currentYear = format(new Date(), 'yyyy'); - public demoAuthToken: string; public deviceType: string; + public hasPermissionForDemo: boolean; public hasPermissionForStatistics: boolean; public hasPermissionForSubscription: boolean; public hasPermissionToCreateUser: boolean; @@ -54,6 +54,7 @@ export class LandingPageComponent implements OnDestroy, OnInit { ) { const { countriesOfSubscribers = [], + demoAuthToken, globalPermissions, statistics } = this.dataService.fetchInfo(); @@ -64,6 +65,7 @@ export class LandingPageComponent implements OnDestroy, OnInit { }; } + this.hasPermissionForDemo = !!demoAuthToken; this.hasPermissionForStatistics = hasPermission( globalPermissions, permissions.enableStatistics diff --git a/apps/client/src/app/pages/landing/landing-page.html b/apps/client/src/app/pages/landing/landing-page.html index 4ba24f77a..f14aae4ba 100644 --- a/apps/client/src/app/pages/landing/landing-page.html +++ b/apps/client/src/app/pages/landing/landing-page.html @@ -40,12 +40,18 @@ > Get Started -
or
- - - Live Demo - + + +
+ or +
+ + Live Demo + +
@@ -379,16 +385,20 @@

Are you ready?

- Join now or check out the example account + Join now + or check out the example account

diff --git a/apps/client/src/app/pages/register/register-page.component.ts b/apps/client/src/app/pages/register/register-page.component.ts index 9752c6012..4ecff5a63 100644 --- a/apps/client/src/app/pages/register/register-page.component.ts +++ b/apps/client/src/app/pages/register/register-page.component.ts @@ -63,7 +63,7 @@ export class RegisterPageComponent implements OnDestroy, OnInit { public async createAccount() { this.dataService - .postUser({ country: this.userService.getCountry() }) + .postUser() .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(({ accessToken, authToken, role }) => { this.openShowAccessTokenDialog(accessToken, authToken, role); diff --git a/apps/client/src/app/pages/register/register-page.html b/apps/client/src/app/pages/register/register-page.html index f1fc1a1e1..5147f5bac 100644 --- a/apps/client/src/app/pages/register/register-page.html +++ b/apps/client/src/app/pages/register/register-page.html @@ -21,7 +21,6 @@ class="d-inline-block" color="primary" mat-flat-button - [disabled]="!demoAuthToken" (click)="createAccount()" > Create Account diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index d34d54ded..d4071caeb 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -405,8 +405,8 @@ export class DataService { return this.http.post(`/api/v1/order`, aOrder); } - public postUser({ country }: { country: string }) { - return this.http.post(`/api/v1/user`, { country }); + public postUser() { + return this.http.post(`/api/v1/user`, {}); } public putAccount(aAccount: UpdateAccountDto) { diff --git a/apps/client/src/app/services/user/user.service.ts b/apps/client/src/app/services/user/user.service.ts index fbbab5a1a..7f903df3a 100644 --- a/apps/client/src/app/services/user/user.service.ts +++ b/apps/client/src/app/services/user/user.service.ts @@ -6,7 +6,6 @@ import { SubscriptionInterstitialDialogParams } from '@ghostfolio/client/compone import { SubscriptionInterstitialDialog } from '@ghostfolio/client/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component'; import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; -import { timezoneCitiesToCountries } from '@ghostfolio/common/timezone-cities-to-countries'; import { DeviceDetectorService } from 'ngx-device-detector'; import { Subject, of } from 'rxjs'; import { throwError } from 'rxjs'; @@ -46,20 +45,6 @@ export class UserService extends ObservableStore { } } - public getCountry() { - let country: string; - - if (Intl) { - const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; - const timeZoneArray = timeZone.split('/'); - const city = timeZoneArray[timeZoneArray.length - 1]; - - country = timezoneCitiesToCountries[city]; - } - - return country; - } - public remove() { this.setState({ user: null }, UserStoreActions.RemoveUser); } diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index 577644116..3319f9a3b 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -2,8 +2,6 @@ import { DataSource } from '@prisma/client'; import { JobOptions, JobStatus } from 'bull'; import ms from 'ms'; -export const DEMO_USER_ID = '9b112b4d-3b7d-4bad-9bdd-3b0f7b4dac2f'; - export const ghostfolioPrefix = 'GF'; export const ghostfolioScraperApiSymbolPrefix = `_${ghostfolioPrefix}_`; export const ghostfolioCashSymbol = `${ghostfolioScraperApiSymbolPrefix}CASH`; @@ -69,12 +67,17 @@ export const GATHER_HISTORICAL_MARKET_DATA_PROCESS_OPTIONS: JobOptions = { } }; +export const HEADER_KEY_IMPERSONATION = 'Impersonation-Id'; +export const HEADER_KEY_TIMEZONE = 'Timezone'; +export const HEADER_KEY_TOKEN = 'Authorization'; + export const MAX_CHART_ITEMS = 365; export const PROPERTY_BENCHMARKS = 'BENCHMARKS'; export const PROPERTY_COUNTRIES_OF_SUBSCRIBERS = 'COUNTRIES_OF_SUBSCRIBERS'; export const PROPERTY_COUPONS = 'COUPONS'; export const PROPERTY_CURRENCIES = 'CURRENCIES'; +export const PROPERTY_DEMO_USER_ID = 'DEMO_USER_ID'; 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/timezone-cities-to-countries.ts b/libs/common/src/lib/timezone-cities-to-countries.ts deleted file mode 100644 index 6f6973179..000000000 --- a/libs/common/src/lib/timezone-cities-to-countries.ts +++ /dev/null @@ -1,426 +0,0 @@ -export const timezoneCitiesToCountries = { - Abidjan: 'CI', - Accra: 'GH', - Adak: 'US', - Addis_Ababa: 'ET', - Adelaide: 'AU', - Aden: 'YE', - Algiers: 'DZ', - Almaty: 'KZ', - Amman: 'JO', - Amsterdam: 'NL', - Anadyr: 'RU', - Anchorage: 'US', - Andorra: 'AD', - Anguilla: 'AI', - Antananarivo: 'MG', - Antigua: 'AG', - Apia: 'WS', - Aqtau: 'KZ', - Aqtobe: 'KZ', - Araguaina: 'BR', - Aruba: 'AW', - Ashgabat: 'TM', - Asmara: 'ER', - Astrakhan: 'RU', - Asuncion: 'PY', - Athens: 'GR', - Atikokan: 'CA', - Atyrau: 'KZ', - Auckland: 'NZ', - Azores: 'PT', - Baghdad: 'IQ', - Bahia: 'BR', - Bahia_Banderas: 'MX', - Bahrain: 'BH', - Baku: 'AZ', - Bamako: 'ML', - Bangkok: 'TH', - Bangui: 'CF', - Banjul: 'GM', - Barbados: 'BB', - Barnaul: 'RU', - Beirut: 'LB', - Belem: 'BR', - Belgrade: 'RS', - Belize: 'BZ', - Berlin: 'DE', - Bermuda: 'BM', - Beulah: 'US', - Bishkek: 'KG', - Bissau: 'GW', - 'Blanc-Sablon': 'CA', - Blantyre: 'MW', - Boa_Vista: 'BR', - Bogota: 'CO', - Boise: 'US', - Bougainville: 'PG', - Bratislava: 'SK', - Brazzaville: 'CG', - Brisbane: 'AU', - Broken_Hill: 'AU', - Brunei: 'BN', - Brussels: 'BE', - Bucharest: 'RO', - Budapest: 'HU', - Buenos_Aires: 'AR', - Bujumbura: 'BI', - Busingen: 'DE', - Cairo: 'EG', - Cambridge_Bay: 'CA', - Campo_Grande: 'BR', - Canary: 'ES', - Cancun: 'MX', - Cape_Verde: 'CV', - Caracas: 'VE', - Casablanca: 'MA', - Casey: 'AQ', - Catamarca: 'AR', - Cayenne: 'GF', - Cayman: 'KY', - Center: 'US', - Ceuta: 'ES', - Chagos: 'IO', - Chatham: 'NZ', - Chicago: 'US', - Chihuahua: 'MX', - Chisinau: 'MD', - Chita: 'RU', - Choibalsan: 'MN', - Christmas: 'CX', - Chuuk: 'FM', - Cocos: 'CC', - Colombo: 'LK', - Comoro: 'KM', - Conakry: 'GN', - Copenhagen: 'DK', - Cordoba: 'AR', - Costa_Rica: 'CR', - Creston: 'CA', - Cuiaba: 'BR', - Curacao: 'CW', - Dakar: 'SN', - Damascus: 'SY', - Danmarkshavn: 'GL', - Dar_es_Salaam: 'TZ', - Darwin: 'AU', - Davis: 'AQ', - Dawson: 'CA', - Dawson_Creek: 'CA', - Denver: 'US', - Detroit: 'US', - Dhaka: 'BD', - Dili: 'TL', - Djibouti: 'DJ', - Dominica: 'DM', - Douala: 'CM', - Dubai: 'AE', - Dublin: 'IE', - DumontDUrville: 'AQ', - Dushanbe: 'TJ', - Easter: 'CL', - Edmonton: 'CA', - Efate: 'VU', - Eirunepe: 'BR', - El_Aaiun: 'EH', - El_Salvador: 'SV', - Eucla: 'AU', - Fakaofo: 'TK', - Famagusta: 'CY', - Faroe: 'FO', - Fiji: 'FJ', - Fort_Nelson: 'CA', - Fortaleza: 'BR', - Freetown: 'SL', - Funafuti: 'TV', - Gaborone: 'BW', - Galapagos: 'EC', - Gambier: 'PF', - Gaza: 'PS', - Gibraltar: 'GI', - Glace_Bay: 'CA', - Goose_Bay: 'CA', - Grand_Turk: 'TC', - Grenada: 'GD', - Guadalcanal: 'SB', - Guadeloupe: 'GP', - Guam: 'GU', - Guatemala: 'GT', - Guayaquil: 'EC', - Guernsey: 'GG', - Guyana: 'GY', - Halifax: 'CA', - Harare: 'ZW', - Havana: 'CU', - Hebron: 'PS', - Helsinki: 'FI', - Hermosillo: 'MX', - Ho_Chi_Minh: 'VN', - Hobart: 'AU', - Hong_Kong: 'HK', - Honolulu: 'US', - Hovd: 'MN', - Indianapolis: 'US', - Inuvik: 'CA', - Iqaluit: 'CA', - Irkutsk: 'RU', - Isle_of_Man: 'IM', - Istanbul: 'TR', - Jakarta: 'ID', - Jamaica: 'JM', - Jayapura: 'ID', - Jersey: 'JE', - Jerusalem: 'IL', - Johannesburg: 'ZA', - Juba: 'SS', - Jujuy: 'AR', - Juneau: 'US', - Kabul: 'AF', - Kaliningrad: 'RU', - Kamchatka: 'RU', - Kampala: 'UG', - Kanton: 'KI', - Karachi: 'PK', - Kathmandu: 'NP', - Kerguelen: 'TF', - Khandyga: 'RU', - Khartoum: 'SD', - Kiev: 'UA', - Kigali: 'RW', - Kinshasa: 'CD', - Kiritimati: 'KI', - Kirov: 'RU', - Knox: 'US', - Kolkata: 'IN', - Kosrae: 'FM', - Kralendijk: 'NL', - Krasnoyarsk: 'RU', - Kuala_Lumpur: 'MY', - Kuching: 'MY', - Kuwait: 'KW', - Kwajalein: 'MH', - La_Paz: 'BO', - La_Rioja: 'AR', - Lagos: 'NG', - Libreville: 'GA', - Lima: 'PE', - Lindeman: 'AU', - Lisbon: 'PT', - Ljubljana: 'SI', - Lome: 'TG', - London: 'GB', - Longyearbyen: 'SJ', - Lord_Howe: 'AU', - Los_Angeles: 'US', - Louisville: 'US', - Lower_Princes: 'SX', - Luanda: 'AO', - Lubumbashi: 'CD', - Lusaka: 'ZM', - Luxembourg: 'LU', - Macau: 'MO', - Maceio: 'BR', - Macquarie: 'AU', - Madeira: 'PT', - Madrid: 'ES', - Magadan: 'RU', - Mahe: 'SC', - Majuro: 'MH', - Makassar: 'ID', - Malabo: 'GQ', - Maldives: 'MV', - Malta: 'MT', - Managua: 'NI', - Manaus: 'BR', - Manila: 'PH', - Maputo: 'MZ', - Marengo: 'US', - Mariehamn: 'AX', - Marigot: 'MF', - Marquesas: 'PF', - Martinique: 'MQ', - Maseru: 'LS', - Matamoros: 'MX', - Mauritius: 'MU', - Mawson: 'AQ', - Mayotte: 'YT', - Mazatlan: 'MX', - Mbabane: 'SZ', - McMurdo: 'AQ', - Melbourne: 'AU', - Mendoza: 'AR', - Menominee: 'US', - Merida: 'MX', - Metlakatla: 'US', - Mexico_City: 'MX', - Midway: 'UM', - Minsk: 'BY', - Miquelon: 'PM', - Mogadishu: 'SO', - Monaco: 'MC', - Moncton: 'CA', - Monrovia: 'LR', - Monterrey: 'MX', - Montevideo: 'UY', - Monticello: 'US', - Montserrat: 'MS', - Moscow: 'RU', - Muscat: 'OM', - Nairobi: 'KE', - Nassau: 'BS', - Nauru: 'NR', - Ndjamena: 'TD', - New_Salem: 'US', - New_York: 'US', - Niamey: 'NE', - Nicosia: 'CY', - Nipigon: 'CA', - Niue: 'NU', - Nome: 'US', - Norfolk: 'NF', - Noronha: 'BR', - Nouakchott: 'MR', - Noumea: 'NC', - Novokuznetsk: 'RU', - Novosibirsk: 'RU', - Nuuk: 'GL', - Ojinaga: 'MX', - Omsk: 'RU', - Oral: 'KZ', - Oslo: 'NO', - Ouagadougou: 'BF', - Pago_Pago: 'AS', - Palau: 'PW', - Palmer: 'AQ', - Panama: 'PA', - Pangnirtung: 'CA', - Paramaribo: 'SR', - Paris: 'FR', - Perth: 'AU', - Petersburg: 'US', - Phnom_Penh: 'KH', - Phoenix: 'US', - Pitcairn: 'PN', - Podgorica: 'ME', - Pohnpei: 'FM', - Pontianak: 'ID', - 'Port-au-Prince': 'HT', - Port_Moresby: 'PG', - Port_of_Spain: 'TT', - 'Porto-Novo': 'BJ', - Porto_Velho: 'BR', - Prague: 'CZ', - Puerto_Rico: 'PR', - Punta_Arenas: 'CL', - Pyongyang: 'KP', - Qatar: 'QA', - Qostanay: 'KZ', - Qyzylorda: 'KZ', - Rainy_River: 'CA', - Rankin_Inlet: 'CA', - Rarotonga: 'CK', - Recife: 'BR', - Regina: 'CA', - Resolute: 'CA', - Reunion: 'RE', - Reykjavik: 'IS', - Riga: 'LV', - Rio_Branco: 'BR', - Rio_Gallegos: 'AR', - Riyadh: 'SA', - Rome: 'IT', - Rothera: 'AQ', - Saipan: 'MP', - Sakhalin: 'RU', - Salta: 'AR', - Samara: 'RU', - Samarkand: 'UZ', - San_Juan: 'AR', - San_Luis: 'AR', - San_Marino: 'SM', - Santarem: 'BR', - Santiago: 'CL', - Santo_Domingo: 'DO', - Sao_Paulo: 'BR', - Sao_Tome: 'ST', - Sarajevo: 'BA', - Saratov: 'RU', - Scoresbysund: 'GL', - Seoul: 'KR', - Shanghai: 'CN', - Simferopol: 'RU', - Singapore: 'SG', - Sitka: 'US', - Skopje: 'MK', - Sofia: 'BG', - South_Georgia: 'GS', - Srednekolymsk: 'RU', - St_Barthelemy: 'BL', - St_Helena: 'SH', - St_Johns: 'CA', - St_Kitts: 'KN', - St_Lucia: 'LC', - St_Thomas: 'VI', - St_Vincent: 'VC', - Stanley: 'FK', - Stockholm: 'SE', - Swift_Current: 'CA', - Sydney: 'AU', - Syowa: 'AQ', - Tahiti: 'PF', - Taipei: 'TW', - Tallinn: 'EE', - Tarawa: 'KI', - Tashkent: 'UZ', - Tbilisi: 'GE', - Tegucigalpa: 'HN', - Tehran: 'IR', - Tell_City: 'US', - Thimphu: 'BT', - Thule: 'GL', - Thunder_Bay: 'CA', - Tijuana: 'MX', - Tirane: 'AL', - Tokyo: 'JP', - Tomsk: 'RU', - Tongatapu: 'TO', - Toronto: 'CA', - Tortola: 'VI (UK)', - Tripoli: 'LY', - Troll: 'AQ', - Tucuman: 'AR', - Tunis: 'TN', - Ulaanbaatar: 'MN', - Ulyanovsk: 'RU', - Urumqi: 'CN', - Ushuaia: 'AR', - 'Ust-Nera': 'RU', - Uzhgorod: 'UA', - Vaduz: 'LI', - Vancouver: 'CA', - Vatican: 'VA', - Vevay: 'US', - Vienna: 'AT', - Vientiane: 'LA', - Vilnius: 'LT', - Vincennes: 'US', - Vladivostok: 'RU', - Volgograd: 'RU', - Vostok: 'AQ', - Wake: 'UM', - Wallis: 'WF', - Warsaw: 'PL', - Whitehorse: 'CA', - Winamac: 'US', - Windhoek: 'NA', - Winnipeg: 'CA', - Yakutat: 'US', - Yakutsk: 'RU', - Yangon: 'MM', - Yekaterinburg: 'RU', - Yellowknife: 'CA', - Yerevan: 'AM', - Zagreb: 'HR', - Zaporozhye: 'UA', - Zurich: 'CH' -}; diff --git a/package.json b/package.json index 33038f0c5..1d54b288e 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "class-transformer": "0.3.2", "class-validator": "0.13.1", "color": "4.2.3", + "countries-and-timezones": "3.4.1", "countries-list": "2.6.1", "countup.js": "2.3.2", "date-fns": "2.29.3", diff --git a/prisma/seed.js b/prisma/seed.js index b94c11316..beafbed44 100644 --- a/prisma/seed.js +++ b/prisma/seed.js @@ -1,293 +1,7 @@ -const { - AccountType, - DataSource, - PrismaClient, - Role, - Type -} = require('@prisma/client'); +const { PrismaClient } = require('@prisma/client'); const prisma = new PrismaClient(); async function main() { - const platformBitcoinSuisse = await prisma.platform.upsert({ - create: { - id: '70b6e475-a2b9-4527-99db-943e4f38ce45', - name: 'Bitcoin Suisse', - url: 'https://www.bitcoinsuisse.com' - }, - update: {}, - where: { id: '70b6e475-a2b9-4527-99db-943e4f38ce45' } - }); - - const platformBitpanda = await prisma.platform.upsert({ - create: { - id: 'debf9110-498f-4811-b972-7ebbd317e730', - name: 'Bitpanda', - url: 'https://www.bitpanda.com' - }, - update: {}, - where: { id: 'debf9110-498f-4811-b972-7ebbd317e730' } - }); - - const platformCoinbase = await prisma.platform.upsert({ - create: { - id: '8dc24b88-bb92-4152-af25-fe6a31643e26', - name: 'Coinbase', - url: 'https://www.coinbase.com' - }, - update: {}, - where: { id: '8dc24b88-bb92-4152-af25-fe6a31643e26' } - }); - - const platformDegiro = await prisma.platform.upsert({ - create: { - id: '94c1a2f4-a666-47be-84cd-4c8952e74c81', - name: 'DEGIRO', - url: 'https://www.degiro.eu' - }, - update: {}, - where: { id: '94c1a2f4-a666-47be-84cd-4c8952e74c81' } - }); - - const platformInteractiveBrokers = await prisma.platform.upsert({ - create: { - id: '9da3a8a7-4795-43e3-a6db-ccb914189737', - name: 'Interactive Brokers', - url: 'https://www.interactivebrokers.com' - }, - update: {}, - where: { id: '9da3a8a7-4795-43e3-a6db-ccb914189737' } - }); - - const platformPostFinance = await prisma.platform.upsert({ - create: { - id: '5377d9df-0d25-42c2-9d9b-e4c63166281e', - name: 'PostFinance', - url: 'https://www.postfinance.ch' - }, - update: {}, - where: { id: '5377d9df-0d25-42c2-9d9b-e4c63166281e' } - }); - - const platformSwissquote = await prisma.platform.upsert({ - create: { - id: '1377d9df-0d25-42c2-9d9b-e4c63156291f', - name: 'Swissquote', - url: 'https://swissquote.com' - }, - update: {}, - where: { id: '1377d9df-0d25-42c2-9d9b-e4c63156291f' } - }); - - const userDemo = await prisma.user.upsert({ - create: { - accessToken: - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjliMTEyYjRkLTNiN2QtNGJhZC05YmRkLTNiMGY3YjRkYWMyZiIsImlhdCI6MTYxODUxMjAxNCwiZXhwIjoxNjIxMTA0MDE0fQ.l3WUxpI0hxuQtdPrD0kd7sem6S2kx_7CrdNvkmlKuWw', - Account: { - create: [ - { - accountType: AccountType.SECURITIES, - balance: 0, - currency: 'USD', - id: 'd804de69-0429-42dc-b6ca-b308fd7dd926', - name: 'Coinbase Account', - platformId: platformCoinbase.id - }, - { - accountType: AccountType.SECURITIES, - balance: 0, - currency: 'EUR', - id: '65cfb79d-b6c7-4591-9d46-73426bc62094', - name: 'DEGIRO Account', - platformId: platformDegiro.id - }, - { - accountType: AccountType.SECURITIES, - balance: 0, - currency: 'USD', - id: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c', - isDefault: true, - name: 'Interactive Brokers Account', - platformId: platformInteractiveBrokers.id - } - ] - }, - id: '9b112b4d-3b7d-4bad-9bdd-3b0f7b4dac2f', - role: Role.DEMO - }, - update: {}, - where: { id: '9b112b4d-3b7d-4bad-9bdd-3b0f7b4dac2f' } - }); - - await prisma.symbolProfile.createMany({ - data: [ - { - assetClass: 'EQUITY', - assetSubClass: 'STOCK', - countries: [{ code: 'US', weight: 1 }], - currency: 'USD', - dataSource: DataSource.YAHOO, - id: '2bd26362-136e-411c-b578-334084b4cdcc', - name: 'Amazon.com Inc.', - sectors: [{ name: 'Consumer Cyclical', weight: 1 }], - symbol: 'AMZN' - }, - { - assetClass: 'CASH', - assetSubClass: 'CRYPTOCURRENCY', - countries: undefined, - currency: 'USD', - dataSource: DataSource.YAHOO, - id: 'fdc42ea6-1321-44f5-9fb0-d7f1f2cf9b1e', - name: 'Bitcoin USD', - sectors: undefined, - symbol: 'BTCUSD' - }, - { - assetClass: 'EQUITY', - assetSubClass: 'STOCK', - countries: [{ code: 'US', weight: 1 }], - currency: 'USD', - dataSource: DataSource.YAHOO, - id: 'd1ee9681-fb21-4f99-a3b7-afd4fc04df2e', - name: 'Tesla Inc.', - sectors: [{ name: 'Consumer Cyclical', weight: 1 }], - symbol: 'TSLA' - }, - { - assetClass: 'EQUITY', - assetSubClass: 'ETF', - countries: [ - { code: 'US', weight: 0.9886789999999981 }, - { code: 'NL', weight: 0.000203 }, - { code: 'CA', weight: 0.000362 } - ], - currency: 'USD', - dataSource: DataSource.YAHOO, - id: '7d9c8540-061e-4e7e-b019-0d0f4a84e796', - name: 'Vanguard Total Stock Market Index Fund ETF', - sectors: [ - { name: 'Technology', weight: 0.31393799999999955 }, - { name: 'Consumer Cyclical', weight: 0.149224 }, - { name: 'Financials', weight: 0.11716100000000002 }, - { name: 'Healthcare', weight: 0.13285199999999994 }, - { name: 'Consumer Staples', weight: 0.053919000000000016 }, - { name: 'Energy', weight: 0.025529999999999997 }, - { name: 'Telecommunications', weight: 0.012579 }, - { name: 'Industrials', weight: 0.09526399999999995 }, - { name: 'Utilities', weight: 0.024791999999999988 }, - { name: 'Materials', weight: 0.027664 }, - { name: 'Real Estate', weight: 0.03239999999999998 }, - { name: 'Communication', weight: 0.0036139999999999996 }, - { name: 'Other', weight: 0.000218 } - ], - symbol: 'VTI' - } - ], - skipDuplicates: true - }); - - await prisma.order.createMany({ - data: [ - { - accountId: '65cfb79d-b6c7-4591-9d46-73426bc62094', - accountUserId: userDemo.id, - date: new Date(Date.UTC(2017, 0, 3, 0, 0, 0)), - fee: 30, - id: 'cf7c0418-8535-4089-ae3d-5dbfa0aec2e1', - quantity: 50, - symbolProfileId: 'd1ee9681-fb21-4f99-a3b7-afd4fc04df2e', // TSLA - type: Type.BUY, - unitPrice: 42.97, - userId: userDemo.id - }, - { - accountId: 'd804de69-0429-42dc-b6ca-b308fd7dd926', - accountUserId: userDemo.id, - date: new Date(Date.UTC(2017, 7, 16, 0, 0, 0)), - fee: 29.9, - id: 'a1c5d73a-8631-44e5-ac44-356827a5212c', - quantity: 0.5614682, - symbolProfileId: 'fdc42ea6-1321-44f5-9fb0-d7f1f2cf9b1e', // BTCUSD - type: Type.BUY, - unitPrice: 3562.089535970158, - userId: userDemo.id - }, - { - accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c', - accountUserId: userDemo.id, - date: new Date(Date.UTC(2018, 9, 1, 0, 0, 0)), - fee: 80.79, - id: '71c08e2a-4a86-44ae-a890-c337de5d5f9b', - quantity: 5, - symbolProfileId: '2bd26362-136e-411c-b578-334084b4cdcc', // AMZN - type: Type.BUY, - unitPrice: 2021.99, - userId: userDemo.id - }, - { - accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c', - accountUserId: userDemo.id, - date: new Date(Date.UTC(2019, 2, 1, 0, 0, 0)), - fee: 19.9, - id: '385f2c2c-d53e-4937-b0e5-e92ef6020d4e', - quantity: 10, - symbolProfileId: '7d9c8540-061e-4e7e-b019-0d0f4a84e796', // VTI - type: Type.BUY, - unitPrice: 144.38, - userId: userDemo.id - }, - { - accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c', - accountUserId: userDemo.id, - date: new Date(Date.UTC(2019, 8, 3, 0, 0, 0)), - fee: 19.9, - id: '185f2c2c-d53e-4937-b0e5-a93ef6020d4e', - quantity: 10, - symbolProfileId: '7d9c8540-061e-4e7e-b019-0d0f4a84e796', // VTI - type: Type.BUY, - unitPrice: 147.99, - userId: userDemo.id - }, - { - accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c', - accountUserId: userDemo.id, - date: new Date(Date.UTC(2020, 2, 2, 0, 0, 0)), - fee: 19.9, - id: '347b0430-a84f-4031-a0f9-390399066ad6', - quantity: 10, - symbolProfileId: '7d9c8540-061e-4e7e-b019-0d0f4a84e796', // VTI - type: Type.BUY, - unitPrice: 151.41, - userId: userDemo.id - }, - { - accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c', - accountUserId: userDemo.id, - date: new Date(Date.UTC(2020, 8, 1, 0, 0, 0)), - fee: 19.9, - id: '67ec3f47-3189-4b63-ba05-60d3a06b302f', - quantity: 10, - symbolProfileId: '7d9c8540-061e-4e7e-b019-0d0f4a84e796', // VTI - type: Type.BUY, - unitPrice: 177.69, - userId: userDemo.id - }, - { - accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c', - accountUserId: userDemo.id, - date: new Date(Date.UTC(2020, 2, 1, 0, 0, 0)), - fee: 19.9, - id: 'd01c6fbc-fa8d-47e6-8e80-66f882d2bfd2', - quantity: 10, - symbolProfileId: '7d9c8540-061e-4e7e-b019-0d0f4a84e796', // VTI - type: Type.BUY, - unitPrice: 203.15, - userId: userDemo.id - } - ], - skipDuplicates: true - }); - await prisma.tag.createMany({ data: [ { @@ -297,17 +11,6 @@ async function main() { ], skipDuplicates: true }); - - console.log({ - platformBitcoinSuisse, - platformBitpanda, - platformCoinbase, - platformDegiro, - platformInteractiveBrokers, - platformPostFinance, - platformSwissquote, - userDemo - }); } main() diff --git a/yarn.lock b/yarn.lock index 855f2e6ea..c86f64c9b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9352,6 +9352,11 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" +countries-and-timezones@3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/countries-and-timezones/-/countries-and-timezones-3.4.1.tgz#0ec2540f57e42f0f740eb2acaede786043347fe1" + integrity sha512-INeHGCony4XUUR8iGL/lmt9s1Oi+n+gFHeJAMfbV5hJfYeDOB8JG1oxz5xFQu5oBZoRCJe/87k1Vzue9DoIauA== + countries-list@2.6.1: version "2.6.1" resolved "https://registry.npmjs.org/countries-list/-/countries-list-2.6.1.tgz"