From 120a47690ee8f0a7677d6a463f6446080c1b5011 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Tue, 12 Aug 2025 23:36:34 +0700 Subject: [PATCH 01/48] feat: send email --- .../src/services/email.service.ts | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/libraries/nestjs-libraries/src/services/email.service.ts b/libraries/nestjs-libraries/src/services/email.service.ts index 0e0943dc..7fdf80fc 100644 --- a/libraries/nestjs-libraries/src/services/email.service.ts +++ b/libraries/nestjs-libraries/src/services/email.service.ts @@ -96,16 +96,21 @@ export class EmailService { `; - const sends = await concurrency('send-email', 1, () => - this.emailService.sendEmail( - to, - subject, - modifiedHtml, - process.env.EMAIL_FROM_NAME, - process.env.EMAIL_FROM_ADDRESS, - replyTo - ) - ); - console.log(sends); + try { + const sends = await concurrency('send-email', 1, () => + this.emailService.sendEmail( + to, + subject, + modifiedHtml, + process.env.EMAIL_FROM_NAME, + process.env.EMAIL_FROM_ADDRESS, + replyTo + ) + ); + + console.log(sends); + } catch (err) { + console.log(err); + } } } From fb75b99eed32032ee732473f27f7b25d6668e560 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Wed, 13 Aug 2025 01:17:59 +0700 Subject: [PATCH 02/48] feat: remove emails from concurrency --- .../src/services/email.service.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/libraries/nestjs-libraries/src/services/email.service.ts b/libraries/nestjs-libraries/src/services/email.service.ts index 7fdf80fc..8c4fc989 100644 --- a/libraries/nestjs-libraries/src/services/email.service.ts +++ b/libraries/nestjs-libraries/src/services/email.service.ts @@ -97,17 +97,14 @@ export class EmailService { `; try { - const sends = await concurrency('send-email', 1, () => - this.emailService.sendEmail( - to, - subject, - modifiedHtml, - process.env.EMAIL_FROM_NAME, - process.env.EMAIL_FROM_ADDRESS, - replyTo - ) + const sends = await this.emailService.sendEmail( + to, + subject, + modifiedHtml, + process.env.EMAIL_FROM_NAME, + process.env.EMAIL_FROM_ADDRESS, + replyTo ); - console.log(sends); } catch (err) { console.log(err); From 8b73c1ed5659ed6b8cb7777fcfbb16bfa277e9c9 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Wed, 13 Aug 2025 11:15:11 +0700 Subject: [PATCH 03/48] feat: stop infinite while --- .../src/integrations/social/bluesky.provider.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts b/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts index 1803992b..8eedd7cc 100644 --- a/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts @@ -6,6 +6,7 @@ import { } from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface'; import { makeId } from '@gitroom/nestjs-libraries/services/make.is'; import { + BadBody, RefreshToken, SocialAbstract, } from '@gitroom/nestjs-libraries/integrations/social.abstract'; @@ -119,6 +120,15 @@ async function uploadVideo( if (status.jobStatus.blob) { blob = status.jobStatus.blob; } + + if (status.jobStatus.state === 'JOB_STATE_FAILED') { + throw new BadBody( + 'bluesky', + JSON.stringify({}), + {} as any, + 'Could not upload video, job failed' + ); + } // wait a second await new Promise((resolve) => setTimeout(resolve, 1000)); } @@ -295,7 +305,7 @@ export class BlueskyProvider extends SocialAbstract implements SocialProvider { aspectRatio: { width: p.width, height: p.height, - } + }, })), }; } From a3047d1855facafba0f71f722065cb1ca13b8077 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Wed, 13 Aug 2025 14:19:16 +0700 Subject: [PATCH 04/48] feat: x error message longer than 2 minutes --- .../src/integrations/social/x.provider.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/libraries/nestjs-libraries/src/integrations/social/x.provider.ts b/libraries/nestjs-libraries/src/integrations/social/x.provider.ts index ca32f23c..14bff67a 100644 --- a/libraries/nestjs-libraries/src/integrations/social/x.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/x.provider.ts @@ -38,8 +38,7 @@ export class XProvider extends SocialAbstract implements SocialProvider { if (body.includes('usage-capped')) { return { type: 'refresh-token', - value: - 'Posting failed - capped reached. Please try again later', + value: 'Posting failed - capped reached. Please try again later', }; } if (body.includes('duplicate-rules')) { @@ -55,6 +54,17 @@ export class XProvider extends SocialAbstract implements SocialProvider { value: 'The Tweet contains a URL that is not allowed on X', }; } + if ( + body.includes( + 'This user is not allowed to post a video longer than 2 minutes' + ) + ) { + return { + type: 'bad-body', + value: + 'The video you are trying to post is longer than 2 minutes, which is not allowed for this account', + }; + } return undefined; } From 0df9fd392ec2512532f21667fbd8f942a4726d43 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Wed, 13 Aug 2025 14:23:40 +0700 Subject: [PATCH 05/48] feat: bluesky more timer, job state failed --- .../src/integrations/social/bluesky.provider.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts b/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts index 8eedd7cc..0e3f9e5c 100644 --- a/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts @@ -129,8 +129,8 @@ async function uploadVideo( 'Could not upload video, job failed' ); } - // wait a second - await new Promise((resolve) => setTimeout(resolve, 1000)); + + await timer(30000); } console.log('posting video...'); From 2db252f97f7e2be8efdbc1e15c8052d1ab75f6cc Mon Sep 17 00:00:00 2001 From: Nevo David Date: Wed, 13 Aug 2025 22:01:20 +0700 Subject: [PATCH 06/48] feat: youtube upload can't have concurrency as it can be a long request --- .../src/integrations/social.abstract.ts | 13 ++++++++++--- .../src/integrations/social/youtube.provider.ts | 3 ++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/libraries/nestjs-libraries/src/integrations/social.abstract.ts b/libraries/nestjs-libraries/src/integrations/social.abstract.ts index 0b4d26b1..b0ef5670 100644 --- a/libraries/nestjs-libraries/src/integrations/social.abstract.ts +++ b/libraries/nestjs-libraries/src/integrations/social.abstract.ts @@ -38,11 +38,17 @@ export abstract class SocialAbstract { d: { query: string }, id: string, integration: Integration - ): Promise<{ id: string; label: string; image: string, doNotCache?: boolean }[] | { none: true }> { + ): Promise< + | { id: string; label: string; image: string; doNotCache?: boolean }[] + | { none: true } + > { return { none: true }; } - async runInConcurrent(func: (...args: any[]) => Promise) { + async runInConcurrent( + func: (...args: any[]) => Promise, + ignoreConcurrency?: boolean + ) { const value = await concurrency( this.identifier, this.maxConcurrentJob, @@ -54,7 +60,8 @@ export abstract class SocialAbstract { const handle = this.handleErrors(JSON.stringify(err)); return { err: true, ...(handle || {}) }; } - } + }, + ignoreConcurrency ); if (value && value?.err && value?.value) { diff --git a/libraries/nestjs-libraries/src/integrations/social/youtube.provider.ts b/libraries/nestjs-libraries/src/integrations/social/youtube.provider.ts index 7b6e6fcf..93caff82 100644 --- a/libraries/nestjs-libraries/src/integrations/social/youtube.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/youtube.provider.ts @@ -222,7 +222,8 @@ export class YoutubeProvider extends SocialAbstract implements SocialProvider { media: { body: response.data, }, - }) + }), + true ); if (settings?.thumbnail?.path) { From 5e5ca78e9aff6ea10e9c98e236549465cfda7e10 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Wed, 13 Aug 2025 22:08:20 +0700 Subject: [PATCH 07/48] feat: long upload should not be in a concurrenct --- .../integrations/social/linkedin.provider.ts | 36 +++++++++++-------- .../src/integrations/social/x.provider.ts | 32 +++++++++-------- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts b/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts index d4731735..da870fd1 100644 --- a/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts @@ -254,20 +254,26 @@ export class LinkedinProvider extends SocialAbstract implements SocialProvider { const etags = []; for (let i = 0; i < picture.length; i += 1024 * 1024 * 2) { - const upload = await this.fetch(sendUrlRequest, { - method: 'PUT', - headers: { - 'X-Restli-Protocol-Version': '2.0.0', - 'LinkedIn-Version': '202501', - Authorization: `Bearer ${accessToken}`, - ...(isVideo - ? { 'Content-Type': 'application/octet-stream' } - : isPdf - ? { 'Content-Type': 'application/pdf' } - : {}), + const upload = await this.fetch( + sendUrlRequest, + { + method: 'PUT', + headers: { + 'X-Restli-Protocol-Version': '2.0.0', + 'LinkedIn-Version': '202501', + Authorization: `Bearer ${accessToken}`, + ...(isVideo + ? { 'Content-Type': 'application/octet-stream' } + : isPdf + ? { 'Content-Type': 'application/pdf' } + : {}), + }, + body: picture.slice(i, i + 1024 * 1024 * 2), }, - body: picture.slice(i, i + 1024 * 1024 * 2), - }); + 'linkedin', + 0, + true + ); etags.push(upload.headers.get('etag')); } @@ -737,7 +743,9 @@ export class LinkedinProvider extends SocialAbstract implements SocialProvider { return elements.map((p: any) => ({ id: String(p.id), label: p.localizedName, - image: p.logoV2?.['original~']?.elements?.[0]?.identifiers?.[0]?.identifier || '', + image: + p.logoV2?.['original~']?.elements?.[0]?.identifiers?.[0]?.identifier || + '', })); } diff --git a/libraries/nestjs-libraries/src/integrations/social/x.provider.ts b/libraries/nestjs-libraries/src/integrations/social/x.provider.ts index 14bff67a..1467352e 100644 --- a/libraries/nestjs-libraries/src/integrations/social/x.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/x.provider.ts @@ -317,22 +317,24 @@ export class XProvider extends SocialAbstract implements SocialProvider { postDetails.flatMap((p) => p?.media?.flatMap(async (m) => { return { - id: await this.runInConcurrent(async () => - client.v1.uploadMedia( - m.path.indexOf('mp4') > -1 - ? Buffer.from(await readOrFetch(m.path)) - : await sharp(await readOrFetch(m.path), { - animated: lookup(m.path) === 'image/gif', - }) - .resize({ - width: 1000, + id: await this.runInConcurrent( + async () => + client.v1.uploadMedia( + m.path.indexOf('mp4') > -1 + ? Buffer.from(await readOrFetch(m.path)) + : await sharp(await readOrFetch(m.path), { + animated: lookup(m.path) === 'image/gif', }) - .gif() - .toBuffer(), - { - mimeType: lookup(m.path) || '', - } - ) + .resize({ + width: 1000, + }) + .gif() + .toBuffer(), + { + mimeType: lookup(m.path) || '', + } + ), + true ), postId: p.id, }; From 7b8fa7fb7bf2f9ac75409023dd3bf5c67e06b820 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Thu, 14 Aug 2025 11:38:27 +0700 Subject: [PATCH 08/48] feat: retry --- .../nestjs-libraries/src/integrations/social.abstract.ts | 9 ++++++++- .../src/integrations/social/instagram.provider.ts | 9 ++++++++- .../integrations/social/instagram.standalone.provider.ts | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/libraries/nestjs-libraries/src/integrations/social.abstract.ts b/libraries/nestjs-libraries/src/integrations/social.abstract.ts index b0ef5670..778864fb 100644 --- a/libraries/nestjs-libraries/src/integrations/social.abstract.ts +++ b/libraries/nestjs-libraries/src/integrations/social.abstract.ts @@ -29,7 +29,9 @@ export abstract class SocialAbstract { public handleErrors( body: string - ): { type: 'refresh-token' | 'bad-body'; value: string } | undefined { + ): + | { type: 'refresh-token' | 'bad-body' | 'retry'; value: string } + | undefined { return undefined; } @@ -112,6 +114,11 @@ export abstract class SocialAbstract { const handleError = this.handleErrors(json || '{}'); + if (handleError?.type === 'retry') { + await timer(5000); + return this.fetch(url, options, identifier, totalRetries + 1); + } + if ( request.status === 401 && (handleError?.type === 'refresh-token' || !handleError) diff --git a/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts b/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts index 2bdf2889..0b7ada53 100644 --- a/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts @@ -46,11 +46,18 @@ export class InstagramProvider public override handleErrors(body: string): | { - type: 'refresh-token' | 'bad-body'; + type: 'refresh-token' | 'bad-body' | 'retry'; value: string; } | undefined { + if (body.indexOf('An unknown error occurred') > -1) { + return { + type: 'retry' as const, + value: 'An unknown error occurred, please try again later', + }; + } + if (body.indexOf('REVOKED_ACCESS_TOKEN') > -1) { return { type: 'refresh-token' as const, 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 68938b17..523bcda2 100644 --- a/libraries/nestjs-libraries/src/integrations/social/instagram.standalone.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/instagram.standalone.provider.ts @@ -30,7 +30,7 @@ export class InstagramStandaloneProvider editor = 'normal' as const; - public override handleErrors(body: string): { type: "refresh-token" | "bad-body"; value: string } | undefined { + public override handleErrors(body: string): { type: "refresh-token" | "bad-body" | "retry"; value: string } | undefined { return instagramProvider.handleErrors(body); } From 3a5e0b14729e79eb299c718ad3d9a66364ca22bb Mon Sep 17 00:00:00 2001 From: Nevo David Date: Thu, 14 Aug 2025 12:07:01 +0700 Subject: [PATCH 09/48] feat: retry --- .../nestjs-libraries/src/integrations/social.abstract.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/nestjs-libraries/src/integrations/social.abstract.ts b/libraries/nestjs-libraries/src/integrations/social.abstract.ts index 778864fb..104cbe77 100644 --- a/libraries/nestjs-libraries/src/integrations/social.abstract.ts +++ b/libraries/nestjs-libraries/src/integrations/social.abstract.ts @@ -109,16 +109,17 @@ export abstract class SocialAbstract { json.includes('Rate limit') ) { await timer(5000); - return this.fetch(url, options, identifier, totalRetries + 1); + return this.fetch(url, options, identifier, totalRetries + 1, ignoreConcurrency); } const handleError = this.handleErrors(json || '{}'); if (handleError?.type === 'retry') { await timer(5000); - return this.fetch(url, options, identifier, totalRetries + 1); + return this.fetch(url, options, identifier, totalRetries + 1, ignoreConcurrency); } + if ( request.status === 401 && (handleError?.type === 'refresh-token' || !handleError) From 5115d812e593debde2003cbfef8f4287fb9a174c Mon Sep 17 00:00:00 2001 From: Nevo David Date: Thu, 14 Aug 2025 12:07:07 +0700 Subject: [PATCH 10/48] feat: retry --- libraries/nestjs-libraries/src/integrations/social.abstract.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/nestjs-libraries/src/integrations/social.abstract.ts b/libraries/nestjs-libraries/src/integrations/social.abstract.ts index 104cbe77..94bb51b0 100644 --- a/libraries/nestjs-libraries/src/integrations/social.abstract.ts +++ b/libraries/nestjs-libraries/src/integrations/social.abstract.ts @@ -119,7 +119,6 @@ export abstract class SocialAbstract { return this.fetch(url, options, identifier, totalRetries + 1, ignoreConcurrency); } - if ( request.status === 401 && (handleError?.type === 'refresh-token' || !handleError) From b04510222b7b0ac991b86236f28ebe12fc5e764a Mon Sep 17 00:00:00 2001 From: Nevo David Date: Tue, 26 Aug 2025 22:18:45 +0700 Subject: [PATCH 11/48] feat: fix date for preview --- .../src/app/(app)/(preview)/p/[id]/page.tsx | 17 ++++++++++++----- .../components/preview/render.preview.date.tsx | 6 ++++++ 2 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 apps/frontend/src/components/preview/render.preview.date.tsx diff --git a/apps/frontend/src/app/(app)/(preview)/p/[id]/page.tsx b/apps/frontend/src/app/(app)/(preview)/p/[id]/page.tsx index e008ba84..85f7aa9a 100644 --- a/apps/frontend/src/app/(app)/(preview)/p/[id]/page.tsx +++ b/apps/frontend/src/app/(app)/(preview)/p/[id]/page.tsx @@ -10,6 +10,16 @@ import utc from 'dayjs/plugin/utc'; import { VideoOrImage } from '@gitroom/react/helpers/video.or.image'; import { CopyClient } from '@gitroom/frontend/components/preview/copy.client'; import { getT } from '@gitroom/react/translation/get.translation.service.backend'; +import dynamicLoad from 'next/dynamic'; + +const RenderPreviewDate = dynamicLoad( + () => + import('@gitroom/frontend/components/preview/render.preview.date').then( + (mod) => mod.RenderPreviewDate + ), + { ssr: false } +); + dayjs.extend(utc); export const metadata: Metadata = { title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Preview`, @@ -91,11 +101,8 @@ export default async function Auth({ )}
- {t('publication_date', 'Publication Date:')} - {dayjs - .utc(post[0].publishDate) - .local() - .format('MMMM D, YYYY h:mm A')} + {t('publication_date', 'Publication Date:')}{' '} +
diff --git a/apps/frontend/src/components/preview/render.preview.date.tsx b/apps/frontend/src/components/preview/render.preview.date.tsx new file mode 100644 index 00000000..8097e5d4 --- /dev/null +++ b/apps/frontend/src/components/preview/render.preview.date.tsx @@ -0,0 +1,6 @@ +import { FC } from 'react'; +import dayjs from 'dayjs'; + +export const RenderPreviewDate: FC<{ date: string }> = ({ date }) => { + return <>{dayjs.utc(date).local().format('MMMM D, YYYY h:mm A')}; +}; From 60da0f6fb1787129af93376de7796273068eb026 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Tue, 26 Aug 2025 22:29:22 +0700 Subject: [PATCH 12/48] feat: fix date for preview --- apps/frontend/src/components/preview/render.preview.date.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/frontend/src/components/preview/render.preview.date.tsx b/apps/frontend/src/components/preview/render.preview.date.tsx index 8097e5d4..0ac9d2f8 100644 --- a/apps/frontend/src/components/preview/render.preview.date.tsx +++ b/apps/frontend/src/components/preview/render.preview.date.tsx @@ -1,5 +1,7 @@ import { FC } from 'react'; import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +dayjs.extend(utc); export const RenderPreviewDate: FC<{ date: string }> = ({ date }) => { return <>{dayjs.utc(date).local().format('MMMM D, YYYY h:mm A')}; From 3bea9ee7e9886f2155a8449c7e45219a44cae9bb Mon Sep 17 00:00:00 2001 From: Nevo David Date: Tue, 26 Aug 2025 23:12:56 +0700 Subject: [PATCH 13/48] feat: disconnect prisma --- .../src/components/preview/render.preview.date.tsx | 1 + .../src/database/prisma/prisma.service.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/frontend/src/components/preview/render.preview.date.tsx b/apps/frontend/src/components/preview/render.preview.date.tsx index 0ac9d2f8..cd0a5264 100644 --- a/apps/frontend/src/components/preview/render.preview.date.tsx +++ b/apps/frontend/src/components/preview/render.preview.date.tsx @@ -4,5 +4,6 @@ import utc from 'dayjs/plugin/utc'; dayjs.extend(utc); export const RenderPreviewDate: FC<{ date: string }> = ({ date }) => { + console.log(date); return <>{dayjs.utc(date).local().format('MMMM D, YYYY h:mm A')}; }; diff --git a/libraries/nestjs-libraries/src/database/prisma/prisma.service.ts b/libraries/nestjs-libraries/src/database/prisma/prisma.service.ts index eeb4ebc3..089afc5d 100644 --- a/libraries/nestjs-libraries/src/database/prisma/prisma.service.ts +++ b/libraries/nestjs-libraries/src/database/prisma/prisma.service.ts @@ -1,8 +1,8 @@ -import { Injectable, OnModuleInit } from '@nestjs/common'; +import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; @Injectable() -export class PrismaService extends PrismaClient implements OnModuleInit { +export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { constructor() { super({ log: [ @@ -16,6 +16,10 @@ export class PrismaService extends PrismaClient implements OnModuleInit { async onModuleInit() { await this.$connect(); } + + async onModuleDestroy() { + await this.$disconnect(); + } } @Injectable() @@ -26,7 +30,6 @@ export class PrismaRepository { } } - @Injectable() export class PrismaTransaction { public model: Pick; From 6a70f2f92d121a849d25b2198ebe972016362329 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Tue, 26 Aug 2025 23:22:21 +0700 Subject: [PATCH 14/48] feat: client --- apps/frontend/src/components/preview/render.preview.date.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/frontend/src/components/preview/render.preview.date.tsx b/apps/frontend/src/components/preview/render.preview.date.tsx index cd0a5264..543f0645 100644 --- a/apps/frontend/src/components/preview/render.preview.date.tsx +++ b/apps/frontend/src/components/preview/render.preview.date.tsx @@ -1,3 +1,5 @@ +'use client'; + import { FC } from 'react'; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; From 3a1a7500aecc37d5e6d1e8d124847aa918024c0f Mon Sep 17 00:00:00 2001 From: Nevo David <100117126+nevo-david@users.noreply.github.com> Date: Wed, 27 Aug 2025 23:18:40 +0700 Subject: [PATCH 15/48] Update README with new GitHub link and image --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5dd97d86..91350317 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- - + + automate

From cede5216b259f8dcb9a0938cb18befef69fb2f29 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Mon, 1 Sep 2025 01:29:22 +0700 Subject: [PATCH 16/48] feat: growchief --- .../src/components/layout/top.menu.tsx | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/apps/frontend/src/components/layout/top.menu.tsx b/apps/frontend/src/components/layout/top.menu.tsx index 1f8eb6a9..66c78d98 100644 --- a/apps/frontend/src/components/layout/top.menu.tsx +++ b/apps/frontend/src/components/layout/top.menu.tsx @@ -1,7 +1,6 @@ 'use client'; import { FC, ReactNode } from 'react'; -import { usePathname } from 'next/navigation'; import { useUser } from '@gitroom/frontend/components/layout/user.context'; import { useVariables } from '@gitroom/react/helpers/variable.context'; import { useT } from '@gitroom/react/translation/get.transation.service.client'; @@ -129,6 +128,39 @@ export const useMenuItem = () => { ] satisfies MenuItemInterface[] as MenuItemInterface[]; const secondMenu = [ + { + name: 'GrowChief', + icon: ( + + + + + + ), + path: 'https://growchief.com', + role: ['ADMIN', 'SUPERADMIN', 'USER'], + requireBilling: true, + }, { name: t('affiliate', 'Affiliate'), icon: ( @@ -237,7 +269,7 @@ export const useMenuItem = () => { ), path: '/settings', - role: ['ADMIN', "USER", 'SUPERADMIN'], + role: ['ADMIN', 'USER', 'SUPERADMIN'], }, ] satisfies MenuItemInterface[] as MenuItemInterface[]; From 5c6617da2aa6bd44d7a2f73ff0486bd40f3b2daf Mon Sep 17 00:00:00 2001 From: Nevo David Date: Mon, 1 Sep 2025 10:43:17 +0700 Subject: [PATCH 17/48] feat: fix gt and lt --- libraries/helpers/src/utils/strip.html.validation.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/helpers/src/utils/strip.html.validation.ts b/libraries/helpers/src/utils/strip.html.validation.ts index 7ad3591c..16f97042 100644 --- a/libraries/helpers/src/utils/strip.html.validation.ts +++ b/libraries/helpers/src/utils/strip.html.validation.ts @@ -207,7 +207,9 @@ export const stripHtmlValidation = ( .replace(/ /gi, ' ') .replace(/^]*>/i, '') .replace(/]*>/gi, '\n') - .replace(/<\/p>/gi, ''); + .replace(/<\/p>/gi, '') + .replace(/>/gi, '>') + .replace(/</gi, '<') if (none) { return striptags(html); From baa2f7a74dcc4687fdf8f5f98fc933cead2b1ed2 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Mon, 1 Sep 2025 10:47:13 +0700 Subject: [PATCH 18/48] feat: api --- apps/backend/src/public-api/public.api.module.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/backend/src/public-api/public.api.module.ts b/apps/backend/src/public-api/public.api.module.ts index 73f85ebd..6610735a 100644 --- a/apps/backend/src/public-api/public.api.module.ts +++ b/apps/backend/src/public-api/public.api.module.ts @@ -34,3 +34,4 @@ export class PublicApiModule implements NestModule { consumer.apply(PublicAuthMiddleware).forRoutes(...authenticatedController); } } + From bcc26b6676d618995cc5258d2feb86b5e2b5b103 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Mon, 1 Sep 2025 10:48:46 +0700 Subject: [PATCH 19/48] feat: main --- apps/workers/src/main.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/workers/src/main.ts b/apps/workers/src/main.ts index dab28879..e2240fe1 100644 --- a/apps/workers/src/main.ts +++ b/apps/workers/src/main.ts @@ -22,3 +22,4 @@ async function bootstrap() { } bootstrap(); + From 477a835d24f010fcf42973e24973ed2504300c2d Mon Sep 17 00:00:00 2001 From: Nevo David Date: Mon, 1 Sep 2025 10:48:51 +0700 Subject: [PATCH 20/48] feat: main --- apps/workers/src/main.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/workers/src/main.ts b/apps/workers/src/main.ts index e2240fe1..dab28879 100644 --- a/apps/workers/src/main.ts +++ b/apps/workers/src/main.ts @@ -22,4 +22,3 @@ async function bootstrap() { } bootstrap(); - From 46f9d2645a977048283df824d500c84c6496ff3d Mon Sep 17 00:00:00 2001 From: Nevo David Date: Mon, 1 Sep 2025 10:49:03 +0700 Subject: [PATCH 21/48] feat: main --- apps/cron/src/main.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/cron/src/main.ts b/apps/cron/src/main.ts index c22bce19..99577af4 100644 --- a/apps/cron/src/main.ts +++ b/apps/cron/src/main.ts @@ -10,3 +10,4 @@ async function bootstrap() { } bootstrap(); + From aedfe24a0b73b110b8b977decfd384ebbdddc539 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Mon, 1 Sep 2025 10:49:07 +0700 Subject: [PATCH 22/48] feat: main --- apps/cron/src/main.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/cron/src/main.ts b/apps/cron/src/main.ts index 99577af4..c22bce19 100644 --- a/apps/cron/src/main.ts +++ b/apps/cron/src/main.ts @@ -10,4 +10,3 @@ async function bootstrap() { } bootstrap(); - From ec2fd4d1c2dc9cbc4142e5b1f4cad4a14eb704c4 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Mon, 1 Sep 2025 10:50:15 +0700 Subject: [PATCH 23/48] feat: deploy worker --- apps/workers/src/app/plugs.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/workers/src/app/plugs.controller.ts b/apps/workers/src/app/plugs.controller.ts index f730323c..a7d1134b 100644 --- a/apps/workers/src/app/plugs.controller.ts +++ b/apps/workers/src/app/plugs.controller.ts @@ -18,7 +18,7 @@ export class PlugsController { return await this._integrationService.processPlugs(data); } catch (err) { console.log( - "Unhandled error, let's avoid crashing the plugs worker", + "Unhandled error, let's avoid crashing the plug worker", err ); } From 93378ded4c6cf132a3def1a952f58d2aaa091317 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Mon, 1 Sep 2025 10:50:37 +0700 Subject: [PATCH 24/48] feat: missing jobs --- apps/cron/src/tasks/check.missing.queues.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/cron/src/tasks/check.missing.queues.ts b/apps/cron/src/tasks/check.missing.queues.ts index 6c9def7f..0664676c 100644 --- a/apps/cron/src/tasks/check.missing.queues.ts +++ b/apps/cron/src/tasks/check.missing.queues.ts @@ -28,6 +28,7 @@ export class CheckMissingQueues { ) ).filter((p) => !p.isJob); + for (const job of notExists) { this._workerServiceProducer.emit('post', { id: job.id, From c11830551c653c82b6bf341ad68a0c82bb03834b Mon Sep 17 00:00:00 2001 From: Nevo David Date: Wed, 3 Sep 2025 16:52:22 +0700 Subject: [PATCH 25/48] feat: fix append --- apps/frontend/src/components/new-launch/store.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/frontend/src/components/new-launch/store.ts b/apps/frontend/src/components/new-launch/store.ts index 754c833d..d7b03718 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, thumbnail?: string }[]; + media: { id: string; path: string; thumbnail?: string }[]; } interface Internal { @@ -504,7 +504,9 @@ export const useLaunchStore = create()((set) => ({ ? { ...item, integrationValue: item.integrationValue.map((v, i) => - i === index ? { ...v, media: [...v.media, ...media] } : v + i === index + ? { ...v, media: [...(v?.media || []), ...media] } + : v ), } : item @@ -516,7 +518,9 @@ export const useLaunchStore = create()((set) => ({ ) => set((state) => ({ global: state.global.map((item, i) => - i === index ? { ...item, media: [...item.media, ...media] } : item + i === index + ? { ...item, media: [...(item?.media || []), ...media] } + : item ), })), setPostComment: (postComment: PostComment) => From 4bfef6badeb9c9fac55a023f3919ae73329ca268 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Thu, 4 Sep 2025 23:55:59 +0700 Subject: [PATCH 26/48] fix: if flatmap is empty --- .../src/components/media/new.uploader.tsx | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/apps/frontend/src/components/media/new.uploader.tsx b/apps/frontend/src/components/media/new.uploader.tsx index 850fdc84..febe58fe 100644 --- a/apps/frontend/src/components/media/new.uploader.tsx +++ b/apps/frontend/src/components/media/new.uploader.tsx @@ -88,18 +88,10 @@ export function useUppyUploader(props: { // Expand generic types to specific ones const expandedTypes = allowedTypes.flatMap((type) => { if (type === 'image/*') { - return [ - 'image/png', - 'image/jpeg', - 'image/jpg', - 'image/gif', - ]; + return ['image/png', 'image/jpeg', 'image/jpg', 'image/gif']; } if (type === 'video/*') { - return [ - 'video/mp4', - 'video/mpeg', - ]; + return ['video/mp4', 'video/mpeg']; } return [type]; }); @@ -214,12 +206,11 @@ export function useUppyUploader(props: { return; } - console.log(result); if (transloadit.length > 0) { // @ts-ignore const allRes = result.transloadit[0].results; const toSave = uniq( - allRes[Object.keys(allRes)[0]].flatMap((item: any) => + (allRes[Object.keys(allRes)[0]] || []).flatMap((item: any) => item.url.split('/').pop() ) ); From 1ed1de5925c5b894386bdd24ae73147315a06c86 Mon Sep 17 00:00:00 2001 From: Mo Date: Sat, 6 Sep 2025 02:58:33 +0700 Subject: [PATCH 27/48] logo jsx attribute fix --- apps/frontend/src/components/new-layout/logo.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/src/components/new-layout/logo.tsx b/apps/frontend/src/components/new-layout/logo.tsx index dfa1481d..3c9113ca 100644 --- a/apps/frontend/src/components/new-layout/logo.tsx +++ b/apps/frontend/src/components/new-layout/logo.tsx @@ -17,7 +17,7 @@ export const Logo = () => { Date: Sat, 6 Sep 2025 14:08:16 +0400 Subject: [PATCH 28/48] =?UTF-8?q?Fully=20translated=20into=20Georgian=20-?= =?UTF-8?q?=20=E1=83=A1=E1=83=A0=E1=83=A3=E1=83=9A=E1=83=90=E1=83=93=20?= =?UTF-8?q?=E1=83=92=E1=83=90=E1=83=93=E1=83=90=E1=83=97=E1=83=90=E1=83=A0?= =?UTF-8?q?=E1=83=92=E1=83=9B=E1=83=9C=E1=83=98=E1=83=9A=E1=83=98=20?= =?UTF-8?q?=E1=83=A5=E1=83=90=E1=83=A0=E1=83=97=E1=83=A3=E1=83=9A=E1=83=90?= =?UTF-8?q?=E1=83=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../locales/ka_ge/translation.json | 505 ++++++++++++++++++ 1 file changed, 505 insertions(+) create mode 100644 libraries/react-shared-libraries/src/translation/locales/ka_ge/translation.json diff --git a/libraries/react-shared-libraries/src/translation/locales/ka_ge/translation.json b/libraries/react-shared-libraries/src/translation/locales/ka_ge/translation.json new file mode 100644 index 00000000..e8614579 --- /dev/null +++ b/libraries/react-shared-libraries/src/translation/locales/ka_ge/translation.json @@ -0,0 +1,505 @@ +{ + "calendar": "კალენდარი", + "webhooks": "ვებჰუქები", + "webhooks_are_a_way_to_get_notified_when_something_happens_in_postiz_via_an_http_request": "ვებჰუქები საშუალებას გაძლევთ მიიღოთ შეტყობინება, როცა Postiz-ში რაიმე ხდება HTTP მოთხოვნის მეშვეობით.", + "name": "სახელი", + "url": "ბმული", + "edit": "რედაქტირება", + "delete": "წაშლა", + "add_a_webhook": "ვებჰუქის დამატება", + "save": "შენახვა", + "send_test": "ტესტის გაგზავნა", + "select_role": "როლის არჩევა", + "video_made_with_ai": "ვიდეო შექმნილია AI-ის მეშვეობით", + "please_add_at_least": "გთხოვთ დაამატოთ მინიმუმ 20 სიმბოლო", + "send_invitation_via_email": "მოწვევა გადაიგზავნოს ელფოსტით?", + "global_settings": "გლობალური პარამეტრები", + "copy_id": "არხის ID-ის კოპირება", + "team_members": "გუნდის წევრები", + "invite_your_assistant_or_team_member_to_manage_your_account": "მოიწვიეთ ასისტენტი ან გუნდის წევრი თქვენი ანგარიშის სამართავად", + "remove": "წაშლა", + "add_another_member": "სხვა წევრის დამატება", + "signatures": "ხელმოწერები", + "you_can_add_signatures_to_your_account_to_be_used_in_your_posts": "შეგიძლიათ დაამატოთ ხელმოწერები, რომლებიც გამოყენებული იქნება თქვენს პოსტებში.", + "content": "კონტენტი", + "auto_add": "ავტომატურად დამატება?", + "actions": "ქმედებები", + "use_signature": "ხელმოწერის გამოყენება", + "add_a_signature": "ხელმოწერის დამატება", + "no": "არა", + "yes": "კი", + "your_git_repository": "თქვენი Git რეპოზიტორია", + "connect_your_github_repository_to_receive_updates_and_analytics": "დააკავშირეთ თქვენი GitHub რეპოზიტორია განახლებებისა და ანალიტიკის მისაღებად", + "connected": "დაკავშირებულია:", + "disconnect": "გათიშვა", + "connect_your_repository": "რეპოზიტორიის დაკავშირება", + "cancel": "გაუქმება", + "connect": "დაკავშირება", + "public_api": "საჯარო API", + "check_n8n": "იხილეთ ჩვენი N8N მორგებული node Postiz-ისთვის.", + "use_postiz_api_to_integrate_with_your_tools": "გამოიყენეთ Postiz API თქვენი ინსტრუმენტების ინტეგრაციისთვის.", + "read_how_to_use_it_over_the_documentation": "იხილეთ დეტალები დოკუმენტაციაში.", + "reveal": "ჩვენება", + "copy_key": "გასაღების კოპირება", + "mcp": "MCP", + "connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "დააკავშირეთ MCP-კლიენტი Postiz-სთან პოსტების უფრო სწრაფად დასაგეგმად!", + "share_with_a_client": "კლიენტთან გაზიარება", + "post": "პოსტი", + "comments": "კომენტარები", + "user": "მომხმარებელი", + "login_register_to_add_comments": "კომენტარის დასამატებლად გაიარეთ ავტორიზაცია ან რეგისტრაცია", + "status": "სტატუსი:", + "there_are_not_plugs_matching_your_channels": "თქვენს არხებთან შესაბამისი მოდულები ვერ მოიძებნა", + "you_have_to_add_x_or_linkedin_or_threads": "უნდა დაამატოთ: X, LinkedIn ან Threads", + "go_to_the_calendar_to_add_channels": "გადადით კალენდარში არხების დასამატებლად", + "channels": "არხები", + "activate": "აქტივაცია", + "this_channel_needs_to_be_refreshed": "ამ არხს განახლება სჭირდება,", + "click_here_to_refresh": "დააჭირეთ აქ განახლებისთვის", + "can_t_show_analytics_yet": "ჯერ ანალიტიკა მიუწვდომელია", + "you_have_to_add_social_media_channels": "დაამატეთ სოციალური მედიის არხები", + "supported": "მხარდაჭერილია:", + "step": "ნაბიჯი", + "skip_onboarding": "გაშვების გამოტოვება", + "onboarding": "გაშვება", + "next": "შემდეგი", + "you_are_done_from_here_you_can": "მზადაა! აქედან შეგიძლიათ:", + "view_analytics": "ანალიტიკის ნახვა", + "schedule_a_new_post": "ახალი პოსტის დაგეგმვა", + "to_sell_posts_you_would_have_to": "პოსტების გასაყიდად საჭიროა:", + "1_connect_at_least_one_channel": "1. მინიმუმ ერთი არხის დაკავშირება", + "2_connect_you_bank_account": "2. საბანკო ანგარიშის დაკავშირება", + "go_back_to_connect_channels": "დაბრუნდით არხების დასაკავშირებლად", + "move_to_the_seller_page_to_connect_you_bank": "გადადით გამყიდველის გვერდზე საბანკო ანგარიშის დასაკავშირებლად", + "connect_channels": "არხების დაკავშირება", + "connect_your_social_media_and_publishing_websites_channels_to_schedule_posts_later": "დააკავშირეთ სოციალური მედიისა და გამოქვეყნების არხები, რომ შემდეგ დაგეგმოთ პოსტები", + "social": "სოციალური", + "publishing_platforms": "გამოქვეყნების პლატფორმები", + "no_channels": "არხები ჯერ არ არის", + "connect_your_accounts": "დააბმითეთ თქვენი ანგარიშები — დაგეგმვა, გამოქვეყნება და ანალიზი ერთ სივრცეში.", + "notifications": "შეტყობინებები", + "no_notifications": "შეტყობინებები არ არის", + "send_message": "შეტყობინების გაგზავნა", + "mar_28": "მარტი 28", + "there_are_no_messages_yet": "ჯერ არ არის შეტყობინებები.", + "checkout_the_marketplace": "ეწვიეთ მარკეტპლეისს", + "go_to_marketplace": "გადასვლა მარკეტპლეისში", + "all_messages": "ყველა შეტყობინება", + "previous": "წინა", + "select_or_upload_pictures_maximum_5_at_a_time": "აირჩიეთ ან ატვირთეთ სურათები (მაქს. 5 ერთდროულად)", + "you_can_also_drag_drop_pictures": "ასევე შეგიძლიათ გადაათრიოთ და ჩააგდოთ სურათები", + "you_don_t_have_any_assets_yet": "ჯერ არ გაქვთ ასეტები", + "click_the_button_below_to_upload_one": "დააჭირეთ ქვედა ღილაკს ატვირთვისთვის", + "add_selected_media": "არჩეული მედიის დამატება", + "insert_media": "მედიის ჩასმა", + "design_media": "მედიის დიზაინი", + "select": "არჩევა", + "editor": "რედაქტორი", + "clear": "გასუფთავება", + "order_completed": "შეკვეთა დასრულდა", + "the_order_has_been_completed": "შეკვეთა დასრულებულია", + "post_has_been_published": "პოსტი გამოქვეყნდა", + "url_1": "ბმული:", + "new_offer": "ახალი შეთავაზება", + "platform": "პლატფორმა", + "posts": "პოსტები", + "pay_accept_offer": "გადახდა და შეთავაზების მიღება", + "accepted": "მიღებულია", + "post_draft": "პოსტის მონახაზი", + "revision_needed": "საჭიროა გადახედვა", + "approve": "დამტკიცება", + "preview": "გადახედვა", + "revision_requested": "მოთხოვნილია გადახედვა", + "accepted_1": "მიღებულია", + "cancelled_by_the_seller": "გაუქმდა გამყიდველის მიერ", + "please_select_your_country_where_your_business_is": "აირჩიეთ თქვენი ბიზნესის ქვეყანა", + "select_country": "--აირჩიეთ ქვეყანა--", + "connect_bank_account": "საბანკო ანგარიშის დაკავშირება", + "seller_mode": "გამყიდველის რეჟიმი", + "active": "აქტიური", + "details": "დეტალები", + "audience_size": "აუდიტორიის ზომა", + "add_another_platform": "სხვა პლატფორმის დამატება", + "send_an_offer_for": "შეთავაზების გაგზავნა $", + "complete_order_and_pay_early": "შეკვეთის დასრულება და გადახდა", + "order_in_progress": "შეკვეთა პროცესშია", + "create_a_new_offer": "ახალი შეთავაზების შექმნა", + "orders": "შეკვეთები", + "price": "ფასი", + "state": "სტატუსი", + "showing": "ნაჩვენებია", + "to": "მდე", + "from": "დან", + "results": "შედეგები", + "content_writer": "კონტენტის ავტორი", + "influencer": "ინფლუენსერი", + "request_service": "მომსახურების მოთხოვნა", + "the_marketplace_is_not_opened_yet": "მარკეტპლეისი ჯერ არ არის გახსნილი", + "check_again_soon": "შეამოწმეთ მალე!", + "filter": "ფილტრი", + "result": "შედეგი", + "seller": "გამყიდველი", + "buyer": "მყიდველი", + "discord_support": "Discord მხარდაჭერა", + "teams": "გუნდები", + "webhooks_1": "ვებჰუქები", + "auto_post": "ავტო-პოსტი", + "logout_from": "გასვლა ანგარიშიდან", + "join_10000_entrepreneurs_who_use_postiz": "შეუერთდით 10,000+ მეწარმეს, ვინც იყენებს Postiz-ს", + "to_manage_all_your_social_media_channels": "ყველა სოციალური არხის სამართავად", + "100_no_risk_trial": "100% რისკის გარეშე საცდელი პერიოდი", + "pay_nothing_for_the_first_7_days": "პირველი 7 დღე უფასოა", + "cancel_anytime_hassle_free": "გაუქმება jederzeit, უპრობლემოდ", + "add_free_subscription": "-- უფასო გამოწერის დამატება --", + "currently_impersonating": "ამჟამად იმპერსონაციაა", + "user_1": "მომხმარებელი:", + "drag_n_drop_some_files_here": "გადაათრიეთ ფაილები აქ", + "add_time_slot": "დროის სლოტის დამატება", + "add_slot": "სლოტის დამატება", + "cancel_publication": "გამოქვეყნების გაუქმება", + "statistics": "სტატისტიკა", + "loading": "იტვირთება", + "short_link": "მოკლე ბმული", + "original_link": "ორიგინალი ბმული", + "clicks": "კლიკები", + "selected_customer": "არჩეული კლიენტი", + "customer": "კლიენტი:", + "repeat_post_every": "პოსტის გამეორება ყოველ...", + "use_this_media": "ამ მედიის გამოყენება", + "create_new_post": "პოსტის შექმნა", + "update_post": "არსებული პოსტის განახლება", + "merge_comments_into_one_post": "კომენტარების გაერთიანება ერთ პოსტად", + "accounts_that_will_engage": "ანგარიშები, რომლებიც ჩაერთვებიან:", + "day": "დღე", + "week": "კვირა", + "month": "თვე", + "remove_from_customer": "კლიენტიდან მოხსნა", + "show_more": "+ მეტი", + "show_less": "- ნაკლები", + "upload": "ატვირთვა", + "ai": "AI", + "add_channel": "არხის დამატება", + "add_platform": "პლატფორმის დამატება", + "articles": "სტატიები", + "add_comment": "კომენტარის დამატება", + "add_post": "პოსტის დამატება თრედში", + "add_comment_or_post": "კომენტარის / პოსტის დამატება", + "you_are_in_global_editing_mode": "გლობალური რედაქტირების რეჟიმში ხართ", + "the_post_should_be_at_least_6_characters_long": "პოსტი უნდა შეიცავდეს მინიმუმ 6 სიმბოლოს", + "are_you_sure_you_want_to_delete_post": "დარწმუნებული ხართ, რომ წაშალოთ ეს პოსტი?", + "post_deleted_successfully": "პოსტი წარმატებით წაიშალა", + "delete_post": "პოსტს წაშლა", + "save_as_draft": "მონახაზად შენახვა", + "post_now": "გამოქვეყნება ახლა", + "please_add": "დაამატეთ, გთხოვთ", + "to_your_telegram_group_channel_and_click_here": "თქვენს Telegram ჯგუფში/არხში და შემდეგ დააჭირეთ აქ:", + "connect_telegram": "Telegram-ის დაკავშირება", + "please_add_the_following_command_in_your_chat": "დაამატეთ ჩატში შემდეგი ბრძანება:", + "copy": "კოპირება", + "settings": "პარამეტრები", + "integrations": "ინტეგრაციები", + "add_integration": "ინტეგრაციის დამატება", + "you_are_now_editing_only": "ახლა რედაქტირებ მხოლოდ", + "tag_a_company": "მიანიჭე ტეგი კომპანიას", + "video_length_is_invalid_must_be_up_to": "ვიდეოს სიგრძე არასწორია, უნდა იყოს მაქსიმუმ", + "seconds": "წამი", + "this_feature_available_only_for_photos": "ეს ფუნქცია ხელმისაწვდომია მხოლოდ ფოტოებისთვის — დაემატება ნაგულისხმევი მუსიკა, რომელსაც შემდეგ შეცვლით.", + "allow_user_to": "ნება მიეცით მომხმარებელს:", + "your_video_will_be_labeled_promotional": "თქვენი ვიდეო მოინათლება როგორც \"რეკლამიური კონტენტი\".", + "this_cannot_be_changed_once_posted": "ეს ვერ შეიცვლება პოსტის გამოქვეყნების შემდეგ.", + "turn_on_to_disclose_video_promotes": "ჩართეთ, რათა მიუთითოთ, რომ ვიდეო ხელს უწყობს საქონელს ან მომსახურებას ღირებულების სანაცვლოდ. ვიდეო შესაძლოა ასახავდეს თქვენ, მესამე პირს ან ორივეს.", + "you_are_promoting_yourself": "აქვეყნებთ თქვენი ბრენდის რეკლამას.", + "this_video_will_be_classified_brand_organic": "ვიდეო კლასიფიცირდება როგორც Brand Organic.", + "you_are_promoting_another_brand": "აქვეყნებთ სხვა ბრენდის/მესამე მხარის რეკლამას.", + "this_video_will_be_classified_branded_content": "ვიდეო კლასიფიცირდება როგორც Branded Content.", + "by_posting_you_agree_to_tiktoks": "პოსტის გამოქვეყნებით ეთანხმებით TikTok-ის", + "music_usage_confirmation": "მუსიკის გამოყენების დადასტურებას", + "branded_content_policy": "ბრენდირებული კონტენტის პოლიტიკას", + "select_1": "--აირჩიეთ--", + "select_flair": "--აირჩიეთ ფლეარი--", + "link": "ბმული", + "add_subreddit": "Subreddit-ის დამატება", + "please_add_at_least_one_subreddit": "დაამატეთ მინიმუმ ერთი Subreddit", + "add_community": "Community-ის დამატება", + "select_post_type": "აირჩიეთ პოსტის ტიპი...", + "we_couldn_t_find_any_business_connected_to_your_linkedin_page": "LinkedIn გვერდს მიერთებული ბიზნესი ვერ ვიპოვეთ.", + "please_close_this_dialog_create_a_new_page_and_add_a_new_channel_again": "დახურეთ ეს ფანჯარა, შექმენით ახალი გვერდი და თავიდან დაამატეთ არხი.", + "select_linkedin_page": "აირჩიეთ LinkedIn გვერდი:", + "we_couldn_t_find_any_business_connected_to_the_selected_pages": "არჩეულ გვერდებს მიერთებული ბიზნესი ვერ ვიპოვეთ.", + "we_recommend_you_to_connect_all_the_pages_and_all_the_businesses": "გირჩევთ დააკავშიროთ ყველა გვერდი და ყველა ბიზნესი.", + "please_close_this_dialog_delete_your_integration_and_add_a_new_channel_again": "დახურეთ ეს ფანჯარა, წაშალეთ ინტეგრაცია და თავიდან დაამატეთ არხი.", + "select_instagram_account": "აირჩიეთ Instagram ანგარიში:", + "select_page": "გვერდის არჩევა:", + "generate_image_with_ai": "სურათის გენერირება AI-ით", + "reconnect_channel": "არხის ხელახლა დაკავშირება", + "update_credentials": "მომართვების განახლება", + "additional_settings": "დამატებითი პარამეტრები", + "change_bot": "ბოტის შეცვლა", + "move_add_to_customer": "გადატანა / დამატება კლიენტზე", + "edit_time_slots": "დროის სლოტების რედაქტირება", + "enable_channel": "არხის ჩართვა", + "disable_channel": "არხის გამორთვა", + "add": "დამატება", + "short_post": "მოკლე პოსტი", + "long_post": "გრძელი პოსტი", + "a_thread_with_short_posts": "თრედი მოკლე პოსტებით", + "a_thread_with_long_posts": "თრედი გრძელი პოსტებით", + "personal_voice_i_am_happy_to_announce": "პირადი ხმით (\"მიხარია განცხადება...\")", + "company_voice_we_are_happy_to_announce": "კომპანიის ხმით (\"მოხარულები ვართ, რომ ვაცხადებთ...\")", + "generate": "გენერირება", + "generate_posts": "პოსტების გენერირება", + "purchase_a_life_time_pro_account_with_sol_199": "შეიძინეთ სამუდამო PRO ანგარიში SOL-ით ($199). გთხოვთ გაითვალისწინოთ, რომ ანაზღაურება არ ხდება.", + "purchase_now": "ყიდვა ახლა", + "pay_today": "გადახდა დღეს", + "we_are_sorry_to_see_you_go": "ანანთაით ხართ, რომ მიდიხართ :(", + "would_you_mind_shortly_tell_us_what_we_could_have_done_better": "გვისურვებდით მოკლედ გვეცნობრებინათ, რა შეგვეძლო გაგვეკეთებინა უკეთ?", + "cancel_subscription": "გამოწერის გაუქმება", + "plans": "გეგმები", + "monthly": "თვიური", + "yearly": "წლიური", + "reactivate_subscription": "გამოწერის ხელახლა აქტივაცია", + "update_payment_method_invoices_history": "გადახდის მეთოდის განახლება / ინვოისების ისტორია", + "cancel_subscription_1": "გამოწერის გაუქმება", + "your_subscription_will_be_canceled_at": "თქვენი გამოწერა გაუქმდება თარიღზე:", + "you_will_never_be_charged_again": "სხვა აღარ ჩამოგეჭრებათ თანხა", + "current_package": "მიმდინარე პაკეტი:", + "next_package": "შემდეგი პაკეტი:", + "claim": "მოთხოვნა", + "frequently_asked_questions": "ხშირად დასმული კითხვები", + "autopost": "ავტოპოსტი", + "autopost_can_automatically_posts_your_rss_new_items_to_social_media": "ავტოპოსტი ავტომატურად გამოაქვეყნებს თქვენს RSS-ის ახალ ჩანაწერებს სოციალურ მედია არხებზე", + "title": "სათაური", + "add_an_autopost": "ავტოპოსტის დამატება", + "post_content": "პოსტის შინაარსი", + "sign_up": "რეგისტრაცია", + "or": "ან", + "by_registering_you_agree_to_our": "რეგისტრაციით ეთანხმებით ჩვენს", + "and": "და", + "terms_of_service": "გამოყენების პირობებს", + "privacy_policy": "კონფიდენციალურობის პოლიტიკას", + "create_account": "ანგარიშის შექმნა", + "already_have_an_account": "უკვე გაქვთ ანგარიში?", + "sign_in": "შესვლა", + "sign_in_1": "შესვლა", + "don_t_have_an_account": "არ გაქვთ ანგარიში?", + "forgot_password": "დაგავიწყდათ პაროლი", + "forgot_password_1": "პაროლის აღდგენა", + "send_password_reset_email": "პაროლის აღდგენის წერილის გაგზავნა", + "go_back_to_login": "დაბრუნება შესვლაზე", + "we_have_send_you_an_email_with_a_link_to_reset_your_password": "გაგიგზავნეთ ელფოსტა პაროლის აღდგენის ბმულით.", + "change_password": "პაროლის შეცვლა", + "we_successfully_reset_your_password_you_can_now_login_with_your": "პაროლი წარმატებით აღდგა. შეგიძლიათ შეხვიდეთ თქვენი", + "click_here_to_go_back_to_login": "დააჭირეთ აქ შესვლაზე დასაბრუნებლად", + "activate_your_account": "ანგარიშის გააქტიურება", + "thank_you_for_registering": "გმადლობთ რეგისტრაციისთვის!", + "please_check_your_email_to_activate_your_account": "გთხოვთ, გადაამოწმოთ ელფოსტა ანგარიშის გასააქტიურებლად.", + "sign_in_with": "შესვლა შემდეგით", + "continue_with_google": "გაგრძელება Google-ით", + "sign_in_with_github": "შესვლა GitHub-ით", + "continue_with_farcaster": "გაგრძელება Farcaster-ით", + "continue_with_your_wallet": "გაგრძელება საფულით", + "stars_per_day": "ვარსკვლავები დღეში", + "media": "მედია", + "check_launch": "გაშვების გადამოწმება", + "load_your_github_repository_from_settings_to_see_analytics": "ჩატვირთეთ GitHub რეპოზიტორია პარამეტრებიდან ანალიტიკის სანახავად", + "stars": "ვარსკვლავები", + "processing_stars": "ვარსკვლავების დამუშავება...", + "forks": "ფორკები", + "registration_is_disabled": "რეგისტრაცია გათიშულია", + "login_instead": "შემოსვლა", + "gitroom": "Gitroom", + "select_a_conversation_and_chat_away": "აირჩიეთ საუბარი და დაიწყეთ ჩათი", + "adding_channel_redirecting_you": "არხის დამატება... გადამისამართება", + "could_not_add_provider": "პროვაიდერის დამატება ვერ მოხერხდა.", + "you_are_being_redirected_back": "ბრუნდებით უკან", + "we_are_experiencing_some_difficulty_try_to_refresh_the_page": "ამჟამად ჭირს მუშაობა, სცადეთ გვერდის განახლება", + "post_not_found": "პოსტი ვერ მოიძებნა", + "publication_date": "გამოქვეყნების თარიღი:", + "analytics": "ანალიტიკა", + "launches": "გაშვებები", + "plugs": "მოდულები", + "billing": "ბილინგი", + "affiliate": "აფილியேიტი", + "monday": "ორშაბათი", + "tuesday": "სამშაბათი", + "wednesday": "ოთხშაბათი", + "thursday": "ხუთშაბათი", + "friday": "პარასკევი", + "saturday": "შაბათი", + "sunday": "კვირა", + "can_t_change_date_remove_post_from_publication": "თარიღის შეცვლა შეუძლებელია — მოხსენით პოსტი გამოქვეყნებიდან", + "predicted_github_trending_change": "პროგნოზირებული ცვლილება GitHub Trending-ში", + "duplicate_post": "პოსტის დუბლირება", + "preview_post": "პოსტზე წინასწარი გადახედვა", + "post_statistics": "პოსტის სტატისტიკა", + "draft": "მონახაზი", + "week_number": "კვირა {{number}}", + "top_title_edit_webhook": "ვებჰუქის რედაქტირება", + "top_title_add_webhook": "ვებჰუქის დამატება", + "top_title_oh_no": "ო, არა", + "top_title_auto_plug": "ავტომოდული: {{title}}", + "top_title_edit_autopost": "ავტოპოსტის რედაქტირება", + "top_title_add_autopost": "ავტოპოსტის დამატება", + "top_title_send_a_new_offer": "ახალი შეთავაზების გაგზავნა", + "top_title_media_library": "მედიალაიბრარი", + "top_title_add_signature": "ხელმოწერის დამატება", + "top_title_send_a_message_to": "შეტყობინების გაგზავნა: {{name}}", + "top_title_configure_provider": "პროვაიდერის კონფიგურაცია", + "top_title_add_member": "წევრის დამატება", + "top_title_change_bot_picture": "ბოტის სურათის შეცვლა", + "top_title_create_a_new_tag": "ახალი ტეგის შექმნა", + "top_title_select_company": "კომპანიის არჩევა", + "top_title_additional_settings": "დამატებითი პარამეტრები", + "top_title_time_table_slots": "დროის ცხრილი / სლოტები", + "top_title_design_media": "მედიის დიზაინი", + "top_title_edit_post": "პოსტის რედაქტირება", + "top_title_create_post": "ახალი პოსტის შექმნა", + "top_title_move__add_to_customer": "გადატანა / დამატება კლიენტზე", + "top_title_add_api_key_for": "API გასაღები — {{name}}", + "top_title_instance_url": "ინსტანციის URL", + "top_title_custom_url": "მორგებული URL", + "top_title_add_channel": "არხის დამატება", + "top_title_add_telegram": "Telegram-ის დამატება", + "top_title_add_wrapcast": "Wrapcast-ის დამატება", + "top_title_comments_for": "კომენტარები — {{date}}", + "top_title_edit_signature": "ხელმოწერის რედაქტირება", + "label_name": "სახელი", + "label_url": "ბმული", + "label_title": "სათაური", + "label_subtitle": "ქვესათაური", + "label_email": "ელ.ფოსტა", + "label_full_name": "სრული სახელი", + "label_password": "პაროლი", + "label_confirm_password": "პაროლის დადასტურება", + "label_api_key": "API გასაღები", + "label_instance_url": "ინსტანციის URL", + "label_custom_url": "მორგებული URL", + "label_feedback": "უკუკავშირი", + "label_bio": "ბიო", + "label_role": "როლი", + "label_country": "ქვეყანა", + "label_audience_size": "აუდიტორიის ზომა ყველა პლატფორმაზე", + "label_pick_time": "დროის არჩევა", + "label_nickname": "მეტსახელი", + "label_write_anything": "დაწერეთ რაც გსურთ", + "label_output_format": "გამოტანის ფორმატი", + "label_add_pictures": "დავამატოთ სურათები?", + "label_hour": "საათი", + "label_minutes": "წუთი", + "label_select_publication": "აირჩიეთ პუბლიკაცია", + "label_canonical_link": "კანონიკური ბმული", + "label_cover_picture": "ქოვერის სურათი", + "label_tags": "ტეგები", + "label_topics": "თემები", + "label_tags_maximum_4": "ტეგები (მაქს. 4)", + "label_attachments": "დანართები", + "label_type": "ტიპი", + "label_thumbnail": "თამბნეილი", + "label_who_can_see_this_video": "ვის შეუძლია ამ ვიდეოს ნახვა?", + "label_content_posting_method": "კონტენტის გამოქვეყნების მეთოდი", + "label_auto_add_music": "მუსიკის ავტომატური დამატება", + "label_duet": "დუეტი", + "label_stitch": "Stitch", + "label_comments": "კომენტარები", + "label_disclose_video_content": "ვიდეოს შინაარსის გამჟღავნება", + "label_your_brand": "თქვენი ბრენდი", + "label_branded_content": "ბრენდირებული კონტენტი", + "label_subreddit": "Subreddit", + "label_flair": "Flair", + "label_media": "მედია", + "label_search_subreddit": "Subreddit-ის ძიება", + "label_delay": "დაყოვნება", + "label_post_type": "პოსტის ტიპი", + "label_collaborators": "თანაავტორები (მაქს. 3) — ანგარიშები არ უნდა იყოს პრაივეტი", + "label_community": "Community", + "label_search_community": "Community-ის ძიება", + "label_channel": "არხი", + "label_search_channel": "არხის ძიება", + "label_select_channel": "აირჩიეთ არხი", + "label_new_password": "ახალი პაროლი", + "label_repeat_password": "პაროლის გამეორება", + "label_platform": "პლატფორმა", + "label_price_per_post": "ფასი ერთ პოსტზე", + "label_integrations": "ინტეგრაციები", + "label_code": "კოდი", + "label_should_sync_last_post": "გავათანაბროთ თუ არა მიმდინარე ბოლო პოსტი?", + "label_when_post": "როდის დავპოსტოთ?", + "label_autogenerate_content": "კონტენტის ავტომატური გენერირება", + "label_generate_picture": "სურათის გენერირება?", + "label_company": "კომპანია", + "label_tag_color": "ტეგის ფერი", + "label_select_board": "ბორდის არჩევა", + "label_select_organization": "ორგანიზაციის არჩევა", + "label_auto_add_signature": "ავტომატურად დავამატოთ ხელმოწერა?", + "enable_color_picker": "ფერების ამრჩევის ჩართვა", + "cancel_the_color_picker": "ფერის ამრჩევის გაუქმება", + "no_content_yet": "კონტენტი ჯერჯერობით არ არის", + "write_your_reply": "დაწერეთ თქვენი პოსტი...", + "add_a_tag": "ტეგის დამატება", + "add_to_calendar": "კალენდარში დამატება", + "select_channels_from_circles": "აირჩიეთ არხები ზემოთ არსებულ წრეებიდან", + "not_matching_order": "შეკვეთას არ ემთხვევა", + "submit_for_order": "გაგზავნა შეკვეთაზე", + "schedule": "დაგეგმვა", + "update": "განახლება", + "attachments": "დანართები", + "tags": "ტეგები", + "public_to_everyone": "ხილულია ყველასთვის", + "mutual_follow_friends": "ურთიერთჩამომყოლები", + "follower_of_creator": "შემქმნელის გამომწერები", + "self_only": "მხოლოდ მე", + "post_content_directly_to_tiktok": "კონტენტის პირდაპირ გამოქვეყნება TikTok-ში", + "upload_content_to_tiktok_without_posting": "კონტენტის ატვირთვა TikTok-ში გამოქვეყნების გარეშე", + "choose_upload_without_posting_description": "აირჩიეთ ატვირთვა გამოქვეყნების გარეშე, თუ გსურთ მასალის გადამოწმება/რედაქტირება TikTok-ის აპში გამოქვეყნებამდე.", + "faq_am_i_going_to_be_charged_by_postiz": "Postiz დამაკისრებს თანხას?", + "faq_to_confirm_credit_card_information_postiz_will_hold": "საკრედიტო ბარათის დადასტურებისთვის Postiz დროებით დაბლოკავს $2-ს და მაშინვე გაათავისუფლებს", + "faq_can_i_trust_postiz": "შემიძლია ვენდო Postiz-ს?", + "faq_postiz_gitroom_is_proudly_open_source": "Postiz არის ღია კოდის! გამჭვირვალე კულტურა — შეგიძლიათ ნახოთ მთელი კოდი ან გამოიყენოთ პირადი პროექტებისთვის. ღია რეპოზიტორიის სანახავად დააჭირეთ აქ.", + "faq_what_are_channels": "რა არის არხები?", + "faq_postiz_gitroom_allows_you_to_schedule_posts": "Postiz გაძლევთ პოსტების დაგეგმვის საშუალებას სხვადასხვა არხებზე — მაგალითად X, Facebook, Instagram, TikTok, YouTube, Reddit, LinkedIn, Dribbble, Threads და Pinterest.", + "faq_what_are_team_members": "რა არის გუნდის წევრები?", + "faq_if_you_have_a_team_with_multiple_members": "თუ თქვენ ჰყავთ გუნდი რამდენიმე წევრით, შეგიძლიათ მოიწვიოთ ისინი workspace-ში, ითანამშრომლოთ პოსტებზე და დაამატონ თავიანთი არხები", + "faq_what_is_ai_auto_complete": "რა არის AI auto-complete?", + "faq_we_automate_chatgpt_to_help_you_write": "ვავტომატებთ ChatGPT-ს, რომ დაგეხმაროთ სოციალური პოსტებისა და სტატიათა წერაში.", + "enter_email": "შეიტანეთ ელფოსტა", + "are_you_sure": "დარწმუნებული ხართ?", + "yes_delete_it": "კი, წაშალე!", + "no_cancel": "არა, გაუქმება!", + "are_you_sure_you_want_to_delete": "ნამდვილად გსურთ წაშალოთ {{name}}?", + "are_you_sure_you_want_to_delete_the_image": "ნამდვილად გსურთ სურათის წაშლა?", + "are_you_sure_you_want_to_logout": "ნამდვილად გსურთ გასვლა?", + "yes_logout": "კი, გამოსვლა", + "are_you_sure_you_want_to_delete_this_slot": "წავშალოთ ეს სლოტი?", + "are_you_sure_you_want_to_delete_this_subreddit": "წავშალოთ ეს Subreddit?", + "are_you_sure_you_want_to_close_the_window": "დავხუროთ ფანჯარა?", + "yes_close": "კი, დახურე", + "link_copied_to_clipboard": "ბმული დაკოპირდა", + "are_you_sure_you_want_to_close_this_modal_all_data_will_be_lost": "დავხუროთ ეს ფანჯარა? (ყველა მონაცემი დაიკარგება)", + "yes_close_it": "კი, დახურე!", + "uploading_pictures": "სურათების ატვირთვა...", + "agent_starting": "აგენტის გაშვება", + "researching_your_content": "თქვენი კონტენტის კვლევა...", + "understanding_the_category": "კატეგორიის გააზრება...", + "finding_the_topic": "თემის ძიება...", + "finding_popular_posts_to_match_with": "პოპულარული პოსტების მოპოვება შესატყვისად...", + "generating_hook": "ჰუკის გენერირება...", + "generating_content": "კონტენტის გენერირება...", + "generating_pictures": "სურათების გენერირება...", + "finding_time_to_post": "საუკეთესო დროის პოვნა...", + "write_anything": "დაწერეთ რაც გსურთ", + "you_can_write_anything_you_want_and_also_add_links_we_will_do_the_research_for_you": "დაწერეთ რაც გინდათ და დაამატეთ ბმულები — კვლევას ჩვენ მოვახდენთ", + "output_format": "გამოტანის ფორმატი", + "add_pictures": "სურათების დამატება?", + "7_days": "7 დღე", + "30_days": "30 დღე", + "90_days": "90 დღე", + "start_7_days_free_trial": "დაიწყე 7-დღიანი უფასო პერიოდი", + "change_language": "ენის შეცვლა", + "that_a_wrap": "დასრულებულია!\n\nთუ მოგეწონა ეს თრედი:\n\n1. გამომყევი @{{username}}\n2. გააზიარეთ ქვემოთ არსებული პოსტი", + "post_as_images_carousel": "გამოქვეყნება სურათების კარუსელად", + "save_set": "სეტის შენახვა", + "separate_post": "თრედის დაყოფა რამდენიმე პოსტად", + "label_who_can_reply_to_this_post": "ვის შეუძლია პასუხის გაცემა ამ პოსტზე?", + "delete_integration": "ინტეგრაციის წაშლა", + "start_writing_your_post": "დაიწყეთ თქვენი პოსტის წერა წინასწარი ხედვისთვის" +} From e2b407cac865a3e1486d4c397bb2e42ad81b5ce8 Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Mon, 8 Sep 2025 22:50:48 +0200 Subject: [PATCH 29/48] DartNode --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 91350317..86290d91 100644 --- a/README.md +++ b/README.md @@ -135,3 +135,5 @@ This repository's source code is available under the [AGPL-3.0 license](LICENSE)

g2

+ +[![Powered by DartNode](https://dartnode.com/branding/DN-Open-Source-sm.png)](https://dartnode.com "Powered by DartNode - Free VPS for Open Source") \ No newline at end of file From 18086edd9f8da2de5ffd69296102a1f7d0f73f45 Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Tue, 9 Sep 2025 07:51:37 +0200 Subject: [PATCH 30/48] remove Dartnode --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 86290d91..91350317 100644 --- a/README.md +++ b/README.md @@ -135,5 +135,3 @@ This repository's source code is available under the [AGPL-3.0 license](LICENSE)

g2

- -[![Powered by DartNode](https://dartnode.com/branding/DN-Open-Source-sm.png)](https://dartnode.com "Powered by DartNode - Free VPS for Open Source") \ No newline at end of file From 43c3af4e35dcf7f1d61d73e15f26002332d83449 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Sun, 14 Sep 2025 14:42:12 +0700 Subject: [PATCH 31/48] feat: proxy video response --- .../src/api/routes/public.controller.ts | 58 ++++++++++++++++++- apps/backend/src/mcp/main.mcp.ts | 2 +- .../helpers/media.settings.component.tsx | 6 +- .../src/upload/cloudflare.storage.ts | 49 +++++++++------- .../src/upload/local.storage.ts | 51 ++++++++-------- 5 files changed, 117 insertions(+), 49 deletions(-) diff --git a/apps/backend/src/api/routes/public.controller.ts b/apps/backend/src/api/routes/public.controller.ts index 66238060..3137fc2e 100644 --- a/apps/backend/src/api/routes/public.controller.ts +++ b/apps/backend/src/api/routes/public.controller.ts @@ -1,4 +1,14 @@ -import { Body, Controller, Get, Param, Post, Req, Res } from '@nestjs/common'; +import { + Body, + Controller, + Get, + Param, + Post, + Query, + Req, + Res, + StreamableFile, +} from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { AgenciesService } from '@gitroom/nestjs-libraries/database/prisma/agencies/agencies.service'; import { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service'; @@ -11,6 +21,10 @@ import { makeId } from '@gitroom/nestjs-libraries/services/make.is'; import { getCookieUrlFromDomain } from '@gitroom/helpers/subdomain/subdomain.management'; import { AgentGraphInsertService } from '@gitroom/nestjs-libraries/agent/agent.graph.insert.service'; import { Nowpayments } from '@gitroom/nestjs-libraries/crypto/nowpayments'; +import { Readable, pipeline } from 'stream'; +import { promisify } from 'util'; + +const pump = promisify(pipeline); @ApiTags('Public') @Controller('/public') @@ -136,4 +150,46 @@ export class PublicController { console.log('cryptoPost', body, path); return this._nowpayments.processPayment(path, body); } + + @Get('/stream') + async streamFile( + @Query('url') url: string, + @Res() res: Response, + @Req() req: Request + ) { + if (!url.endsWith('mp4')) { + return res.status(400).send('Invalid video URL'); + } + + const ac = new AbortController(); + const onClose = () => ac.abort(); + req.on('aborted', onClose); + res.on('close', onClose); + + const r = await fetch(url, { signal: ac.signal }); + + if (!r.ok && r.status !== 206) { + res.status(r.status); + throw new Error(`Upstream error: ${r.statusText}`); + } + + const type = r.headers.get('content-type') ?? 'application/octet-stream'; + res.setHeader('Content-Type', type); + + const contentRange = r.headers.get('content-range'); + if (contentRange) res.setHeader('Content-Range', contentRange); + + const len = r.headers.get('content-length'); + if (len) res.setHeader('Content-Length', len); + + const acceptRanges = r.headers.get('accept-ranges') ?? 'bytes'; + res.setHeader('Accept-Ranges', acceptRanges); + + if (r.status === 206) res.status(206); // Partial Content for range responses + + try { + await pump(Readable.fromWeb(r.body as any), res); + } catch (err) { + } + } } diff --git a/apps/backend/src/mcp/main.mcp.ts b/apps/backend/src/mcp/main.mcp.ts index 483ae851..83a05ade 100644 --- a/apps/backend/src/mcp/main.mcp.ts +++ b/apps/backend/src/mcp/main.mcp.ts @@ -50,7 +50,7 @@ export class MainMcp { @McpTool({ toolName: 'POSTIZ_SCHEDULE_POST', zod: { - type: eenum(['draft', 'scheduled']), + type: eenum(['draft', 'schedule']), configId: string(), generatePictures: boolean(), date: string().describe('UTC TIME'), 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 3cf774d1..2a0531e8 100644 --- a/apps/frontend/src/components/launches/helpers/media.settings.component.tsx +++ b/apps/frontend/src/components/launches/helpers/media.settings.component.tsx @@ -5,6 +5,7 @@ import React, { FC, useCallback, useEffect, useRef, useState } from 'react'; import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component'; import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store'; +import { useVariables } from '@gitroom/react/helpers/variable.context'; const postUrlEmitter = new EventEmitter(); export const MediaSettingsLayout = () => { @@ -97,7 +98,8 @@ export const CreateThumbnail: FC<{ altText?: string; onAltTextChange?: (altText: string) => void; }> = (props) => { - const { onSelect, media, altText, onAltTextChange } = props; + const { onSelect, media } = props; + const { backendUrl } = useVariables(); const videoRef = useRef(null); const canvasRef = useRef(null); const [currentTime, setCurrentTime] = useState(0); @@ -211,7 +213,7 @@ export const CreateThumbnail: FC<{