From 813420427ffb184c9043b8224f7f452d78ea64df Mon Sep 17 00:00:00 2001 From: Nevo David Date: Fri, 8 Aug 2025 20:49:08 +0700 Subject: [PATCH 01/23] feat: bluesky fix images sizes aspect ratio --- .../src/integrations/social/bluesky.provider.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts b/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts index c90990fb..621549ad 100644 --- a/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts @@ -52,7 +52,7 @@ async function reduceImageBySize(url: string, maxSizeKB = 976) { if (width < 10 || height < 10) break; // Prevent overly small dimensions } - return imageBuffer; + return { width, height, buffer: imageBuffer }; } catch (error) { console.error('Error processing image:', error); throw error; @@ -259,9 +259,12 @@ export class BlueskyProvider extends SocialAbstract implements SocialProvider { // Upload images const images = await Promise.all( imageMedia.map(async (p) => { - return await agent.uploadBlob( - new Blob([await reduceImageBySize(p.path)]) - ); + const { buffer, width, height } = await reduceImageBySize(p.path); + return { + width, + height, + buffer: await agent.uploadBlob(new Blob([buffer])), + }; }) ); @@ -288,7 +291,11 @@ export class BlueskyProvider extends SocialAbstract implements SocialProvider { $type: 'app.bsky.embed.images', images: images.map((p, index) => ({ alt: imageMedia?.[index]?.alt || '', - image: p.data.blob, + image: p.buffer.data.blob, + aspectRatio: { + width: p.width, + height: p.height, + } })), }; } From 00fd1512c58b5fd6b3d675aee199014d9995c9b2 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Fri, 8 Aug 2025 22:02:59 +0700 Subject: [PATCH 02/23] feat: remove concurrency from the long tasks --- .../helpers/src/utils/concurrency.service.ts | 6 +++++- .../src/integrations/social.abstract.ts | 6 ++++-- .../integrations/social/instagram.provider.ts | 12 ++++++++++-- .../integrations/social/pinterest.provider.ts | 17 +++++++++++------ .../src/integrations/social/tiktok.provider.ts | 15 +++++++++++---- 5 files changed, 41 insertions(+), 15 deletions(-) diff --git a/libraries/helpers/src/utils/concurrency.service.ts b/libraries/helpers/src/utils/concurrency.service.ts index 7d447e87..906e5f52 100644 --- a/libraries/helpers/src/utils/concurrency.service.ts +++ b/libraries/helpers/src/utils/concurrency.service.ts @@ -11,7 +11,8 @@ const mapper = {} as Record; export const concurrency = async ( identifier: string, maxConcurrent = 1, - func: (...args: any[]) => Promise + func: (...args: any[]) => Promise, + ignoreConcurrency = false ) => { const strippedIdentifier = identifier.toLowerCase().split('-')[0]; mapper[strippedIdentifier] ??= new Bottleneck({ @@ -23,6 +24,9 @@ export const concurrency = async ( }); let load: T; try { + if (ignoreConcurrency) { + return await func(); + } load = await mapper[strippedIdentifier].schedule( { expiration: 600000 }, async () => { diff --git a/libraries/nestjs-libraries/src/integrations/social.abstract.ts b/libraries/nestjs-libraries/src/integrations/social.abstract.ts index 3c3aedfd..0b4d26b1 100644 --- a/libraries/nestjs-libraries/src/integrations/social.abstract.ts +++ b/libraries/nestjs-libraries/src/integrations/social.abstract.ts @@ -68,12 +68,14 @@ export abstract class SocialAbstract { url: string, options: RequestInit = {}, identifier = '', - totalRetries = 0 + totalRetries = 0, + ignoreConcurrency = false ): Promise { const request = await concurrency( this.identifier, this.maxConcurrentJob, - () => fetch(url, options) + () => fetch(url, options), + ignoreConcurrency ); if (request.status === 200 || request.status === 201) { diff --git a/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts b/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts index 3927c3d1..e9bbbae7 100644 --- a/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts @@ -498,7 +498,11 @@ export class InstagramProvider while (status === 'IN_PROGRESS') { const { status_code } = await ( await this.fetch( - `https://${type}/v20.0/${photoId}?access_token=${accessToken}&fields=status_code` + `https://${type}/v20.0/${photoId}?access_token=${accessToken}&fields=status_code`, + undefined, + '', + 0, + true, ) ).json(); await timer(10000); @@ -558,7 +562,11 @@ export class InstagramProvider while (status === 'IN_PROGRESS') { const { status_code } = await ( await this.fetch( - `https://${type}/v20.0/${containerId}?fields=status_code&access_token=${accessToken}` + `https://${type}/v20.0/${containerId}?fields=status_code&access_token=${accessToken}`, + undefined, + '', + 0, + true ) ).json(); await timer(10000); diff --git a/libraries/nestjs-libraries/src/integrations/social/pinterest.provider.ts b/libraries/nestjs-libraries/src/integrations/social/pinterest.provider.ts index 8ee14294..1e53ea68 100644 --- a/libraries/nestjs-libraries/src/integrations/social/pinterest.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/pinterest.provider.ts @@ -37,7 +37,6 @@ export class PinterestProvider value: string; } | undefined { - if (body.indexOf('cover_image_url or cover_image_content_type') > -1) { return { type: 'bad-body' as const, @@ -212,12 +211,18 @@ export class PinterestProvider let statusCode = ''; while (statusCode !== 'succeeded') { const mediafile = await ( - await this.fetch('https://api.pinterest.com/v5/media/' + media_id, { - method: 'GET', - headers: { - Authorization: `Bearer ${accessToken}`, + await this.fetch( + 'https://api.pinterest.com/v5/media/' + media_id, + { + method: 'GET', + headers: { + Authorization: `Bearer ${accessToken}`, + }, }, - }) + '', + 0, + true + ) ).json(); await timer(30000); diff --git a/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts b/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts index fe0700f0..2de298c6 100644 --- a/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts @@ -375,7 +375,10 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider { body: JSON.stringify({ publish_id: publishId, }), - } + }, + '', + 0, + true ) ).json(); @@ -399,11 +402,11 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider { 'titok-error-upload', JSON.stringify(post), Buffer.from(JSON.stringify(post)), - handleError?.value || '', + handleError?.value || '' ); } - await timer(3000); + await timer(10000); } } @@ -496,7 +499,11 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider { photo_cover_index: 0, photo_images: firstPost.media?.map((p) => p.path), }, - post_mode: firstPost?.settings?.content_posting_method === 'DIRECT_POST' ? 'DIRECT_POST' : 'MEDIA_UPLOAD', + post_mode: + firstPost?.settings?.content_posting_method === + 'DIRECT_POST' + ? 'DIRECT_POST' + : 'MEDIA_UPLOAD', media_type: 'PHOTO', }), }), From 85d015f34920c0b101149d532098b29875ef4880 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Sat, 9 Aug 2025 00:07:46 +0700 Subject: [PATCH 03/23] feat: check also waiting --- apps/cron/src/tasks/check.missing.queues.ts | 8 +++++--- apps/cron/src/tasks/post.now.pending.queues.ts | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/cron/src/tasks/check.missing.queues.ts b/apps/cron/src/tasks/check.missing.queues.ts index c383ab6a..6c9def7f 100644 --- a/apps/cron/src/tasks/check.missing.queues.ts +++ b/apps/cron/src/tasks/check.missing.queues.ts @@ -19,9 +19,11 @@ export class CheckMissingQueues { id: p.id, publishDate: p.publishDate, isJob: - (await this._workerServiceProducer - .getQueue('post') - .getJobState(p.id)) === 'delayed', + ['delayed', 'waiting'].indexOf( + await this._workerServiceProducer + .getQueue('post') + .getJobState(p.id) + ) > -1, })) ) ).filter((p) => !p.isJob); diff --git a/apps/cron/src/tasks/post.now.pending.queues.ts b/apps/cron/src/tasks/post.now.pending.queues.ts index 3138a25c..69105304 100644 --- a/apps/cron/src/tasks/post.now.pending.queues.ts +++ b/apps/cron/src/tasks/post.now.pending.queues.ts @@ -18,9 +18,11 @@ export class PostNowPendingQueues { id: p.id, publishDate: p.publishDate, isJob: - (await this._workerServiceProducer - .getQueue('post') - .getJobState(p.id)) === 'delayed', + ['delayed', 'waiting'].indexOf( + await this._workerServiceProducer + .getQueue('post') + .getJobState(p.id) + ) > -1, })) ) ).filter((p) => !p.isJob); From d698732a6c60ea416113f4d854e265ff9c93edce Mon Sep 17 00:00:00 2001 From: Nevo David Date: Sat, 9 Aug 2025 01:06:36 +0700 Subject: [PATCH 04/23] feat: high concurrency --- .../nestjs-libraries/src/bull-mq-transport-new/strategy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/nestjs-libraries/src/bull-mq-transport-new/strategy.ts b/libraries/nestjs-libraries/src/bull-mq-transport-new/strategy.ts index 8cb52190..d6d7efa7 100644 --- a/libraries/nestjs-libraries/src/bull-mq-transport-new/strategy.ts +++ b/libraries/nestjs-libraries/src/bull-mq-transport-new/strategy.ts @@ -34,7 +34,7 @@ export class BullMqServer extends Server implements CustomTransportStrategy { }, { maxStalledCount: 10, - concurrency: 5, + concurrency: 300, connection: ioRedis, removeOnComplete: { count: 0, From e0fe7d0381b394d54e7c769b11b861361166525d Mon Sep 17 00:00:00 2001 From: Nevo David Date: Sat, 9 Aug 2025 01:28:24 +0700 Subject: [PATCH 05/23] feat: expiration --- .../helpers/src/utils/concurrency.service.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/libraries/helpers/src/utils/concurrency.service.ts b/libraries/helpers/src/utils/concurrency.service.ts index 906e5f52..ce2065ae 100644 --- a/libraries/helpers/src/utils/concurrency.service.ts +++ b/libraries/helpers/src/utils/concurrency.service.ts @@ -1,6 +1,7 @@ import { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service'; import Bottleneck from 'bottleneck'; import { timer } from '@gitroom/helpers/utils/timer'; +import { BadBody } from '@gitroom/nestjs-libraries/integrations/social.abstract'; const connection = new Bottleneck.IORedisConnection({ client: ioRedis, @@ -23,19 +24,28 @@ export const concurrency = async ( minTime: 1000, }); let load: T; + + if (ignoreConcurrency) { + return await func(); + } + try { - if (ignoreConcurrency) { - return await func(); - } load = await mapper[strippedIdentifier].schedule( - { expiration: 600000 }, + { expiration: 10000 }, async () => { try { return await func(); } catch (err) {} } ); - } catch (err) {} + } catch (err) { + throw new BadBody( + identifier, + JSON.stringify({}), + {} as any, + `Something is wrong with ${identifier}` + ); + } return load; }; From 113003b83285798f4e5edf8a8fe0d5bb5cf41b75 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Sat, 9 Aug 2025 02:14:52 +0700 Subject: [PATCH 06/23] feat: more expiration for jobs --- libraries/helpers/src/utils/concurrency.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/helpers/src/utils/concurrency.service.ts b/libraries/helpers/src/utils/concurrency.service.ts index ce2065ae..1e6e4494 100644 --- a/libraries/helpers/src/utils/concurrency.service.ts +++ b/libraries/helpers/src/utils/concurrency.service.ts @@ -31,7 +31,7 @@ export const concurrency = async ( try { load = await mapper[strippedIdentifier].schedule( - { expiration: 10000 }, + { expiration: 60000 }, async () => { try { return await func(); From 4ace1dc8868fdd75deae73dce719dbd95ca142d6 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Sat, 9 Aug 2025 02:48:39 +0700 Subject: [PATCH 07/23] feat: more timeout for instagram --- .../src/integrations/social/instagram.provider.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts b/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts index e9bbbae7..5a7edf4a 100644 --- a/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts @@ -505,7 +505,7 @@ export class InstagramProvider true, ) ).json(); - await timer(10000); + await timer(30000); status = status_code; } console.log('in progress3', id); @@ -569,7 +569,7 @@ export class InstagramProvider true ) ).json(); - await timer(10000); + await timer(30000); status = status_code; } From fcfc4109e263bd9b4263043138064f8c12210662 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Sat, 9 Aug 2025 02:59:35 +0700 Subject: [PATCH 08/23] feat set maximum stalled as maximum concurrency --- .../nestjs-libraries/src/bull-mq-transport-new/strategy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/nestjs-libraries/src/bull-mq-transport-new/strategy.ts b/libraries/nestjs-libraries/src/bull-mq-transport-new/strategy.ts index d6d7efa7..f6068144 100644 --- a/libraries/nestjs-libraries/src/bull-mq-transport-new/strategy.ts +++ b/libraries/nestjs-libraries/src/bull-mq-transport-new/strategy.ts @@ -33,7 +33,7 @@ export class BullMqServer extends Server implements CustomTransportStrategy { }); }, { - maxStalledCount: 10, + maxStalledCount: 300, concurrency: 300, connection: ioRedis, removeOnComplete: { From e09187db502e5bbb37b6f23762d67de10da1f09e Mon Sep 17 00:00:00 2001 From: Nevo David Date: Sat, 9 Aug 2025 03:01:52 +0700 Subject: [PATCH 09/23] feat set maximum stalled as maximum concurrency --- .../nestjs-libraries/src/bull-mq-transport-new/strategy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/nestjs-libraries/src/bull-mq-transport-new/strategy.ts b/libraries/nestjs-libraries/src/bull-mq-transport-new/strategy.ts index f6068144..d6d7efa7 100644 --- a/libraries/nestjs-libraries/src/bull-mq-transport-new/strategy.ts +++ b/libraries/nestjs-libraries/src/bull-mq-transport-new/strategy.ts @@ -33,7 +33,7 @@ export class BullMqServer extends Server implements CustomTransportStrategy { }); }, { - maxStalledCount: 300, + maxStalledCount: 10, concurrency: 300, connection: ioRedis, removeOnComplete: { From 27f1db769a664ba60b2d381142c6e468faf40f97 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Sat, 9 Aug 2025 12:05:09 +0700 Subject: [PATCH 10/23] feat: find missing jobs fix --- .../src/database/prisma/posts/posts.repository.ts | 10 ++++++++++ .../nestjs-libraries/src/database/prisma/schema.prisma | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts b/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts index 9508a26c..96fccaff 100644 --- a/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts +++ b/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts @@ -48,6 +48,11 @@ export class PostsRepository { searchForMissingThreeHoursPosts() { return this._post.model.post.findMany({ where: { + integration: { + refreshNeeded: false, + inBetweenSteps: false, + disabled: false, + }, publishDate: { gte: dayjs.utc().toDate(), lt: dayjs.utc().add(3, 'hour').toDate(), @@ -66,6 +71,11 @@ export class PostsRepository { getOldPosts(orgId: string, date: string) { return this._post.model.post.findMany({ where: { + integration: { + refreshNeeded: false, + inBetweenSteps: false, + disabled: false, + }, organizationId: orgId, publishDate: { lte: dayjs(date).toDate(), diff --git a/libraries/nestjs-libraries/src/database/prisma/schema.prisma b/libraries/nestjs-libraries/src/database/prisma/schema.prisma index 6027caa2..0958133a 100644 --- a/libraries/nestjs-libraries/src/database/prisma/schema.prisma +++ b/libraries/nestjs-libraries/src/database/prisma/schema.prisma @@ -338,9 +338,14 @@ model Integration { @@unique([organizationId, internalId]) @@index([rootInternalId]) @@index([organizationId]) + @@index([providerIdentifier]) @@index([updatedAt]) + @@index([createdAt]) @@index([deletedAt]) @@index([customerId]) + @@index([inBetweenSteps]) + @@index([refreshNeeded]) + @@index([disabled]) } model Signatures { From caf99d38c5d2ff457a3e8b47aa0177ac46100f08 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Sat, 9 Aug 2025 18:21:07 +0700 Subject: [PATCH 11/23] feat: images and videos to reddit --- .../billing/main.billing.component.tsx | 2 +- .../helpers/media.settings.component.tsx | 21 +-- .../providers/high.order.provider.tsx | 1 + .../providers/reddit/reddit.provider.tsx | 24 ++- .../new-launch/providers/reddit/subreddit.tsx | 18 -- .../src/components/new-launch/store.ts | 2 +- .../posts/providers-settings/reddit.dto.ts | 7 - .../integrations/social/reddit.provider.ts | 163 ++++++++++++++---- 8 files changed, 169 insertions(+), 69 deletions(-) diff --git a/apps/frontend/src/components/billing/main.billing.component.tsx b/apps/frontend/src/components/billing/main.billing.component.tsx index 00476764..cad4e3bc 100644 --- a/apps/frontend/src/components/billing/main.billing.component.tsx +++ b/apps/frontend/src/components/billing/main.billing.component.tsx @@ -505,7 +505,7 @@ export const MainBillingComponent: FC<{ {t( 'your_subscription_will_be_canceled_at', 'Your subscription will be canceled at' - )} + )}{' '} {newDayjs(subscription.cancelAt).local().format('D MMM, YYYY')}
{t( diff --git a/apps/frontend/src/components/launches/helpers/media.settings.component.tsx b/apps/frontend/src/components/launches/helpers/media.settings.component.tsx index 581d2088..3cf774d1 100644 --- a/apps/frontend/src/components/launches/helpers/media.settings.component.tsx +++ b/apps/frontend/src/components/launches/helpers/media.settings.component.tsx @@ -106,16 +106,12 @@ export const CreateThumbnail: FC<{ const [isCapturing, setIsCapturing] = useState(false); const handleLoadedMetadata = useCallback(() => { - if (videoRef.current) { - setDuration(videoRef.current.duration); - setIsLoaded(true); - } + setDuration(videoRef?.current?.duration); + setIsLoaded(true); }, []); const handleTimeUpdate = useCallback(() => { - if (videoRef.current) { - setCurrentTime(videoRef.current.currentTime); - } + setCurrentTime(videoRef?.current?.currentTime); }, []); const handleSeek = useCallback((e: React.ChangeEvent) => { @@ -127,8 +123,6 @@ export const CreateThumbnail: FC<{ }, []); const captureFrame = useCallback(async () => { - if (!videoRef.current || !canvasRef.current) return; - setIsCapturing(true); try { @@ -299,7 +293,14 @@ export const MediaComponentInner: FC<{ alt: string; }) => void; media: - | { id: string; name: string; path: string; thumbnail: string; alt: string, thumbnailTimestamp?: number } + | { + id: string; + name: string; + path: string; + thumbnail: string; + alt: string; + thumbnailTimestamp?: number; + } | undefined; }> = (props) => { const { onClose, onSelect, media } = props; diff --git a/apps/frontend/src/components/new-launch/providers/high.order.provider.tsx b/apps/frontend/src/components/new-launch/providers/high.order.provider.tsx index 1a02aa41..eb888169 100644 --- a/apps/frontend/src/components/new-launch/providers/high.order.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/high.order.provider.tsx @@ -54,6 +54,7 @@ export const withProvider = function (params: { value: Array< Array<{ path: string; + thumbnail?: string; }> >, settings: T, diff --git a/apps/frontend/src/components/new-launch/providers/reddit/reddit.provider.tsx b/apps/frontend/src/components/new-launch/providers/reddit/reddit.provider.tsx index 68e4aefe..18ce5cae 100644 --- a/apps/frontend/src/components/new-launch/providers/reddit/reddit.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/reddit/reddit.provider.tsx @@ -111,7 +111,6 @@ const RedditPreview: FC = (props) => {
{value.title}
-
{ + if ( + settings?.subreddit?.some( + (p: any, index: number) => + p?.value?.type === 'media' && posts[0].length !== 1 + ) + ) { + return 'When posting a media post, you must attached exactly one media file.'; + } + + if ( + posts.some((p) => + p.some((a) => !a.thumbnail && a.path.indexOf('mp4') > -1) + ) + ) { + return 'You must attach a thumbnail to your video post.'; + } + + return true; + }, maximumCharacters: 10000, }); diff --git a/apps/frontend/src/components/new-launch/providers/reddit/subreddit.tsx b/apps/frontend/src/components/new-launch/providers/reddit/subreddit.tsx index f53db361..9e002e1f 100644 --- a/apps/frontend/src/components/new-launch/providers/reddit/subreddit.tsx +++ b/apps/frontend/src/components/new-launch/providers/reddit/subreddit.tsx @@ -233,24 +233,6 @@ export const Subreddit: FC<{ onChange={setURL} /> )} - {value.type === 'media' && ( -
-
-
- -
-
- )} ) : (
diff --git a/apps/frontend/src/components/new-launch/store.ts b/apps/frontend/src/components/new-launch/store.ts index d92d169e..754c833d 100644 --- a/apps/frontend/src/components/new-launch/store.ts +++ b/apps/frontend/src/components/new-launch/store.ts @@ -11,7 +11,7 @@ import { newDayjs } from '@gitroom/frontend/components/layout/set.timezone'; interface Values { id: string; content: string; - media: { id: string; path: string }[]; + media: { id: string; path: string, thumbnail?: string }[]; } interface Internal { diff --git a/libraries/nestjs-libraries/src/dtos/posts/providers-settings/reddit.dto.ts b/libraries/nestjs-libraries/src/dtos/posts/providers-settings/reddit.dto.ts index 1eeef00f..8785ddbe 100644 --- a/libraries/nestjs-libraries/src/dtos/posts/providers-settings/reddit.dto.ts +++ b/libraries/nestjs-libraries/src/dtos/posts/providers-settings/reddit.dto.ts @@ -9,7 +9,6 @@ import { ValidateIf, ValidateNested, } from 'class-validator'; -import { MediaDto } from '@gitroom/nestjs-libraries/dtos/media/media.dto'; import { Type } from 'class-transformer'; export class RedditFlairDto { @@ -57,12 +56,6 @@ export class RedditSettingsDtoInner { @IsDefined() @ValidateNested() flair: RedditFlairDto; - - @ValidateIf((e) => e.type === 'media') - @ValidateNested({ each: true }) - @Type(() => MediaDto) - @ArrayMinSize(1) - media: MediaDto[]; } export class RedditSettingsValueDto { diff --git a/libraries/nestjs-libraries/src/integrations/social/reddit.provider.ts b/libraries/nestjs-libraries/src/integrations/social/reddit.provider.ts index 2676706a..c29194c8 100644 --- a/libraries/nestjs-libraries/src/integrations/social/reddit.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/reddit.provider.ts @@ -9,6 +9,12 @@ import { RedditSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/provider import { timer } from '@gitroom/helpers/utils/timer'; import { groupBy } from 'lodash'; import { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract'; +import { lookup } from 'mime-types'; +import axios from 'axios'; +import WebSocket from 'ws'; + +// @ts-ignore +global.WebSocket = WebSocket; export class RedditProvider extends SocialAbstract implements SocialProvider { override maxConcurrentJob = 1; // Reddit has strict rate limits (1 request per second) @@ -117,6 +123,55 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { }; } + private async uploadFileToReddit(accessToken: string, path: string) { + const mimeType = lookup(path); + const formData = new FormData(); + formData.append('filepath', path.split('/').pop()); + formData.append('mimetype', mimeType || 'application/octet-stream'); + + const { + args: { action, fields }, + } = await ( + await this.fetch( + 'https://oauth.reddit.com/api/media/asset', + { + method: 'POST', + headers: { + Authorization: `Bearer ${accessToken}`, + }, + body: formData, + }, + 'reddit', + 0, + true + ) + ).json(); + + const { data } = await axios.get(path, { + responseType: 'arraybuffer', + }); + + const upload = (fields as { name: string; value: string }[]).reduce( + (acc, value) => { + acc.append(value.name, value.value); + return acc; + }, + new FormData() + ); + + upload.append( + 'file', + new Blob([Buffer.from(data)], { type: mimeType as string }) + ); + + const d = await fetch('https:' + action, { + method: 'POST', + body: upload, + }); + + return [...(await d.text()).matchAll(/(.*?)<\/Location>/g)][0][1]; + } + async post( id: string, accessToken: string, @@ -131,7 +186,9 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { title: firstPostSettings.value.title || '', kind: firstPostSettings.value.type === 'media' - ? 'image' + ? post.media[0].path.indexOf('mp4') > -1 + ? 'video' + : 'image' : firstPostSettings.value.type, ...(firstPostSettings.value.flair ? { flair_id: firstPostSettings.value.flair.id } @@ -143,22 +200,25 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { : {}), ...(firstPostSettings.value.type === 'media' ? { - url: `${ - firstPostSettings.value.media[0].path.indexOf('http') === -1 - ? `${process.env.NEXT_PUBLIC_BACKEND_URL}/uploads` - : `` - }${firstPostSettings.value.media[0].path}`, + url: await this.uploadFileToReddit( + accessToken, + post.media[0].path + ), + ...(post.media[0].path.indexOf('mp4') > -1 + ? { + video_poster_url: await this.uploadFileToReddit( + accessToken, + post.media[0].thumbnail + ), + } + : {}), } : {}), text: post.message, sr: firstPostSettings.value.subreddit, }; - const { - json: { - data: { id, name, url }, - }, - } = await ( + const all = await ( await this.fetch('https://oauth.reddit.com/api/submit', { method: 'POST', headers: { @@ -169,6 +229,38 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { }) ).json(); + const { id, name, url } = await new Promise<{ + id: string; + name: string; + url: string; + }>((res) => { + if (all?.json?.data?.id) { + res(all.json.data); + } + + const ws = new WebSocket(all.json.data.websocket_url); + ws.on('message', (data: any) => { + setTimeout(() => { + res({ id: '', name: '', url: '' }); + ws.close(); + }, 30_000); + try { + const parsedData = JSON.parse(data.toString()); + if (parsedData?.payload?.redirect) { + const onlyId = parsedData?.payload?.redirect.replace( + /https:\/\/www\.reddit\.com\/r\/.*?\/comments\/(.*?)\/.*/g, + '$1' + ); + res({ + id: onlyId, + name: `t3_${onlyId}`, + url: parsedData?.payload?.redirect, + }); + } + } catch (err) {} + }); + }); + valueArray.push({ postId: id, releaseURL: url, @@ -202,8 +294,6 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { }) ).json(); - // console.log(JSON.stringify(allTop, null, 2), JSON.stringify(allJson, null, 2), JSON.stringify(allData, null, 2)); - valueArray.push({ postId: commentId, releaseURL: 'https://www.reddit.com' + permalink, @@ -233,7 +323,7 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { const { data: { children }, } = await ( - await fetch( + await this.fetch( `https://oauth.reddit.com/subreddits/search?show=public&q=${data.word}&sort=activity&show_users=false&limit=10`, { method: 'GET', @@ -241,7 +331,10 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/x-www-form-urlencoded', }, - } + }, + 'reddit', + 0, + false ) ).json(); @@ -267,28 +360,34 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { permissions.push('link'); } - // if (submissionType === 'any' || allow_images) { - // permissions.push('media'); - // } + if (allow_images) { + permissions.push('media'); + } return permissions; } async restrictions(accessToken: string, data: { subreddit: string }) { const { - data: { submission_type, allow_images }, + data: { submission_type, allow_images, ...all2 }, } = await ( - await fetch(`https://oauth.reddit.com/${data.subreddit}/about`, { - method: 'GET', - headers: { - Authorization: `Bearer ${accessToken}`, - 'Content-Type': 'application/x-www-form-urlencoded', + await this.fetch( + `https://oauth.reddit.com/${data.subreddit}/about`, + { + method: 'GET', + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/x-www-form-urlencoded', + }, }, - }) + 'reddit', + 0, + false + ) ).json(); const { is_flair_required, ...all } = await ( - await fetch( + await this.fetch( `https://oauth.reddit.com/api/v1/${ data.subreddit.split('/r/')[1] }/post_requirements`, @@ -298,7 +397,10 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/x-www-form-urlencoded', }, - } + }, + 'reddit', + 0, + false ) ).json(); @@ -307,7 +409,7 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { async (res) => { try { const flair = await ( - await fetch( + await this.fetch( `https://oauth.reddit.com/${data.subreddit}/api/link_flair_v2`, { method: 'GET', @@ -315,7 +417,10 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/x-www-form-urlencoded', }, - } + }, + 'reddit', + 0, + false ) ).json(); From a8e32dc39993ecdd25972fc8fd184e5143c2d11b Mon Sep 17 00:00:00 2001 From: Nevo David Date: Sat, 9 Aug 2025 20:22:38 +0700 Subject: [PATCH 12/23] feat: suspicious registartion --- .../src/api/routes/integrations.controller.ts | 26 ++++++----- .../launches/add.provider.component.tsx | 11 ++++- .../launches/continue.integration.tsx | 7 ++- .../src/components/layout/layout.settings.tsx | 1 - .../layout/pre-condition.component.tsx | 43 +++++++++++++++++++ .../new-layout/layout.component.tsx | 3 +- .../integrations/integration.repository.ts | 15 ++++++- .../integrations/integration.service.ts | 4 ++ 8 files changed, 93 insertions(+), 17 deletions(-) create mode 100644 apps/frontend/src/components/layout/pre-condition.component.tsx diff --git a/apps/backend/src/api/routes/integrations.controller.ts b/apps/backend/src/api/routes/integrations.controller.ts index fb3eee3b..8980ab27 100644 --- a/apps/backend/src/api/routes/integrations.controller.ts +++ b/apps/backend/src/api/routes/integrations.controller.ts @@ -1,13 +1,5 @@ import { - Body, - Controller, - Delete, - Get, - Param, - Post, - Put, - Query, - UseFilters, + Body, Controller, Delete, Get, HttpException, Param, Post, Put, 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'; @@ -261,7 +253,7 @@ export class IntegrationsController { throw new Error('Invalid integration'); } - let newList: any[] | {none: true} = []; + let newList: any[] | { none: true } = []; try { newList = (await this.functionIntegration(org, body)) || []; } catch (err) { @@ -298,7 +290,7 @@ export class IntegrationsController { image: p.image, label: p.name, })), - ...newList as any[], + ...(newList as any[]), ], (p) => p.id ).filter((f) => f.label && f.id); @@ -487,6 +479,18 @@ export class IntegrationsController { validName = `Channel_${String(id).slice(0, 8)}`; } } + + if ( + process.env.STRIPE_PUBLISHABLE_KEY && + org.isTrailing && + !!(await this._integrationService.checkPreviousConnections( + org.id, + String(id) + )) + ) { + throw new HttpException('', 412); + } + return this._integrationService.createOrUpdateIntegration( additionalSettings, !!integrationProvider.oneTimeToken, diff --git a/apps/frontend/src/components/launches/add.provider.component.tsx b/apps/frontend/src/components/launches/add.provider.component.tsx index a72c4b1a..bf83b7ed 100644 --- a/apps/frontend/src/components/launches/add.provider.component.tsx +++ b/apps/frontend/src/components/launches/add.provider.component.tsx @@ -1,14 +1,14 @@ 'use client'; import { useModals } from '@mantine/modals'; -import React, { FC, useCallback, useMemo } from 'react'; +import React, { FC, useCallback, useEffect, useMemo } from 'react'; import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; import { Input } from '@gitroom/react/form/input'; import { FieldValues, FormProvider, useForm } from 'react-hook-form'; import { Button } from '@gitroom/react/form/button'; import { classValidatorResolver } from '@hookform/resolvers/class-validator'; import { ApiKeyDto } from '@gitroom/nestjs-libraries/dtos/integrations/api.key.dto'; -import { useRouter } from 'next/navigation'; +import { useRouter, useSearchParams } from 'next/navigation'; import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component'; import { useVariables } from '@gitroom/react/helpers/variable.context'; import { useToaster } from '@gitroom/react/toaster/toaster'; @@ -42,9 +42,16 @@ export const AddProviderButton: FC<{ update?: () => void; }> = (props) => { const { update } = props; + const query = useSearchParams(); const add = useAddProvider(update); const t = useT(); + useEffect(() => { + if (query.get('onboarding')) { + add(); + } + }, []); + return ( + +
+
+ ); +}; +export const PreConditionComponent: FC = () => { + const modal = useModals(); + const query = useSearchParams(); + useEffect(() => { + if (query.get('precondition')) { + modal.openModal({ + title: '', + withCloseButton: false, + classNames: { + modal: 'text-textColor', + }, + size: 'auto', + children: ( + + + + ), + }); + } + }, []); + return null; +}; diff --git a/apps/frontend/src/components/new-layout/layout.component.tsx b/apps/frontend/src/components/new-layout/layout.component.tsx index 14063dbb..abcfe4c5 100644 --- a/apps/frontend/src/components/new-layout/layout.component.tsx +++ b/apps/frontend/src/components/new-layout/layout.component.tsx @@ -38,6 +38,7 @@ import { ChromeExtensionComponent } from '@gitroom/frontend/components/layout/ch import NotificationComponent from '@gitroom/frontend/components/notifications/notification.component'; import { BillingAfter } from '@gitroom/frontend/components/new-layout/billing.after'; import { OrganizationSelector } from '@gitroom/frontend/components/layout/organization.selector'; +import { PreConditionComponent } from '@gitroom/frontend/components/layout/pre-condition.component'; const jakartaSans = Plus_Jakarta_Sans({ weight: ['600', '500'], @@ -79,8 +80,8 @@ export const LayoutComponent = ({ children }: { children: ReactNode }) => { + - {user.tier !== 'FREE' && }
({ @@ -68,6 +68,19 @@ export class IntegrationRepository { }); } + async checkPreviousConnections(org: string, id: string) { + const findIt = await this._integration.model.integration.findFirst({ + where: { + organizationId: { + not: org, + }, + rootInternalId: id.split('_').pop(), + }, + }); + + return findIt; + } + updateProviderSettings(org: string, id: string, settings: string) { return this._integration.model.integration.update({ where: { diff --git a/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts b/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts index d7519354..a54758e6 100644 --- a/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts +++ b/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts @@ -73,6 +73,10 @@ export class IntegrationService { ); } + checkPreviousConnections(org: string, id: string) { + return this._integrationRepository.checkPreviousConnections(org, id); + } + async createOrUpdateIntegration( additionalSettings: | { From 2ab65bb375e789bbc182f9a0ad6e9bafa466f756 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Sat, 9 Aug 2025 20:29:13 +0700 Subject: [PATCH 13/23] feat: fix billing --- .../src/api/routes/integrations.controller.ts | 13 +++++++++++-- .../prisma/integrations/integration.repository.ts | 11 ++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/apps/backend/src/api/routes/integrations.controller.ts b/apps/backend/src/api/routes/integrations.controller.ts index 8980ab27..7ae2136d 100644 --- a/apps/backend/src/api/routes/integrations.controller.ts +++ b/apps/backend/src/api/routes/integrations.controller.ts @@ -1,5 +1,14 @@ import { - Body, Controller, Delete, Get, HttpException, Param, Post, Put, Query, UseFilters + Body, + Controller, + Delete, + Get, + HttpException, + Param, + Post, + Put, + 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'; @@ -483,7 +492,7 @@ export class IntegrationsController { if ( process.env.STRIPE_PUBLISHABLE_KEY && org.isTrailing && - !!(await this._integrationService.checkPreviousConnections( + (await this._integrationService.checkPreviousConnections( org.id, String(id) )) diff --git a/libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts b/libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts index 3c97e97c..db9c5f22 100644 --- a/libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts +++ b/libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts @@ -69,16 +69,17 @@ export class IntegrationRepository { } async checkPreviousConnections(org: string, id: string) { - const findIt = await this._integration.model.integration.findFirst({ + const findIt = await this._integration.model.integration.findMany({ where: { - organizationId: { - not: org, - }, rootInternalId: id.split('_').pop(), }, + select: { + organizationId: true, + id: true, + }, }); - return findIt; + return findIt.some((f) => f.organizationId === org) || findIt.length === 0; } updateProviderSettings(org: string, id: string, settings: string) { From dee9a6d5f07e2832834aeb58daa40e2b0588a830 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Sun, 10 Aug 2025 03:14:56 +0700 Subject: [PATCH 14/23] feat: remove timezone --- apps/frontend/src/app/(app)/layout.tsx | 16 ++++----- .../src/components/layout/set.timezone.tsx | 2 +- .../components/settings/metric.component.tsx | 35 ++++++++++--------- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/apps/frontend/src/app/(app)/layout.tsx b/apps/frontend/src/app/(app)/layout.tsx index e30d05f2..bea3e05e 100644 --- a/apps/frontend/src/app/(app)/layout.tsx +++ b/apps/frontend/src/app/(app)/layout.tsx @@ -18,13 +18,13 @@ import { FacebookComponent } from '@gitroom/frontend/components/layout/facebook. import { headers } from 'next/headers'; import { headerName } from '@gitroom/react/translation/i18n.config'; import { HtmlComponent } from '@gitroom/frontend/components/layout/html.component'; -import dynamicLoad from 'next/dynamic'; -const SetTimezone = dynamicLoad( - () => import('@gitroom/frontend/components/layout/set.timezone'), - { - ssr: false, - } -); +// import dynamicLoad from 'next/dynamic'; +// const SetTimezone = dynamicLoad( +// () => import('@gitroom/frontend/components/layout/set.timezone'), +// { +// ssr: false, +// } +// ); const jakartaSans = Plus_Jakarta_Sans({ weight: ['600', '500'], @@ -79,7 +79,7 @@ export default async function AppLayout({ children }: { children: ReactNode }) { } > - + {/**/} diff --git a/apps/frontend/src/components/layout/set.timezone.tsx b/apps/frontend/src/components/layout/set.timezone.tsx index 1835b96e..f37b4f0f 100644 --- a/apps/frontend/src/components/layout/set.timezone.tsx +++ b/apps/frontend/src/components/layout/set.timezone.tsx @@ -16,7 +16,7 @@ export const getTimezone = () => { }; export const newDayjs = (config?: ConfigType) => { - return dayjs.tz(config, getTimezone()); + return dayjs(config); }; const SetTimezone: FC = () => { diff --git a/apps/frontend/src/components/settings/metric.component.tsx b/apps/frontend/src/components/settings/metric.component.tsx index d91ae891..23096d49 100644 --- a/apps/frontend/src/components/settings/metric.component.tsx +++ b/apps/frontend/src/components/settings/metric.component.tsx @@ -11,6 +11,7 @@ const dateMetrics = [ import dayjs from 'dayjs'; import timezone from 'dayjs/plugin/timezone'; +import { timezoneSaver } from '@gitroom/frontend/components/layout/set.timezone'; dayjs.extend(timezone); const MetricComponent = () => { @@ -46,23 +47,23 @@ const MetricComponent = () => { ))} -
Current Timezone
- + {/*
Current Timezone
*/} + {/**/} + {/* {timezones.map((metric) => (*/} + {/* */} + {/* {metric.label}*/} + {/* */} + {/* ))}*/} + {/**/}
); }; From 9aea717ed22a47c2f8d442909928f06eaf7fd964 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Sun, 10 Aug 2025 03:20:32 +0700 Subject: [PATCH 15/23] feat: remove timezone --- apps/frontend/src/components/settings/metric.component.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/frontend/src/components/settings/metric.component.tsx b/apps/frontend/src/components/settings/metric.component.tsx index 23096d49..29efeaf5 100644 --- a/apps/frontend/src/components/settings/metric.component.tsx +++ b/apps/frontend/src/components/settings/metric.component.tsx @@ -11,7 +11,6 @@ const dateMetrics = [ import dayjs from 'dayjs'; import timezone from 'dayjs/plugin/timezone'; -import { timezoneSaver } from '@gitroom/frontend/components/layout/set.timezone'; dayjs.extend(timezone); const MetricComponent = () => { From 07177c7929d20064e21e6fb3bb90c84d3274e4ba Mon Sep 17 00:00:00 2001 From: Nevo David Date: Sun, 10 Aug 2025 11:08:58 +0700 Subject: [PATCH 16/23] feat: fix connections --- .../database/prisma/integrations/integration.repository.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts b/libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts index db9c5f22..6024b921 100644 --- a/libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts +++ b/libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts @@ -79,7 +79,11 @@ export class IntegrationRepository { }, }); - return findIt.some((f) => f.organizationId === org) || findIt.length === 0; + if (findIt.some((f) => f.organizationId === org)) { + return false; + } + + return findIt.length > 0; } updateProviderSettings(org: string, id: string, settings: string) { From e0fa9f49c6d2c31d6ca3bc5faf65f938c2a2153e Mon Sep 17 00:00:00 2001 From: Nevo David Date: Sun, 10 Aug 2025 18:46:02 +0700 Subject: [PATCH 17/23] feat: normal should not have html tags --- libraries/helpers/src/utils/strip.html.validation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/helpers/src/utils/strip.html.validation.ts b/libraries/helpers/src/utils/strip.html.validation.ts index e5abdab4..7ad3591c 100644 --- a/libraries/helpers/src/utils/strip.html.validation.ts +++ b/libraries/helpers/src/utils/strip.html.validation.ts @@ -232,7 +232,7 @@ export const stripHtmlValidation = ( convertMentionFunction ); - return striptags(processedHtml, ['h1', 'h2', 'h3']); + return striptags(processedHtml); } // Strip all other tags From 3cf7ed409636b498247917445141a41134a8d754 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Mon, 11 Aug 2025 19:02:09 +0700 Subject: [PATCH 18/23] feat: shortlink fix --- .../src/short-linking/short.link.service.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/libraries/nestjs-libraries/src/short-linking/short.link.service.ts b/libraries/nestjs-libraries/src/short-linking/short.link.service.ts index 3301606c..b3706713 100644 --- a/libraries/nestjs-libraries/src/short-linking/short.link.service.ts +++ b/libraries/nestjs-libraries/src/short-linking/short.link.service.ts @@ -37,7 +37,8 @@ export class ShortLinkService { } const mergeMessages = messages.join(' '); - const urlRegex = /(https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*))/gm; + const urlRegex = + /(https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*))/gm; const urls = mergeMessages.match(urlRegex); if (!urls) { // No URLs found, return the original text @@ -49,12 +50,20 @@ export class ShortLinkService { ); } - async convertTextToShortLinks(id: string, messages: string[]) { + async convertTextToShortLinks(id: string, messagesList: string[]) { if (ShortLinkService.provider.shortLinkDomain === 'empty') { - return messages; + return messagesList; } - const urlRegex = /(https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*))/gm; + const messages = messagesList.map((text) => { + return text + .replace(/&/g, '&') + .replace(/?/g, '?') + .replace(/#/g, '#'); + }); + + const urlRegex = + /(https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*))/gm; return Promise.all( messages.map(async (text) => { const urls = uniq(text.match(urlRegex)); From 08e7dfbe1dc896642ba3f8f9ad535683d51a58d8 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Mon, 11 Aug 2025 19:28:44 +0700 Subject: [PATCH 19/23] feat: fix editor --- .../src/components/new-launch/editor.tsx | 132 ++++++++++-------- 1 file changed, 77 insertions(+), 55 deletions(-) diff --git a/apps/frontend/src/components/new-launch/editor.tsx b/apps/frontend/src/components/new-launch/editor.tsx index 681589fc..6d6c9b09 100644 --- a/apps/frontend/src/components/new-launch/editor.tsx +++ b/apps/frontend/src/components/new-launch/editor.tsx @@ -759,66 +759,84 @@ export const OnlyEditor = forwardRef< InterceptUnderlineShortcut, BulletList, ListItem, - Link.configure({ - openOnClick: false, - autolink: true, - defaultProtocol: 'https', - protocols: ['http', 'https'], - isAllowedUri: (url, ctx) => { - try { - // construct URL - const parsedUrl = url.includes(':') - ? new URL(url) - : new URL(`${ctx.defaultProtocol}://${url}`); + ...(editorType === 'html' || editorType === 'markdown' + ? [ + Link.configure({ + openOnClick: false, + autolink: true, + defaultProtocol: 'https', + protocols: ['http', 'https'], + isAllowedUri: (url, ctx) => { + try { + // prevent transforming plain emails like foo@bar.com into links + const trimmed = String(url).trim(); + const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (emailPattern.test(trimmed)) { + return false; + } - // use default validation - if (!ctx.defaultValidate(parsedUrl.href)) { - return false; - } + // construct URL + const parsedUrl = url.includes(':') + ? new URL(url) + : new URL(`${ctx.defaultProtocol}://${url}`); - // disallowed protocols - const disallowedProtocols = ['ftp', 'file', 'mailto']; - const protocol = parsedUrl.protocol.replace(':', ''); + // use default validation + if (!ctx.defaultValidate(parsedUrl.href)) { + return false; + } - if (disallowedProtocols.includes(protocol)) { - return false; - } + // disallowed protocols + const disallowedProtocols = ['ftp', 'file', 'mailto']; + const protocol = parsedUrl.protocol.replace(':', ''); - // only allow protocols specified in ctx.protocols - const allowedProtocols = ctx.protocols.map((p) => - typeof p === 'string' ? p : p.scheme - ); + if (disallowedProtocols.includes(protocol)) { + return false; + } - if (!allowedProtocols.includes(protocol)) { - return false; - } + // only allow protocols specified in ctx.protocols + const allowedProtocols = ctx.protocols.map((p) => + typeof p === 'string' ? p : p.scheme + ); - // all checks have passed - return true; - } catch { - return false; - } - }, - shouldAutoLink: (url) => { - try { - // construct URL - const parsedUrl = url.includes(':') - ? new URL(url) - : new URL(`https://${url}`); + if (!allowedProtocols.includes(protocol)) { + return false; + } - // only auto-link if the domain is not in the disallowed list - const disallowedDomains = [ - 'example-no-autolink.com', - 'another-no-autolink.com', - ]; - const domain = parsedUrl.hostname; + // all checks have passed + return true; + } catch { + return false; + } + }, + shouldAutoLink: (url) => { + try { + // prevent auto-linking of plain emails like foo@bar.com + const trimmed = String(url).trim(); + const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (emailPattern.test(trimmed)) { + return false; + } - return !disallowedDomains.includes(domain); - } catch { - return false; - } - }, - }), + // construct URL + const parsedUrl = url.includes(':') + ? new URL(url) + : new URL(`https://${url}`); + + // only auto-link if the domain is not in the disallowed list + const disallowedDomains = [ + 'example-no-autolink.com', + 'another-no-autolink.com', + ]; + const domain = parsedUrl.hostname; + + return !disallowedDomains.includes(domain); + } catch { + return false; + } + }, + }), + ] + : []), ...(internal?.integration?.id ? [ Mention.configure({ @@ -839,9 +857,13 @@ export const OnlyEditor = forwardRef< }), ] : []), - Heading.configure({ - levels: [1, 2, 3], - }), + ...(editorType === 'html' || editorType === 'markdown' + ? [ + Heading.configure({ + levels: [1, 2, 3], + }), + ] + : []), History.configure({ depth: 100, // default is 100 newGroupDelay: 100, // default is 500ms From 11c02f8433705aae61c70adf2a52b64565238894 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Tue, 12 Aug 2025 13:52:32 +0700 Subject: [PATCH 20/23] feat: fix resend --- .../src/emails/resend.provider.ts | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/libraries/nestjs-libraries/src/emails/resend.provider.ts b/libraries/nestjs-libraries/src/emails/resend.provider.ts index b08c0eed..3cca6da8 100644 --- a/libraries/nestjs-libraries/src/emails/resend.provider.ts +++ b/libraries/nestjs-libraries/src/emails/resend.provider.ts @@ -14,14 +14,18 @@ export class ResendProvider implements EmailInterface { emailFromAddress: string, replyTo?: string ) { - const sends = await resend.emails.send({ - from: `${emailFromName} <${emailFromAddress}>`, - to, - subject, - html, - ...(replyTo && { reply_to: replyTo }), - }); + try { + const sends = await resend.emails.send({ + from: `${emailFromName} <${emailFromAddress}>`, + to, + subject, + html, + ...(replyTo && { reply_to: replyTo }), + }); - return sends; + return sends; + } catch (err) { + console.log(err); + } } } From 8a1e45aa10082612260c4f4c3181cbb2097c2ecf Mon Sep 17 00:00:00 2001 From: Nevo David Date: Tue, 12 Aug 2025 13:55:03 +0700 Subject: [PATCH 21/23] feat: sent false --- libraries/nestjs-libraries/src/emails/resend.provider.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/nestjs-libraries/src/emails/resend.provider.ts b/libraries/nestjs-libraries/src/emails/resend.provider.ts index 3cca6da8..c0519b0f 100644 --- a/libraries/nestjs-libraries/src/emails/resend.provider.ts +++ b/libraries/nestjs-libraries/src/emails/resend.provider.ts @@ -27,5 +27,7 @@ export class ResendProvider implements EmailInterface { } catch (err) { console.log(err); } + + return { sent: false }; } } From 54b2b95cf3824cd9f93f5eb9b9be63269ba113e4 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Tue, 12 Aug 2025 14:39:13 +0700 Subject: [PATCH 22/23] feat: connect accounts with not picture --- .../src/integrations/social/bluesky.provider.ts | 2 +- .../src/integrations/social/dev.to.provider.ts | 2 +- .../src/integrations/social/discord.provider.ts | 2 +- .../src/integrations/social/dribbble.provider.ts | 2 +- .../src/integrations/social/facebook.provider.ts | 6 ++---- .../src/integrations/social/farcaster.provider.ts | 2 +- .../src/integrations/social/hashnode.provider.ts | 2 +- .../src/integrations/social/instagram.provider.ts | 6 ++---- .../social/instagram.standalone.provider.ts | 4 ++-- .../src/integrations/social/lemmy.provider.ts | 2 +- .../src/integrations/social/linkedin.provider.ts | 2 +- .../src/integrations/social/mastodon.provider.ts | 2 +- .../src/integrations/social/medium.provider.ts | 2 +- .../src/integrations/social/nostr.provider.ts | 2 +- .../src/integrations/social/pinterest.provider.ts | 2 +- .../src/integrations/social/reddit.provider.ts | 4 ++-- .../src/integrations/social/slack.provider.ts | 2 +- .../src/integrations/social/telegram.provider.ts | 2 +- .../src/integrations/social/threads.provider.ts | 12 ++++-------- .../src/integrations/social/tiktok.provider.ts | 2 +- .../src/integrations/social/vk.provider.ts | 4 ++-- .../src/integrations/social/x.provider.ts | 2 +- .../src/integrations/social/youtube.provider.ts | 4 ++-- 23 files changed, 32 insertions(+), 40 deletions(-) diff --git a/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts b/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts index 621549ad..1803992b 100644 --- a/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts @@ -213,7 +213,7 @@ export class BlueskyProvider extends SocialAbstract implements SocialProvider { accessToken: accessJwt, id: did, name: profile.data.displayName!, - picture: profile.data.avatar!, + picture: profile?.data?.avatar || '', username: profile.data.handle!, }; } catch (e) { diff --git a/libraries/nestjs-libraries/src/integrations/social/dev.to.provider.ts b/libraries/nestjs-libraries/src/integrations/social/dev.to.provider.ts index a8e422b4..53487b3d 100644 --- a/libraries/nestjs-libraries/src/integrations/social/dev.to.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/dev.to.provider.ts @@ -81,7 +81,7 @@ export class DevToProvider extends SocialAbstract implements SocialProvider { accessToken: body.apiKey, id, name, - picture: profile_image, + picture: profile_image || '', username, }; } catch (err) { diff --git a/libraries/nestjs-libraries/src/integrations/social/discord.provider.ts b/libraries/nestjs-libraries/src/integrations/social/discord.provider.ts index 54728649..8796147c 100644 --- a/libraries/nestjs-libraries/src/integrations/social/discord.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/discord.provider.ts @@ -242,7 +242,7 @@ export class DiscordProvider extends SocialAbstract implements SocialProvider { .filter((role: any) => role.name.toLowerCase().includes(data.query.toLowerCase()) ) - .filter((f) => f.name !== '@everyone' && f.name !== '@here'); + .filter((f: any) => f.name !== '@everyone' && f.name !== '@here'); const list = await ( await fetch( diff --git a/libraries/nestjs-libraries/src/integrations/social/dribbble.provider.ts b/libraries/nestjs-libraries/src/integrations/social/dribbble.provider.ts index a8b76758..4e231085 100644 --- a/libraries/nestjs-libraries/src/integrations/social/dribbble.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/dribbble.provider.ts @@ -54,7 +54,7 @@ export class DribbbleProvider extends SocialAbstract implements SocialProvider { accessToken: access_token, refreshToken: refreshToken, expiresIn: expires_in, - picture: profile_image, + picture: profile_image || '', username, }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/facebook.provider.ts b/libraries/nestjs-libraries/src/integrations/social/facebook.provider.ts index be62e6d3..c788d5b6 100644 --- a/libraries/nestjs-libraries/src/integrations/social/facebook.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/facebook.provider.ts @@ -236,9 +236,7 @@ export class FacebookProvider extends SocialAbstract implements SocialProvider { const { id, name, - picture: { - data: { url }, - }, + picture } = await ( await fetch( `https://graph.facebook.com/v20.0/me?fields=id,name,picture&access_token=${access_token}` @@ -251,7 +249,7 @@ export class FacebookProvider extends SocialAbstract implements SocialProvider { accessToken: access_token, refreshToken: access_token, expiresIn: dayjs().add(59, 'days').unix() - dayjs().unix(), - picture: url, + picture: picture?.data?.url || '', username: '', }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/farcaster.provider.ts b/libraries/nestjs-libraries/src/integrations/social/farcaster.provider.ts index 4bb81bdd..212b4010 100644 --- a/libraries/nestjs-libraries/src/integrations/social/farcaster.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/farcaster.provider.ts @@ -61,7 +61,7 @@ export class FarcasterProvider accessToken: data.signer_uuid, refreshToken: '', expiresIn: dayjs().add(200, 'year').unix() - dayjs().unix(), - picture: data.pfp_url, + picture: data?.pfp_url || '', username: data.username, }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/hashnode.provider.ts b/libraries/nestjs-libraries/src/integrations/social/hashnode.provider.ts index eee8978e..8fdd768e 100644 --- a/libraries/nestjs-libraries/src/integrations/social/hashnode.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/hashnode.provider.ts @@ -91,7 +91,7 @@ export class HashnodeProvider extends SocialAbstract implements SocialProvider { accessToken: body.apiKey, id, name, - picture: profilePicture, + picture: profilePicture || '', username, }; } catch (err) { diff --git a/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts b/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts index 5a7edf4a..2bdf2889 100644 --- a/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts @@ -364,9 +364,7 @@ export class InstagramProvider const { id, name, - picture: { - data: { url }, - }, + picture } = await ( await fetch( `https://graph.facebook.com/v20.0/me?fields=id,name,picture&access_token=${access_token}` @@ -379,7 +377,7 @@ export class InstagramProvider accessToken: access_token, refreshToken: access_token, expiresIn: dayjs().add(59, 'days').unix() - dayjs().unix(), - picture: url, + picture: picture?.data?.url || '', username: '', }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/instagram.standalone.provider.ts b/libraries/nestjs-libraries/src/integrations/social/instagram.standalone.provider.ts index a74ab431..68938b17 100644 --- a/libraries/nestjs-libraries/src/integrations/social/instagram.standalone.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/instagram.standalone.provider.ts @@ -41,7 +41,7 @@ export class InstagramStandaloneProvider ) ).json(); - const { user_id, name, username, profile_picture_url } = await ( + const { user_id, name, username, profile_picture_url = '' } = await ( await fetch( `https://graph.instagram.com/v21.0/me?fields=user_id,username,name,profile_picture_url&access_token=${access_token}` ) @@ -53,7 +53,7 @@ export class InstagramStandaloneProvider accessToken: access_token, refreshToken: access_token, expiresIn: dayjs().add(59, 'days').unix() - dayjs().unix(), - picture: profile_picture_url, + picture: profile_picture_url || '', username, }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/lemmy.provider.ts b/libraries/nestjs-libraries/src/integrations/social/lemmy.provider.ts index 5f2e0e6d..6b5a9759 100644 --- a/libraries/nestjs-libraries/src/integrations/social/lemmy.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/lemmy.provider.ts @@ -107,7 +107,7 @@ export class LemmyProvider extends SocialAbstract implements SocialProvider { user.person_view.person.display_name || user.person_view.person.name || '', - picture: user.person_view.person.avatar || '', + picture: user?.person_view?.person?.avatar || '', username: body.identifier || '', }; } catch (e) { diff --git a/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts b/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts index aac124f7..d4731735 100644 --- a/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts @@ -80,7 +80,7 @@ export class LinkedinProvider extends SocialAbstract implements SocialProvider { refreshToken, expiresIn: expires_in, name, - picture, + picture: picture || '', username: vanityName, }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/mastodon.provider.ts b/libraries/nestjs-libraries/src/integrations/social/mastodon.provider.ts index 413f637c..72957765 100644 --- a/libraries/nestjs-libraries/src/integrations/social/mastodon.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/mastodon.provider.ts @@ -91,7 +91,7 @@ export class MastodonProvider extends SocialAbstract implements SocialProvider { accessToken: tokenInformation.access_token, refreshToken: 'null', expiresIn: dayjs().add(100, 'years').unix() - dayjs().unix(), - picture: personalInformation.avatar, + picture: personalInformation?.avatar || '', username: personalInformation.username, }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/medium.provider.ts b/libraries/nestjs-libraries/src/integrations/social/medium.provider.ts index db838978..00b91c01 100644 --- a/libraries/nestjs-libraries/src/integrations/social/medium.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/medium.provider.ts @@ -72,7 +72,7 @@ export class MediumProvider extends SocialAbstract implements SocialProvider { accessToken: body.apiKey, id, name, - picture: imageUrl, + picture: imageUrl || '', username, }; } catch (err) { diff --git a/libraries/nestjs-libraries/src/integrations/social/nostr.provider.ts b/libraries/nestjs-libraries/src/integrations/social/nostr.provider.ts index ccfd2871..ec961d60 100644 --- a/libraries/nestjs-libraries/src/integrations/social/nostr.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/nostr.provider.ts @@ -147,7 +147,7 @@ export class NostrProvider extends SocialAbstract implements SocialProvider { accessToken: AuthService.signJWT({ password: body.password }), refreshToken: '', expiresIn: dayjs().add(200, 'year').unix() - dayjs().unix(), - picture: user.picture, + picture: user?.picture || '', username: user.name || 'nousername', }; } catch (e) { diff --git a/libraries/nestjs-libraries/src/integrations/social/pinterest.provider.ts b/libraries/nestjs-libraries/src/integrations/social/pinterest.provider.ts index 1e53ea68..585d188c 100644 --- a/libraries/nestjs-libraries/src/integrations/social/pinterest.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/pinterest.provider.ts @@ -82,7 +82,7 @@ export class PinterestProvider accessToken: access_token, refreshToken: refreshToken, expiresIn: expires_in, - picture: profile_image, + picture: profile_image || '', username, }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/reddit.provider.ts b/libraries/nestjs-libraries/src/integrations/social/reddit.provider.ts index c29194c8..f249c023 100644 --- a/libraries/nestjs-libraries/src/integrations/social/reddit.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/reddit.provider.ts @@ -59,7 +59,7 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { accessToken, refreshToken: newRefreshToken, expiresIn, - picture: icon_img.split('?')[0], + picture: icon_img?.split?.('?')?.[0] || '', username: name, }; } @@ -118,7 +118,7 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { accessToken, refreshToken, expiresIn, - picture: icon_img.split('?')[0], + picture: icon_img?.split?.('?')?.[0] || '', username: name, }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/slack.provider.ts b/libraries/nestjs-libraries/src/integrations/social/slack.provider.ts index 459acff2..762c2bba 100644 --- a/libraries/nestjs-libraries/src/integrations/social/slack.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/slack.provider.ts @@ -95,7 +95,7 @@ export class SlackProvider extends SocialAbstract implements SocialProvider { accessToken: access_token, refreshToken: 'null', expiresIn: dayjs().add(100, 'years').unix() - dayjs().unix(), - picture: user.profile.image_original, + picture: user?.profile?.image_original || '', username: user.name, }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts b/libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts index 0fc8e8c7..ad521f89 100644 --- a/libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts @@ -71,7 +71,7 @@ export class TelegramProvider extends SocialAbstract implements SocialProvider { accessToken: String(chat.id), refreshToken: '', expiresIn: dayjs().add(200, 'year').unix() - dayjs().unix(), - picture: photo, + picture: photo || '', username: chat.username!, }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/threads.provider.ts b/libraries/nestjs-libraries/src/integrations/social/threads.provider.ts index 932466b1..e86ac41f 100644 --- a/libraries/nestjs-libraries/src/integrations/social/threads.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/threads.provider.ts @@ -41,9 +41,7 @@ export class ThreadsProvider extends SocialAbstract implements SocialProvider { id, name, username, - picture: { - data: { url }, - }, + picture } = await this.fetchPageInformation(access_token); return { @@ -52,7 +50,7 @@ export class ThreadsProvider extends SocialAbstract implements SocialProvider { accessToken: access_token, refreshToken: access_token, expiresIn: dayjs().add(59, 'days').unix() - dayjs().unix(), - picture: url, + picture: picture?.data?.url || '', username: '', }; } @@ -112,9 +110,7 @@ export class ThreadsProvider extends SocialAbstract implements SocialProvider { id, name, username, - picture: { - data: { url }, - }, + picture, } = await this.fetchPageInformation(access_token); return { @@ -123,7 +119,7 @@ export class ThreadsProvider extends SocialAbstract implements SocialProvider { accessToken: access_token, refreshToken: access_token, expiresIn: dayjs().add(59, 'days').unix() - dayjs().unix(), - picture: url, + picture: picture?.data?.url || '', username: username, }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts b/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts index 2de298c6..85b6418e 100644 --- a/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts @@ -250,7 +250,7 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider { accessToken: access_token, id: open_id.replace(/-/g, ''), name: display_name, - picture: avatar_url, + picture: avatar_url || '', username: username, }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/vk.provider.ts b/libraries/nestjs-libraries/src/integrations/social/vk.provider.ts index 12811204..670dd803 100644 --- a/libraries/nestjs-libraries/src/integrations/social/vk.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/vk.provider.ts @@ -65,7 +65,7 @@ export class VkProvider extends SocialAbstract implements SocialProvider { accessToken: access_token, refreshToken: refresh_token + '&&&&' + device_id, expiresIn: dayjs().add(expires_in, 'seconds').unix() - dayjs().unix(), - picture: avatar, + picture: avatar || '', username: first_name.toLowerCase(), }; } @@ -150,7 +150,7 @@ export class VkProvider extends SocialAbstract implements SocialProvider { accessToken: access_token, refreshToken: refresh_token + '&&&&' + device_id, expiresIn: dayjs().add(expires_in, 'seconds').unix() - dayjs().unix(), - picture: avatar, + picture: avatar || '', username: first_name.toLowerCase(), }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/x.provider.ts b/libraries/nestjs-libraries/src/integrations/social/x.provider.ts index c73431e2..ca32f23c 100644 --- a/libraries/nestjs-libraries/src/integrations/social/x.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/x.provider.ts @@ -258,7 +258,7 @@ export class XProvider extends SocialAbstract implements SocialProvider { name, refreshToken: '', expiresIn: 999999999, - picture: profile_image_url, + picture: profile_image_url || '', username, additionalSettings: [ { diff --git a/libraries/nestjs-libraries/src/integrations/social/youtube.provider.ts b/libraries/nestjs-libraries/src/integrations/social/youtube.provider.ts index a9f0c369..7b6e6fcf 100644 --- a/libraries/nestjs-libraries/src/integrations/social/youtube.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/youtube.provider.ts @@ -132,7 +132,7 @@ export class YoutubeProvider extends SocialAbstract implements SocialProvider { refreshToken: credentials.refresh_token!, id: data.id!, name: data.name!, - picture: data.picture!, + picture: data?.picture || '', username: '', }; } @@ -178,7 +178,7 @@ export class YoutubeProvider extends SocialAbstract implements SocialProvider { refreshToken: tokens.refresh_token!, id: data.id!, name: data.name!, - picture: data.picture!, + picture: data?.picture || '', username: '', }; } From f37c913943c9827872d30469ecacc4558d6a5a41 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Tue, 12 Aug 2025 22:59:34 +0700 Subject: [PATCH 23/23] feat: something is wrong console --- libraries/helpers/src/utils/concurrency.service.ts | 1 + .../nestjs-libraries/src/integrations/social/threads.provider.ts | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/helpers/src/utils/concurrency.service.ts b/libraries/helpers/src/utils/concurrency.service.ts index 1e6e4494..4f241493 100644 --- a/libraries/helpers/src/utils/concurrency.service.ts +++ b/libraries/helpers/src/utils/concurrency.service.ts @@ -39,6 +39,7 @@ export const concurrency = async ( } ); } catch (err) { + console.log(err); throw new BadBody( identifier, JSON.stringify({}), diff --git a/libraries/nestjs-libraries/src/integrations/social/threads.provider.ts b/libraries/nestjs-libraries/src/integrations/social/threads.provider.ts index e86ac41f..b4a9a2f0 100644 --- a/libraries/nestjs-libraries/src/integrations/social/threads.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/threads.provider.ts @@ -13,7 +13,6 @@ import { capitalize, chunk } from 'lodash'; import { Plug } from '@gitroom/helpers/decorators/plug.decorator'; import { Integration } from '@prisma/client'; import { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation'; -import { TwitterApi } from 'twitter-api-v2'; export class ThreadsProvider extends SocialAbstract implements SocialProvider { identifier = 'threads';