postiz-app/apps/backend/src/api/routes/integrations.controller.ts
2026-03-16 22:45:23 +07:00

477 lines
14 KiB
TypeScript

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,
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('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);
}
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<any> {
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 };
}
}
}