From 7e0603776f88a7e77aa8496a21661a0b852bc5de Mon Sep 17 00:00:00 2001 From: ArafatHossain403 Date: Sat, 21 Dec 2024 23:53:52 +0600 Subject: [PATCH 01/31] user registration company name and password max length set for unhandled error in backend --- .../src/dtos/auth/create.org.user.dto.ts | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/libraries/nestjs-libraries/src/dtos/auth/create.org.user.dto.ts b/libraries/nestjs-libraries/src/dtos/auth/create.org.user.dto.ts index 0505e2b00..25f633a94 100644 --- a/libraries/nestjs-libraries/src/dtos/auth/create.org.user.dto.ts +++ b/libraries/nestjs-libraries/src/dtos/auth/create.org.user.dto.ts @@ -1,29 +1,31 @@ -import {IsDefined, IsEmail, IsString, MinLength, ValidateIf} from "class-validator"; +import {IsDefined, IsEmail, IsString, MaxLength, MinLength, ValidateIf} from "class-validator"; import {Provider} from '@prisma/client'; export class CreateOrgUserDto { - @IsString() - @MinLength(3) - @IsDefined() - @ValidateIf(o => !o.providerToken) - password: string; + @IsString() + @MinLength(3) + @MaxLength(64) + @IsDefined() + @ValidateIf((o) => !o.providerToken) + password: string; - @IsString() - @IsDefined() - provider: Provider; + @IsString() + @IsDefined() + provider: Provider; - @IsString() - @IsDefined() - @ValidateIf(o => !o.password) - providerToken: string; + @IsString() + @IsDefined() + @ValidateIf((o) => !o.password) + providerToken: string; - @IsEmail() - @IsDefined() - @ValidateIf(o => !o.providerToken) - email: string; + @IsEmail() + @IsDefined() + @ValidateIf((o) => !o.providerToken) + email: string; - @IsString() - @IsDefined() - @MinLength(3) - company: string; + @IsString() + @IsDefined() + @MinLength(3) + @MaxLength(128) + company: string; } From 9c10d0f1a65c1a9288e32a28eb39b4be9030daa7 Mon Sep 17 00:00:00 2001 From: Aniket Kumar Ghosh Date: Sun, 19 Jan 2025 14:57:13 +0530 Subject: [PATCH 02/31] short.io integration is in progress. --- .../src/short-linking/providers/short.io.ts | 85 +++++++++++++++++++ .../src/short-linking/short.link.service.ts | 5 ++ 2 files changed, 90 insertions(+) create mode 100644 libraries/nestjs-libraries/src/short-linking/providers/short.io.ts diff --git a/libraries/nestjs-libraries/src/short-linking/providers/short.io.ts b/libraries/nestjs-libraries/src/short-linking/providers/short.io.ts new file mode 100644 index 000000000..f94e90491 --- /dev/null +++ b/libraries/nestjs-libraries/src/short-linking/providers/short.io.ts @@ -0,0 +1,85 @@ +import { ShortLinking } from '@gitroom/nestjs-libraries/short-linking/short-linking.interface'; + + +const options = { + headers: { + Authorization: `Bearer ${process.env.SHORT_IO_SECRET_KEY}`, + 'Content-Type': 'application/json', + }, +}; + +export class ShortIo implements ShortLinking { + shortLinkDomain = 'aniket.short.gy'; + + async linksStatistics ( links: string[] ) { + return Promise.all( + links.map(async (link) => { + const url = `https://api.short.io/links/expand?domain=${this.shortLinkDomain}&path=${link.split('/').pop()}`; + const response = await ( fetch( url, options ) + .then( res => res.json() ) ); + + const linkStatisticsUrl = `https://statistics.short.io/statistics/link/${response.id}?period=last30&tz=UTC`; + + const statResponse = await ( fetch( linkStatisticsUrl, options ).then( ( res ) => res.json() ) ); + + return { + short: response.shortURL, + original: response.originalURL, + clicks: statResponse.totalClicks, + }; + }) + ); + } + + async convertLinkToShortLink(id: string, link: string) { + return ( + await ( + await fetch(`https://api.dub.co/links`, { + ...options, + method: 'POST', + body: JSON.stringify({ + url: link, + tenantId: id, + domain: this.shortLinkDomain, + }), + }) + ).json() + ).shortLink; + } + + async convertShortLinkToLink(shortLink: string) { + return await ( + await ( + await fetch( + `https://api.dub.co/links/info?domain=${shortLink}`, + options + ) + ).json() + ).url; + } + + // recursive functions that gets maximum 100 links per request if there are less than 100 links stop the recursion + async getAllLinksStatistics( + id: string, + page = 1 + ): Promise<{ short: string; original: string; clicks: string }[]> { + const response = await ( + await fetch( + `https://api.dub.co/links?tenantId=${id}&page=${page}&pageSize=100`, + options + ) + ).json(); + + const mapLinks = response.links.map((link: any) => ({ + short: link, + original: response.url, + clicks: response.clicks, + })); + + if (mapLinks.length < 100) { + return mapLinks; + } + + return [...mapLinks, ...(await this.getAllLinksStatistics(id, page + 1))]; + } +} 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 cdff93ce0..4815e7f27 100644 --- a/libraries/nestjs-libraries/src/short-linking/short.link.service.ts +++ b/libraries/nestjs-libraries/src/short-linking/short.link.service.ts @@ -2,12 +2,17 @@ import { Dub } from '@gitroom/nestjs-libraries/short-linking/providers/dub'; import { Empty } from '@gitroom/nestjs-libraries/short-linking/providers/empty'; import { ShortLinking } from '@gitroom/nestjs-libraries/short-linking/short-linking.interface'; import { Injectable } from '@nestjs/common'; +import { ShortIo } from './providers/short.io'; const getProvider = (): ShortLinking => { if (process.env.DUB_TOKEN) { return new Dub(); } + if ( process.env.SHORT_IO_SECRET_KEY ) { + return new ShortIo(); + } + return new Empty(); }; From 4d9ef69476968a30413dfab0d4f575122e978f5e Mon Sep 17 00:00:00 2001 From: Aniket Kumar Ghosh Date: Sun, 19 Jan 2025 16:54:10 +0530 Subject: [PATCH 03/31] code implementation complete. --- .../src/short-linking/providers/short.io.ts | 56 ++++++++++--------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/libraries/nestjs-libraries/src/short-linking/providers/short.io.ts b/libraries/nestjs-libraries/src/short-linking/providers/short.io.ts index f94e90491..585d73dc4 100644 --- a/libraries/nestjs-libraries/src/short-linking/providers/short.io.ts +++ b/libraries/nestjs-libraries/src/short-linking/providers/short.io.ts @@ -9,7 +9,7 @@ const options = { }; export class ShortIo implements ShortLinking { - shortLinkDomain = 'aniket.short.gy'; + shortLinkDomain = 'short.io'; async linksStatistics ( links: string[] ) { return Promise.all( @@ -31,31 +31,31 @@ export class ShortIo implements ShortLinking { ); } - async convertLinkToShortLink(id: string, link: string) { - return ( - await ( - await fetch(`https://api.dub.co/links`, { - ...options, - method: 'POST', - body: JSON.stringify({ - url: link, - tenantId: id, - domain: this.shortLinkDomain, - }), - }) - ).json() - ).shortLink; + async convertLinkToShortLink ( id: string, link: string ) { + const response = await fetch( `https://api.short.io/links`, { + ...options, + method: 'POST', + body: JSON.stringify( { + url: link, + tenantId: id, + domain: this.shortLinkDomain, + originalURL: link + + } ), + } ).then( ( res ) => res.json() ); + + return response.shortURL; } async convertShortLinkToLink(shortLink: string) { - return await ( - await ( + return await( + await( await fetch( - `https://api.dub.co/links/info?domain=${shortLink}`, + `https://api.short.io/links/expand?domain=${this.shortLinkDomain}&path=${shortLink.split('/').pop()}`, options ) ).json() - ).url; + ).originalURL; } // recursive functions that gets maximum 100 links per request if there are less than 100 links stop the recursion @@ -65,16 +65,22 @@ export class ShortIo implements ShortLinking { ): Promise<{ short: string; original: string; clicks: string }[]> { const response = await ( await fetch( - `https://api.dub.co/links?tenantId=${id}&page=${page}&pageSize=100`, + `https://api.short.io/api/links?domain_id=${id}&limit=150`, options ) ).json(); - const mapLinks = response.links.map((link: any) => ({ - short: link, - original: response.url, - clicks: response.clicks, - })); + const mapLinks = response.links.map(async ( link: any ) => { + const linkStatisticsUrl = `https://statistics.short.io/statistics/link/${response.id}?period=last30&tz=UTC`; + + const statResponse = await(fetch(linkStatisticsUrl, options).then((res) => res.json())); + + return { + short: link, + original: response.url, + clicks: statResponse.totalClicks, + }; + } ); if (mapLinks.length < 100) { return mapLinks; From 13b26030b4099b9a99f5c248943973804d807059 Mon Sep 17 00:00:00 2001 From: Keiwan Mosaddegh Date: Thu, 23 Jan 2025 09:49:08 +0100 Subject: [PATCH 04/31] feat(tiktok): add content posting method to tiktok dto --- .../src/dtos/posts/providers-settings/tiktok.dto.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/nestjs-libraries/src/dtos/posts/providers-settings/tiktok.dto.ts b/libraries/nestjs-libraries/src/dtos/posts/providers-settings/tiktok.dto.ts index 28c49024f..91eece6cc 100644 --- a/libraries/nestjs-libraries/src/dtos/posts/providers-settings/tiktok.dto.ts +++ b/libraries/nestjs-libraries/src/dtos/posts/providers-settings/tiktok.dto.ts @@ -32,4 +32,8 @@ export class TikTokDto { @IsIn(['true']) @IsDefined() isValidVideo: boolean; + + @IsIn(['DIRECT_POST', 'UPLOAD']) + @IsString() + content_posting_method: 'DIRECT_POST' | 'UPLOAD'; } From 912895b6c7a9f9756aed441df73fe9cd8731c7cf Mon Sep 17 00:00:00 2001 From: Keiwan Mosaddegh Date: Thu, 23 Jan 2025 09:52:12 +0100 Subject: [PATCH 05/31] feat(tiktok): add content posting method alternative to settings --- .../providers/tiktok/tiktok.provider.tsx | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx b/apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx index 6f2ca47cc..6b704027e 100644 --- a/apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx +++ b/apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx @@ -33,6 +33,17 @@ const privacyLevel = [ }, ]; +const contentPostingMethod = [ + { + value: 'DIRECT_POST', + label: 'Post content directly to TikTok', + }, + { + value: 'UPLOAD', + label: 'Upload content to TikTok without posting it', + }, +]; + const yesNo = [ { value: 'true', @@ -126,6 +137,19 @@ const TikTokSettings: FC<{ values?: any }> = (props) => { ))} +
Allow User To:
From 81eacbe6d5bb14da56b3efc75d17e73416a017de Mon Sep 17 00:00:00 2001 From: Keiwan Mosaddegh Date: Thu, 23 Jan 2025 10:09:35 +0100 Subject: [PATCH 06/31] feat(tiktok): update conetnt posting method based on setting --- .../integrations/social/tiktok.provider.ts | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts b/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts index aa299b419..1cbb39cbe 100644 --- a/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts @@ -226,11 +226,21 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider { ): Promise { const [firstPost, ...comments] = postDetails; + const endpoint: string = (() => { + switch (firstPost.settings.content_posting_method) { + case 'UPLOAD': + return '/inbox/video/init/'; + case 'DIRECT_POST': + default: + return '/video/init/'; + } + })(); + const { data: { publish_id }, } = await ( await this.fetch( - 'https://open.tiktokapis.com/v2/post/publish/video/init/', + `https://open.tiktokapis.com/v2/post/publish${endpoint}`, { method: 'POST', headers: { @@ -238,15 +248,17 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider { Authorization: `Bearer ${accessToken}`, }, body: JSON.stringify({ - post_info: { - title: firstPost.message, - privacy_level: firstPost.settings.privacy_level, - disable_duet: !firstPost.settings.duet, - disable_comment: !firstPost.settings.comment, - disable_stitch: !firstPost.settings.stitch, - brand_content_toggle: firstPost.settings.brand_content_toggle, - brand_organic_toggle: firstPost.settings.brand_organic_toggle, - }, + ...(firstPost.settings.content_posting_method === 'DIRECT_POST' ? { + post_info: { + title: firstPost.message, + privacy_level: firstPost.settings.privacy_level, + disable_duet: !firstPost.settings.duet, + disable_comment: !firstPost.settings.comment, + disable_stitch: !firstPost.settings.stitch, + brand_content_toggle: firstPost.settings.brand_content_toggle, + brand_organic_toggle: firstPost.settings.brand_organic_toggle, + } + } : {}), source_info: { source: 'PULL_FROM_URL', video_url: firstPost?.media?.[0]?.url!, From aa3ffac193d4d1adcd294b64dead441a3e7986ff Mon Sep 17 00:00:00 2001 From: Keiwan Mosaddegh Date: Thu, 23 Jan 2025 10:29:50 +0100 Subject: [PATCH 07/31] fix(tiktok): extract endopoint method outside of post() --- .../integrations/social/tiktok.provider.ts | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts b/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts index 1cbb39cbe..14b4b0c5a 100644 --- a/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts @@ -218,6 +218,16 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider { } } + private postingMethod(method: TikTokDto["content_posting_method"]): string { + switch (method) { + case 'UPLOAD': + return '/inbox/video/init/'; + case 'DIRECT_POST': + default: + return '/video/init/'; + } + } + async post( id: string, accessToken: string, @@ -225,22 +235,11 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider { integration: Integration ): Promise { const [firstPost, ...comments] = postDetails; - - const endpoint: string = (() => { - switch (firstPost.settings.content_posting_method) { - case 'UPLOAD': - return '/inbox/video/init/'; - case 'DIRECT_POST': - default: - return '/video/init/'; - } - })(); - const { data: { publish_id }, } = await ( await this.fetch( - `https://open.tiktokapis.com/v2/post/publish${endpoint}`, + `https://open.tiktokapis.com/v2/post/publish${this.postingMethod(firstPost.settings.content_posting_method)}`, { method: 'POST', headers: { From 1df0b98de7eae8c977ccd62e07c483455b0bdb09 Mon Sep 17 00:00:00 2001 From: Keiwan Mosaddegh Date: Thu, 23 Jan 2025 10:46:23 +0100 Subject: [PATCH 08/31] fix(tiktok): add explanation about example use case for upload method --- .../components/launches/providers/tiktok/tiktok.provider.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx b/apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx index 6b704027e..88af11a87 100644 --- a/apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx +++ b/apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx @@ -137,6 +137,10 @@ const TikTokSettings: FC<{ values?: any }> = (props) => { ))} +
+ Choose upload without posting if you want to review and edit your content within TikTok's app before publishing. + This gives you access to TikTok's built-in editing tools and lets you make final adjustments before posting. +
= (props) => {
- Choose upload without posting if you want to review and edit your content within TikTok's app before publishing. - This gives you access to TikTok's built-in editing tools and lets you make final adjustments before posting. + {`Choose upload without posting if you want to review and edit your content within TikTok's app before publishing. + This gives you access to TikTok's built-in editing tools and lets you make final adjustments before posting.`}