diff --git a/packages/server/src/api/controllers/Subscription/PaymentMethod.ts b/packages/server/src/api/controllers/Subscription/PaymentMethod.ts deleted file mode 100644 index 2c954c307..000000000 --- a/packages/server/src/api/controllers/Subscription/PaymentMethod.ts +++ /dev/null @@ -1,31 +0,0 @@ -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(); - } -} \ No newline at end of file diff --git a/packages/server/src/api/controllers/Subscription/SubscriptionController.ts b/packages/server/src/api/controllers/Subscription/SubscriptionController.ts new file mode 100644 index 000000000..7f394a666 --- /dev/null +++ b/packages/server/src/api/controllers/Subscription/SubscriptionController.ts @@ -0,0 +1,88 @@ +import { Router, Request, Response, NextFunction } from 'express'; +import { Service, Inject } from 'typedi'; +import { body } from 'express-validator'; +import JWTAuth from '@/api/middleware/jwtAuth'; +import TenancyMiddleware from '@/api/middleware/TenancyMiddleware'; +import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser'; +import SubscriptionService from '@/services/Subscription/SubscriptionService'; +import asyncMiddleware from '@/api/middleware/asyncMiddleware'; +import BaseController from '../BaseController'; +import { LemonSqueezyService } from '@/services/Subscription/LemonSqueezyService'; + +@Service() +export class SubscriptionController extends BaseController { + @Inject() + private subscriptionService: SubscriptionService; + + @Inject() + private lemonSqueezyService: LemonSqueezyService; + + /** + * Router constructor. + */ + router() { + const router = Router(); + + router.use(JWTAuth); + router.use(AttachCurrentTenantUser); + router.use(TenancyMiddleware); + + router.post( + '/lemon/checkout_url', + [body('variantId').exists().trim()], + this.validationResult, + this.getCheckoutUrl.bind(this) + ); + 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 + */ + private 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); + } + } + + /** + * Retrieves the LemonSqueezy checkout url. + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + */ + private async getCheckoutUrl( + req: Request, + res: Response, + next: NextFunction + ) { + const { variantId } = this.matchedBodyData(req); + const { user } = req; + + try { + const checkout = await this.lemonSqueezyService.getCheckout( + variantId, + user + ); + return res.status(200).send(checkout); + } catch (error) { + next(error); + } + } +} diff --git a/packages/server/src/api/controllers/Subscription/index.ts b/packages/server/src/api/controllers/Subscription/index.ts index 02e00a6ba..67ff37623 100644 --- a/packages/server/src/api/controllers/Subscription/index.ts +++ b/packages/server/src/api/controllers/Subscription/index.ts @@ -1,88 +1 @@ -import { Router, Request, Response, NextFunction } from 'express'; -import { Service, Inject } from 'typedi'; -import { body } from 'express-validator'; -import JWTAuth from '@/api/middleware/jwtAuth'; -import TenancyMiddleware from '@/api/middleware/TenancyMiddleware'; -import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser'; -import SubscriptionService from '@/services/Subscription/SubscriptionService'; -import asyncMiddleware from '@/api/middleware/asyncMiddleware'; -import BaseController from '../BaseController'; -import { LemonSqueezyService } from '@/services/Subscription/LemonSqueezyService'; - -@Service() -export default class SubscriptionController extends BaseController { - @Inject() - private subscriptionService: SubscriptionService; - - @Inject() - private lemonSqueezyService: LemonSqueezyService; - - /** - * Router constructor. - */ - router() { - const router = Router(); - - router.use(JWTAuth); - router.use(AttachCurrentTenantUser); - router.use(TenancyMiddleware); - - router.post( - '/lemon/checkout_url', - [body('variantId').exists().trim()], - this.validationResult, - this.getCheckoutUrl.bind(this) - ); - 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 - */ - private 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); - } - } - - /** - * Retrieves the LemonSqueezy checkout url. - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next - */ - private async getCheckoutUrl( - req: Request, - res: Response, - next: NextFunction - ) { - const { variantId } = this.matchedBodyData(req); - const { user } = req; - - try { - const checkout = await this.lemonSqueezyService.getCheckout( - variantId, - user - ); - return res.status(200).send(checkout); - } catch (error) { - next(error); - } - } -} +export * from './SubscriptionController'; \ No newline at end of file diff --git a/packages/server/src/api/index.ts b/packages/server/src/api/index.ts index 13de30877..623e5a0f7 100644 --- a/packages/server/src/api/index.ts +++ b/packages/server/src/api/index.ts @@ -37,7 +37,7 @@ import Resources from './controllers/Resources'; import ExchangeRates from '@/api/controllers/ExchangeRates'; import Media from '@/api/controllers/Media'; import Ping from '@/api/controllers/Ping'; -import Subscription from '@/api/controllers/Subscription'; +import { SubscriptionController } from '@/api/controllers/Subscription'; import InventoryAdjustments from '@/api/controllers/Inventory/InventoryAdjustments'; import asyncRenderMiddleware from './middleware/AsyncRenderMiddleware'; import Jobs from './controllers/Jobs'; @@ -72,7 +72,7 @@ export default () => { app.use('/auth', Container.get(Authentication).router()); app.use('/invite', Container.get(InviteUsers).nonAuthRouter()); - app.use('/subscription', Container.get(Subscription).router()); + app.use('/subscription', Container.get(SubscriptionController).router()); app.use('/organization', Container.get(Organization).router()); app.use('/ping', Container.get(Ping).router()); app.use('/jobs', Container.get(Jobs).router()); @@ -140,12 +140,10 @@ export default () => { dashboard.use('/warehouses', Container.get(WarehousesController).router()); dashboard.use('/projects', Container.get(ProjectsController).router()); dashboard.use('/tax-rates', Container.get(TaxRatesController).router()); - dashboard.use('/import', Container.get(ImportController).router()); dashboard.use('/', Container.get(ProjectTasksController).router()); dashboard.use('/', Container.get(ProjectTimesController).router()); - dashboard.use('/', Container.get(WarehousesItemController).router()); dashboard.use('/dashboard', Container.get(DashboardController).router()); diff --git a/packages/server/src/services/Subscription/LemonSqueezyWebhooks.ts b/packages/server/src/services/Subscription/LemonSqueezyWebhooks.ts index 4301b924a..ff21c1871 100644 --- a/packages/server/src/services/Subscription/LemonSqueezyWebhooks.ts +++ b/packages/server/src/services/Subscription/LemonSqueezyWebhooks.ts @@ -49,16 +49,17 @@ export class LemonSqueezyWebhooks { /** * This action will process a webhook event in the database. + * @param {unknown} eventBody - + * @returns {Promise} */ - private async processWebhookEvent(eventBody) { - let processingError = ''; + private async processWebhookEvent(eventBody): Promise { const webhookEvent = eventBody.meta.event_name; const userId = eventBody.meta.custom_data?.user_id; const tenantId = eventBody.meta.custom_data?.tenant_id; if (!webhookHasMeta(eventBody)) { - processingError = "Event body is missing the 'meta' property."; + throw new Error("Event body is missing the 'meta' property."); } else if (webhookHasData(eventBody)) { if (webhookEvent.startsWith('subscription_payment_')) { // Save subscription invoices; eventBody is a SubscriptionInvoice @@ -72,7 +73,7 @@ export class LemonSqueezyWebhooks { const plan = await Plan.query().findOne('slug', 'essentials-yearly'); if (!plan) { - processingError = `Plan with variantId ${variantId} not found.`; + throw new Error(`Plan with variantId ${variantId} not found.`); } else { // Update the subscription in the database. const priceId = attributes.first_subscription_item.price_id; @@ -81,7 +82,9 @@ export class LemonSqueezyWebhooks { const priceData = await getPrice(priceId); if (priceData.error) { - processingError = `Failed to get the price data for the subscription ${eventBody.data.id}.`; + throw new Error( + `Failed to get the price data for the subscription ${eventBody.data.id}.` + ); } const isUsageBased = attributes.first_subscription_item.is_usage_based; diff --git a/packages/webapp/src/hooks/query/organization.tsx b/packages/webapp/src/hooks/query/organization.tsx index aea3ab96c..3a68efa89 100644 --- a/packages/webapp/src/hooks/query/organization.tsx +++ b/packages/webapp/src/hooks/query/organization.tsx @@ -1,11 +1,11 @@ // @ts-nocheck import { useMutation, useQueryClient } from 'react-query'; import { batch } from 'react-redux'; +import { omit } from 'lodash'; import t from './types'; import useApiRequest from '../useRequest'; import { useRequestQuery } from '../useQueryRequest'; import { useSetOrganizations, useSetSubscriptions } from '../state'; -import { omit } from 'lodash'; /** * Retrieve organizations of the authenticated user.