import { Body, Controller, Delete, Get, Param, Post, Put, Query, } from '@nestjs/common'; import { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service'; import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager'; import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service'; import { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request'; import { Organization, User } from '@prisma/client'; import { IntegrationFunctionDto } from '@gitroom/nestjs-libraries/dtos/integrations/integration.function.dto'; import { CheckPolicies } from '@gitroom/backend/services/auth/permissions/permissions.ability'; import { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing'; import { ApiTags } from '@nestjs/swagger'; import { GetUserFromRequest } from '@gitroom/nestjs-libraries/user/user.from.request'; import { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service'; import { IntegrationTimeDto } from '@gitroom/nestjs-libraries/dtos/integrations/integration.time.dto'; import { PlugDto } from '@gitroom/nestjs-libraries/dtos/plugs/plug.dto'; import { RefreshToken } from '@gitroom/nestjs-libraries/integrations/social.abstract'; import { timer } from '@gitroom/helpers/utils/timer'; import { TelegramProvider } from '@gitroom/nestjs-libraries/integrations/social/telegram.provider'; import { MoltbookProvider } from '@gitroom/nestjs-libraries/integrations/social/moltbook.provider'; import { AuthorizationActions, Sections, } from '@gitroom/backend/services/auth/permissions/permission.exception.class'; import { uniqBy } from 'lodash'; import { RefreshIntegrationService } from '@gitroom/nestjs-libraries/integrations/refresh.integration.service'; @ApiTags('Integrations') @Controller('/integrations') export class IntegrationsController { constructor( private _integrationManager: IntegrationManager, private _integrationService: IntegrationService, private _postService: PostsService, private _refreshIntegrationService: RefreshIntegrationService ) {} @Post('/provider/:id/connect') @CheckPolicies([AuthorizationActions.Create, Sections.CHANNEL]) async saveProviderPage( @GetOrgFromRequest() org: Organization, @Param('id') id: string, @Body() body: any ) { return this._integrationService.saveProviderPage(org.id, id, body); } @Get('/:identifier/internal-plugs') getInternalPlugs(@Param('identifier') identifier: string) { return this._integrationManager.getInternalPlugs(identifier); } @Get('/customers') getCustomers(@GetOrgFromRequest() org: Organization) { return this._integrationService.customers(org.id); } @Put('/:id/group') async updateIntegrationGroup( @GetOrgFromRequest() org: Organization, @Param('id') id: string, @Body() body: { group: string } ) { return this._integrationService.updateIntegrationGroup( org.id, id, body.group ); } @Put('/:id/customer-name') async updateOnCustomerName( @GetOrgFromRequest() org: Organization, @Param('id') id: string, @Body() body: { name: string } ) { return this._integrationService.updateOnCustomerName(org.id, id, body.name); } @Get('/list') async getIntegrationList(@GetOrgFromRequest() org: Organization) { return { integrations: await Promise.all( ( await this._integrationService.getIntegrationsList(org.id) ).map(async (p) => { const findIntegration = this._integrationManager.getSocialIntegration( p.providerIdentifier ); return { name: p.name, id: p.id, internalId: p.internalId, disabled: p.disabled, editor: findIntegration.editor, stripLinks: !!findIntegration?.stripLinks?.(), picture: p.picture || '/no-picture.jpg', identifier: p.providerIdentifier, inBetweenSteps: p.inBetweenSteps, refreshNeeded: p.refreshNeeded, isCustomFields: !!findIntegration.customFields, ...(findIntegration.customFields ? { customFields: await findIntegration.customFields() } : {}), display: p.profile, type: p.type, time: JSON.parse(p.postingTimes), changeProfilePicture: !!findIntegration?.changeProfilePicture, changeNickName: !!findIntegration?.changeNickname, customer: p.customer, additionalSettings: p.additionalSettings || '[]', }; }) ), }; } @Post('/:id/settings') async updateProviderSettings( @GetOrgFromRequest() org: Organization, @Param('id') id: string, @Body('additionalSettings') body: string ) { if (typeof body !== 'string') { throw new Error('Invalid body'); } await this._integrationService.updateProviderSettings(org.id, id, body); } @Post('/:id/nickname') async setNickname( @GetOrgFromRequest() org: Organization, @Param('id') id: string, @Body() body: { name: string; picture: string } ) { const integration = await this._integrationService.getIntegrationById( org.id, id ); if (!integration) { throw new Error('Invalid integration'); } const manager = this._integrationManager.getSocialIntegration( integration.providerIdentifier ); if (!manager.changeProfilePicture && !manager.changeNickname) { throw new Error('Invalid integration'); } const { url } = manager.changeProfilePicture ? await manager.changeProfilePicture( integration.internalId, integration.token, body.picture ) : { url: '' }; const { name } = manager.changeNickname ? await manager.changeNickname( integration.internalId, integration.token, body.name ) : { name: '' }; return this._integrationService.updateNameAndUrl(id, name, url); } @Get('/:id') getSingleIntegration( @Param('id') id: string, @Query('order') order: string, @GetUserFromRequest() user: User, @GetOrgFromRequest() org: Organization ) { return this._integrationService.getIntegrationForOrder( id, order, user.id, org.id ); } @Get('/social/:integration') @CheckPolicies([AuthorizationActions.Create, Sections.CHANNEL]) async getIntegrationUrl( @Param('integration') integration: string, @Query('refresh') refresh: string, @Query('externalUrl') externalUrl: string, @Query('redirectUrl') redirectUrl: string, @Query('onboarding') onboarding: string, @GetOrgFromRequest() org: Organization ) { if ( !this._integrationManager .getAllowedSocialsIntegrations() .includes(integration) ) { throw new Error('Integration not allowed'); } const integrationProvider = this._integrationManager.getSocialIntegration(integration); if (integrationProvider.externalUrl && !externalUrl) { throw new Error('Missing external url'); } try { const getExternalUrl = integrationProvider.externalUrl ? { ...(await integrationProvider.externalUrl(externalUrl)), instanceUrl: externalUrl, } : undefined; const { codeVerifier, state, url } = await integrationProvider.generateAuthUrl(getExternalUrl); if (refresh) { await ioRedis.set(`refresh:${state}`, refresh, 'EX', 3600); } if (onboarding === 'true') { await ioRedis.set(`onboarding:${state}`, 'true', 'EX', 3600); } if (redirectUrl) { await ioRedis.set(`redirect:${state}`, redirectUrl, 'EX', 3600); } await ioRedis.set(`organization:${state}`, org.id, 'EX', 3600); await ioRedis.set(`login:${state}`, codeVerifier, 'EX', 3600); await ioRedis.set( `external:${state}`, JSON.stringify(getExternalUrl), 'EX', 3600 ); return { url }; } catch (err) { return { err: true }; } } @Post('/:id/time') async setTime( @GetOrgFromRequest() org: Organization, @Param('id') id: string, @Body() body: IntegrationTimeDto ) { return this._integrationService.setTimes(org.id, id, body); } @Post('/mentions') async mentions( @GetOrgFromRequest() org: Organization, @Body() body: IntegrationFunctionDto ) { const getIntegration = await this._integrationService.getIntegrationById( org.id, body.id ); if (!getIntegration) { throw new Error('Invalid integration'); } let newList: any[] | { none: true } = []; try { newList = (await this.functionIntegration(org, body)) || []; } catch (err) { console.log(err); } if (!Array.isArray(newList) && newList?.none) { return newList; } const list = await this._integrationService.getMentions( getIntegration.providerIdentifier, body?.data?.query ); if (Array.isArray(newList) && newList.length) { await this._integrationService.insertMentions( getIntegration.providerIdentifier, newList .map((p: any) => ({ name: p.label || '', username: p.id || '', image: p.image || '', doNotCache: p.doNotCache || false, })) .filter((f: any) => f.name && !f.doNotCache) ); } return uniqBy( [ ...list.map((p) => ({ id: p.username, image: p.image, label: p.name, })), ...(newList as any[]), ], (p) => p.id ).filter((f) => f.label && f.id); } @Post('/function') async functionIntegration( @GetOrgFromRequest() org: Organization, @Body() body: IntegrationFunctionDto ): Promise { const getIntegration = await this._integrationService.getIntegrationById( org.id, body.id ); if (!getIntegration) { throw new Error('Invalid integration'); } const integrationProvider = this._integrationManager.getSocialIntegration( getIntegration.providerIdentifier ); if (!integrationProvider) { throw new Error('Invalid provider'); } // @ts-ignore if (integrationProvider[body.name]) { try { // @ts-ignore const load = await integrationProvider[body.name]( getIntegration.token, body.data, getIntegration.internalId, getIntegration ); return load; } catch (err) { if (err instanceof RefreshToken) { const data = await this._refreshIntegrationService.refresh( getIntegration ); if (!data) { return; } const { accessToken } = data; if (accessToken) { if (integrationProvider.refreshWait) { await timer(10000); } return this.functionIntegration(org, body); } return false; } return false; } } throw new Error('Function not found'); } @Post('/disable') disableChannel( @GetOrgFromRequest() org: Organization, @Body('id') id: string ) { return this._integrationService.disableChannel(org.id, id); } @Post('/enable') enableChannel( @GetOrgFromRequest() org: Organization, @Body('id') id: string ) { return this._integrationService.enableChannel( org.id, // @ts-ignore org?.subscription?.totalChannels || pricing.FREE.channel, id ); } @Delete('/') async deleteChannel( @GetOrgFromRequest() org: Organization, @Body('id') id: string ) { const isTherePosts = await this._integrationService.getPostsForChannel( org.id, id ); if (isTherePosts.length) { for (const post of isTherePosts) { this._postService.deletePost(org.id, post.group).catch((err) => {}); } } return this._integrationService.deleteChannel(org.id, id); } @Get('/plug/list') async getPlugList() { return { plugs: this._integrationManager.getAllPlugs() }; } @Get('/:id/plugs') async getPlugsByIntegrationId( @Param('id') id: string, @GetOrgFromRequest() org: Organization ) { return this._integrationService.getPlugsByIntegrationId(org.id, id); } @Post('/:id/plugs') async postPlugsByIntegrationId( @Param('id') id: string, @GetOrgFromRequest() org: Organization, @Body() body: PlugDto ) { return this._integrationService.createOrUpdatePlug(org.id, id, body); } @Put('/plugs/:id/activate') async changePlugActivation( @Param('id') id: string, @GetOrgFromRequest() org: Organization, @Body('status') status: boolean ) { return this._integrationService.changePlugActivation(org.id, id, status); } @Get('/telegram/updates') async getUpdates(@Query() query: { word: string; id?: number }) { return new TelegramProvider().getBotId(query); } @Post('/moltbook/register') async moltbookRegister(@Body() body: { name: string; description: string }) { try { const provider = new MoltbookProvider(); const result = await provider.registerAgent(body.name, body.description); return { apiKey: result.api_key, claimUrl: result.claim_url, verificationCode: result.verification_code, }; } catch (err: any) { return { error: err.message || 'Registration failed' }; } } @Get('/moltbook/status') async moltbookStatus(@Query('apiKey') apiKey: string) { try { const provider = new MoltbookProvider(); const result = await provider.checkAgentStatus(apiKey); return { claimed: result?.status === 'claimed' }; } catch (err) { return { claimed: false }; } } }