feat: remove logic and add logic to enterprise
This commit is contained in:
parent
78b834d18f
commit
748e2190e9
8 changed files with 41 additions and 337 deletions
11
.github/dependabot.yml
vendored
11
.github/dependabot.yml
vendored
|
|
@ -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"
|
||||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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<Request>) {
|
||||
// @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<number, 'STANDARD' | 'TEAM' | 'PRO'> = {
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
? ''
|
||||
|
|
|
|||
|
|
@ -923,7 +923,6 @@ enum Provider {
|
|||
FARCASTER
|
||||
WALLET
|
||||
GENERIC
|
||||
APPSUMO
|
||||
}
|
||||
|
||||
enum Role {
|
||||
|
|
|
|||
|
|
@ -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<number, 'STANDARD' | 'TEAM' | 'PRO'> = {
|
||||
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 };
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue