UNPKG

@sourceloop/ctrl-plane-subscription-service

Version:

Subscription management microservice for SaaS control plane.

393 lines (375 loc) 10.7 kB
import { Count, CountSchema, Filter, FilterExcludingWhere, repository, Where, } from '@loopback/repository'; import { post, param, get, getModelSchemaRef, patch, put, del, requestBody, } from '@loopback/rest'; import moment, {unitOfTime} from 'moment'; import {Subscription} from '../models'; import { BillingCycleRepository, PlanRepository, SubscriptionRepository, } from '../repositories'; import {authorize} from 'loopback4-authorization'; import {authenticate, STRATEGY} from 'loopback4-authentication'; import {PermissionKey} from '../permissions'; import {OPERATION_SECURITY_SPEC, STATUS_CODE} from '@sourceloop/core'; import {SubscriptionStatus} from '../enums'; const basePath = '/subscriptions'; const DATE_FORMAT = 'YYYY-MM-DD'; const description = 'Array of Subscription model instances'; export class SubscriptionController { constructor( @repository(SubscriptionRepository) public subscriptionRepository: SubscriptionRepository, @repository(PlanRepository) public planRepository: PlanRepository, @repository(BillingCycleRepository) public billingCycleRepository: BillingCycleRepository, ) {} @authorize({ permissions: [PermissionKey.CreateSubscription], }) @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @post(basePath, { security: OPERATION_SECURITY_SPEC, responses: { [STATUS_CODE.OK]: { description: 'Subscription model instance', content: { 'application/json': {schema: getModelSchemaRef(Subscription)}, }, }, }, }) async create( @requestBody({ content: { 'application/json': { schema: getModelSchemaRef(Subscription, { title: 'NewSubscription', exclude: ['id', 'startDate', 'endDate'], }), }, }, }) subscription: Omit<Subscription, 'id' | 'startDate' | 'endDate'>, ): Promise<Subscription> { const plan = await this.planRepository.findById(subscription.planId); const billingCycle = await this.billingCycleRepository.findById( plan.billingCycleId, ); const startDate = moment().format(DATE_FORMAT); const endDate = moment() .add(billingCycle.duration, this._unitMap(billingCycle.durationUnit)) .format(DATE_FORMAT); return this.subscriptionRepository.create({ ...subscription, startDate, endDate, }); } private _unitMap(durationUnit: string): unitOfTime.DurationConstructor { switch (durationUnit) { case 'month': return 'M'; case 'year': return 'y'; case 'week': return 'week'; default: return 'days'; } } @authorize({ permissions: [PermissionKey.ViewSubscription], }) @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @get(`${basePath}/count`, { security: OPERATION_SECURITY_SPEC, responses: { [STATUS_CODE.OK]: { description: 'Subscription model count', content: {'application/json': {schema: CountSchema}}, }, }, }) async count( @param.where(Subscription) where?: Where<Subscription>, ): Promise<Count> { return this.subscriptionRepository.count(where); } @authorize({ permissions: [PermissionKey.ViewSubscription], }) @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @get(basePath, { security: OPERATION_SECURITY_SPEC, responses: { [STATUS_CODE.OK]: { description, content: { 'application/json': { schema: { type: 'array', items: getModelSchemaRef(Subscription, {includeRelations: true}), }, }, }, }, }, }) async find( @param.filter(Subscription) filter?: Filter<Subscription>, ): Promise<Subscription[]> { return this.subscriptionRepository.find(filter); } @authorize({ permissions: [PermissionKey.ViewSubscription], }) @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @get(`${basePath}/expire-soon`, { security: OPERATION_SECURITY_SPEC, responses: { [STATUS_CODE.OK]: { description, content: { 'application/json': { schema: { type: 'array', items: getModelSchemaRef(Subscription, {includeRelations: true}), }, }, }, }, }, }) async expireSoonSubscription( @param.filter(Subscription) filter?: Filter<Subscription>, ): Promise< {id: string; daysRemainingToExpiry: number; subscriberId: string}[] > { const daysRemaining = 7; const subscriptions = await this.subscriptionRepository.find({ where: {status: SubscriptionStatus.ACTIVE}, }); const expiringSoonSubscriptionObj = []; for (const subscription of subscriptions) { // check for if less then 7 days remaining and send notification if ( moment(subscription.endDate).isBefore( moment().add(daysRemaining, 'days'), ) && moment(subscription.endDate).isAfter(moment()) ) { const daysRemainingToExpiry = moment(subscription.endDate).diff( moment(), 'days', ); expiringSoonSubscriptionObj.push({ id: subscription.id, daysRemainingToExpiry, subscriberId: subscription.subscriberId, }); } } return expiringSoonSubscriptionObj; } @authorize({ permissions: [PermissionKey.ViewSubscription], }) @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @get(`${basePath}/expired`, { description: 'api that will return newly expired subscription', security: OPERATION_SECURITY_SPEC, responses: { [STATUS_CODE.OK]: { description, content: { 'application/json': { schema: { type: 'array', items: getModelSchemaRef(Subscription, {includeRelations: true}), }, }, }, }, }, }) async expiredSubscription( @param.header.number('days') dayCount: number, @param.filter(Subscription) filter?: Filter<Subscription>, ): Promise<{subscriptionId: string; subscriberId: string}[]> { const subscriptions = await this.subscriptionRepository.find({ where: {status: SubscriptionStatus.ACTIVE}, }); const markSubscriptionsAsExpiredPromises = []; for (const subscription of subscriptions) { // check for if subscription is expired if (moment(subscription.endDate).isBefore(moment())) { markSubscriptionsAsExpiredPromises.push( this.subscriptionRepository.updateById(subscription.id, { status: SubscriptionStatus.EXPIRED, }), ); } } await Promise.all(markSubscriptionsAsExpiredPromises); // calculate date before dayCount number of days from current date const range = moment().subtract(dayCount, 'days').format(DATE_FORMAT); const expiredSubscriptionWithInRange = []; const expiredSubscription = await this.subscriptionRepository.find({ where: {status: SubscriptionStatus.EXPIRED}, }); for (const subscription of expiredSubscription) { if (moment(subscription.endDate).isAfter(range)) { expiredSubscriptionWithInRange.push({ subscriptionId: subscription.id, subscriberId: subscription.subscriberId, }); } } return expiredSubscriptionWithInRange; } @authorize({ permissions: [PermissionKey.UpdateSubscription], }) @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @patch(basePath, { security: OPERATION_SECURITY_SPEC, responses: { [STATUS_CODE.OK]: { description: 'Subscription PATCH success count', content: {'application/json': {schema: CountSchema}}, }, }, }) async updateAll( @requestBody({ content: { 'application/json': { schema: getModelSchemaRef(Subscription, {partial: true}), }, }, }) subscription: Subscription, @param.where(Subscription) where?: Where<Subscription>, ): Promise<Count> { return this.subscriptionRepository.updateAll(subscription, where); } @authorize({ permissions: [PermissionKey.ViewSubscription], }) @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @get(`${basePath}/{id}`, { security: OPERATION_SECURITY_SPEC, responses: { [STATUS_CODE.OK]: { description: 'Subscription model instance', content: { 'application/json': { schema: getModelSchemaRef(Subscription, {includeRelations: true}), }, }, }, }, }) async findById( @param.path.string('id') id: string, @param.filter(Subscription, {exclude: 'where'}) filter?: FilterExcludingWhere<Subscription>, ): Promise<Subscription> { return this.subscriptionRepository.findById(id, filter); } @authorize({ permissions: [PermissionKey.UpdateSubscription], }) @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @patch(`${basePath}/{id}`, { security: OPERATION_SECURITY_SPEC, responses: { [STATUS_CODE.NO_CONTENT]: { description: 'Subscription PATCH success', }, }, }) async updateById( @param.path.string('id') id: string, @requestBody({ content: { 'application/json': { schema: getModelSchemaRef(Subscription, {partial: true}), }, }, }) subscription: Subscription, ): Promise<void> { await this.subscriptionRepository.updateById(id, subscription); } @authorize({ permissions: [PermissionKey.UpdateSubscription], }) @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @put(`${basePath}/{id}`, { security: OPERATION_SECURITY_SPEC, responses: { [STATUS_CODE.NO_CONTENT]: { description: 'Subscription PUT success', }, }, }) async replaceById( @param.path.string('id') id: string, @requestBody() subscription: Subscription, ): Promise<void> { await this.subscriptionRepository.replaceById(id, subscription); } @authorize({ permissions: [PermissionKey.DeleteSubscription], }) @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @del(`${basePath}/{id}`, { security: OPERATION_SECURITY_SPEC, responses: { [STATUS_CODE.NO_CONTENT]: { description: 'Subscription DELETE success', }, }, }) async deleteById(@param.path.string('id') id: string): Promise<void> { await this.subscriptionRepository.deleteById(id); } }