Skip to content

Commit dd60c93

Browse files
authored
Merge pull request #655 from gitroomhq/feat/tiktok-images
Upload tiktok images
2 parents 3de66b7 + 8399aee commit dd60c93

File tree

7 files changed

+220
-86
lines changed

7 files changed

+220
-86
lines changed

apps/backend/src/api/routes/marketplace.controller.ts

+16-5
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,12 @@ export class MarketplaceController {
5656
connectBankAccount(
5757
@GetUserFromRequest() user: User,
5858
@Query('country') country: string
59-
) {
60-
return this._stripeService.createAccountProcess(user.id, user.email, country);
59+
) {
60+
return this._stripeService.createAccountProcess(
61+
user.id,
62+
user.email,
63+
country
64+
);
6165
}
6266

6367
@Post('/item')
@@ -126,12 +130,19 @@ export class MarketplaceController {
126130
@GetOrgFromRequest() organization: Organization,
127131
@Param('id') id: string
128132
) {
129-
const getPost = await this._messagesService.getPost(user.id, organization.id, id);
133+
const getPost = await this._messagesService.getPost(
134+
user.id,
135+
organization.id,
136+
id
137+
);
130138
if (!getPost) {
131-
return ;
139+
return;
132140
}
133141

134-
return {...await this._postsService.getPost(getPost.organizationId, id), providerId: getPost.integration.providerIdentifier};
142+
return {
143+
...(await this._postsService.getPost(getPost.organizationId, id)),
144+
providerId: getPost.integration.providerIdentifier,
145+
};
135146
}
136147

137148
@Post('/posts/:id/revision')

apps/frontend/src/components/launches/helpers/use.values.ts

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export const useValues = (
3838
criteriaMode: 'all',
3939
});
4040

41+
console.log(form.formState.errors);
42+
4143
const getValues = useMemo(() => {
4244
return () => ({ ...form.getValues(), __type: identifier });
4345
}, [form, integration]);

apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx

+40-19
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ const contentPostingMethod = [
4646

4747
const yesNo = [
4848
{
49-
value: 'true',
49+
value: 'yes',
5050
label: 'Yes',
5151
},
5252
{
53-
value: 'false',
53+
value: 'no',
5454
label: 'No',
5555
},
5656
];
@@ -120,7 +120,7 @@ const TikTokSettings: FC<{ values?: any }> = (props) => {
120120
const disclose = watch('disclose');
121121
const brand_organic_toggle = watch('brand_organic_toggle');
122122
const brand_content_toggle = watch('brand_content_toggle');
123-
const content_posting_method = watch('content_posting_method');
123+
const content_posting_method = watch('content_posting_method');
124124

125125
const isUploadMode = content_posting_method === 'UPLOAD';
126126

@@ -129,7 +129,8 @@ const content_posting_method = watch('content_posting_method');
129129
<CheckTikTokValidity picture={props?.values?.[0]?.image?.[0]?.path} />
130130
<Select
131131
label="Who can see this video?"
132-
disabled={isUploadMode}
132+
hideErrors={true}
133+
disabled={isUploadMode}
133134
{...register('privacy_level', {
134135
value: 'PUBLIC_TO_EVERYONE',
135136
})}
@@ -141,13 +142,13 @@ disabled={isUploadMode}
141142
</option>
142143
))}
143144
</Select>
144-
<div className="text-[14px] mb-[10px] text-balance">
145+
<div className="text-[14px] mt-[10px] mb-[18px] text-balance">
145146
{`Choose upload without posting if you want to review and edit your content within TikTok's app before publishing.
146147
This gives you access to TikTok's built-in editing tools and lets you make final adjustments before posting.`}
147148
</div>
148149
<Select
149150
label="Content posting method"
150-
disabled={isUploadMode}
151+
disabled={isUploadMode}
151152
{...register('content_posting_method', {
152153
value: 'DIRECT_POST',
153154
})}
@@ -159,29 +160,47 @@ disabled={isUploadMode}
159160
</option>
160161
))}
161162
</Select>
163+
<Select
164+
hideErrors={true}
165+
label="Auto add music"
166+
{...register('autoAddMusic', {
167+
value: 'no',
168+
})}
169+
>
170+
<option value="">Select</option>
171+
{yesNo.map((item) => (
172+
<option key={item.value} value={item.value}>
173+
{item.label}
174+
</option>
175+
))}
176+
</Select>
177+
<div className="text-[14px] mt-[10px] mb-[24px] text-balance">
178+
This feature available only for photos, it will add a default music that
179+
you can change later.
180+
</div>
162181
<hr className="mb-[15px] border-tableBorder" />
163182
<div className="text-[14px] mb-[10px]">Allow User To:</div>
164183
<div className="flex gap-[40px]">
165184
<Checkbox
166185
variant="hollow"
167186
label="Duet"
168-
disabled={isUploadMode}
187+
disabled={isUploadMode}
169188
{...register('duet', {
170189
value: false,
171190
})}
172191
/>
173192
<Checkbox
174193
label="Stitch"
175194
variant="hollow"
176-
disabled={isUploadMode}
195+
disabled={isUploadMode}
177196
{...register('stitch', {
178197
value: false,
179198
})}
180199
/>
181200
<Checkbox
182201
label="Comments"
183202
variant="hollow"
184-
disabled={isUploadMode}
203+
disabled={isUploadMode}
185204
{...register('comment', {
186205
value: false,
187206
})}
@@ -192,7 +211,7 @@ disabled={isUploadMode}
192211
<Checkbox
193212
variant="hollow"
194213
label="Disclose Video Content"
195-
disabled={isUploadMode}
214+
disabled={isUploadMode}
196215
{...register('disclose', {
197216
value: false,
198217
})}
@@ -225,12 +244,11 @@ disabled={isUploadMode}
225244
third party, or both.
226245
</div>
227246
</div>
228-
229247
<div className={clsx(!disclose && 'invisible', 'mt-[20px]')}>
230248
<Checkbox
231249
variant="hollow"
232250
label="Your brand"
233-
disabled={isUploadMode}
251+
disabled={isUploadMode}
234252
{...register('brand_organic_toggle', {
235253
value: false,
236254
})}
@@ -243,7 +261,7 @@ disabled={isUploadMode}
243261
<Checkbox
244262
variant="hollow"
245263
label="Branded content"
246-
disabled={isUploadMode}
264+
disabled={isUploadMode}
247265
{...register('brand_content_toggle', {
248266
value: false,
249267
})}
@@ -290,19 +308,22 @@ export default withProvider(
290308
TikTokDto,
291309
async (items) => {
292310
const [firstItems] = items;
293-
294311
if (items.length !== 1) {
295312
return 'Tiktok items should be one';
296313
}
297314

298-
if (items[0].length !== 1) {
315+
if (
316+
firstItems.length > 1 &&
317+
firstItems?.some((p) => p?.path?.indexOf('mp4') > -1)
318+
) {
319+
return 'Only pictures are supported when selecting multiple items';
320+
} else if (
321+
firstItems?.length !== 1 &&
322+
firstItems?.[0]?.path?.indexOf('mp4') > -1
323+
) {
299324
return 'You need one media';
300325
}
301326

302-
if (firstItems[0].path.indexOf('mp4') === -1) {
303-
return 'Item must be a video';
304-
}
305-
306327
return true;
307328
},
308329
2200

libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts

+92-28
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ import { MediaService } from '@gitroom/nestjs-libraries/database/prisma/media/me
2424
import { ShortLinkService } from '@gitroom/nestjs-libraries/short-linking/short.link.service';
2525
import { WebhooksService } from '@gitroom/nestjs-libraries/database/prisma/webhooks/webhooks.service';
2626
import { CreateTagDto } from '@gitroom/nestjs-libraries/dtos/posts/create.tag.dto';
27+
import axios from 'axios';
28+
import sharp from 'sharp';
29+
import { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';
30+
import { Readable } from 'stream';
2731
dayjs.extend(utc);
2832

2933
type PostWithConditionals = Post & {
@@ -33,6 +37,7 @@ type PostWithConditionals = Post & {
3337

3438
@Injectable()
3539
export class PostsService {
40+
private storage = UploadFactory.createStorage();
3641
constructor(
3742
private _postRepository: PostsRepository,
3843
private _workerServiceProducer: BullMqClient,
@@ -92,36 +97,90 @@ export class PostsService {
9297
return this._postRepository.getPosts(orgId, query);
9398
}
9499

95-
async updateMedia(id: string, imagesList: any[]) {
100+
async updateMedia(id: string, imagesList: any[], convertToJPEG = false) {
96101
let imageUpdateNeeded = false;
97-
const getImageList = (
98-
await Promise.all(
99-
imagesList.map(async (p: any) => {
100-
if (!p.path && p.id) {
102+
const getImageList = await Promise.all(
103+
(
104+
await Promise.all(
105+
imagesList.map(async (p: any) => {
106+
if (!p.path && p.id) {
107+
imageUpdateNeeded = true;
108+
return this._mediaService.getMediaById(p.id);
109+
}
110+
111+
return p;
112+
})
113+
)
114+
)
115+
.map((m) => {
116+
return {
117+
...m,
118+
url:
119+
m.path.indexOf('http') === -1
120+
? process.env.FRONTEND_URL +
121+
'/' +
122+
process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY +
123+
m.path
124+
: m.path,
125+
type: 'image',
126+
path:
127+
m.path.indexOf('http') === -1
128+
? process.env.UPLOAD_DIRECTORY + m.path
129+
: m.path,
130+
};
131+
})
132+
.map(async (m) => {
133+
if (!convertToJPEG) {
134+
return m;
135+
}
136+
137+
if (m.path.indexOf('.png') > -1) {
101138
imageUpdateNeeded = true;
102-
return this._mediaService.getMediaById(p.id);
139+
const response = await axios.get(m.url, {
140+
responseType: 'arraybuffer',
141+
});
142+
143+
const imageBuffer = Buffer.from(response.data);
144+
145+
// Use sharp to get the metadata of the image
146+
const buffer = await sharp(imageBuffer)
147+
.jpeg({ quality: 100 })
148+
.toBuffer();
149+
150+
const { path, originalname } = await this.storage.uploadFile({
151+
buffer,
152+
mimetype: 'image/jpeg',
153+
size: buffer.length,
154+
path: '',
155+
fieldname: '',
156+
destination: '',
157+
stream: new Readable(),
158+
filename: '',
159+
originalname: '',
160+
encoding: '',
161+
});
162+
163+
return {
164+
...m,
165+
name: originalname,
166+
url:
167+
path.indexOf('http') === -1
168+
? process.env.FRONTEND_URL +
169+
'/' +
170+
process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY +
171+
path
172+
: path,
173+
type: 'image',
174+
path:
175+
path.indexOf('http') === -1
176+
? process.env.UPLOAD_DIRECTORY + path
177+
: path,
178+
};
103179
}
104180

105-
return p;
181+
return m;
106182
})
107-
)
108-
).map((m) => {
109-
return {
110-
...m,
111-
url:
112-
m.path.indexOf('http') === -1
113-
? process.env.FRONTEND_URL +
114-
'/' +
115-
process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY +
116-
m.path
117-
: m.path,
118-
type: 'image',
119-
path:
120-
m.path.indexOf('http') === -1
121-
? process.env.UPLOAD_DIRECTORY + m.path
122-
: m.path,
123-
};
124-
});
183+
);
125184

126185
if (imageUpdateNeeded) {
127186
await this._postRepository.updateImages(id, JSON.stringify(getImageList));
@@ -130,7 +189,7 @@ export class PostsService {
130189
return getImageList;
131190
}
132191

133-
async getPost(orgId: string, id: string) {
192+
async getPost(orgId: string, id: string, convertToJPEG = false) {
134193
const posts = await this.getPostsRecursively(id, true, orgId, true);
135194
const list = {
136195
group: posts?.[0]?.group,
@@ -139,7 +198,8 @@ export class PostsService {
139198
...post,
140199
image: await this.updateMedia(
141200
post.id,
142-
JSON.parse(post.image || '[]')
201+
JSON.parse(post.image || '[]'),
202+
convertToJPEG,
143203
),
144204
}))
145205
),
@@ -361,7 +421,11 @@ export class PostsService {
361421
id: p.id,
362422
message: p.content,
363423
settings: JSON.parse(p.settings || '{}'),
364-
media: await this.updateMedia(p.id, JSON.parse(p.image || '[]')),
424+
media: await this.updateMedia(
425+
p.id,
426+
JSON.parse(p.image || '[]'),
427+
getIntegration.convertToJPEG
428+
),
365429
}))
366430
),
367431
integration

libraries/nestjs-libraries/src/dtos/posts/providers-settings/tiktok.dto.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,18 @@ export class TikTokDto {
2323
@IsBoolean()
2424
comment: boolean;
2525

26+
@IsIn(['yes', 'no'])
27+
autoAddMusic: 'yes' | 'no';
28+
2629
@IsBoolean()
2730
brand_content_toggle: boolean;
2831

2932
@IsBoolean()
3033
brand_organic_toggle: boolean;
3134

32-
@IsIn(['true'])
33-
@IsDefined()
34-
isValidVideo: boolean;
35+
// @IsIn(['true'])
36+
// @IsDefined()
37+
// isValidVideo: boolean;
3538

3639
@IsIn(['DIRECT_POST', 'UPLOAD'])
3740
@IsString()

0 commit comments

Comments
 (0)