Browse Source

Update permissions and add discount

pull/178/head
Thomas 4 years ago
parent
commit
2ae2470a33
  1. 7
      apps/api/src/app/subscription/subscription.controller.ts
  2. 18
      apps/api/src/app/subscription/subscription.service.ts
  3. 7
      apps/api/src/app/user/interfaces/user-settings-params.interface.ts
  4. 19
      apps/api/src/app/user/user.controller.ts
  5. 35
      apps/api/src/app/user/user.service.ts
  6. 18
      apps/client/src/app/pages/account/account-page.component.ts
  7. 11
      apps/client/src/app/pages/account/account-page.html
  8. 2
      apps/client/src/app/pages/pricing/pricing-page.component.ts
  9. 10
      apps/client/src/app/pages/pricing/pricing-page.html
  10. 11
      apps/client/src/app/services/data.service.ts
  11. 2
      libs/common/src/lib/interfaces/subscription.interface.ts
  12. 4
      libs/common/src/lib/interfaces/user-settings.interface.ts
  13. 1
      libs/common/src/lib/interfaces/user-with-settings.ts
  14. 9
      libs/common/src/lib/permissions.ts

7
apps/api/src/app/subscription/subscription.controller.ts

@ -34,11 +34,14 @@ export class SubscriptionController {
res.redirect(`${this.configurationService.get('ROOT_URL')}/account`); res.redirect(`${this.configurationService.get('ROOT_URL')}/account`);
} }
@Post('stripe/checkout-session/create') @Post('stripe/checkout-session')
@UseGuards(AuthGuard('jwt')) @UseGuards(AuthGuard('jwt'))
public async createCheckoutSession(@Body() { priceId }: { priceId: string }) { public async createCheckoutSession(
@Body() { couponId, priceId }: { couponId: string; priceId: string }
) {
try { try {
return await this.subscriptionService.createCheckoutSession({ return await this.subscriptionService.createCheckoutSession({
couponId,
priceId, priceId,
userId: this.request.user.id userId: this.request.user.id
}); });

18
apps/api/src/app/subscription/subscription.service.ts

@ -21,13 +21,15 @@ export class SubscriptionService {
} }
public async createCheckoutSession({ public async createCheckoutSession({
couponId,
priceId, priceId,
userId userId
}: { }: {
couponId?: string;
priceId: string; priceId: string;
userId: string; userId: string;
}) { }) {
const session = await this.stripe.checkout.sessions.create({ const checkoutSessionCreateParams: Stripe.Checkout.SessionCreateParams = {
cancel_url: `${this.configurationService.get('ROOT_URL')}/account`, cancel_url: `${this.configurationService.get('ROOT_URL')}/account`,
client_reference_id: userId, client_reference_id: userId,
line_items: [ line_items: [
@ -44,7 +46,19 @@ export class SubscriptionService {
success_url: `${this.configurationService.get( success_url: `${this.configurationService.get(
'ROOT_URL' 'ROOT_URL'
)}/api/subscription/stripe/callback?checkoutSessionId={CHECKOUT_SESSION_ID}` )}/api/subscription/stripe/callback?checkoutSessionId={CHECKOUT_SESSION_ID}`
}); };
if (couponId) {
checkoutSessionCreateParams.discounts = [
{
coupon: couponId
}
];
}
const session = await this.stripe.checkout.sessions.create(
checkoutSessionCreateParams
);
return { return {
sessionId: session.id sessionId: session.id

7
apps/api/src/app/user/interfaces/user-settings-params.interface.ts

@ -0,0 +1,7 @@
import { Currency, ViewMode } from '@prisma/client';
export interface UserSettingsParams {
currency?: Currency;
userId: string;
viewMode?: ViewMode;
}

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

@ -25,6 +25,7 @@ import { User as UserModel } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { UserItem } from './interfaces/user-item.interface'; import { UserItem } from './interfaces/user-item.interface';
import { UserSettingsParams } from './interfaces/user-settings-params.interface';
import { UpdateUserSettingsDto } from './update-user-settings.dto'; import { UpdateUserSettingsDto } from './update-user-settings.dto';
import { UserService } from './user.service'; import { UserService } from './user.service';
@ -92,10 +93,20 @@ export class UserController {
); );
} }
return await this.userService.updateUserSettings({ const userSettings: UserSettingsParams = {
currency: data.baseCurrency, currency: data.baseCurrency,
userId: this.request.user.id, userId: this.request.user.id
viewMode: data.viewMode };
});
if (
hasPermission(
getPermissions(this.request.user.role),
permissions.updateViewMode
)
) {
userSettings.viewMode = data.viewMode;
}
return await this.userService.updateUserSettings(userSettings);
} }
} }

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

@ -1,13 +1,13 @@
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';
import { resetHours } from '@ghostfolio/common/helper';
import { User as IUser, UserWithSettings } from '@ghostfolio/common/interfaces'; import { User as IUser, UserWithSettings } from '@ghostfolio/common/interfaces';
import { getPermissions, permissions } from '@ghostfolio/common/permissions'; 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 { add, isBefore } from 'date-fns'; import { isBefore } from 'date-fns';
import { UserSettingsParams } from './interfaces/user-settings-params.interface';
const crypto = require('crypto'); const crypto = require('crypto');
@ -24,7 +24,7 @@ export class UserService {
Account, Account,
alias, alias,
id, id,
role, permissions,
Settings, Settings,
subscription subscription
}: UserWithSettings): Promise<IUser> { }: UserWithSettings): Promise<IUser> {
@ -36,15 +36,10 @@ export class UserService {
where: { GranteeUser: { id } } where: { GranteeUser: { id } }
}); });
const currentPermissions = getPermissions(role);
if (this.configurationService.get('ENABLE_FEATURE_FEAR_AND_GREED_INDEX')) {
currentPermissions.push(permissions.accessFearAndGreedIndex);
}
return { return {
alias, alias,
id, id,
permissions,
subscription, subscription,
access: access.map((accessItem) => { access: access.map((accessItem) => {
return { return {
@ -53,7 +48,6 @@ export class UserService {
}; };
}), }),
accounts: Account, accounts: Account,
permissions: currentPermissions,
settings: { settings: {
locale, locale,
baseCurrency: Settings?.currency ?? UserService.DEFAULT_CURRENCY, baseCurrency: Settings?.currency ?? UserService.DEFAULT_CURRENCY,
@ -72,6 +66,14 @@ export class UserService {
const user: UserWithSettings = userFromDatabase; const user: UserWithSettings = userFromDatabase;
const currentPermissions = getPermissions(userFromDatabase.role);
if (this.configurationService.get('ENABLE_FEATURE_FEAR_AND_GREED_INDEX')) {
currentPermissions.push(permissions.accessFearAndGreedIndex);
}
user.permissions = currentPermissions;
if (userFromDatabase?.Settings) { if (userFromDatabase?.Settings) {
if (!userFromDatabase.Settings.currency) { if (!userFromDatabase.Settings.currency) {
// Set default currency if needed // Set default currency if needed
@ -106,6 +108,13 @@ export class UserService {
type: SubscriptionType.Basic type: SubscriptionType.Basic
}; };
} }
if (user.subscription.type === SubscriptionType.Basic) {
user.permissions = user.permissions.filter((permission) => {
return permission !== permissions.updateViewMode;
});
user.Settings.viewMode = ViewMode.ZEN;
}
} }
return user; return user;
@ -213,11 +222,7 @@ export class UserService {
currency, currency,
userId, userId,
viewMode viewMode
}: { }: UserSettingsParams) {
currency?: Currency;
userId: string;
viewMode?: ViewMode;
}) {
await this.prisma.settings.upsert({ await this.prisma.settings.upsert({
create: { create: {
currency, currency,

18
apps/client/src/app/pages/account/account-page.component.ts

@ -31,8 +31,11 @@ export class AccountPageComponent implements OnDestroy, OnInit {
public accesses: Access[]; public accesses: Access[];
public baseCurrency: Currency; public baseCurrency: Currency;
public coupon: number;
public couponId: string;
public currencies: Currency[] = []; public currencies: Currency[] = [];
public defaultDateFormat = DEFAULT_DATE_FORMAT; public defaultDateFormat = DEFAULT_DATE_FORMAT;
public hasPermissionToUpdateViewMode: boolean;
public hasPermissionToUpdateUserSettings: boolean; public hasPermissionToUpdateUserSettings: boolean;
public price: number; public price: number;
public priceId: string; public priceId: string;
@ -54,9 +57,11 @@ export class AccountPageComponent implements OnDestroy, OnInit {
.fetchInfo() .fetchInfo()
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ currencies, subscriptions }) => { .subscribe(({ currencies, subscriptions }) => {
this.coupon = subscriptions?.[0]?.coupon;
this.couponId = subscriptions?.[0]?.couponId;
this.currencies = currencies; this.currencies = currencies;
this.price = subscriptions?.[0].price; this.price = subscriptions?.[0]?.price;
this.priceId = subscriptions?.[0].priceId; this.priceId = subscriptions?.[0]?.priceId;
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
@ -72,6 +77,11 @@ export class AccountPageComponent implements OnDestroy, OnInit {
permissions.updateUserSettings permissions.updateUserSettings
); );
this.hasPermissionToUpdateViewMode = hasPermission(
this.user.permissions,
permissions.updateViewMode
);
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
} }
}); });
@ -107,9 +117,9 @@ export class AccountPageComponent implements OnDestroy, OnInit {
}); });
} }
public onCheckout(priceId: string) { public onCheckout() {
this.dataService this.dataService
.createCheckoutSession(priceId) .createCheckoutSession({ couponId: this.couponId, priceId: this.priceId })
.pipe( .pipe(
switchMap(({ sessionId }: { sessionId: string }) => { switchMap(({ sessionId }: { sessionId: string }) => {
return this.stripeService.redirectToCheckout({ return this.stripeService.redirectToCheckout({

11
apps/client/src/app/pages/account/account-page.html

@ -32,8 +32,13 @@
Upgrade Upgrade
</button> </button>
<div *ngIf="price" class="mt-1"> <div *ngIf="price" class="mt-1">
{{ user.settings.baseCurrency }} {{ price }} {{ user.settings.baseCurrency }}
<span i18n>per year</span> <ng-container *ngIf="coupon"
>{{ price - coupon }}
<del>{{ user.settings.baseCurrency }} {{ price }}</del>
</ng-container>
<ng-container *ngIf="!coupon">{{ price }}</ng-container>
<span i18n> per year</span>
</div> </div>
</div> </div>
</div> </div>
@ -61,7 +66,7 @@
<mat-label i18n>View Mode</mat-label> <mat-label i18n>View Mode</mat-label>
<mat-select <mat-select
name="viewMode" name="viewMode"
[disabled]="!hasPermissionToUpdateUserSettings" [disabled]="!hasPermissionToUpdateViewMode"
[value]="user.settings.viewMode" [value]="user.settings.viewMode"
(selectionChange)="onChangeUserSettings('viewMode', $event.value)" (selectionChange)="onChangeUserSettings('viewMode', $event.value)"
> >

2
apps/client/src/app/pages/pricing/pricing-page.component.ts

@ -13,6 +13,7 @@ import { takeUntil } from 'rxjs/operators';
}) })
export class PricingPageComponent implements OnInit { export class PricingPageComponent implements OnInit {
public baseCurrency = baseCurrency; public baseCurrency = baseCurrency;
public coupon: number;
public isLoggedIn: boolean; public isLoggedIn: boolean;
public price: number; public price: number;
public user: User; public user: User;
@ -31,6 +32,7 @@ export class PricingPageComponent implements OnInit {
.fetchInfo() .fetchInfo()
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ subscriptions }) => { .subscribe(({ subscriptions }) => {
this.coupon = this.price = subscriptions?.[0]?.coupon;
this.price = subscriptions?.[0]?.price; this.price = subscriptions?.[0]?.price;
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();

10
apps/client/src/app/pages/pricing/pricing-page.html

@ -179,8 +179,14 @@
<p class="h5 text-right" [hidden]="!price"> <p class="h5 text-right" [hidden]="!price">
<span class="font-weight-normal" <span class="font-weight-normal"
>{{ user?.settings.baseCurrency || baseCurrency }} >{{ user?.settings.baseCurrency || baseCurrency }}
<strong>{{ price }}</strong> <ng-container *ngIf="coupon"
per year</span ><strong>{{ price - coupon }} </strong>
<del>{{ user.settings.baseCurrency }} {{ price }}</del>
</ng-container>
<ng-container *ngIf="!coupon"
><strong>{{ price }}</strong></ng-container
>
<span i18n> per year</span></span
> >
</p> </p>
</mat-card> </mat-card>

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

@ -43,8 +43,15 @@ export class DataService {
private settingsStorageService: SettingsStorageService private settingsStorageService: SettingsStorageService
) {} ) {}
public createCheckoutSession(priceId) { public createCheckoutSession({
return this.http.post('/api/subscription/stripe/checkout-session/create', { couponId,
priceId
}: {
couponId?: string;
priceId: string;
}) {
return this.http.post('/api/subscription/stripe/checkout-session', {
couponId,
priceId priceId
}); });
} }

2
libs/common/src/lib/interfaces/subscription.interface.ts

@ -1,4 +1,6 @@
export interface Subscription { export interface Subscription {
coupon?: number;
couponId?: string;
price: number; price: number;
priceId: string; priceId: string;
} }

4
libs/common/src/lib/interfaces/user-settings.interface.ts

@ -1,7 +1,7 @@
import { Currency, ViewMode } from '@prisma/client'; import { Currency, ViewMode } from '@prisma/client';
export interface UserSettings { export interface UserSettings {
baseCurrency: Currency; baseCurrency?: Currency;
locale: string; locale: string;
viewMode: ViewMode; viewMode?: ViewMode;
} }

1
libs/common/src/lib/interfaces/user-with-settings.ts

@ -3,6 +3,7 @@ import { Account, Settings, User } from '@prisma/client';
export type UserWithSettings = User & { export type UserWithSettings = User & {
Account: Account[]; Account: Account[];
permissions?: string[];
Settings: Settings; Settings: Settings;
subscription?: { subscription?: {
expiresAt?: Date; expiresAt?: Date;

9
libs/common/src/lib/permissions.ts

@ -21,7 +21,8 @@ export const permissions = {
updateAccount: 'updateAccount', updateAccount: 'updateAccount',
updateAuthDevice: 'updateAuthDevice', updateAuthDevice: 'updateAuthDevice',
updateOrder: 'updateOrder', updateOrder: 'updateOrder',
updateUserSettings: 'updateUserSettings' updateUserSettings: 'updateUserSettings',
updateViewMode: 'updateViewMode'
}; };
export function hasPermission( export function hasPermission(
@ -46,7 +47,8 @@ export function getPermissions(aRole: Role): string[] {
permissions.updateAccount, permissions.updateAccount,
permissions.updateAuthDevice, permissions.updateAuthDevice,
permissions.updateOrder, permissions.updateOrder,
permissions.updateUserSettings permissions.updateUserSettings,
permissions.updateViewMode
]; ];
case 'DEMO': case 'DEMO':
@ -62,7 +64,8 @@ export function getPermissions(aRole: Role): string[] {
permissions.updateAccount, permissions.updateAccount,
permissions.updateAuthDevice, permissions.updateAuthDevice,
permissions.updateOrder, permissions.updateOrder,
permissions.updateUserSettings permissions.updateUserSettings,
permissions.updateViewMode
]; ];
default: default:

Loading…
Cancel
Save