Browse Source

Extend analytics by country

pull/1661/head
Thomas 3 years ago
parent
commit
c3610e21e8
  1. 12
      apps/api/src/app/auth/auth.service.ts
  2. 7
      apps/api/src/app/user/create-user.dto.ts
  3. 6
      apps/api/src/app/user/user.controller.ts
  4. 17
      apps/api/src/app/user/user.service.ts
  5. 6
      apps/client/src/app/pages/register/register-page.component.ts
  6. 4
      apps/client/src/app/services/data.service.ts
  7. 15
      apps/client/src/app/services/user/user.service.ts
  8. 426
      libs/common/src/lib/timezone-cities-to-countries.ts
  9. 1
      libs/common/src/lib/types/analyticsEventType.type.ts
  10. 2
      libs/common/src/lib/types/index.ts
  11. 7
      prisma/schema.prisma

12
apps/api/src/app/auth/auth.service.ts

@ -61,8 +61,10 @@ export class AuthService {
// Create new user if not found
user = await this.userService.createUser({
provider,
thirdPartyId: principalId
data: {
provider,
thirdPartyId: principalId
}
});
}
@ -96,8 +98,10 @@ export class AuthService {
// Create new user if not found
user = await this.userService.createUser({
provider,
thirdPartyId
data: {
provider,
thirdPartyId
}
});
}

7
apps/api/src/app/user/create-user.dto.ts

@ -0,0 +1,7 @@
import { IsOptional, IsString } from 'class-validator';
export class CreateUserDto {
@IsString()
@IsOptional()
country?: string;
}

6
apps/api/src/app/user/user.controller.ts

@ -22,6 +22,7 @@ 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';
@ -65,7 +66,7 @@ export class UserController {
}
@Post()
public async signupUser(): Promise<UserItem> {
public async signupUser(@Body() data: CreateUserDto): Promise<UserItem> {
const isUserSignupEnabled =
await this.propertyService.isUserSignupEnabled();
@ -79,7 +80,8 @@ export class UserController {
const hasAdmin = await this.userService.hasAdmin();
const { accessToken, id, role } = await this.userService.createUser({
role: hasAdmin ? 'USER' : 'ADMIN'
country: data.country,
data: { role: hasAdmin ? 'USER' : 'ADMIN' }
});
return {

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

@ -14,6 +14,7 @@ import {
hasRole,
permissions
} from '@ghostfolio/common/permissions';
import { AnalyticsEventType } from '@ghostfolio/common/types';
import { Injectable } from '@nestjs/common';
import { Prisma, Role, User } from '@prisma/client';
import { sortBy } from 'lodash';
@ -231,7 +232,10 @@ export class UserService {
return hash.digest('hex');
}
public async createUser(data: Prisma.UserCreateInput): Promise<User> {
public async createUser({
country,
data
}: { country?: string } & { data: Prisma.UserCreateInput }): Promise<User> {
if (!data?.provider) {
data.provider = 'ANONYMOUS';
}
@ -256,6 +260,17 @@ export class UserService {
}
});
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
await this.prismaService.analyticsEvent.create({
data: {
data: {
country
},
type: <AnalyticsEventType>'createUser'
}
});
}
if (data.provider === 'ANONYMOUS') {
const accessToken = this.createAccessToken(
user.id,

6
apps/client/src/app/pages/register/register-page.component.ts

@ -4,6 +4,7 @@ import { Router } from '@angular/router';
import { DataService } from '@ghostfolio/client/services/data.service';
import { InternetIdentityService } from '@ghostfolio/client/services/internet-identity.service';
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { InfoItem, LineChartItem } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { Role } from '@prisma/client';
@ -37,7 +38,8 @@ export class RegisterPageComponent implements OnDestroy, OnInit {
private dialog: MatDialog,
private internetIdentityService: InternetIdentityService,
private router: Router,
private tokenStorageService: TokenStorageService
private tokenStorageService: TokenStorageService,
private userService: UserService
) {
this.info = this.dataService.fetchInfo();
@ -61,7 +63,7 @@ export class RegisterPageComponent implements OnDestroy, OnInit {
public async createAccount() {
this.dataService
.postUser()
.postUser({ country: this.userService.getCountry() })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ accessToken, authToken, role }) => {
this.openShowAccessTokenDialog(accessToken, authToken, role);

4
apps/client/src/app/services/data.service.ts

@ -405,8 +405,8 @@ export class DataService {
return this.http.post<OrderModel>(`/api/v1/order`, aOrder);
}
public postUser() {
return this.http.post<UserItem>(`/api/v1/user`, {});
public postUser({ country }: { country: string }) {
return this.http.post<UserItem>(`/api/v1/user`, { country });
}
public putAccount(aAccount: UpdateAccountDto) {

15
apps/client/src/app/services/user/user.service.ts

@ -6,6 +6,7 @@ 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';
@ -45,6 +46,20 @@ export class UserService extends ObservableStore<UserStoreState> {
}
}
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);
}

426
libs/common/src/lib/timezone-cities-to-countries.ts

@ -0,0 +1,426 @@
export const timezoneCitiesToCountries = {
Abidjan: "Côte d'Ivoire",
Accra: 'Ghana',
Adak: 'United States',
Addis_Ababa: 'Ethiopia',
Adelaide: 'Australia',
Aden: 'Yemen',
Algiers: 'Algeria',
Almaty: 'Kazakhstan',
Amman: 'Jordan',
Amsterdam: 'Netherlands',
Anadyr: 'Russia',
Anchorage: 'United States',
Andorra: 'Andorra',
Anguilla: 'Anguilla',
Antananarivo: 'Madagascar',
Antigua: 'Antigua & Barbuda',
Apia: 'Samoa (western)',
Aqtau: 'Kazakhstan',
Aqtobe: 'Kazakhstan',
Araguaina: 'Brazil',
Aruba: 'Aruba',
Ashgabat: 'Turkmenistan',
Asmara: 'Eritrea',
Astrakhan: 'Russia',
Asuncion: 'Paraguay',
Athens: 'Greece',
Atikokan: 'Canada',
Atyrau: 'Kazakhstan',
Auckland: 'New Zealand',
Azores: 'Portugal',
Baghdad: 'Iraq',
Bahia: 'Brazil',
Bahia_Banderas: 'Mexico',
Bahrain: 'Bahrain',
Baku: 'Azerbaijan',
Bamako: 'Mali',
Bangkok: 'Thailand',
Bangui: 'Central African Rep.',
Banjul: 'Gambia',
Barbados: 'Barbados',
Barnaul: 'Russia',
Beirut: 'Lebanon',
Belem: 'Brazil',
Belgrade: 'Serbia',
Belize: 'Belize',
Berlin: 'Germany',
Bermuda: 'Bermuda',
Beulah: 'United States',
Bishkek: 'Kyrgyzstan',
Bissau: 'Guinea-Bissau',
'Blanc-Sablon': 'Canada',
Blantyre: 'Malawi',
Boa_Vista: 'Brazil',
Bogota: 'Colombia',
Boise: 'United States',
Bougainville: 'Papua New Guinea',
Bratislava: 'Slovakia',
Brazzaville: 'Congo (Rep.)',
Brisbane: 'Australia',
Broken_Hill: 'Australia',
Brunei: 'Brunei',
Brussels: 'Belgium',
Bucharest: 'Romania',
Budapest: 'Hungary',
Buenos_Aires: 'Argentina',
Bujumbura: 'Burundi',
Busingen: 'Germany',
Cairo: 'Egypt',
Cambridge_Bay: 'Canada',
Campo_Grande: 'Brazil',
Canary: 'Spain',
Cancun: 'Mexico',
Cape_Verde: 'Cape Verde',
Caracas: 'Venezuela',
Casablanca: 'Morocco',
Casey: 'Antarctica',
Catamarca: 'Argentina',
Cayenne: 'French Guiana',
Cayman: 'Cayman Islands',
Center: 'United States',
Ceuta: 'Spain',
Chagos: 'British Indian Ocean Territory',
Chatham: 'New Zealand',
Chicago: 'United States',
Chihuahua: 'Mexico',
Chisinau: 'Moldova',
Chita: 'Russia',
Choibalsan: 'Mongolia',
Christmas: 'Christmas Island',
Chuuk: 'Micronesia',
Cocos: 'Cocos (Keeling) Islands',
Colombo: 'Sri Lanka',
Comoro: 'Comoros',
Conakry: 'Guinea',
Copenhagen: 'Denmark',
Cordoba: 'Argentina',
Costa_Rica: 'Costa Rica',
Creston: 'Canada',
Cuiaba: 'Brazil',
Curacao: 'Curaçao',
Dakar: 'Senegal',
Damascus: 'Syria',
Danmarkshavn: 'Greenland',
Dar_es_Salaam: 'Tanzania',
Darwin: 'Australia',
Davis: 'Antarctica',
Dawson: 'Canada',
Dawson_Creek: 'Canada',
Denver: 'United States',
Detroit: 'United States',
Dhaka: 'Bangladesh',
Dili: 'East Timor',
Djibouti: 'Djibouti',
Dominica: 'Dominica',
Douala: 'Cameroon',
Dubai: 'United Arab Emirates',
Dublin: 'Ireland',
DumontDUrville: 'Antarctica',
Dushanbe: 'Tajikistan',
Easter: 'Chile',
Edmonton: 'Canada',
Efate: 'Vanuatu',
Eirunepe: 'Brazil',
El_Aaiun: 'Western Sahara',
El_Salvador: 'El Salvador',
Eucla: 'Australia',
Fakaofo: 'Tokelau',
Famagusta: 'Cyprus',
Faroe: 'Faroe Islands',
Fiji: 'Fiji',
Fort_Nelson: 'Canada',
Fortaleza: 'Brazil',
Freetown: 'Sierra Leone',
Funafuti: 'Tuvalu',
Gaborone: 'Botswana',
Galapagos: 'Ecuador',
Gambier: 'French Polynesia',
Gaza: 'Palestine',
Gibraltar: 'Gibraltar',
Glace_Bay: 'Canada',
Goose_Bay: 'Canada',
Grand_Turk: 'Turks & Caicos Is',
Grenada: 'Grenada',
Guadalcanal: 'Solomon Islands',
Guadeloupe: 'Guadeloupe',
Guam: 'Guam',
Guatemala: 'Guatemala',
Guayaquil: 'Ecuador',
Guernsey: 'Guernsey',
Guyana: 'Guyana',
Halifax: 'Canada',
Harare: 'Zimbabwe',
Havana: 'Cuba',
Hebron: 'Palestine',
Helsinki: 'Finland',
Hermosillo: 'Mexico',
Ho_Chi_Minh: 'Vietnam',
Hobart: 'Australia',
Hong_Kong: 'Hong Kong',
Honolulu: 'United States',
Hovd: 'Mongolia',
Indianapolis: 'United States',
Inuvik: 'Canada',
Iqaluit: 'Canada',
Irkutsk: 'Russia',
Isle_of_Man: 'Isle of Man',
Istanbul: 'Turkey',
Jakarta: 'Indonesia',
Jamaica: 'Jamaica',
Jayapura: 'Indonesia',
Jersey: 'Jersey',
Jerusalem: 'Israel',
Johannesburg: 'South Africa',
Juba: 'South Sudan',
Jujuy: 'Argentina',
Juneau: 'United States',
Kabul: 'Afghanistan',
Kaliningrad: 'Russia',
Kamchatka: 'Russia',
Kampala: 'Uganda',
Kanton: 'Kiribati',
Karachi: 'Pakistan',
Kathmandu: 'Nepal',
Kerguelen: 'French Southern & Antarctic Lands',
Khandyga: 'Russia',
Khartoum: 'Sudan',
Kiev: 'Ukraine',
Kigali: 'Rwanda',
Kinshasa: 'Congo (Dem. Rep.)',
Kiritimati: 'Kiribati',
Kirov: 'Russia',
Knox: 'United States',
Kolkata: 'India',
Kosrae: 'Micronesia',
Kralendijk: 'Caribbean NL',
Krasnoyarsk: 'Russia',
Kuala_Lumpur: 'Malaysia',
Kuching: 'Malaysia',
Kuwait: 'Kuwait',
Kwajalein: 'Marshall Islands',
La_Paz: 'Bolivia',
La_Rioja: 'Argentina',
Lagos: 'Nigeria',
Libreville: 'Gabon',
Lima: 'Peru',
Lindeman: 'Australia',
Lisbon: 'Portugal',
Ljubljana: 'Slovenia',
Lome: 'Togo',
London: 'Britain (UK)',
Longyearbyen: 'Svalbard & Jan Mayen',
Lord_Howe: 'Australia',
Los_Angeles: 'United States',
Louisville: 'United States',
Lower_Princes: 'St Maarten (Dutch)',
Luanda: 'Angola',
Lubumbashi: 'Congo (Dem. Rep.)',
Lusaka: 'Zambia',
Luxembourg: 'Luxembourg',
Macau: 'Macau',
Maceio: 'Brazil',
Macquarie: 'Australia',
Madeira: 'Portugal',
Madrid: 'Spain',
Magadan: 'Russia',
Mahe: 'Seychelles',
Majuro: 'Marshall Islands',
Makassar: 'Indonesia',
Malabo: 'Equatorial Guinea',
Maldives: 'Maldives',
Malta: 'Malta',
Managua: 'Nicaragua',
Manaus: 'Brazil',
Manila: 'Philippines',
Maputo: 'Mozambique',
Marengo: 'United States',
Mariehamn: 'Åland Islands',
Marigot: 'St Martin (French)',
Marquesas: 'French Polynesia',
Martinique: 'Martinique',
Maseru: 'Lesotho',
Matamoros: 'Mexico',
Mauritius: 'Mauritius',
Mawson: 'Antarctica',
Mayotte: 'Mayotte',
Mazatlan: 'Mexico',
Mbabane: 'Eswatini (Swaziland)',
McMurdo: 'Antarctica',
Melbourne: 'Australia',
Mendoza: 'Argentina',
Menominee: 'United States',
Merida: 'Mexico',
Metlakatla: 'United States',
Mexico_City: 'Mexico',
Midway: 'US minor outlying islands',
Minsk: 'Belarus',
Miquelon: 'St Pierre & Miquelon',
Mogadishu: 'Somalia',
Monaco: 'Monaco',
Moncton: 'Canada',
Monrovia: 'Liberia',
Monterrey: 'Mexico',
Montevideo: 'Uruguay',
Monticello: 'United States',
Montserrat: 'Montserrat',
Moscow: 'Russia',
Muscat: 'Oman',
Nairobi: 'Kenya',
Nassau: 'Bahamas',
Nauru: 'Nauru',
Ndjamena: 'Chad',
New_Salem: 'United States',
New_York: 'United States',
Niamey: 'Niger',
Nicosia: 'Cyprus',
Nipigon: 'Canada',
Niue: 'Niue',
Nome: 'United States',
Norfolk: 'Norfolk Island',
Noronha: 'Brazil',
Nouakchott: 'Mauritania',
Noumea: 'New Caledonia',
Novokuznetsk: 'Russia',
Novosibirsk: 'Russia',
Nuuk: 'Greenland',
Ojinaga: 'Mexico',
Omsk: 'Russia',
Oral: 'Kazakhstan',
Oslo: 'Norway',
Ouagadougou: 'Burkina Faso',
Pago_Pago: 'Samoa (American)',
Palau: 'Palau',
Palmer: 'Antarctica',
Panama: 'Panama',
Pangnirtung: 'Canada',
Paramaribo: 'Suriname',
Paris: 'France',
Perth: 'Australia',
Petersburg: 'United States',
Phnom_Penh: 'Cambodia',
Phoenix: 'United States',
Pitcairn: 'Pitcairn',
Podgorica: 'Montenegro',
Pohnpei: 'Micronesia',
Pontianak: 'Indonesia',
'Port-au-Prince': 'Haiti',
Port_Moresby: 'Papua New Guinea',
Port_of_Spain: 'Trinidad & Tobago',
'Porto-Novo': 'Benin',
Porto_Velho: 'Brazil',
Prague: 'Czech Republic',
Puerto_Rico: 'Puerto Rico',
Punta_Arenas: 'Chile',
Pyongyang: 'Korea (North)',
Qatar: 'Qatar',
Qostanay: 'Kazakhstan',
Qyzylorda: 'Kazakhstan',
Rainy_River: 'Canada',
Rankin_Inlet: 'Canada',
Rarotonga: 'Cook Islands',
Recife: 'Brazil',
Regina: 'Canada',
Resolute: 'Canada',
Reunion: 'Réunion',
Reykjavik: 'Iceland',
Riga: 'Latvia',
Rio_Branco: 'Brazil',
Rio_Gallegos: 'Argentina',
Riyadh: 'Saudi Arabia',
Rome: 'Italy',
Rothera: 'Antarctica',
Saipan: 'Northern Mariana Islands',
Sakhalin: 'Russia',
Salta: 'Argentina',
Samara: 'Russia',
Samarkand: 'Uzbekistan',
San_Juan: 'Argentina',
San_Luis: 'Argentina',
San_Marino: 'San Marino',
Santarem: 'Brazil',
Santiago: 'Chile',
Santo_Domingo: 'Dominican Republic',
Sao_Paulo: 'Brazil',
Sao_Tome: 'Sao Tome & Principe',
Sarajevo: 'Bosnia & Herzegovina',
Saratov: 'Russia',
Scoresbysund: 'Greenland',
Seoul: 'Korea (South)',
Shanghai: 'China',
Simferopol: 'Russia',
Singapore: 'Singapore',
Sitka: 'United States',
Skopje: 'North Macedonia',
Sofia: 'Bulgaria',
South_Georgia: 'South Georgia & the South Sandwich Islands',
Srednekolymsk: 'Russia',
St_Barthelemy: 'St Barthelemy',
St_Helena: 'St Helena',
St_Johns: 'Canada',
St_Kitts: 'St Kitts & Nevis',
St_Lucia: 'St Lucia',
St_Thomas: 'Virgin Islands (US)',
St_Vincent: 'St Vincent',
Stanley: 'Falkland Islands',
Stockholm: 'Sweden',
Swift_Current: 'Canada',
Sydney: 'Australia',
Syowa: 'Antarctica',
Tahiti: 'French Polynesia',
Taipei: 'Taiwan',
Tallinn: 'Estonia',
Tarawa: 'Kiribati',
Tashkent: 'Uzbekistan',
Tbilisi: 'Georgia',
Tegucigalpa: 'Honduras',
Tehran: 'Iran',
Tell_City: 'United States',
Thimphu: 'Bhutan',
Thule: 'Greenland',
Thunder_Bay: 'Canada',
Tijuana: 'Mexico',
Tirane: 'Albania',
Tokyo: 'Japan',
Tomsk: 'Russia',
Tongatapu: 'Tonga',
Toronto: 'Canada',
Tortola: 'Virgin Islands (UK)',
Tripoli: 'Libya',
Troll: 'Antarctica',
Tucuman: 'Argentina',
Tunis: 'Tunisia',
Ulaanbaatar: 'Mongolia',
Ulyanovsk: 'Russia',
Urumqi: 'China',
Ushuaia: 'Argentina',
'Ust-Nera': 'Russia',
Uzhgorod: 'Ukraine',
Vaduz: 'Liechtenstein',
Vancouver: 'Canada',
Vatican: 'Vatican City',
Vevay: 'United States',
Vienna: 'Austria',
Vientiane: 'Laos',
Vilnius: 'Lithuania',
Vincennes: 'United States',
Vladivostok: 'Russia',
Volgograd: 'Russia',
Vostok: 'Antarctica',
Wake: 'US minor outlying islands',
Wallis: 'Wallis & Futuna',
Warsaw: 'Poland',
Whitehorse: 'Canada',
Winamac: 'United States',
Windhoek: 'Namibia',
Winnipeg: 'Canada',
Yakutat: 'United States',
Yakutsk: 'Russia',
Yangon: 'Myanmar (Burma)',
Yekaterinburg: 'Russia',
Yellowknife: 'Canada',
Yerevan: 'Armenia',
Zagreb: 'Croatia',
Zaporozhye: 'Ukraine',
Zurich: 'Switzerland'
};

1
libs/common/src/lib/types/analyticsEventType.type.ts

@ -0,0 +1 @@
export type AnalyticsEventType = 'createUser';

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

@ -1,6 +1,7 @@
import type { AccessWithGranteeUser } from './access-with-grantee-user.type';
import { AccountWithPlatform } from './account-with-platform.type';
import { AccountWithValue } from './account-with-value.type';
import { AnalyticsEventType } from './analyticsEventType.type';
import type { ColorScheme } from './color-scheme';
import type { DateRange } from './date-range.type';
import type { Granularity } from './granularity.type';
@ -16,6 +17,7 @@ export type {
AccessWithGranteeUser,
AccountWithPlatform,
AccountWithValue,
AnalyticsEventType,
ColorScheme,
DateRange,
Granularity,

7
prisma/schema.prisma

@ -46,6 +46,13 @@ model Analytics {
User User @relation(fields: [userId], references: [id])
}
model AnalyticsEvent {
createdAt DateTime @default(now())
data Json?
id String @id @default(uuid())
type String
}
model AuthDevice {
createdAt DateTime @default(now())
credentialId Bytes

Loading…
Cancel
Save