feat: add slug to integration

This commit is contained in:
Nevo David 2024-05-17 20:18:54 +07:00
parent ccb9c50894
commit c7fc90f86c
12 changed files with 128 additions and 31 deletions

View File

@ -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);
}
}
}

View File

@ -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) }

View File

@ -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
);
}

View File

@ -185,6 +185,7 @@ model Integration {
tokenExpiration DateTime?
refreshToken String?
posts Post[]
profile String?
deletedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime? @updatedAt

View File

@ -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;
}

View File

@ -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
};
}

View File

@ -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: ''
};
}
}

View File

@ -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',
}),

View File

@ -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,
};
}

View File

@ -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,
};
}

View File

@ -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 {

View File

@ -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;
// }
}