From 99655604d9e53b8763d55b6d654db012ff2fee3a Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 6 Mar 2022 12:26:04 +0100 Subject: [PATCH] Feature/add support for coupon duration (#743) * Add support for coupon duration * Update changelog --- CHANGELOG.md | 4 +++ apps/api/src/app/import/import.service.ts | 2 +- .../subscription/subscription.controller.ts | 17 ++++++----- .../app/subscription/subscription.service.ts | 19 ++++++++---- .../admin-overview.component.ts | 11 ++++++- .../admin-overview/admin-overview.html | 30 +++++++++++++++---- .../admin-overview/admin-overview.module.ts | 7 ++++- .../admin-overview/admin-overview.scss | 6 ++++ .../src/lib/interfaces/coupon.interface.ts | 3 ++ package.json | 1 + yarn.lock | 5 ++++ 11 files changed, 83 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 440e41418..3275fb54b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added support for setting a duration in the coupon system + ### Changed - Upgraded `ngx-skeleton-loader` from version `2.9.1` to `5.0.0` diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index d68e60bac..3ddd29040 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -1,10 +1,10 @@ import { AccountService } from '@ghostfolio/api/app/account/account.service'; +import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { OrderService } from '@ghostfolio/api/app/order/order.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { Injectable } from '@nestjs/common'; import { isSameDay, parseISO } from 'date-fns'; -import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; @Injectable() export class ImportService { diff --git a/apps/api/src/app/subscription/subscription.controller.ts b/apps/api/src/app/subscription/subscription.controller.ts index 1f68c8f72..e064b01a5 100644 --- a/apps/api/src/app/subscription/subscription.controller.ts +++ b/apps/api/src/app/subscription/subscription.controller.ts @@ -46,22 +46,25 @@ export class SubscriptionController { ((await this.propertyService.getByKey(PROPERTY_COUPONS)) as Coupon[]) ?? []; - const isValid = coupons.some((coupon) => { - return coupon.code === couponCode; + const coupon = coupons.find((currentCoupon) => { + return currentCoupon.code === couponCode; }); - if (!isValid) { + if (coupon === undefined) { throw new HttpException( getReasonPhrase(StatusCodes.BAD_REQUEST), StatusCodes.BAD_REQUEST ); } - await this.subscriptionService.createSubscription(this.request.user.id); + await this.subscriptionService.createSubscription({ + duration: coupon.duration, + userId: this.request.user.id + }); // Destroy coupon - coupons = coupons.filter((coupon) => { - return coupon.code !== couponCode; + coupons = coupons.filter((currentCoupon) => { + return currentCoupon.code !== couponCode; }); await this.propertyService.put({ key: PROPERTY_COUPONS, @@ -69,7 +72,7 @@ export class SubscriptionController { }); Logger.log( - `Subscription for user '${this.request.user.id}' has been created with coupon` + `Subscription for user '${this.request.user.id}' has been created with a coupon for ${coupon.duration}` ); return { diff --git a/apps/api/src/app/subscription/subscription.service.ts b/apps/api/src/app/subscription/subscription.service.ts index 97e910e14..1111ac0e0 100644 --- a/apps/api/src/app/subscription/subscription.service.ts +++ b/apps/api/src/app/subscription/subscription.service.ts @@ -2,8 +2,9 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration.ser import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { SubscriptionType } from '@ghostfolio/common/types/subscription.type'; import { Injectable, Logger } from '@nestjs/common'; -import { Subscription, User } from '@prisma/client'; -import { addDays, isBefore } from 'date-fns'; +import { Subscription } from '@prisma/client'; +import { addMilliseconds, isBefore } from 'date-fns'; +import ms, { StringValue } from 'ms'; import Stripe from 'stripe'; @Injectable() @@ -64,13 +65,19 @@ export class SubscriptionService { }; } - public async createSubscription(aUserId: string) { + public async createSubscription({ + duration = '1 year', + userId + }: { + duration?: StringValue; + userId: string; + }) { await this.prismaService.subscription.create({ data: { - expiresAt: addDays(new Date(), 365), + expiresAt: addMilliseconds(new Date(), ms(duration)), User: { connect: { - id: aUserId + id: userId } } } @@ -83,7 +90,7 @@ export class SubscriptionService { aCheckoutSessionId ); - await this.createSubscription(session.client_reference_id); + await this.createSubscription({ userId: session.client_reference_id }); await this.stripe.customers.update(session.customer as string, { description: session.client_reference_id diff --git a/apps/client/src/app/components/admin-overview/admin-overview.component.ts b/apps/client/src/app/components/admin-overview/admin-overview.component.ts index dc15cca12..013633c00 100644 --- a/apps/client/src/app/components/admin-overview/admin-overview.component.ts +++ b/apps/client/src/app/components/admin-overview/admin-overview.component.ts @@ -20,6 +20,7 @@ import { parseISO } from 'date-fns'; import { uniq } from 'lodash'; +import { StringValue } from 'ms'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -29,6 +30,7 @@ import { takeUntil } from 'rxjs/operators'; templateUrl: './admin-overview.html' }) export class AdminOverviewComponent implements OnDestroy, OnInit { + public couponDuration: StringValue = '30 days'; public coupons: Coupon[]; public customCurrencies: string[]; public dataGatheringInProgress: boolean; @@ -105,7 +107,10 @@ export class AdminOverviewComponent implements OnDestroy, OnInit { } public onAddCoupon() { - const coupons = [...this.coupons, { code: this.generateCouponCode(16) }]; + const coupons = [ + ...this.coupons, + { code: this.generateCouponCode(16), duration: this.couponDuration } + ]; this.putCoupons(coupons); } @@ -118,6 +123,10 @@ export class AdminOverviewComponent implements OnDestroy, OnInit { } } + public onChangeCouponDuration(aCouponDuration: StringValue) { + this.couponDuration = aCouponDuration; + } + public onDeleteCoupon(aCouponCode: string) { const confirmation = confirm('Do you really want to delete this coupon?'); diff --git a/apps/client/src/app/components/admin-overview/admin-overview.html b/apps/client/src/app/components/admin-overview/admin-overview.html index 04f8ba4ec..2457a12b6 100644 --- a/apps/client/src/app/components/admin-overview/admin-overview.html +++ b/apps/client/src/app/components/admin-overview/admin-overview.html @@ -156,11 +156,14 @@ > -