feat: add slug to integration
This commit is contained in:
parent
ccb9c50894
commit
c7fc90f86c
|
|
@ -1,4 +1,12 @@
|
|||
import { Body, Controller, Delete, Get, Param, Post, Query } from '@nestjs/common';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Query,
|
||||
} 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';
|
||||
|
|
@ -12,8 +20,8 @@ import {
|
|||
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 { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { GetUserFromRequest } from '@gitroom/nestjs-libraries/user/user.from.request';
|
||||
|
||||
@ApiTags('Integrations')
|
||||
|
|
@ -51,7 +59,12 @@ export class IntegrationsController {
|
|||
@GetUserFromRequest() user: User,
|
||||
@GetOrgFromRequest() org: Organization
|
||||
) {
|
||||
return this._integrationService.getIntegrationForOrder(id, order, user.id, org.id);
|
||||
return this._integrationService.getIntegrationForOrder(
|
||||
id,
|
||||
order,
|
||||
user.id,
|
||||
org.id
|
||||
);
|
||||
}
|
||||
|
||||
@Get('/social/:integration')
|
||||
|
|
@ -138,9 +151,8 @@ export class IntegrationsController {
|
|||
|
||||
const integrationProvider =
|
||||
this._integrationManager.getArticlesIntegration(integration);
|
||||
const { id, name, token, picture } = await integrationProvider.authenticate(
|
||||
api.api
|
||||
);
|
||||
const { id, name, token, picture, username } =
|
||||
await integrationProvider.authenticate(api.api);
|
||||
|
||||
if (!id) {
|
||||
throw new Error('Invalid api key');
|
||||
|
|
@ -153,7 +165,10 @@ export class IntegrationsController {
|
|||
'article',
|
||||
String(id),
|
||||
integration,
|
||||
token
|
||||
token,
|
||||
'',
|
||||
undefined,
|
||||
username
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -179,11 +194,18 @@ export class IntegrationsController {
|
|||
|
||||
const integrationProvider =
|
||||
this._integrationManager.getSocialIntegration(integration);
|
||||
const { accessToken, expiresIn, refreshToken, id, name, picture } =
|
||||
await integrationProvider.authenticate({
|
||||
code: body.code,
|
||||
codeVerifier: getCodeVerifier,
|
||||
});
|
||||
const {
|
||||
accessToken,
|
||||
expiresIn,
|
||||
refreshToken,
|
||||
id,
|
||||
name,
|
||||
picture,
|
||||
username,
|
||||
} = await integrationProvider.authenticate({
|
||||
code: body.code,
|
||||
codeVerifier: getCodeVerifier,
|
||||
});
|
||||
|
||||
if (!id) {
|
||||
throw new Error('Invalid api key');
|
||||
|
|
@ -198,7 +220,8 @@ export class IntegrationsController {
|
|||
integration,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
expiresIn
|
||||
expiresIn,
|
||||
username
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -231,4 +254,4 @@ export class IntegrationsController {
|
|||
// @ts-ignore
|
||||
return this._integrationService.deleteChannel(org.id, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ export class IntegrationRepository {
|
|||
provider: string,
|
||||
token: string,
|
||||
refreshToken = '',
|
||||
expiresIn = 999999999
|
||||
expiresIn = 999999999,
|
||||
username?: string
|
||||
) {
|
||||
return this._integration.model.integration.upsert({
|
||||
where: {
|
||||
|
|
@ -33,6 +34,7 @@ export class IntegrationRepository {
|
|||
name,
|
||||
providerIdentifier: provider,
|
||||
token,
|
||||
profile: username,
|
||||
picture,
|
||||
refreshToken,
|
||||
...(expiresIn
|
||||
|
|
@ -47,6 +49,7 @@ export class IntegrationRepository {
|
|||
providerIdentifier: provider,
|
||||
token,
|
||||
picture,
|
||||
profile: username,
|
||||
refreshToken,
|
||||
...(expiresIn
|
||||
? { tokenExpiration: new Date(Date.now() + expiresIn * 1000) }
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ export class IntegrationService {
|
|||
provider: string,
|
||||
token: string,
|
||||
refreshToken = '',
|
||||
expiresIn?: number
|
||||
expiresIn?: number,
|
||||
username?: string
|
||||
) {
|
||||
return this._integrationRepository.createOrUpdateIntegration(
|
||||
org,
|
||||
|
|
@ -28,7 +29,8 @@ export class IntegrationService {
|
|||
provider,
|
||||
token,
|
||||
refreshToken,
|
||||
expiresIn
|
||||
expiresIn,
|
||||
username
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -185,6 +185,7 @@ model Integration {
|
|||
tokenExpiration DateTime?
|
||||
refreshToken String?
|
||||
posts Post[]
|
||||
profile String?
|
||||
deletedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime? @updatedAt
|
||||
|
|
|
|||
|
|
@ -1,9 +1,21 @@
|
|||
export interface ArticleIntegrationsInterface {
|
||||
authenticate(token: string): Promise<{id: string, name: string, token: string, picture: string}>;
|
||||
post(token: string, content: string, settings: object): Promise<{postId: string, releaseURL: string}>;
|
||||
authenticate(
|
||||
token: string
|
||||
): Promise<{
|
||||
id: string;
|
||||
name: string;
|
||||
token: string;
|
||||
picture: string;
|
||||
username: string;
|
||||
}>;
|
||||
post(
|
||||
token: string,
|
||||
content: string,
|
||||
settings: object
|
||||
): Promise<{ postId: string; releaseURL: string }>;
|
||||
}
|
||||
|
||||
export interface ArticleProvider extends ArticleIntegrationsInterface {
|
||||
identifier: string;
|
||||
name: string;
|
||||
}
|
||||
identifier: string;
|
||||
name: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export class DevToProvider implements ArticleProvider {
|
|||
identifier = 'devto';
|
||||
name = 'Dev.to';
|
||||
async authenticate(token: string) {
|
||||
const { name, id, profile_image } = await (
|
||||
const { name, id, profile_image, username } = await (
|
||||
await fetch('https://dev.to/api/users/me', {
|
||||
headers: {
|
||||
'api-key': token,
|
||||
|
|
@ -18,6 +18,7 @@ export class DevToProvider implements ArticleProvider {
|
|||
name,
|
||||
token,
|
||||
picture: profile_image,
|
||||
username
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export class HashnodeProvider implements ArticleProvider {
|
|||
try {
|
||||
const {
|
||||
data: {
|
||||
me: { name, id, profilePicture },
|
||||
me: { name, id, profilePicture, username },
|
||||
},
|
||||
} = await (
|
||||
await fetch('https://gql.hashnode.com', {
|
||||
|
|
@ -26,6 +26,7 @@ export class HashnodeProvider implements ArticleProvider {
|
|||
name,
|
||||
id,
|
||||
profilePicture
|
||||
username
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
|
@ -38,6 +39,7 @@ export class HashnodeProvider implements ArticleProvider {
|
|||
name,
|
||||
token,
|
||||
picture: profilePicture,
|
||||
username
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
|
|
@ -45,6 +47,7 @@ export class HashnodeProvider implements ArticleProvider {
|
|||
name: '',
|
||||
token: '',
|
||||
picture: '',
|
||||
username: ''
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export class MediumProvider implements ArticleProvider {
|
|||
|
||||
async authenticate(token: string) {
|
||||
const {
|
||||
data: { name, id, imageUrl },
|
||||
data: { name, id, imageUrl, username },
|
||||
} = await (
|
||||
await fetch('https://api.medium.com/v1/me', {
|
||||
headers: {
|
||||
|
|
@ -21,6 +21,7 @@ export class MediumProvider implements ArticleProvider {
|
|||
name,
|
||||
token,
|
||||
picture: imageUrl,
|
||||
username,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -52,7 +53,7 @@ export class MediumProvider implements ArticleProvider {
|
|||
content,
|
||||
...(settings.canonical ? { canonicalUrl: settings.canonical } : {}),
|
||||
...(settings?.tags?.length
|
||||
? { tags: settings?.tags?.map(p => p.value) }
|
||||
? { tags: settings?.tags?.map((p) => p.value) }
|
||||
: {}),
|
||||
publishStatus: settings?.publication ? 'draft' : 'public',
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
|||
import sharp from 'sharp';
|
||||
import { lookup } from 'mime-types';
|
||||
import { readOrFetch } from '@gitroom/helpers/utils/read.or.fetch';
|
||||
import removeMd from 'remove-markdown';
|
||||
import { removeMarkdown } from '@gitroom/helpers/utils/remove.markdown';
|
||||
|
||||
export class LinkedinProvider implements SocialProvider {
|
||||
|
|
@ -30,6 +29,14 @@ export class LinkedinProvider implements SocialProvider {
|
|||
})
|
||||
).json();
|
||||
|
||||
const { vanityName } = await (
|
||||
await fetch('https://api.linkedin.com/v2/me', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
})
|
||||
).json();
|
||||
|
||||
const {
|
||||
name,
|
||||
sub: id,
|
||||
|
|
@ -48,6 +55,7 @@ export class LinkedinProvider implements SocialProvider {
|
|||
refreshToken,
|
||||
name,
|
||||
picture,
|
||||
username: vanityName,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -59,7 +67,7 @@ export class LinkedinProvider implements SocialProvider {
|
|||
}&redirect_uri=${encodeURIComponent(
|
||||
`${process.env.FRONTEND_URL}/integrations/social/linkedin`
|
||||
)}&state=${state}&scope=${encodeURIComponent(
|
||||
'openid profile w_member_social'
|
||||
'openid profile w_member_social r_basicprofile'
|
||||
)}`;
|
||||
return {
|
||||
url,
|
||||
|
|
@ -105,6 +113,14 @@ export class LinkedinProvider implements SocialProvider {
|
|||
})
|
||||
).json();
|
||||
|
||||
const { vanityName } = await (
|
||||
await fetch('https://api.linkedin.com/v2/me', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
})
|
||||
).json();
|
||||
|
||||
return {
|
||||
id,
|
||||
accessToken,
|
||||
|
|
@ -112,6 +128,7 @@ export class LinkedinProvider implements SocialProvider {
|
|||
expiresIn,
|
||||
name,
|
||||
picture,
|
||||
username: vanityName,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ export class RedditProvider implements SocialProvider {
|
|||
refreshToken: newRefreshToken,
|
||||
expiresIn,
|
||||
picture: icon_img.split('?')[0],
|
||||
username: name,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -105,6 +106,7 @@ export class RedditProvider implements SocialProvider {
|
|||
refreshToken,
|
||||
expiresIn,
|
||||
picture: icon_img.split('?')[0],
|
||||
username: name,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export type AuthTokenDetails = {
|
|||
refreshToken?: string; // The refresh token, if applicable
|
||||
expiresIn?: number; // The duration in seconds for which the access token is valid
|
||||
picture?: string;
|
||||
username: string;
|
||||
};
|
||||
|
||||
export interface ISocialMediaIntegration {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
import { lookup } from 'mime-types';
|
||||
import sharp from 'sharp';
|
||||
import { readOrFetch } from '@gitroom/helpers/utils/read.or.fetch';
|
||||
import removeMd from "remove-markdown";
|
||||
import removeMd from 'remove-markdown';
|
||||
|
||||
export class XProvider implements SocialProvider {
|
||||
identifier = 'x';
|
||||
|
|
@ -27,6 +27,13 @@ export class XProvider implements SocialProvider {
|
|||
const {
|
||||
data: { id, name, profile_image_url },
|
||||
} = await client.v2.me();
|
||||
|
||||
const {
|
||||
data: { username },
|
||||
} = await client.v2.me({
|
||||
'user.fields': 'username',
|
||||
});
|
||||
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
|
|
@ -34,6 +41,7 @@ export class XProvider implements SocialProvider {
|
|||
refreshToken: newRefreshToken,
|
||||
expiresIn,
|
||||
picture: profile_image_url,
|
||||
username,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -78,6 +86,12 @@ export class XProvider implements SocialProvider {
|
|||
true
|
||||
);
|
||||
|
||||
const {
|
||||
data: { username },
|
||||
} = await client.v2.me({
|
||||
'user.fields': 'username',
|
||||
});
|
||||
|
||||
return {
|
||||
id: String(id),
|
||||
accessToken: accessToken + ':' + accessSecret,
|
||||
|
|
@ -85,6 +99,7 @@ export class XProvider implements SocialProvider {
|
|||
refreshToken: '',
|
||||
expiresIn: 999999999,
|
||||
picture: profile_image_url_https,
|
||||
username,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -146,7 +161,10 @@ export class XProvider implements SocialProvider {
|
|||
const media_ids = (uploadAll[post.id] || []).filter((f) => f);
|
||||
|
||||
const { data }: { data: { id: string } } = await client.v2.tweet({
|
||||
text: removeMd(post.message.replace('\n', '𝔫𝔢𝔴𝔩𝔦𝔫𝔢')).replace('𝔫𝔢𝔴𝔩𝔦𝔫𝔢', '\n'),
|
||||
text: removeMd(post.message.replace('\n', '𝔫𝔢𝔴𝔩𝔦𝔫𝔢')).replace(
|
||||
'𝔫𝔢𝔴𝔩𝔦𝔫𝔢',
|
||||
'\n'
|
||||
),
|
||||
...(media_ids.length ? { media: { media_ids } } : {}),
|
||||
...(ids.length
|
||||
? { reply: { in_reply_to_tweet_id: ids[ids.length - 1].postId } }
|
||||
|
|
@ -165,4 +183,17 @@ export class XProvider implements SocialProvider {
|
|||
status: 'posted',
|
||||
}));
|
||||
}
|
||||
|
||||
// async analytics(accessToken: string) {
|
||||
// const [accessTokenSplit, accessSecretSplit] = accessToken.split(':');
|
||||
// const client = new TwitterApi({
|
||||
// appKey: process.env.X_API_KEY!,
|
||||
// appSecret: process.env.X_API_SECRET!,
|
||||
// accessToken: accessTokenSplit,
|
||||
// accessSecret: accessSecretSplit,
|
||||
// });
|
||||
// const {
|
||||
// data: { username },
|
||||
// } = await client.v2;
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue