diff --git a/apps/backend/src/api/routes/marketplace.controller.ts b/apps/backend/src/api/routes/marketplace.controller.ts
index a9d500e5..afb961e9 100644
--- a/apps/backend/src/api/routes/marketplace.controller.ts
+++ b/apps/backend/src/api/routes/marketplace.controller.ts
@@ -56,8 +56,12 @@ export class MarketplaceController {
connectBankAccount(
@GetUserFromRequest() user: User,
@Query('country') country: string
- ) {
- return this._stripeService.createAccountProcess(user.id, user.email, country);
+ ) {
+ return this._stripeService.createAccountProcess(
+ user.id,
+ user.email,
+ country
+ );
}
@Post('/item')
@@ -126,12 +130,19 @@ export class MarketplaceController {
@GetOrgFromRequest() organization: Organization,
@Param('id') id: string
) {
- const getPost = await this._messagesService.getPost(user.id, organization.id, id);
+ const getPost = await this._messagesService.getPost(
+ user.id,
+ organization.id,
+ id
+ );
if (!getPost) {
- return ;
+ return;
}
- return {...await this._postsService.getPost(getPost.organizationId, id), providerId: getPost.integration.providerIdentifier};
+ return {
+ ...(await this._postsService.getPost(getPost.organizationId, id)),
+ providerId: getPost.integration.providerIdentifier,
+ };
}
@Post('/posts/:id/revision')
diff --git a/apps/frontend/src/components/launches/helpers/use.values.ts b/apps/frontend/src/components/launches/helpers/use.values.ts
index a863e776..3b1e0492 100644
--- a/apps/frontend/src/components/launches/helpers/use.values.ts
+++ b/apps/frontend/src/components/launches/helpers/use.values.ts
@@ -38,6 +38,8 @@ export const useValues = (
criteriaMode: 'all',
});
+ console.log(form.formState.errors);
+
const getValues = useMemo(() => {
return () => ({ ...form.getValues(), __type: identifier });
}, [form, integration]);
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 58d0db42..ffed977a 100644
--- a/apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx
+++ b/apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx
@@ -46,11 +46,11 @@ const contentPostingMethod = [
const yesNo = [
{
- value: 'true',
+ value: 'yes',
label: 'Yes',
},
{
- value: 'false',
+ value: 'no',
label: 'No',
},
];
@@ -120,7 +120,7 @@ const TikTokSettings: FC<{ values?: any }> = (props) => {
const disclose = watch('disclose');
const brand_organic_toggle = watch('brand_organic_toggle');
const brand_content_toggle = watch('brand_content_toggle');
-const content_posting_method = watch('content_posting_method');
+ const content_posting_method = watch('content_posting_method');
const isUploadMode = content_posting_method === 'UPLOAD';
@@ -129,7 +129,8 @@ const content_posting_method = watch('content_posting_method');
))}
-
+
{`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.`}
))}
+
+ Select
+ {yesNo.map((item) => (
+
+ {item.label}
+
+ ))}
+
+
+ This feature available only for photos, it will add a default music that
+ you can change later.
+
Allow User To:
-
{
const [firstItems] = items;
-
if (items.length !== 1) {
return 'Tiktok items should be one';
}
- if (items[0].length !== 1) {
+ if (
+ firstItems.length > 1 &&
+ firstItems?.some((p) => p?.path?.indexOf('mp4') > -1)
+ ) {
+ return 'Only pictures are supported when selecting multiple items';
+ } else if (
+ firstItems?.length !== 1 &&
+ firstItems?.[0]?.path?.indexOf('mp4') > -1
+ ) {
return 'You need one media';
}
- if (firstItems[0].path.indexOf('mp4') === -1) {
- return 'Item must be a video';
- }
-
return true;
},
2200
diff --git a/libraries/nestjs-libraries/src/bull-mq-transport-new/client.ts b/libraries/nestjs-libraries/src/bull-mq-transport-new/client.ts
index 00d54911..0354b3f3 100644
--- a/libraries/nestjs-libraries/src/bull-mq-transport-new/client.ts
+++ b/libraries/nestjs-libraries/src/bull-mq-transport-new/client.ts
@@ -80,7 +80,7 @@ export class BullMqClient extends ClientProxy {
async dispatchEvent(packet: ReadPacket): Promise {
console.log('event to dispatch: ', packet);
const queue = this.getQueue(packet.pattern);
- if (packet.data.options.every) {
+ if (packet?.data?.options?.every) {
const { every, immediately } = packet.data.options;
const id = packet.data.id ?? v4();
await queue.upsertJobScheduler(
diff --git a/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts b/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts
index 71db47c0..1404b1ab 100644
--- a/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts
+++ b/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts
@@ -24,6 +24,10 @@ import { MediaService } from '@gitroom/nestjs-libraries/database/prisma/media/me
import { ShortLinkService } from '@gitroom/nestjs-libraries/short-linking/short.link.service';
import { WebhooksService } from '@gitroom/nestjs-libraries/database/prisma/webhooks/webhooks.service';
import { CreateTagDto } from '@gitroom/nestjs-libraries/dtos/posts/create.tag.dto';
+import axios from 'axios';
+import sharp from 'sharp';
+import { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';
+import { Readable } from 'stream';
dayjs.extend(utc);
type PostWithConditionals = Post & {
@@ -33,6 +37,7 @@ type PostWithConditionals = Post & {
@Injectable()
export class PostsService {
+ private storage = UploadFactory.createStorage();
constructor(
private _postRepository: PostsRepository,
private _workerServiceProducer: BullMqClient,
@@ -92,36 +97,90 @@ export class PostsService {
return this._postRepository.getPosts(orgId, query);
}
- async updateMedia(id: string, imagesList: any[]) {
+ async updateMedia(id: string, imagesList: any[], convertToJPEG = false) {
let imageUpdateNeeded = false;
- const getImageList = (
- await Promise.all(
- imagesList.map(async (p: any) => {
- if (!p.path && p.id) {
- imageUpdateNeeded = true;
- return this._mediaService.getMediaById(p.id);
+ const getImageList = await Promise.all(
+ (
+ await Promise.all(
+ imagesList.map(async (p: any) => {
+ if (!p.path && p.id) {
+ imageUpdateNeeded = true;
+ return this._mediaService.getMediaById(p.id);
+ }
+
+ return p;
+ })
+ )
+ )
+ .map((m) => {
+ return {
+ ...m,
+ url:
+ m.path.indexOf('http') === -1
+ ? process.env.FRONTEND_URL +
+ '/' +
+ process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY +
+ m.path
+ : m.path,
+ type: 'image',
+ path:
+ m.path.indexOf('http') === -1
+ ? process.env.UPLOAD_DIRECTORY + m.path
+ : m.path,
+ };
+ })
+ .map(async (m) => {
+ if (!convertToJPEG) {
+ return m;
}
- return p;
+ if (m.path.indexOf('.png') > -1) {
+ imageUpdateNeeded = true;
+ const response = await axios.get(m.url, {
+ responseType: 'arraybuffer',
+ });
+
+ const imageBuffer = Buffer.from(response.data);
+
+ // Use sharp to get the metadata of the image
+ const buffer = await sharp(imageBuffer)
+ .jpeg({ quality: 100 })
+ .toBuffer();
+
+ const { path, originalname } = await this.storage.uploadFile({
+ buffer,
+ mimetype: 'image/jpeg',
+ size: buffer.length,
+ path: '',
+ fieldname: '',
+ destination: '',
+ stream: new Readable(),
+ filename: '',
+ originalname: '',
+ encoding: '',
+ });
+
+ return {
+ ...m,
+ name: originalname,
+ url:
+ path.indexOf('http') === -1
+ ? process.env.FRONTEND_URL +
+ '/' +
+ process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY +
+ path
+ : path,
+ type: 'image',
+ path:
+ path.indexOf('http') === -1
+ ? process.env.UPLOAD_DIRECTORY + path
+ : path,
+ };
+ }
+
+ return m;
})
- )
- ).map((m) => {
- return {
- ...m,
- url:
- m.path.indexOf('http') === -1
- ? process.env.FRONTEND_URL +
- '/' +
- process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY +
- m.path
- : m.path,
- type: 'image',
- path:
- m.path.indexOf('http') === -1
- ? process.env.UPLOAD_DIRECTORY + m.path
- : m.path,
- };
- });
+ );
if (imageUpdateNeeded) {
await this._postRepository.updateImages(id, JSON.stringify(getImageList));
@@ -130,7 +189,7 @@ export class PostsService {
return getImageList;
}
- async getPost(orgId: string, id: string) {
+ async getPost(orgId: string, id: string, convertToJPEG = false) {
const posts = await this.getPostsRecursively(id, true, orgId, true);
const list = {
group: posts?.[0]?.group,
@@ -139,7 +198,8 @@ export class PostsService {
...post,
image: await this.updateMedia(
post.id,
- JSON.parse(post.image || '[]')
+ JSON.parse(post.image || '[]'),
+ convertToJPEG,
),
}))
),
@@ -361,7 +421,11 @@ export class PostsService {
id: p.id,
message: p.content,
settings: JSON.parse(p.settings || '{}'),
- media: await this.updateMedia(p.id, JSON.parse(p.image || '[]')),
+ media: await this.updateMedia(
+ p.id,
+ JSON.parse(p.image || '[]'),
+ getIntegration.convertToJPEG
+ ),
}))
),
integration
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 91eece6c..1c0c4de3 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
@@ -23,15 +23,18 @@ export class TikTokDto {
@IsBoolean()
comment: boolean;
+ @IsIn(['yes', 'no'])
+ autoAddMusic: 'yes' | 'no';
+
@IsBoolean()
brand_content_toggle: boolean;
@IsBoolean()
brand_organic_toggle: boolean;
- @IsIn(['true'])
- @IsDefined()
- isValidVideo: boolean;
+ // @IsIn(['true'])
+ // @IsDefined()
+ // isValidVideo: boolean;
@IsIn(['DIRECT_POST', 'UPLOAD'])
@IsString()
diff --git a/libraries/nestjs-libraries/src/integrations/social/social.integrations.interface.ts b/libraries/nestjs-libraries/src/integrations/social/social.integrations.interface.ts
index 5d43bc84..673e2fd2 100644
--- a/libraries/nestjs-libraries/src/integrations/social/social.integrations.interface.ts
+++ b/libraries/nestjs-libraries/src/integrations/social/social.integrations.interface.ts
@@ -110,6 +110,7 @@ export interface SocialProvider
ISocialMediaIntegration {
identifier: string;
refreshWait?: boolean;
+ convertToJPEG?: boolean;
isWeb3?: boolean;
customFields?: () => Promise<
{
diff --git a/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts b/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts
index 14b4b0c5..45a3e6a8 100644
--- a/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts
+++ b/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts
@@ -17,6 +17,7 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider {
identifier = 'tiktok';
name = 'Tiktok';
isBetweenSteps = false;
+ convertToJPEG = true;
scopes = [
'user.info.basic',
'video.publish',
@@ -103,10 +104,10 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider {
grant_type: 'authorization_code',
code_verifier: params.codeVerifier,
redirect_uri: `${
- process?.env?.FRONTEND_URL?.indexOf('https') === -1
- ? 'https://redirectmeto.com/'
- : ''
- }${process?.env?.FRONTEND_URL}/integrations/social/tiktok`
+ process?.env?.FRONTEND_URL?.indexOf('https') === -1
+ ? 'https://redirectmeto.com/'
+ : ''
+ }${process?.env?.FRONTEND_URL}/integrations/social/tiktok`,
};
const { access_token, refresh_token, scope } = await (
@@ -208,23 +209,27 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider {
}
if (status === 'FAILED') {
- throw new BadBody('titok-error-upload', JSON.stringify(post), {
- // @ts-ignore
- postDetails,
- });
+ throw new BadBody(
+ 'titok-error-upload',
+ JSON.stringify(post),
+ Buffer.from(JSON.stringify(post))
+ );
}
await timer(3000);
}
}
- private postingMethod(method: TikTokDto["content_posting_method"]): string {
- switch (method) {
- case 'UPLOAD':
- return '/inbox/video/init/';
- case 'DIRECT_POST':
- default:
- return '/video/init/';
+ private postingMethod(
+ method: TikTokDto['content_posting_method'],
+ isPhoto: boolean
+ ): string {
+ switch (method) {
+ case 'UPLOAD':
+ return isPhoto ? '/content/init/' : '/inbox/video/init/';
+ case 'DIRECT_POST':
+ default:
+ return isPhoto ? '/content/init/' : '/video/init/';
}
}
@@ -235,11 +240,15 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider {
integration: Integration
): Promise {
const [firstPost, ...comments] = postDetails;
+
const {
data: { publish_id },
} = await (
await this.fetch(
- `https://open.tiktokapis.com/v2/post/publish${this.postingMethod(firstPost.settings.content_posting_method)}`,
+ `https://open.tiktokapis.com/v2/post/publish${this.postingMethod(
+ firstPost.settings.content_posting_method,
+ (firstPost?.media?.[0]?.url?.indexOf('mp4') || -1) === -1
+ )}`,
{
method: 'POST',
headers: {
@@ -247,21 +256,44 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider {
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
- ...(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!,
- },
+ ...(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,
+ ...((firstPost?.media?.[0]?.url?.indexOf('mp4') || -1) ===
+ -1
+ ? {
+ auto_add_music:
+ firstPost.settings.autoAddMusic === 'yes',
+ }
+ : {}),
+ },
+ }
+ : {}),
+ ...((firstPost?.media?.[0]?.url?.indexOf('mp4') || -1) > -1
+ ? {
+ source_info: {
+ source: 'PULL_FROM_URL',
+ video_url: firstPost?.media?.[0]?.url!,
+ },
+ }
+ : {
+ source_info: {
+ source: 'PULL_FROM_URL',
+ photo_cover_index: 1,
+ photo_images: firstPost.media?.map((p) => p.url),
+ },
+ post_mode: 'DIRECT_POST',
+ media_type: 'PHOTO',
+ }),
}),
}
)