mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-22 07:40:32 +00:00
Revert "feat(server): deprecated the subscription module."
This reverts commit 44fc26b156.
This commit is contained in:
@@ -6,6 +6,7 @@ import { check, ValidationChain } from 'express-validator';
|
|||||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
import JWTAuth from '@/api/middleware/jwtAuth';
|
import JWTAuth from '@/api/middleware/jwtAuth';
|
||||||
import TenancyMiddleware from '@/api/middleware/TenancyMiddleware';
|
import TenancyMiddleware from '@/api/middleware/TenancyMiddleware';
|
||||||
|
import SubscriptionMiddleware from '@/api/middleware/SubscriptionMiddleware';
|
||||||
import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser';
|
import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser';
|
||||||
import OrganizationService from '@/services/Organization/OrganizationService';
|
import OrganizationService from '@/services/Organization/OrganizationService';
|
||||||
import { MONTHS, ACCEPTED_LOCALES } from '@/services/Organization/constants';
|
import { MONTHS, ACCEPTED_LOCALES } from '@/services/Organization/constants';
|
||||||
@@ -17,7 +18,7 @@ import BaseController from '@/api/controllers/BaseController';
|
|||||||
@Service()
|
@Service()
|
||||||
export default class OrganizationController extends BaseController {
|
export default class OrganizationController extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
private organizationService: OrganizationService;
|
organizationService: OrganizationService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
@@ -25,10 +26,13 @@ export default class OrganizationController extends BaseController {
|
|||||||
router() {
|
router() {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
|
// Should before build tenant database the user be authorized and
|
||||||
|
// most important than that, should be subscribed to any plan.
|
||||||
router.use(JWTAuth);
|
router.use(JWTAuth);
|
||||||
router.use(AttachCurrentTenantUser);
|
router.use(AttachCurrentTenantUser);
|
||||||
router.use(TenancyMiddleware);
|
router.use(TenancyMiddleware);
|
||||||
|
|
||||||
|
router.use('/build', SubscriptionMiddleware('main'));
|
||||||
router.post(
|
router.post(
|
||||||
'/build',
|
'/build',
|
||||||
this.buildOrganizationValidationSchema,
|
this.buildOrganizationValidationSchema,
|
||||||
|
|||||||
250
packages/server/src/api/controllers/Subscription/Licenses.ts
Normal file
250
packages/server/src/api/controllers/Subscription/Licenses.ts
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
import { Service, Inject } from 'typedi';
|
||||||
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
|
import { check, oneOf, ValidationChain } from 'express-validator';
|
||||||
|
import basicAuth from 'express-basic-auth';
|
||||||
|
import config from '@/config';
|
||||||
|
import { License } from '@/system/models';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
|
import LicenseService from '@/services/Payment/License';
|
||||||
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
|
import { ILicensesFilter, ISendLicenseDTO } from '@/interfaces';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class LicensesController extends BaseController {
|
||||||
|
@Inject()
|
||||||
|
licenseService: LicenseService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Router constructor.
|
||||||
|
*/
|
||||||
|
router() {
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.use(
|
||||||
|
basicAuth({
|
||||||
|
users: {
|
||||||
|
[config.licensesAuth.user]: config.licensesAuth.password,
|
||||||
|
},
|
||||||
|
challenge: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
'/generate',
|
||||||
|
this.generateLicenseSchema,
|
||||||
|
this.validationResult,
|
||||||
|
asyncMiddleware(this.generateLicense.bind(this)),
|
||||||
|
this.catchServiceErrors,
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
'/disable/:licenseId',
|
||||||
|
this.validationResult,
|
||||||
|
asyncMiddleware(this.disableLicense.bind(this)),
|
||||||
|
this.catchServiceErrors,
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
'/send',
|
||||||
|
this.sendLicenseSchemaValidation,
|
||||||
|
this.validationResult,
|
||||||
|
asyncMiddleware(this.sendLicense.bind(this)),
|
||||||
|
this.catchServiceErrors,
|
||||||
|
);
|
||||||
|
router.delete(
|
||||||
|
'/:licenseId',
|
||||||
|
asyncMiddleware(this.deleteLicense.bind(this)),
|
||||||
|
this.catchServiceErrors,
|
||||||
|
);
|
||||||
|
router.get('/', asyncMiddleware(this.listLicenses.bind(this)));
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate license validation schema.
|
||||||
|
*/
|
||||||
|
get generateLicenseSchema(): ValidationChain[] {
|
||||||
|
return [
|
||||||
|
check('loop').exists().isNumeric().toInt(),
|
||||||
|
check('period').exists().isNumeric().toInt(),
|
||||||
|
check('period_interval')
|
||||||
|
.exists()
|
||||||
|
.isIn(['month', 'months', 'year', 'years', 'day', 'days']),
|
||||||
|
check('plan_slug').exists().trim().escape(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specific license validation schema.
|
||||||
|
*/
|
||||||
|
get specificLicenseSchema(): ValidationChain[] {
|
||||||
|
return [
|
||||||
|
oneOf(
|
||||||
|
[check('license_id').exists().isNumeric().toInt()],
|
||||||
|
[check('license_code').exists().isNumeric().toInt()]
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send license validation schema.
|
||||||
|
*/
|
||||||
|
get sendLicenseSchemaValidation(): ValidationChain[] {
|
||||||
|
return [
|
||||||
|
check('period').exists().isNumeric(),
|
||||||
|
check('period_interval').exists().trim().escape(),
|
||||||
|
check('plan_slug').exists().trim().escape(),
|
||||||
|
oneOf([
|
||||||
|
check('phone_number').exists().trim().escape(),
|
||||||
|
check('email').exists().trim().escape(),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate licenses codes with given period in bulk.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @return {Response}
|
||||||
|
*/
|
||||||
|
async generateLicense(req: Request, res: Response, next: Function) {
|
||||||
|
const { loop = 10, period, periodInterval, planSlug } = this.matchedBodyData(
|
||||||
|
req
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.licenseService.generateLicenses(
|
||||||
|
loop,
|
||||||
|
period,
|
||||||
|
periodInterval,
|
||||||
|
planSlug
|
||||||
|
);
|
||||||
|
return res.status(200).send({
|
||||||
|
code: 100,
|
||||||
|
type: 'LICENSEES.GENERATED.SUCCESSFULLY',
|
||||||
|
message: 'The licenses have been generated successfully.',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable the given license on the storage.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @return {Response}
|
||||||
|
*/
|
||||||
|
async disableLicense(req: Request, res: Response, next: Function) {
|
||||||
|
const { licenseId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.licenseService.disableLicense(licenseId);
|
||||||
|
|
||||||
|
return res.status(200).send({ license_id: licenseId });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the given license code on the storage.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @return {Response}
|
||||||
|
*/
|
||||||
|
async deleteLicense(req: Request, res: Response, next: Function) {
|
||||||
|
const { licenseId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.licenseService.deleteLicense(licenseId);
|
||||||
|
|
||||||
|
return res.status(200).send({ license_id: licenseId });
|
||||||
|
} catch (error) {
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send license code in the given period to the customer via email or phone number
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @return {Response}
|
||||||
|
*/
|
||||||
|
async sendLicense(req: Request, res: Response, next: Function) {
|
||||||
|
const sendLicenseDTO: ISendLicenseDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.licenseService.sendLicenseToCustomer(sendLicenseDTO);
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
status: 100,
|
||||||
|
code: 'LICENSE.CODE.SENT',
|
||||||
|
message: 'The license has been sent to the given customer.',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listing licenses.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
*/
|
||||||
|
async listLicenses(req: Request, res: Response) {
|
||||||
|
const filter: ILicensesFilter = {
|
||||||
|
disabled: false,
|
||||||
|
used: false,
|
||||||
|
sent: false,
|
||||||
|
active: false,
|
||||||
|
...req.query,
|
||||||
|
};
|
||||||
|
const licenses = await License.query().onBuild((builder) => {
|
||||||
|
builder.modify('filter', filter);
|
||||||
|
builder.orderBy('createdAt', 'ASC');
|
||||||
|
});
|
||||||
|
return res.status(200).send({ licenses });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catches all service errors.
|
||||||
|
*/
|
||||||
|
catchServiceErrors(error, req: Request, res: Response, next: NextFunction) {
|
||||||
|
if (error instanceof ServiceError) {
|
||||||
|
if (error.errorType === 'PLAN_NOT_FOUND') {
|
||||||
|
return res.status(400).send({
|
||||||
|
errors: [{
|
||||||
|
type: 'PLAN.NOT.FOUND',
|
||||||
|
code: 100,
|
||||||
|
message: 'The given plan not found.',
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'LICENSE_NOT_FOUND') {
|
||||||
|
return res.status(400).send({
|
||||||
|
errors: [{
|
||||||
|
type: 'LICENSE_NOT_FOUND',
|
||||||
|
code: 200,
|
||||||
|
message: 'The given license id not found.'
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'LICENSE_ALREADY_DISABLED') {
|
||||||
|
return res.status(400).send({
|
||||||
|
errors: [{
|
||||||
|
type: 'LICENSE.ALREADY.DISABLED',
|
||||||
|
code: 200,
|
||||||
|
message: 'License is already disabled.'
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.errorType === 'NO_AVALIABLE_LICENSE_CODE') {
|
||||||
|
return res.status(400).send({
|
||||||
|
status: 110,
|
||||||
|
message: 'There is no licenses availiable right now with the given period and plan.',
|
||||||
|
code: 'NO.AVALIABLE.LICENSE.CODE',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { Inject } from 'typedi';
|
||||||
|
import { Request, Response } from 'express';
|
||||||
|
import { Plan } from '@/system/models';
|
||||||
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
|
import SubscriptionService from '@/services/Subscription/SubscriptionService';
|
||||||
|
|
||||||
|
export default class PaymentMethodController extends BaseController {
|
||||||
|
@Inject()
|
||||||
|
subscriptionService: SubscriptionService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the given plan slug exists on the storage.
|
||||||
|
*
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*
|
||||||
|
* @return {Response|void}
|
||||||
|
*/
|
||||||
|
async validatePlanSlugExistance(req: Request, res: Response, next: Function) {
|
||||||
|
const { planSlug } = this.matchedBodyData(req);
|
||||||
|
const foundPlan = await Plan.query().where('slug', planSlug).first();
|
||||||
|
|
||||||
|
if (!foundPlan) {
|
||||||
|
return res.status(400).send({
|
||||||
|
errors: [{ type: 'PLAN.SLUG.NOT.EXISTS', code: 110 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { NextFunction, Router, Request, Response } from 'express';
|
||||||
|
import { check } from 'express-validator';
|
||||||
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
|
import PaymentMethodController from '@/api/controllers/Subscription/PaymentMethod';
|
||||||
|
import {
|
||||||
|
NotAllowedChangeSubscriptionPlan,
|
||||||
|
NoPaymentModelWithPricedPlan,
|
||||||
|
PaymentAmountInvalidWithPlan,
|
||||||
|
PaymentInputInvalid,
|
||||||
|
VoucherCodeRequired,
|
||||||
|
} from '@/exceptions';
|
||||||
|
import { ILicensePaymentModel } from '@/interfaces';
|
||||||
|
import instance from 'tsyringe/dist/typings/dependency-container';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class PaymentViaLicenseController extends PaymentMethodController {
|
||||||
|
@Inject('logger')
|
||||||
|
logger: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Router constructor.
|
||||||
|
*/
|
||||||
|
router() {
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/payment',
|
||||||
|
this.paymentViaLicenseSchema,
|
||||||
|
this.validationResult,
|
||||||
|
asyncMiddleware(this.validatePlanSlugExistance.bind(this)),
|
||||||
|
asyncMiddleware(this.paymentViaLicense.bind(this)),
|
||||||
|
this.handleErrors,
|
||||||
|
);
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment via license validation schema.
|
||||||
|
*/
|
||||||
|
get paymentViaLicenseSchema() {
|
||||||
|
return [
|
||||||
|
check('plan_slug').exists().trim().escape(),
|
||||||
|
check('license_code').exists().trim().escape(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the subscription payment via license code.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @return {Response}
|
||||||
|
*/
|
||||||
|
async paymentViaLicense(req: Request, res: Response, next: Function) {
|
||||||
|
const { planSlug, licenseCode } = this.matchedBodyData(req);
|
||||||
|
const { tenant } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const licenseModel: ILicensePaymentModel = { licenseCode };
|
||||||
|
|
||||||
|
await this.subscriptionService.subscriptionViaLicense(
|
||||||
|
tenant.id,
|
||||||
|
planSlug,
|
||||||
|
licenseModel
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
type: 'success',
|
||||||
|
code: 'PAYMENT.SUCCESSFULLY.MADE',
|
||||||
|
message: 'Payment via license has been made successfully.',
|
||||||
|
});
|
||||||
|
} catch (exception) {
|
||||||
|
next(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle service errors.
|
||||||
|
* @param {Error} error
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
private handleErrors(
|
||||||
|
exception: Error,
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const errorReasons = [];
|
||||||
|
|
||||||
|
if (exception instanceof VoucherCodeRequired) {
|
||||||
|
errorReasons.push({
|
||||||
|
type: 'VOUCHER_CODE_REQUIRED',
|
||||||
|
code: 100,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (exception instanceof NoPaymentModelWithPricedPlan) {
|
||||||
|
errorReasons.push({
|
||||||
|
type: 'NO_PAYMENT_WITH_PRICED_PLAN',
|
||||||
|
code: 140,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (exception instanceof NotAllowedChangeSubscriptionPlan) {
|
||||||
|
errorReasons.push({
|
||||||
|
type: 'NOT.ALLOWED.RENEW.SUBSCRIPTION.WHILE.ACTIVE',
|
||||||
|
code: 120,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (errorReasons.length > 0) {
|
||||||
|
return res.status(400).send({ errors: errorReasons });
|
||||||
|
}
|
||||||
|
if (exception instanceof PaymentInputInvalid) {
|
||||||
|
return res.status(400).send({
|
||||||
|
errors: [{ type: 'LICENSE.CODE.IS.INVALID', code: 120 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (exception instanceof PaymentAmountInvalidWithPlan) {
|
||||||
|
return res.status(400).send({
|
||||||
|
errors: [{ type: 'LICENSE.NOT.FOR.GIVEN.PLAN' }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
next(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
49
packages/server/src/api/controllers/Subscription/index.ts
Normal file
49
packages/server/src/api/controllers/Subscription/index.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
|
import { Container, Service, Inject } from 'typedi';
|
||||||
|
import JWTAuth from '@/api/middleware/jwtAuth';
|
||||||
|
import TenancyMiddleware from '@/api/middleware/TenancyMiddleware';
|
||||||
|
import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser';
|
||||||
|
import PaymentViaLicenseController from '@/api/controllers/Subscription/PaymentViaLicense';
|
||||||
|
import SubscriptionService from '@/services/Subscription/SubscriptionService';
|
||||||
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class SubscriptionController {
|
||||||
|
@Inject()
|
||||||
|
subscriptionService: SubscriptionService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Router constructor.
|
||||||
|
*/
|
||||||
|
router() {
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.use(JWTAuth);
|
||||||
|
router.use(AttachCurrentTenantUser);
|
||||||
|
router.use(TenancyMiddleware);
|
||||||
|
|
||||||
|
router.use('/license', Container.get(PaymentViaLicenseController).router());
|
||||||
|
router.get('/', asyncMiddleware(this.getSubscriptions.bind(this)));
|
||||||
|
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all subscriptions of the authenticated user's tenant.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
async getSubscriptions(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const subscriptions = await this.subscriptionService.getSubscriptions(
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
return res.status(200).send({ subscriptions });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { Container } from 'typedi';
|
|||||||
// Middlewares
|
// Middlewares
|
||||||
import JWTAuth from '@/api/middleware/jwtAuth';
|
import JWTAuth from '@/api/middleware/jwtAuth';
|
||||||
import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser';
|
import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser';
|
||||||
|
import SubscriptionMiddleware from '@/api/middleware/SubscriptionMiddleware';
|
||||||
import TenancyMiddleware from '@/api/middleware/TenancyMiddleware';
|
import TenancyMiddleware from '@/api/middleware/TenancyMiddleware';
|
||||||
import EnsureTenantIsInitialized from '@/api/middleware/EnsureTenantIsInitialized';
|
import EnsureTenantIsInitialized from '@/api/middleware/EnsureTenantIsInitialized';
|
||||||
import SettingsMiddleware from '@/api/middleware/SettingsMiddleware';
|
import SettingsMiddleware from '@/api/middleware/SettingsMiddleware';
|
||||||
@@ -36,6 +37,8 @@ import Resources from './controllers/Resources';
|
|||||||
import ExchangeRates from '@/api/controllers/ExchangeRates';
|
import ExchangeRates from '@/api/controllers/ExchangeRates';
|
||||||
import Media from '@/api/controllers/Media';
|
import Media from '@/api/controllers/Media';
|
||||||
import Ping from '@/api/controllers/Ping';
|
import Ping from '@/api/controllers/Ping';
|
||||||
|
import Subscription from '@/api/controllers/Subscription';
|
||||||
|
import Licenses from '@/api/controllers/Subscription/Licenses';
|
||||||
import InventoryAdjustments from '@/api/controllers/Inventory/InventoryAdjustments';
|
import InventoryAdjustments from '@/api/controllers/Inventory/InventoryAdjustments';
|
||||||
import asyncRenderMiddleware from './middleware/AsyncRenderMiddleware';
|
import asyncRenderMiddleware from './middleware/AsyncRenderMiddleware';
|
||||||
import Jobs from './controllers/Jobs';
|
import Jobs from './controllers/Jobs';
|
||||||
@@ -70,6 +73,8 @@ export default () => {
|
|||||||
|
|
||||||
app.use('/auth', Container.get(Authentication).router());
|
app.use('/auth', Container.get(Authentication).router());
|
||||||
app.use('/invite', Container.get(InviteUsers).nonAuthRouter());
|
app.use('/invite', Container.get(InviteUsers).nonAuthRouter());
|
||||||
|
app.use('/licenses', Container.get(Licenses).router());
|
||||||
|
app.use('/subscription', Container.get(Subscription).router());
|
||||||
app.use('/organization', Container.get(Organization).router());
|
app.use('/organization', Container.get(Organization).router());
|
||||||
app.use('/ping', Container.get(Ping).router());
|
app.use('/ping', Container.get(Ping).router());
|
||||||
app.use('/jobs', Container.get(Jobs).router());
|
app.use('/jobs', Container.get(Jobs).router());
|
||||||
@@ -83,6 +88,7 @@ export default () => {
|
|||||||
dashboard.use(JWTAuth);
|
dashboard.use(JWTAuth);
|
||||||
dashboard.use(AttachCurrentTenantUser);
|
dashboard.use(AttachCurrentTenantUser);
|
||||||
dashboard.use(TenancyMiddleware);
|
dashboard.use(TenancyMiddleware);
|
||||||
|
dashboard.use(SubscriptionMiddleware('main'));
|
||||||
dashboard.use(EnsureTenantIsInitialized);
|
dashboard.use(EnsureTenantIsInitialized);
|
||||||
dashboard.use(SettingsMiddleware);
|
dashboard.use(SettingsMiddleware);
|
||||||
dashboard.use(I18nAuthenticatedMiddlware);
|
dashboard.use(I18nAuthenticatedMiddlware);
|
||||||
|
|||||||
41
packages/server/src/api/middleware/SubscriptionMiddleware.ts
Normal file
41
packages/server/src/api/middleware/SubscriptionMiddleware.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { Container } from 'typedi';
|
||||||
|
|
||||||
|
export default (subscriptionSlug = 'main') => async (
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) => {
|
||||||
|
const { tenant, tenantId } = req;
|
||||||
|
const Logger = Container.get('logger');
|
||||||
|
const { subscriptionRepository } = Container.get('repositories');
|
||||||
|
|
||||||
|
if (!tenant) {
|
||||||
|
throw new Error('Should load `TenancyMiddlware` before this middleware.');
|
||||||
|
}
|
||||||
|
Logger.info('[subscription_middleware] trying get tenant main subscription.');
|
||||||
|
const subscription = await subscriptionRepository.getBySlugInTenant(
|
||||||
|
subscriptionSlug,
|
||||||
|
tenantId
|
||||||
|
);
|
||||||
|
// Validate in case there is no any already subscription.
|
||||||
|
if (!subscription) {
|
||||||
|
Logger.info('[subscription_middleware] tenant has no subscription.', {
|
||||||
|
tenantId,
|
||||||
|
});
|
||||||
|
return res.boom.badRequest('Tenant has no subscription.', {
|
||||||
|
errors: [{ type: 'TENANT.HAS.NO.SUBSCRIPTION' }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Validate in case the subscription is inactive.
|
||||||
|
else if (subscription.inactive()) {
|
||||||
|
Logger.info(
|
||||||
|
'[subscription_middleware] tenant main subscription is expired.',
|
||||||
|
{ tenantId }
|
||||||
|
);
|
||||||
|
return res.boom.badRequest(null, {
|
||||||
|
errors: [{ type: 'ORGANIZATION.SUBSCRIPTION.INACTIVE' }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
exports.up = function(knex) {
|
||||||
|
return knex.schema.createTable('subscriptions_plans', table => {
|
||||||
|
table.increments();
|
||||||
|
|
||||||
|
table.string('name');
|
||||||
|
table.string('description');
|
||||||
|
table.decimal('price');
|
||||||
|
table.string('currency', 3);
|
||||||
|
|
||||||
|
table.integer('trial_period');
|
||||||
|
table.string('trial_interval');
|
||||||
|
|
||||||
|
table.integer('invoice_period');
|
||||||
|
table.string('invoice_interval');
|
||||||
|
table.timestamps();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function(knex) {
|
||||||
|
return knex.schema.dropTableIfExists('subscriptions_plans')
|
||||||
|
};
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
|
||||||
|
exports.up = function(knex) {
|
||||||
|
return knex.schema.createTable('subscription_plans', table => {
|
||||||
|
table.increments();
|
||||||
|
table.string('slug');
|
||||||
|
table.string('name');
|
||||||
|
table.string('desc');
|
||||||
|
table.boolean('active');
|
||||||
|
|
||||||
|
table.decimal('price').unsigned();
|
||||||
|
table.string('currency', 3);
|
||||||
|
|
||||||
|
table.decimal('trial_period').nullable();
|
||||||
|
table.string('trial_interval').nullable();
|
||||||
|
|
||||||
|
table.decimal('invoice_period').nullable();
|
||||||
|
table.string('invoice_interval').nullable();
|
||||||
|
|
||||||
|
table.integer('index').unsigned();
|
||||||
|
table.timestamps();
|
||||||
|
}).then(() => {
|
||||||
|
return knex.seed.run({
|
||||||
|
specific: 'seed_subscriptions_plans.js',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function(knex) {
|
||||||
|
return knex.schema.dropTableIfExists('subscription_plans')
|
||||||
|
};
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
exports.up = function(knex) {
|
||||||
|
return knex.schema.createTable('subscription_plan_features', table => {
|
||||||
|
table.increments();
|
||||||
|
table.integer('plan_id').unsigned().index().references('id').inTable('subscription_plans');
|
||||||
|
table.string('slug');
|
||||||
|
table.string('name');
|
||||||
|
table.string('description');
|
||||||
|
table.timestamps();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function(knex) {
|
||||||
|
return knex.schema.dropTableIfExists('subscription_plan_features');
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
exports.up = function(knex) {
|
||||||
|
return knex.schema.createTable('subscription_plan_subscriptions', table => {
|
||||||
|
table.increments('id');
|
||||||
|
table.string('slug');
|
||||||
|
|
||||||
|
table.integer('plan_id').unsigned().index().references('id').inTable('subscription_plans');
|
||||||
|
table.bigInteger('tenant_id').unsigned().index().references('id').inTable('tenants');
|
||||||
|
|
||||||
|
table.dateTime('starts_at').nullable();
|
||||||
|
table.dateTime('ends_at').nullable();
|
||||||
|
|
||||||
|
table.dateTime('cancels_at').nullable();
|
||||||
|
table.dateTime('canceled_at').nullable();
|
||||||
|
|
||||||
|
table.timestamps();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function(knex) {
|
||||||
|
return knex.schema.dropTableIfExists('subscription_plan_subscriptions');
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
exports.up = function(knex) {
|
||||||
|
return knex.schema.createTable('subscription_licenses', (table) => {
|
||||||
|
table.increments();
|
||||||
|
|
||||||
|
table.string('license_code').unique().index();
|
||||||
|
table.integer('plan_id').unsigned().index().references('id').inTable('subscription_plans');
|
||||||
|
|
||||||
|
table.integer('license_period').unsigned();
|
||||||
|
table.string('period_interval');
|
||||||
|
|
||||||
|
table.dateTime('sent_at').index();
|
||||||
|
table.dateTime('disabled_at').index();
|
||||||
|
table.dateTime('used_at').index();
|
||||||
|
|
||||||
|
table.timestamps();
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function(knex) {
|
||||||
|
return knex.schema.dropTableIfExists('subscription_licenses');
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user