@sourceloop/ctrl-plane-subscription-service
Version:
Subscription management microservice for SaaS control plane.
393 lines (375 loc) • 10.7 kB
text/typescript
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(
public subscriptionRepository: SubscriptionRepository,
public planRepository: PlanRepository,
public billingCycleRepository: BillingCycleRepository,
) {}
async create(
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';
}
}
async count(
.where(Subscription) where?: Where<Subscription>,
): Promise<Count> {
return this.subscriptionRepository.count(where);
}
async find(
.filter(Subscription) filter?: Filter<Subscription>,
): Promise<Subscription[]> {
return this.subscriptionRepository.find(filter);
}
async expireSoonSubscription(
.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;
}
async expiredSubscription(
.header.number('days') dayCount: number,
.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;
}
async updateAll(
subscription: Subscription,
.where(Subscription) where?: Where<Subscription>,
): Promise<Count> {
return this.subscriptionRepository.updateAll(subscription, where);
}
async findById(
.path.string('id') id: string,
.filter(Subscription, {exclude: 'where'})
filter?: FilterExcludingWhere<Subscription>,
): Promise<Subscription> {
return this.subscriptionRepository.findById(id, filter);
}
async updateById(
.path.string('id') id: string,
subscription: Subscription,
): Promise<void> {
await this.subscriptionRepository.updateById(id, subscription);
}
async replaceById(
.path.string('id') id: string,
subscription: Subscription,
): Promise<void> {
await this.subscriptionRepository.replaceById(id, subscription);
}
async deleteById(.path.string('id') id: string): Promise<void> {
await this.subscriptionRepository.deleteById(id);
}
}