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 @@ > -
+
Coupons
- {{ coupon.code }} + {{ coupon.code }} ({{ coupon.duration }})
- +
+ + + 30 Days + 1 Year + + + +
diff --git a/apps/client/src/app/components/admin-overview/admin-overview.module.ts b/apps/client/src/app/components/admin-overview/admin-overview.module.ts index f75f312ce..4f9dd7a2a 100644 --- a/apps/client/src/app/components/admin-overview/admin-overview.module.ts +++ b/apps/client/src/app/components/admin-overview/admin-overview.module.ts @@ -1,7 +1,9 @@ import { CommonModule } from '@angular/common'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; +import { MatSelectModule } from '@angular/material/select'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { CacheService } from '@ghostfolio/client/services/cache.service'; import { GfValueModule } from '@ghostfolio/ui/value'; @@ -12,11 +14,14 @@ import { AdminOverviewComponent } from './admin-overview.component'; declarations: [AdminOverviewComponent], exports: [], imports: [ + FormsModule, CommonModule, GfValueModule, MatButtonModule, MatCardModule, - MatSlideToggleModule + MatSelectModule, + MatSlideToggleModule, + ReactiveFormsModule ], providers: [CacheService], schemas: [CUSTOM_ELEMENTS_SCHEMA] diff --git a/apps/client/src/app/components/admin-overview/admin-overview.scss b/apps/client/src/app/components/admin-overview/admin-overview.scss index 46cadd6d7..f44df0eba 100644 --- a/apps/client/src/app/components/admin-overview/admin-overview.scss +++ b/apps/client/src/app/components/admin-overview/admin-overview.scss @@ -20,4 +20,10 @@ } } } + + .subscription { + .mat-form-field { + max-width: 100%; + } + } } diff --git a/libs/common/src/lib/interfaces/coupon.interface.ts b/libs/common/src/lib/interfaces/coupon.interface.ts index 3caa218e6..cbf8525a2 100644 --- a/libs/common/src/lib/interfaces/coupon.interface.ts +++ b/libs/common/src/lib/interfaces/coupon.interface.ts @@ -1,3 +1,6 @@ +import { StringValue } from 'ms'; + export interface Coupon { code: string; + duration?: StringValue; } diff --git a/package.json b/package.json index d9bdfe470..eed08aee0 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "http-status-codes": "2.2.0", "ionicons": "5.5.1", "lodash": "4.17.21", + "ms": "3.0.0-canary.1", "ngx-device-detector": "3.0.0", "ngx-markdown": "13.0.0", "ngx-skeleton-loader": "5.0.0", diff --git a/yarn.lock b/yarn.lock index 7f317823a..32db36af8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13556,6 +13556,11 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +ms@3.0.0-canary.1: + version "3.0.0-canary.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-3.0.0-canary.1.tgz#c7b34fbce381492fd0b345d1cf56e14d67b77b80" + integrity sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g== + ms@^2.0.0, ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"