From 748e2190e908cfa55f5f72d67a551704bfb9e19b Mon Sep 17 00:00:00 2001 From: Nevo David Date: Thu, 5 Mar 2026 12:03:06 +0700 Subject: [PATCH] feat: remove logic and add logic to enterprise --- .github/dependabot.yml | 11 -- apps/backend/src/api/api.module.ts | 6 - .../src/api/routes/appsumo.controller.ts | 40 ----- .../src/api/routes/enterprise.controller.ts | 41 ++++- .../auth/providers/appsumo.provider.ts | 114 ------------ apps/frontend/src/middleware.ts | 3 +- .../src/database/prisma/schema.prisma | 1 - .../src/services/appsumo.service.ts | 162 ------------------ 8 files changed, 41 insertions(+), 337 deletions(-) delete mode 100644 .github/dependabot.yml delete mode 100644 apps/backend/src/api/routes/appsumo.controller.ts delete mode 100644 apps/backend/src/services/auth/providers/appsumo.provider.ts delete mode 100644 libraries/nestjs-libraries/src/services/appsumo.service.ts diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 5f0889ce..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file - -version: 2 -updates: - - package-ecosystem: "npm" # See documentation for possible values - directory: "/" # Location of package manifests - schedule: - interval: "weekly" diff --git a/apps/backend/src/api/api.module.ts b/apps/backend/src/api/api.module.ts index af2dbbce..646f4f52 100644 --- a/apps/backend/src/api/api.module.ts +++ b/apps/backend/src/api/api.module.ts @@ -36,15 +36,12 @@ import { EnterpriseController } from '@gitroom/backend/api/routes/enterprise.con import { OAuthAppController } from '@gitroom/backend/api/routes/oauth-app.controller'; import { ApprovedAppsController } from '@gitroom/backend/api/routes/approved-apps.controller'; import { OAuthController, OAuthAuthorizedController } from '@gitroom/backend/api/routes/oauth.controller'; -import { AppSumoController } from '@gitroom/backend/api/routes/appsumo.controller'; -import { AppSumoService } from '@gitroom/nestjs-libraries/services/appsumo.service'; import { AuthProviderManager } from '@gitroom/backend/services/auth/providers/providers.manager'; import { GithubProvider } from '@gitroom/backend/services/auth/providers/github.provider'; import { GoogleProvider } from '@gitroom/backend/services/auth/providers/google.provider'; import { FarcasterProvider } from '@gitroom/backend/services/auth/providers/farcaster.provider'; import { WalletProvider } from '@gitroom/backend/services/auth/providers/wallet.provider'; import { OauthProvider } from '@gitroom/backend/services/auth/providers/oauth.provider'; -import { AppSumoProvider } from '@gitroom/backend/services/auth/providers/appsumo.provider'; const authenticatedController = [ UsersController, @@ -70,7 +67,6 @@ const authenticatedController = [ controllers: [ RootController, StripeController, - AppSumoController, AuthController, PublicController, MonitorController, @@ -82,7 +78,6 @@ const authenticatedController = [ providers: [ AuthService, StripeService, - AppSumoService, OpenaiService, ExtractContentService, AuthMiddleware, @@ -99,7 +94,6 @@ const authenticatedController = [ FarcasterProvider, WalletProvider, OauthProvider, - AppSumoProvider, ], get exports() { return [...this.imports, ...this.providers]; diff --git a/apps/backend/src/api/routes/appsumo.controller.ts b/apps/backend/src/api/routes/appsumo.controller.ts deleted file mode 100644 index 03703d9f..00000000 --- a/apps/backend/src/api/routes/appsumo.controller.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { - Controller, - HttpException, - Post, - RawBodyRequest, - Req, -} from '@nestjs/common'; -import { AppSumoService } from '@gitroom/nestjs-libraries/services/appsumo.service'; -import { ApiTags } from '@nestjs/swagger'; - -@ApiTags('AppSumo') -@Controller('/appsumo') -export class AppSumoController { - constructor(private readonly _appSumoService: AppSumoService) {} - - @Post('/') - async webhook(@Req() req: RawBodyRequest) { - // @ts-ignore - const timestamp = req.headers['x-appsumo-timestamp'] as string; - // @ts-ignore - const signature = req.headers['x-appsumo-signature'] as string; - - if (!timestamp || !signature) { - throw new HttpException('Missing signature headers', 401); - } - - this._appSumoService.validateSignature( - req.rawBody!, - timestamp, - signature - ); - - try { - const payload = JSON.parse(req.rawBody!.toString()); - return this._appSumoService.handleWebhook(payload); - } catch (e) { - throw new HttpException(e, 500); - } - } -} diff --git a/apps/backend/src/api/routes/enterprise.controller.ts b/apps/backend/src/api/routes/enterprise.controller.ts index 6be0fc7c..5e9bbdc8 100644 --- a/apps/backend/src/api/routes/enterprise.controller.ts +++ b/apps/backend/src/api/routes/enterprise.controller.ts @@ -4,13 +4,17 @@ import { AuthService } from '@gitroom/helpers/auth/auth.service'; import { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service'; import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager'; import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service'; +import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service'; +import { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service'; @ApiTags('Enterprise') @Controller('/enterprise') export class EnterpriseController { constructor( private _integrationManager: IntegrationManager, - private _organizationService: OrganizationService + private _organizationService: OrganizationService, + private _integrationService: IntegrationService, + private _postsService: PostsService ) {} @Post('/create-user') @@ -86,4 +90,39 @@ export class EnterpriseController { return url; } catch (err) {} } + + @Post('/delete-channel') + async deleteChannel(@Body('params') params: string) { + try { + const load = AuthService.verifyJWT(params) as { + apiKey: string; + id: string; + }; + + if (!load || !load.apiKey || !load.id) { + return { success: false }; + } + + const org = await this._organizationService.getOrgByApiKey(load.apiKey); + + if (!org) { + return { success: false }; + } + + const isTherePosts = await this._integrationService.getPostsForChannel( + org.id, + load.id + ); + if (isTherePosts.length) { + for (const post of isTherePosts) { + this._postsService.deletePost(org.id, post.group).catch(() => {}); + } + } + + await this._integrationService.deleteChannel(org.id, load.id); + return { success: true }; + } catch (err) { + return { success: false }; + } + } } diff --git a/apps/backend/src/services/auth/providers/appsumo.provider.ts b/apps/backend/src/services/auth/providers/appsumo.provider.ts deleted file mode 100644 index 91028d94..00000000 --- a/apps/backend/src/services/auth/providers/appsumo.provider.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { - AuthProvider, - AuthProviderAbstract, -} from '@gitroom/backend/services/auth/providers.interface'; -import { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service'; -import { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing'; - -const APPSUMO_OPENID_BASE = 'https://appsumo.com/openid'; - -const APPSUMO_TIER_MAP: Record = { - 1: 'STANDARD', - 2: 'TEAM', - 3: 'PRO', -}; - -@AuthProvider({ provider: 'APPSUMO' }) -export class AppSumoProvider extends AuthProviderAbstract { - constructor(private _subscriptionService: SubscriptionService) { - super(); - } - - generateLink() { - const params = new URLSearchParams({ - client_id: process.env.APPSUMO_CLIENT_ID!, - redirect_uri: `${process.env.FRONTEND_URL}/integrations/social/appsumo`, - response_type: 'code', - scope: 'openid', - }); - - return `${APPSUMO_OPENID_BASE}/authorize/?${params.toString()}`; - } - - async getToken(code: string) { - const clientId = process.env.APPSUMO_CLIENT_ID; - const clientSecret = process.env.APPSUMO_CLIENT_SECRET; - if (!clientId || !clientSecret) { - return ''; - } - - const tokenResponse = await fetch(`${APPSUMO_OPENID_BASE}/token/`, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: new URLSearchParams({ - grant_type: 'authorization_code', - client_id: clientId, - client_secret: clientSecret, - code, - redirect_uri: `${process.env.FRONTEND_URL}/settings`, - }), - }); - - if (!tokenResponse.ok) { - return ''; - } - - const { access_token } = await tokenResponse.json(); - return access_token || ''; - } - - async getUser(accessToken: string) { - if (!accessToken) { - return { id: '', email: '' }; - } - - const response = await fetch( - `${APPSUMO_OPENID_BASE}/license_key/?access_token=${accessToken}`, - ); - - if (!response.ok) { - return { id: '', email: '' }; - } - - const data = await response.json(); - if (!data.license_key) { - return { id: '', email: '' }; - } - - return { - id: String(`appsumo_${data.license_key}`), - email: String(`appsumo_${data.license_key}`), - }; - } - - async postRegistration(accessToken: string, orgId: string) { - if (!accessToken) { - return; - } - - const response = await fetch( - `${APPSUMO_OPENID_BASE}/license_key/?access_token=${accessToken}`, - ); - - if (!response.ok) { - return; - } - - const data = await response.json(); - const billing = APPSUMO_TIER_MAP[data.tier] || 'STANDARD'; - - await this._subscriptionService.createOrUpdateSubscription( - false, - data.license_key, - data.license_key, - pricing[billing].channel!, - billing, - 'YEARLY', - null, - data.license_key, - orgId - ); - } -} diff --git a/apps/frontend/src/middleware.ts b/apps/frontend/src/middleware.ts index 6b20a111..6152560b 100644 --- a/apps/frontend/src/middleware.ts +++ b/apps/frontend/src/middleware.ts @@ -44,7 +44,6 @@ export async function middleware(request: NextRequest) { } if ( - nextUrl.href.indexOf('appsumo') === -1 && nextUrl.pathname.startsWith('/integrations/social/') && nextUrl.href.indexOf('state=login') === -1 ) { @@ -74,7 +73,7 @@ export async function middleware(request: NextRequest) { const org = nextUrl.searchParams.get('org'); const url = new URL(nextUrl).search; if (!nextUrl.pathname.startsWith('/auth') && !authCookie) { - const providers = ['google', 'appsumo', 'settings']; + const providers = ['google', 'settings']; const findIndex = providers.find((p) => nextUrl.href.indexOf(p) > -1); const additional = !findIndex ? '' diff --git a/libraries/nestjs-libraries/src/database/prisma/schema.prisma b/libraries/nestjs-libraries/src/database/prisma/schema.prisma index eda20f1f..c1bacd57 100644 --- a/libraries/nestjs-libraries/src/database/prisma/schema.prisma +++ b/libraries/nestjs-libraries/src/database/prisma/schema.prisma @@ -923,7 +923,6 @@ enum Provider { FARCASTER WALLET GENERIC - APPSUMO } enum Role { diff --git a/libraries/nestjs-libraries/src/services/appsumo.service.ts b/libraries/nestjs-libraries/src/services/appsumo.service.ts deleted file mode 100644 index 5d203309..00000000 --- a/libraries/nestjs-libraries/src/services/appsumo.service.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { HttpException, Injectable } from '@nestjs/common'; -import crypto from 'crypto'; -import { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service'; -import { UsersService } from '@gitroom/nestjs-libraries/database/prisma/users/users.service'; -import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service'; -import { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing'; -import { Provider } from '@prisma/client'; - -const APPSUMO_TIER_MAP: Record = { - 1: 'STANDARD', - 2: 'TEAM', - 3: 'PRO', -}; - -export interface AppSumoWebhookPayload { - license_key: string; - event: - | 'purchase' - | 'activate' - | 'upgrade' - | 'downgrade' - | 'deactivate' - | 'migrate'; - license_status: string; - event_timestamp: number; - created_at: number; - tier: number; - prev_license_key?: string; - test?: boolean; -} - -@Injectable() -export class AppSumoService { - constructor( - private _subscriptionService: SubscriptionService, - private _usersService: UsersService, - private _organizationService: OrganizationService - ) {} - - validateSignature(rawBody: Buffer, timestamp: string, signature: string) { - const apiKey = process.env.APPSUMO_API_KEY; - if (!apiKey) { - throw new HttpException('AppSumo API key not configured', 500); - } - - const message = timestamp + rawBody.toString(); - const expectedSignature = crypto - .createHmac('sha256', apiKey) - .update(message) - .digest('hex'); - - const isValid = crypto.timingSafeEqual( - Buffer.from(signature), - Buffer.from(expectedSignature) - ); - - if (!isValid) { - throw new HttpException('Invalid signature', 401); - } - } - - async handleWebhook(payload: AppSumoWebhookPayload) { - if (payload.test) { - return { success: true, event: payload.event }; - } - - switch (payload.event) { - case 'activate': - return this.handlePurchaseOrActivate(payload); - case 'upgrade': - return this.handleUpgradeOrDowngrade(payload); - case 'downgrade': - return this.handleUpgradeOrDowngrade(payload); - case 'deactivate': - return this.handleDeactivate(payload); - default: - return { success: true, event: payload.event }; - } - } - - private async handlePurchaseOrActivate(payload: AppSumoWebhookPayload) { - const orgId = await this.findOrgByLicenseKey(payload.license_key); - - if (!orgId) { - return { success: true, event: payload.event }; - } - - const billing = APPSUMO_TIER_MAP[payload.tier] || 'STANDARD'; - await this._subscriptionService.createOrUpdateSubscription( - false, - payload.license_key, - payload.license_key, - pricing[billing].channel!, - billing, - 'YEARLY', - null, - payload.license_key, - orgId - ); - - return { success: true, event: payload.event }; - } - - private async findOrgByLicenseKey(licenseKey: string) { - const subscription = - await this._subscriptionService.getSubscriptionByIdentifier(licenseKey); - - if (subscription) { - return subscription.organizationId; - } - - const user = await this._usersService.getUserByProvider( - `appsumo_${licenseKey}`, - Provider.APPSUMO - ); - - if (!user) { - return null; - } - - const orgs = await this._organizationService.getOrgsByUserId(user.id); - return orgs[0]?.id || null; - } - - private async handleUpgradeOrDowngrade(payload: AppSumoWebhookPayload) { - const identifier = payload.prev_license_key || payload.license_key; - const subscription = - await this._subscriptionService.getSubscriptionByIdentifier(identifier); - - if (subscription) { - const billing = APPSUMO_TIER_MAP[payload.tier] || 'STANDARD'; - await this._subscriptionService.createOrUpdateSubscription( - false, - payload.license_key, - payload.license_key, - pricing[billing].channel!, - billing, - 'YEARLY', - null, - payload.license_key, - subscription.organizationId - ); - } - - return { success: true, event: payload.event }; - } - - private async handleDeactivate(payload: AppSumoWebhookPayload) { - const subscription = - await this._subscriptionService.getSubscriptionByIdentifier( - payload.license_key - ); - - if (subscription) { - await this._subscriptionService.deleteSubscription( - subscription.organization.paymentId - ); - } - - return { success: true, event: payload.event }; - } -}