postiz/apps/backend/src/api/routes/integrations.controller.ts

424 lines
12 KiB
TypeScript

import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Query,
UseFilters,
} from '@nestjs/common';
import { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service';
import { ConnectIntegrationDto } from '@gitroom/nestjs-libraries/dtos/integrations/connect.integration.dto';
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 { ApiKeyDto } from '@gitroom/nestjs-libraries/dtos/integrations/api.key.dto';
import { IntegrationFunctionDto } from '@gitroom/nestjs-libraries/dtos/integrations/integration.function.dto';
import {
AuthorizationActions,
Sections,
} from '@gitroom/backend/services/auth/permissions/permissions.service';
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 { NotEnoughScopesFilter } from '@gitroom/nestjs-libraries/integrations/integration.missing.scopes';
import { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';
import { IntegrationTimeDto } from '@gitroom/nestjs-libraries/dtos/integrations/integration.time.dto';
import { AuthService } from '@gitroom/helpers/auth/auth.service';
@ApiTags('Integrations')
@Controller('/integrations')
export class IntegrationsController {
constructor(
private _integrationManager: IntegrationManager,
private _integrationService: IntegrationService,
private _postService: PostsService
) {}
@Get('/')
getIntegration() {
return this._integrationManager.getAllIntegrations();
}
@Get('/list')
async getIntegrationList(@GetOrgFromRequest() org: Organization) {
return {
integrations: (
await this._integrationService.getIntegrationsList(org.id)
).map((p) => {
const findIntegration = this._integrationManager.getSocialIntegration(
p.providerIdentifier
);
return {
name: p.name,
id: p.id,
internalId: p.internalId,
disabled: p.disabled,
picture: p.picture,
identifier: p.providerIdentifier,
inBetweenSteps: p.inBetweenSteps,
refreshNeeded: p.refreshNeeded,
type: p.type,
time: JSON.parse(p.postingTimes),
changeProfilePicture: !!findIntegration?.changeProfilePicture,
changeNickName: !!findIntegration?.changeNickname,
};
}),
};
}
@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
) {
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(refresh, getExternalUrl);
await ioRedis.set(`login:${state}`, codeVerifier, 'EX', 300);
await ioRedis.set(
`external:${state}`,
JSON.stringify(getExternalUrl),
'EX',
300
);
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('/function')
async functionIntegration(
@GetOrgFromRequest() org: Organization,
@Body() body: IntegrationFunctionDto
) {
const getIntegration = await this._integrationService.getIntegrationById(
org.id,
body.id
);
if (!getIntegration) {
throw new Error('Invalid integration');
}
if (getIntegration.type === 'social') {
const integrationProvider = this._integrationManager.getSocialIntegration(
getIntegration.providerIdentifier
);
if (!integrationProvider) {
throw new Error('Invalid provider');
}
if (integrationProvider[body.name]) {
return integrationProvider[body.name](
getIntegration.token,
body.data,
getIntegration.internalId
);
}
throw new Error('Function not found');
}
if (getIntegration.type === 'article') {
const integrationProvider =
this._integrationManager.getArticlesIntegration(
getIntegration.providerIdentifier
);
if (!integrationProvider) {
throw new Error('Invalid provider');
}
if (integrationProvider[body.name]) {
return integrationProvider[body.name](
getIntegration.token,
body.data,
getIntegration.internalId
);
}
throw new Error('Function not found');
}
}
@Post('/article/:integration/connect')
@CheckPolicies([AuthorizationActions.Create, Sections.CHANNEL])
async connectArticle(
@GetOrgFromRequest() org: Organization,
@Param('integration') integration: string,
@Body() api: ApiKeyDto
) {
if (
!this._integrationManager
.getAllowedArticlesIntegrations()
.includes(integration)
) {
throw new Error('Integration not allowed');
}
if (!api) {
throw new Error('Missing api');
}
const integrationProvider =
this._integrationManager.getArticlesIntegration(integration);
const { id, name, token, picture, username } =
await integrationProvider.authenticate(api.api);
if (!id) {
throw new Error('Invalid api key');
}
return this._integrationService.createOrUpdateIntegration(
org.id,
name,
picture,
'article',
String(id),
integration,
token,
'',
undefined,
username,
false
);
}
@Post('/social/:integration/connect')
@CheckPolicies([AuthorizationActions.Create, Sections.CHANNEL])
@UseFilters(new NotEnoughScopesFilter())
async connectSocialMedia(
@GetOrgFromRequest() org: Organization,
@Param('integration') integration: string,
@Body() body: ConnectIntegrationDto
) {
if (
!this._integrationManager
.getAllowedSocialsIntegrations()
.includes(integration)
) {
throw new Error('Integration not allowed');
}
const integrationProvider =
this._integrationManager.getSocialIntegration(integration);
const getCodeVerifier = integrationProvider.customFields
? 'none'
: await ioRedis.get(`login:${body.state}`);
if (!getCodeVerifier) {
throw new Error('Invalid state');
}
if (!integrationProvider.customFields) {
await ioRedis.del(`login:${body.state}`);
}
const details = integrationProvider.externalUrl
? await ioRedis.get(`external:${body.state}`)
: undefined;
if (details) {
await ioRedis.del(`external:${body.state}`);
}
const {
accessToken,
expiresIn,
refreshToken,
id,
name,
picture,
username,
} = await integrationProvider.authenticate(
{
code: body.code,
codeVerifier: getCodeVerifier,
refresh: body.refresh,
},
details ? JSON.parse(details) : undefined
);
if (!id) {
throw new Error('Invalid api key');
}
return this._integrationService.createOrUpdateIntegration(
org.id,
name,
picture,
'social',
String(id),
integration,
accessToken,
refreshToken,
expiresIn,
username,
integrationProvider.isBetweenSteps,
body.refresh,
+body.timezone,
details
? AuthService.fixedEncryption(details)
: integrationProvider.customFields
? AuthService.fixedEncryption(
Buffer.from(body.code, 'base64').toString()
)
: undefined
);
}
@Post('/disable')
disableChannel(
@GetOrgFromRequest() org: Organization,
@Body('id') id: string
) {
return this._integrationService.disableChannel(org.id, id);
}
@Post('/instagram/:id')
async saveInstagram(
@Param('id') id: string,
@Body() body: { pageId: string; id: string },
@GetOrgFromRequest() org: Organization
) {
return this._integrationService.saveInstagram(org.id, id, body);
}
@Post('/facebook/:id')
async saveFacebook(
@Param('id') id: string,
@Body() body: { page: string },
@GetOrgFromRequest() org: Organization
) {
return this._integrationService.saveFacebook(org.id, id, body.page);
}
@Post('/linkedin-page/:id')
async saveLinkedin(
@Param('id') id: string,
@Body() body: { page: string },
@GetOrgFromRequest() org: Organization
) {
return this._integrationService.saveLinkedin(org.id, id, body.page);
}
@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) {
await this._postService.deletePost(org.id, post.group);
}
}
return this._integrationService.deleteChannel(org.id, id);
}
}