Feature/add subscription type to the admin user table (#311)
* Add the subscription type to the user table in the admin control panel * Update changelog
This commit is contained in:
parent
2083d28d02
commit
98dac4052a
@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added the subscription type to the users table of the admin control panel
|
||||||
|
|
||||||
## 1.41.0 - 21.08.2021
|
## 1.41.0 - 21.08.2021
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.module';
|
||||||
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
|
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
|
||||||
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.module';
|
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.module';
|
||||||
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
||||||
@ -14,9 +15,11 @@ import { AdminService } from './admin.service';
|
|||||||
DataGatheringModule,
|
DataGatheringModule,
|
||||||
DataProviderModule,
|
DataProviderModule,
|
||||||
ExchangeRateDataModule,
|
ExchangeRateDataModule,
|
||||||
PrismaModule
|
PrismaModule,
|
||||||
|
SubscriptionModule
|
||||||
],
|
],
|
||||||
controllers: [AdminController],
|
controllers: [AdminController],
|
||||||
providers: [AdminService]
|
providers: [AdminService],
|
||||||
|
exports: [AdminService]
|
||||||
})
|
})
|
||||||
export class AdminModule {}
|
export class AdminModule {}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service';
|
||||||
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||||
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service';
|
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service';
|
||||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
||||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||||
@ -9,9 +11,11 @@ import { differenceInDays } from 'date-fns';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class AdminService {
|
export class AdminService {
|
||||||
public constructor(
|
public constructor(
|
||||||
|
private readonly configurationService: ConfigurationService,
|
||||||
private readonly dataGatheringService: DataGatheringService,
|
private readonly dataGatheringService: DataGatheringService,
|
||||||
private readonly exchangeRateDataService: ExchangeRateDataService,
|
private readonly exchangeRateDataService: ExchangeRateDataService,
|
||||||
private readonly prismaService: PrismaService
|
private readonly prismaService: PrismaService,
|
||||||
|
private readonly subscriptionService: SubscriptionService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async get(): Promise<AdminData> {
|
public async get(): Promise<AdminData> {
|
||||||
@ -107,7 +111,8 @@ export class AdminService {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
id: true
|
id: true,
|
||||||
|
Subscription: true
|
||||||
},
|
},
|
||||||
take: 30,
|
take: 30,
|
||||||
where: {
|
where: {
|
||||||
@ -118,16 +123,23 @@ export class AdminService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return usersWithAnalytics.map(
|
return usersWithAnalytics.map(
|
||||||
({ _count, alias, Analytics, createdAt, id }) => {
|
({ _count, alias, Analytics, createdAt, id, Subscription }) => {
|
||||||
const daysSinceRegistration =
|
const daysSinceRegistration =
|
||||||
differenceInDays(new Date(), createdAt) + 1;
|
differenceInDays(new Date(), createdAt) + 1;
|
||||||
const engagement = Analytics.activityCount / daysSinceRegistration;
|
const engagement = Analytics.activityCount / daysSinceRegistration;
|
||||||
|
|
||||||
|
const subscription = this.configurationService.get(
|
||||||
|
'ENABLE_FEATURE_SUBSCRIPTION'
|
||||||
|
)
|
||||||
|
? this.subscriptionService.getSubscription(Subscription)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
alias,
|
alias,
|
||||||
createdAt,
|
createdAt,
|
||||||
engagement,
|
engagement,
|
||||||
id,
|
id,
|
||||||
|
subscription,
|
||||||
accountCount: _count.Account || 0,
|
accountCount: _count.Account || 0,
|
||||||
lastActivity: Analytics.updatedAt,
|
lastActivity: Analytics.updatedAt,
|
||||||
transactionCount: _count.Order || 0
|
transactionCount: _count.Order || 0
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.service';
|
import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.service';
|
||||||
import { WebAuthService } from '@ghostfolio/api/app/auth/web-auth.service';
|
import { WebAuthService } from '@ghostfolio/api/app/auth/web-auth.service';
|
||||||
|
import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.module';
|
||||||
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
||||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||||
@ -17,7 +18,8 @@ import { JwtStrategy } from './jwt.strategy';
|
|||||||
JwtModule.register({
|
JwtModule.register({
|
||||||
secret: process.env.JWT_SECRET_KEY,
|
secret: process.env.JWT_SECRET_KEY,
|
||||||
signOptions: { expiresIn: '180 days' }
|
signOptions: { expiresIn: '180 days' }
|
||||||
})
|
}),
|
||||||
|
SubscriptionModule
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
AuthDeviceService,
|
AuthDeviceService,
|
||||||
|
@ -8,6 +8,7 @@ import { SubscriptionService } from './subscription.service';
|
|||||||
@Module({
|
@Module({
|
||||||
imports: [],
|
imports: [],
|
||||||
controllers: [SubscriptionController],
|
controllers: [SubscriptionController],
|
||||||
providers: [ConfigurationService, PrismaService, SubscriptionService]
|
providers: [ConfigurationService, PrismaService, SubscriptionService],
|
||||||
|
exports: [SubscriptionService]
|
||||||
})
|
})
|
||||||
export class SubscriptionModule {}
|
export class SubscriptionModule {}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||||
|
import { SubscriptionType } from '@ghostfolio/common/types/subscription.type';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { addDays } from 'date-fns';
|
import { Subscription } from '@prisma/client';
|
||||||
|
import { addDays, isBefore } from 'date-fns';
|
||||||
import Stripe from 'stripe';
|
import Stripe from 'stripe';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -86,4 +88,23 @@ export class SubscriptionService {
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getSubscription(aSubscriptions: Subscription[]) {
|
||||||
|
if (aSubscriptions.length > 0) {
|
||||||
|
const latestSubscription = aSubscriptions.reduce((a, b) => {
|
||||||
|
return new Date(a.expiresAt) > new Date(b.expiresAt) ? a : b;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
expiresAt: latestSubscription.expiresAt,
|
||||||
|
type: isBefore(new Date(), latestSubscription.expiresAt)
|
||||||
|
? SubscriptionType.Premium
|
||||||
|
: SubscriptionType.Basic
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
type: SubscriptionType.Basic
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.module';
|
||||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
@ -11,7 +12,8 @@ import { UserService } from './user.service';
|
|||||||
JwtModule.register({
|
JwtModule.register({
|
||||||
secret: process.env.JWT_SECRET_KEY,
|
secret: process.env.JWT_SECRET_KEY,
|
||||||
signOptions: { expiresIn: '30 days' }
|
signOptions: { expiresIn: '30 days' }
|
||||||
})
|
}),
|
||||||
|
SubscriptionModule
|
||||||
],
|
],
|
||||||
controllers: [UserController],
|
controllers: [UserController],
|
||||||
providers: [ConfigurationService, PrismaService, UserService],
|
providers: [ConfigurationService, PrismaService, UserService],
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service';
|
||||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||||
import { locale } from '@ghostfolio/common/config';
|
import { locale } from '@ghostfolio/common/config';
|
||||||
@ -6,7 +7,6 @@ import { getPermissions, permissions } from '@ghostfolio/common/permissions';
|
|||||||
import { SubscriptionType } from '@ghostfolio/common/types/subscription.type';
|
import { SubscriptionType } from '@ghostfolio/common/types/subscription.type';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Currency, Prisma, Provider, User, ViewMode } from '@prisma/client';
|
import { Currency, Prisma, Provider, User, ViewMode } from '@prisma/client';
|
||||||
import { isBefore } from 'date-fns';
|
|
||||||
|
|
||||||
import { UserSettingsParams } from './interfaces/user-settings-params.interface';
|
import { UserSettingsParams } from './interfaces/user-settings-params.interface';
|
||||||
import { UserSettings } from './interfaces/user-settings.interface';
|
import { UserSettings } from './interfaces/user-settings.interface';
|
||||||
@ -19,7 +19,8 @@ export class UserService {
|
|||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly configurationService: ConfigurationService,
|
private readonly configurationService: ConfigurationService,
|
||||||
private readonly prismaService: PrismaService
|
private readonly prismaService: PrismaService,
|
||||||
|
private readonly subscriptionService: SubscriptionService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async getUser({
|
public async getUser({
|
||||||
@ -98,25 +99,10 @@ export class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
|
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
|
||||||
if (userFromDatabase?.Subscription?.length > 0) {
|
user.subscription = this.subscriptionService.getSubscription(
|
||||||
const latestSubscription = userFromDatabase.Subscription.reduce(
|
userFromDatabase?.Subscription
|
||||||
(a, b) => {
|
|
||||||
return new Date(a.expiresAt) > new Date(b.expiresAt) ? a : b;
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
user.subscription = {
|
|
||||||
expiresAt: latestSubscription.expiresAt,
|
|
||||||
type: isBefore(new Date(), latestSubscription.expiresAt)
|
|
||||||
? SubscriptionType.Premium
|
|
||||||
: SubscriptionType.Basic
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
user.subscription = {
|
|
||||||
type: SubscriptionType.Basic
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.subscription.type === SubscriptionType.Basic) {
|
if (user.subscription.type === SubscriptionType.Basic) {
|
||||||
user.permissions = user.permissions.filter((permission) => {
|
user.permissions = user.permissions.filter((permission) => {
|
||||||
return permission !== permissions.updateViewMode;
|
return permission !== permissions.updateViewMode;
|
||||||
|
@ -116,7 +116,20 @@
|
|||||||
<tr *ngFor="let userItem of users; let i = index" class="mat-row">
|
<tr *ngFor="let userItem of users; let i = index" class="mat-row">
|
||||||
<td class="mat-cell px-1 py-2 text-right">{{ i + 1 }}</td>
|
<td class="mat-cell px-1 py-2 text-right">{{ i + 1 }}</td>
|
||||||
<td class="mat-cell px-1 py-2">
|
<td class="mat-cell px-1 py-2">
|
||||||
{{ userItem.alias || userItem.id }}
|
<div class="d-flex align-items-center">
|
||||||
|
<span class="d-none d-sm-inline-block"
|
||||||
|
>{{ userItem.alias || userItem.id }}</span
|
||||||
|
>
|
||||||
|
<span class="d-inline-block d-sm-none"
|
||||||
|
>{{ userItem.alias || (userItem.id | slice:0:5) +
|
||||||
|
'...' }}</span
|
||||||
|
>
|
||||||
|
<ion-icon
|
||||||
|
*ngIf="userItem?.subscription?.type === 'Premium'"
|
||||||
|
class="ml-1 text-muted"
|
||||||
|
name="diamond-outline"
|
||||||
|
></ion-icon>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="mat-cell px-1 py-2 text-right">
|
<td class="mat-cell px-1 py-2 text-right">
|
||||||
{{ formatDistanceToNow(userItem.createdAt) }}
|
{{ formatDistanceToNow(userItem.createdAt) }}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user