Feature/extend analytics by country (#1661)
* Extend analytics by country * Fix Upgrade Plan button of subscription interstitial * Update changelog
This commit is contained in:
parent
e4468252c6
commit
1b2f8e5586
@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Migrated the style of `GfMarketDataDetailDialogModule` to `@angular/material` `15` (mdc)
|
- Migrated the style of `GfMarketDataDetailDialogModule` to `@angular/material` `15` (mdc)
|
||||||
- Upgraded `ng-extract-i18n-merge` from version `2.1.2` to `2.5.0`
|
- Upgraded `ng-extract-i18n-merge` from version `2.1.2` to `2.5.0`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed the `Upgrade Plan` button of the interstitial for the subscription
|
||||||
|
|
||||||
## 1.231.0 - 2023-02-04
|
## 1.231.0 - 2023-02-04
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -244,6 +244,7 @@ export class AdminService {
|
|||||||
Analytics: {
|
Analytics: {
|
||||||
select: {
|
select: {
|
||||||
activityCount: true,
|
activityCount: true,
|
||||||
|
country: true,
|
||||||
updatedAt: true
|
updatedAt: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -277,6 +278,7 @@ export class AdminService {
|
|||||||
id,
|
id,
|
||||||
subscription,
|
subscription,
|
||||||
accountCount: _count.Account || 0,
|
accountCount: _count.Account || 0,
|
||||||
|
country: Analytics.country,
|
||||||
lastActivity: Analytics.updatedAt,
|
lastActivity: Analytics.updatedAt,
|
||||||
transactionCount: _count.Order || 0
|
transactionCount: _count.Order || 0
|
||||||
};
|
};
|
||||||
|
@ -61,8 +61,10 @@ export class AuthService {
|
|||||||
|
|
||||||
// Create new user if not found
|
// Create new user if not found
|
||||||
user = await this.userService.createUser({
|
user = await this.userService.createUser({
|
||||||
provider,
|
data: {
|
||||||
thirdPartyId: principalId
|
provider,
|
||||||
|
thirdPartyId: principalId
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,8 +98,10 @@ export class AuthService {
|
|||||||
|
|
||||||
// Create new user if not found
|
// Create new user if not found
|
||||||
user = await this.userService.createUser({
|
user = await this.userService.createUser({
|
||||||
provider,
|
data: {
|
||||||
thirdPartyId
|
provider,
|
||||||
|
thirdPartyId
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
7
apps/api/src/app/user/create-user.dto.ts
Normal file
7
apps/api/src/app/user/create-user.dto.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class CreateUserDto {
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
country?: string;
|
||||||
|
}
|
@ -22,6 +22,7 @@ import { User as UserModel } from '@prisma/client';
|
|||||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||||
import { size } from 'lodash';
|
import { size } from 'lodash';
|
||||||
|
|
||||||
|
import { CreateUserDto } from './create-user.dto';
|
||||||
import { UserItem } from './interfaces/user-item.interface';
|
import { UserItem } from './interfaces/user-item.interface';
|
||||||
import { UpdateUserSettingDto } from './update-user-setting.dto';
|
import { UpdateUserSettingDto } from './update-user-setting.dto';
|
||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
@ -65,7 +66,7 @@ export class UserController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
public async signupUser(): Promise<UserItem> {
|
public async signupUser(@Body() data: CreateUserDto): Promise<UserItem> {
|
||||||
const isUserSignupEnabled =
|
const isUserSignupEnabled =
|
||||||
await this.propertyService.isUserSignupEnabled();
|
await this.propertyService.isUserSignupEnabled();
|
||||||
|
|
||||||
@ -79,7 +80,8 @@ export class UserController {
|
|||||||
const hasAdmin = await this.userService.hasAdmin();
|
const hasAdmin = await this.userService.hasAdmin();
|
||||||
|
|
||||||
const { accessToken, id, role } = await this.userService.createUser({
|
const { accessToken, id, role } = await this.userService.createUser({
|
||||||
role: hasAdmin ? 'USER' : 'ADMIN'
|
country: data.country,
|
||||||
|
data: { role: hasAdmin ? 'USER' : 'ADMIN' }
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -18,6 +18,8 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { Prisma, Role, User } from '@prisma/client';
|
import { Prisma, Role, User } from '@prisma/client';
|
||||||
import { sortBy } from 'lodash';
|
import { sortBy } from 'lodash';
|
||||||
|
|
||||||
|
import { CreateUserDto } from './create-user.dto';
|
||||||
|
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -231,7 +233,10 @@ export class UserService {
|
|||||||
return hash.digest('hex');
|
return hash.digest('hex');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async createUser(data: Prisma.UserCreateInput): Promise<User> {
|
public async createUser({
|
||||||
|
country,
|
||||||
|
data
|
||||||
|
}: CreateUserDto & { data: Prisma.UserCreateInput }): Promise<User> {
|
||||||
if (!data?.provider) {
|
if (!data?.provider) {
|
||||||
data.provider = 'ANONYMOUS';
|
data.provider = 'ANONYMOUS';
|
||||||
}
|
}
|
||||||
@ -256,6 +261,15 @@ export class UserService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
|
||||||
|
await this.prismaService.analytics.create({
|
||||||
|
data: {
|
||||||
|
country,
|
||||||
|
User: { connect: { id: user.id } }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (data.provider === 'ANONYMOUS') {
|
if (data.provider === 'ANONYMOUS') {
|
||||||
const accessToken = this.createAccessToken(
|
const accessToken = this.createAccessToken(
|
||||||
user.id,
|
user.id,
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import { AdminData, User } from '@ghostfolio/common/interfaces';
|
import { AdminData, InfoItem, User } from '@ghostfolio/common/interfaces';
|
||||||
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
import {
|
import {
|
||||||
differenceInSeconds,
|
differenceInSeconds,
|
||||||
formatDistanceToNowStrict,
|
formatDistanceToNowStrict,
|
||||||
@ -16,6 +17,8 @@ import { takeUntil } from 'rxjs/operators';
|
|||||||
templateUrl: './admin-users.html'
|
templateUrl: './admin-users.html'
|
||||||
})
|
})
|
||||||
export class AdminUsersComponent implements OnDestroy, OnInit {
|
export class AdminUsersComponent implements OnDestroy, OnInit {
|
||||||
|
public hasPermissionForSubscription: boolean;
|
||||||
|
public info: InfoItem;
|
||||||
public user: User;
|
public user: User;
|
||||||
public users: AdminData['users'];
|
public users: AdminData['users'];
|
||||||
|
|
||||||
@ -26,6 +29,13 @@ export class AdminUsersComponent implements OnDestroy, OnInit {
|
|||||||
private dataService: DataService,
|
private dataService: DataService,
|
||||||
private userService: UserService
|
private userService: UserService
|
||||||
) {
|
) {
|
||||||
|
this.info = this.dataService.fetchInfo();
|
||||||
|
|
||||||
|
this.hasPermissionForSubscription = hasPermission(
|
||||||
|
this.info?.globalPermissions,
|
||||||
|
permissions.enableSubscription
|
||||||
|
);
|
||||||
|
|
||||||
this.userService.stateChanged
|
this.userService.stateChanged
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((state) => {
|
.subscribe((state) => {
|
||||||
|
@ -7,7 +7,13 @@
|
|||||||
<tr class="mat-header-row">
|
<tr class="mat-header-row">
|
||||||
<th class="mat-header-cell px-1 py-2 text-right">#</th>
|
<th class="mat-header-cell px-1 py-2 text-right">#</th>
|
||||||
<th class="mat-header-cell px-1 py-2" i18n>User</th>
|
<th class="mat-header-cell px-1 py-2" i18n>User</th>
|
||||||
<th class="mat-header-cell px-1 py-2 text-right">
|
<th
|
||||||
|
*ngIf="hasPermissionForSubscription"
|
||||||
|
class="mat-header-cell px-1 py-2 text-right"
|
||||||
|
>
|
||||||
|
<ng-container i18n>Country</ng-container>
|
||||||
|
</th>
|
||||||
|
<th class="mat-header-cell px-1 py-2">
|
||||||
<ng-container i18n>Registration</ng-container>
|
<ng-container i18n>Registration</ng-container>
|
||||||
</th>
|
</th>
|
||||||
<th class="mat-header-cell px-1 py-2 text-right">
|
<th class="mat-header-cell px-1 py-2 text-right">
|
||||||
@ -16,7 +22,10 @@
|
|||||||
<th class="mat-header-cell px-1 py-2 text-right">
|
<th class="mat-header-cell px-1 py-2 text-right">
|
||||||
<ng-container i18n>Activities</ng-container>
|
<ng-container i18n>Activities</ng-container>
|
||||||
</th>
|
</th>
|
||||||
<th class="mat-header-cell px-1 py-2 text-right">
|
<th
|
||||||
|
*ngIf="hasPermissionForSubscription"
|
||||||
|
class="mat-header-cell px-1 py-2 text-right"
|
||||||
|
>
|
||||||
<ng-container i18n>Engagement per Day</ng-container>
|
<ng-container i18n>Engagement per Day</ng-container>
|
||||||
</th>
|
</th>
|
||||||
<th class="mat-header-cell px-1 py-2" i18n>Last Request</th>
|
<th class="mat-header-cell px-1 py-2" i18n>Last Request</th>
|
||||||
@ -41,7 +50,13 @@
|
|||||||
></gf-premium-indicator>
|
></gf-premium-indicator>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="mat-cell px-1 py-2 text-right">
|
<td
|
||||||
|
*ngIf="hasPermissionForSubscription"
|
||||||
|
class="mat-cell px-1 py-2 text-right"
|
||||||
|
>
|
||||||
|
{{ userItem.country }}
|
||||||
|
</td>
|
||||||
|
<td class="mat-cell px-1 py-2">
|
||||||
{{ formatDistanceToNow(userItem.createdAt) }}
|
{{ formatDistanceToNow(userItem.createdAt) }}
|
||||||
</td>
|
</td>
|
||||||
<td class="mat-cell px-1 py-2 text-right">
|
<td class="mat-cell px-1 py-2 text-right">
|
||||||
@ -58,7 +73,10 @@
|
|||||||
[value]="userItem.transactionCount"
|
[value]="userItem.transactionCount"
|
||||||
></gf-value>
|
></gf-value>
|
||||||
</td>
|
</td>
|
||||||
<td class="mat-cell px-1 py-2 text-right">
|
<td
|
||||||
|
*ngIf="hasPermissionForSubscription"
|
||||||
|
class="mat-cell px-1 py-2 text-right"
|
||||||
|
>
|
||||||
<gf-value
|
<gf-value
|
||||||
class="d-inline-block justify-content-end"
|
class="d-inline-block justify-content-end"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
|
@ -19,7 +19,7 @@ export class SubscriptionInterstitialDialog {
|
|||||||
public dialogRef: MatDialogRef<SubscriptionInterstitialDialog>
|
public dialogRef: MatDialogRef<SubscriptionInterstitialDialog>
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public onCancel() {
|
public closeDialog() {
|
||||||
this.dialogRef.close({});
|
this.dialogRef.close({});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,8 +34,13 @@
|
|||||||
<p>Refine your personal investment strategy now.</p>
|
<p>Refine your personal investment strategy now.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="justify-content-end" mat-dialog-actions>
|
<div class="justify-content-end" mat-dialog-actions>
|
||||||
<button i18n mat-button (click)="onCancel()">Skip</button>
|
<button i18n mat-button (click)="closeDialog()">Skip</button>
|
||||||
<a color="primary" mat-flat-button [routerLink]="['/pricing']">
|
<a
|
||||||
|
color="primary"
|
||||||
|
mat-flat-button
|
||||||
|
[routerLink]="['/pricing']"
|
||||||
|
(click)="closeDialog()"
|
||||||
|
>
|
||||||
<span i18n>Upgrade Plan</span>
|
<span i18n>Upgrade Plan</span>
|
||||||
<ion-icon class="ml-1" name="arrow-forward-outline"></ion-icon>
|
<ion-icon class="ml-1" name="arrow-forward-outline"></ion-icon>
|
||||||
</a>
|
</a>
|
||||||
|
@ -4,6 +4,7 @@ import { Router } from '@angular/router';
|
|||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { InternetIdentityService } from '@ghostfolio/client/services/internet-identity.service';
|
import { InternetIdentityService } from '@ghostfolio/client/services/internet-identity.service';
|
||||||
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.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 { InfoItem, LineChartItem } from '@ghostfolio/common/interfaces';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
import { Role } from '@prisma/client';
|
import { Role } from '@prisma/client';
|
||||||
@ -37,7 +38,8 @@ export class RegisterPageComponent implements OnDestroy, OnInit {
|
|||||||
private dialog: MatDialog,
|
private dialog: MatDialog,
|
||||||
private internetIdentityService: InternetIdentityService,
|
private internetIdentityService: InternetIdentityService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private tokenStorageService: TokenStorageService
|
private tokenStorageService: TokenStorageService,
|
||||||
|
private userService: UserService
|
||||||
) {
|
) {
|
||||||
this.info = this.dataService.fetchInfo();
|
this.info = this.dataService.fetchInfo();
|
||||||
|
|
||||||
@ -61,7 +63,7 @@ export class RegisterPageComponent implements OnDestroy, OnInit {
|
|||||||
|
|
||||||
public async createAccount() {
|
public async createAccount() {
|
||||||
this.dataService
|
this.dataService
|
||||||
.postUser()
|
.postUser({ country: this.userService.getCountry() })
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(({ accessToken, authToken, role }) => {
|
.subscribe(({ accessToken, authToken, role }) => {
|
||||||
this.openShowAccessTokenDialog(accessToken, authToken, role);
|
this.openShowAccessTokenDialog(accessToken, authToken, role);
|
||||||
|
@ -405,8 +405,8 @@ export class DataService {
|
|||||||
return this.http.post<OrderModel>(`/api/v1/order`, aOrder);
|
return this.http.post<OrderModel>(`/api/v1/order`, aOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
public postUser() {
|
public postUser({ country }: { country: string }) {
|
||||||
return this.http.post<UserItem>(`/api/v1/user`, {});
|
return this.http.post<UserItem>(`/api/v1/user`, { country });
|
||||||
}
|
}
|
||||||
|
|
||||||
public putAccount(aAccount: UpdateAccountDto) {
|
public putAccount(aAccount: UpdateAccountDto) {
|
||||||
|
@ -6,6 +6,7 @@ import { SubscriptionInterstitialDialogParams } from '@ghostfolio/client/compone
|
|||||||
import { SubscriptionInterstitialDialog } from '@ghostfolio/client/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component';
|
import { SubscriptionInterstitialDialog } from '@ghostfolio/client/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component';
|
||||||
import { User } from '@ghostfolio/common/interfaces';
|
import { User } from '@ghostfolio/common/interfaces';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
|
import { timezoneCitiesToCountries } from '@ghostfolio/common/timezone-cities-to-countries';
|
||||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||||
import { Subject, of } from 'rxjs';
|
import { Subject, of } from 'rxjs';
|
||||||
import { throwError } 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() {
|
public remove() {
|
||||||
this.setState({ user: null }, UserStoreActions.RemoveUser);
|
this.setState({ user: null }, UserStoreActions.RemoveUser);
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ export interface AdminData {
|
|||||||
userCount: number;
|
userCount: number;
|
||||||
users: {
|
users: {
|
||||||
accountCount: number;
|
accountCount: number;
|
||||||
|
country: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
engagement: number;
|
engagement: number;
|
||||||
id: string;
|
id: string;
|
||||||
|
426
libs/common/src/lib/timezone-cities-to-countries.ts
Normal file
426
libs/common/src/lib/timezone-cities-to-countries.ts
Normal file
@ -0,0 +1,426 @@
|
|||||||
|
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'
|
||||||
|
};
|
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Analytics" ADD COLUMN "country" TEXT;
|
@ -41,6 +41,7 @@ model Account {
|
|||||||
|
|
||||||
model Analytics {
|
model Analytics {
|
||||||
activityCount Int @default(0)
|
activityCount Int @default(0)
|
||||||
|
country String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
userId String @id
|
userId String @id
|
||||||
User User @relation(fields: [userId], references: [id])
|
User User @relation(fields: [userId], references: [id])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user