From d8a6215155050224bbaf644fc6037cae947d0d8a Mon Sep 17 00:00:00 2001 From: Nevo David Date: Mon, 5 Jan 2026 11:35:15 +0700 Subject: [PATCH 1/3] feat: before split --- apps/backend/src/app.module.ts | 4 + apps/orchestrator/.gitignore | 8 + apps/orchestrator/.swcrc | 38 ++ apps/orchestrator/nest-cli.json | 20 + apps/orchestrator/package.json | 14 + .../src/activities/post.activity.ts | 249 ++++++++++ apps/orchestrator/src/app.module.ts | 22 + apps/orchestrator/src/main.ts | 15 + apps/orchestrator/src/workflows/index.ts | 1 + .../src/workflows/post.workflow.ts | 331 +++++++++++++ apps/orchestrator/tsconfig.build.json | 23 + apps/orchestrator/tsconfig.json | 11 + apps/workers/src/app/plugs.controller.ts | 46 -- apps/workers/src/app/posts.controller.ts | 22 - .../helpers/src/utils/concurrency.service.ts | 6 +- .../integrations/integration.service.ts | 75 +-- .../database/prisma/posts/posts.repository.ts | 26 + .../database/prisma/posts/posts.service.ts | 454 ++++++------------ .../src/integrations/social.abstract.ts | 67 +-- .../integrations/social/linkedin.provider.ts | 15 +- .../social/social.integrations.interface.ts | 19 +- .../integrations/social/tiktok.provider.ts | 1 - .../src/temporal/temporal.module.ts | 28 ++ .../src/temporal/temporal.register.ts | 50 ++ .../src/temporal/temporal.search.attribute.ts | 14 + package.json | 10 +- pnpm-lock.yaml | 424 ++++++++++++++++ tsconfig.base.json | 1 + 28 files changed, 1518 insertions(+), 476 deletions(-) create mode 100644 apps/orchestrator/.gitignore create mode 100644 apps/orchestrator/.swcrc create mode 100644 apps/orchestrator/nest-cli.json create mode 100644 apps/orchestrator/package.json create mode 100644 apps/orchestrator/src/activities/post.activity.ts create mode 100644 apps/orchestrator/src/app.module.ts create mode 100644 apps/orchestrator/src/main.ts create mode 100644 apps/orchestrator/src/workflows/index.ts create mode 100644 apps/orchestrator/src/workflows/post.workflow.ts create mode 100644 apps/orchestrator/tsconfig.build.json create mode 100644 apps/orchestrator/tsconfig.json delete mode 100644 apps/workers/src/app/plugs.controller.ts create mode 100644 libraries/nestjs-libraries/src/temporal/temporal.module.ts create mode 100644 libraries/nestjs-libraries/src/temporal/temporal.register.ts create mode 100644 libraries/nestjs-libraries/src/temporal/temporal.search.attribute.ts diff --git a/apps/backend/src/app.module.ts b/apps/backend/src/app.module.ts index fb6d7bd4..d4f800d4 100644 --- a/apps/backend/src/app.module.ts +++ b/apps/backend/src/app.module.ts @@ -13,6 +13,8 @@ import { VideoModule } from '@gitroom/nestjs-libraries/videos/video.module'; import { SentryModule } from '@sentry/nestjs/setup'; import { FILTER } from '@gitroom/nestjs-libraries/sentry/sentry.exception'; import { ChatModule } from '@gitroom/nestjs-libraries/chat/chat.module'; +import { getTemporalModule } from '@gitroom/nestjs-libraries/temporal/temporal.module'; +import { TemporalRegisterMissingSearchAttributesModule } from '@gitroom/nestjs-libraries/temporal/temporal.register'; @Global() @Module({ @@ -26,6 +28,8 @@ import { ChatModule } from '@gitroom/nestjs-libraries/chat/chat.module'; ThirdPartyModule, VideoModule, ChatModule, + getTemporalModule(false), + TemporalRegisterMissingSearchAttributesModule, ThrottlerModule.forRoot([ { ttl: 3600000, diff --git a/apps/orchestrator/.gitignore b/apps/orchestrator/.gitignore new file mode 100644 index 00000000..0dff6fb6 --- /dev/null +++ b/apps/orchestrator/.gitignore @@ -0,0 +1,8 @@ +dist/ +node_modules/ +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + diff --git a/apps/orchestrator/.swcrc b/apps/orchestrator/.swcrc new file mode 100644 index 00000000..7d41ef14 --- /dev/null +++ b/apps/orchestrator/.swcrc @@ -0,0 +1,38 @@ +{ + "jsc": { + "parser": { + "syntax": "typescript", + "tsx": false, + "decorators": true, + "dynamicImport": true + }, + "target": "es2020", + "baseUrl": "/Users/nevodavid/Projects/gitroom", + "paths": { + "@gitroom/backend/*": ["apps/backend/src/*"], + "@gitroom/cron/*": ["apps/cron/src/*"], + "@gitroom/frontend/*": ["apps/frontend/src/*"], + "@gitroom/helpers/*": ["libraries/helpers/src/*"], + "@gitroom/nestjs-libraries/*": ["libraries/nestjs-libraries/src/*"], + "@gitroom/react/*": ["libraries/react-shared-libraries/src/*"], + "@gitroom/plugins/*": ["libraries/plugins/src/*"], + "@gitroom/workers/*": ["apps/workers/src/*"], + "@gitroom/extension/*": ["apps/extension/src/*"] + }, + "keepClassNames": true, + "transform": { + "legacyDecorator": true, + "decoratorMetadata": true + }, + "loose": true + }, + "module": { + "type": "commonjs", + "strict": false, + "strictMode": true, + "lazy": false, + "noInterop": false + }, + "sourceMaps": true, + "minify": false +} \ No newline at end of file diff --git a/apps/orchestrator/nest-cli.json b/apps/orchestrator/nest-cli.json new file mode 100644 index 00000000..aef73dc1 --- /dev/null +++ b/apps/orchestrator/nest-cli.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "monorepo": false, + "sourceRoot": "src", + "entryFile": "../../dist/orchestrator/apps/orchestrator/src/main", + "language": "ts", + "generateOptions": { + "spec": false + }, + "compilerOptions": { + "manualRestart": true, + "tsConfigPath": "./tsconfig.build.json", + "webpack": false, + "deleteOutDir": true, + "assets": [], + "watchAssets": false, + "plugins": [] + } +} diff --git a/apps/orchestrator/package.json b/apps/orchestrator/package.json new file mode 100644 index 00000000..daa427f2 --- /dev/null +++ b/apps/orchestrator/package.json @@ -0,0 +1,14 @@ +{ + "name": "postiz-orchestrator", + "version": "1.0.0", + "description": "", + "scripts": { + "dev": "dotenv -e ../../.env -- nest start --watch --entryFile=./apps/orchestrator/src/main", + "build": "cross-env NODE_ENV=production nest build", + "start": "dotenv -e ../../.env -- node --experimental-require-module ./dist/apps/orchestrator/src/main.js", + "pm2": "pm2 start pnpm --name orchestrator -- start" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/apps/orchestrator/src/activities/post.activity.ts b/apps/orchestrator/src/activities/post.activity.ts new file mode 100644 index 00000000..88fdcef0 --- /dev/null +++ b/apps/orchestrator/src/activities/post.activity.ts @@ -0,0 +1,249 @@ +import { Injectable } from '@nestjs/common'; +import { Activity, ActivityMethod } from 'nestjs-temporal-core'; +import { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service'; +import { + NotificationService, + NotificationType, +} from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service'; +import { Integration, Post, State } from '@prisma/client'; +import { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation'; +import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager'; +import { AuthTokenDetails } from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface'; +import { RefreshIntegrationService } from '@gitroom/nestjs-libraries/integrations/refresh.integration.service'; +import { timer } from '@gitroom/helpers/utils/timer'; +import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service'; +import { WebhooksService } from '@gitroom/nestjs-libraries/database/prisma/webhooks/webhooks.service'; + +@Injectable() +@Activity() +export class PostActivity { + constructor( + private _postService: PostsService, + private _notificationService: NotificationService, + private _integrationManager: IntegrationManager, + private _integrationService: IntegrationService, + private _refreshIntegrationService: RefreshIntegrationService, + private _webhookService: WebhooksService + ) {} + + @ActivityMethod() + async updatePost(id: string, postId: string, releaseURL: string) { + return this._postService.updatePost(id, postId, releaseURL); + } + + @ActivityMethod() + async getPostsList(orgId: string, postId: string) { + return this._postService.getPostsRecursively(postId, true, orgId); + } + + @ActivityMethod() + async isCommentable(integration: Integration) { + const getIntegration = this._integrationManager.getSocialIntegration( + integration.providerIdentifier + ); + + return !!getIntegration.comment; + } + + @ActivityMethod() + async postComment( + postId: string, + lastPostId: string | undefined, + integration: Integration, + posts: Post[] + ) { + const getIntegration = this._integrationManager.getSocialIntegration( + integration.providerIdentifier + ); + + const newPosts = await this._postService.updateTags( + integration.organizationId, + posts + ); + + return getIntegration.comment( + integration.internalId, + postId, + lastPostId, + integration.token, + await Promise.all( + (newPosts || []).map(async (p) => ({ + id: p.id, + message: stripHtmlValidation( + getIntegration.editor, + p.content, + true, + false, + !/<\/?[a-z][\s\S]*>/i.test(p.content), + getIntegration.mentionFormat + ), + settings: JSON.parse(p.settings || '{}'), + media: await this._postService.updateMedia( + p.id, + JSON.parse(p.image || '[]'), + getIntegration?.convertToJPEG || false + ), + })) + ), + integration + ); + } + + @ActivityMethod() + async postSocial(integration: Integration, posts: Post[]) { + const getIntegration = this._integrationManager.getSocialIntegration( + integration.providerIdentifier + ); + + const newPosts = await this._postService.updateTags( + integration.organizationId, + posts + ); + + return getIntegration.post( + integration.internalId, + integration.token, + await Promise.all( + (newPosts || []).map(async (p) => ({ + id: p.id, + message: stripHtmlValidation( + getIntegration.editor, + p.content, + true, + false, + !/<\/?[a-z][\s\S]*>/i.test(p.content), + getIntegration.mentionFormat + ), + settings: JSON.parse(p.settings || '{}'), + media: await this._postService.updateMedia( + p.id, + JSON.parse(p.image || '[]'), + getIntegration?.convertToJPEG || false + ), + })) + ), + integration + ); + } + + @ActivityMethod() + async inAppNotification( + orgId: string, + subject: string, + message: string, + sendEmail = false, + digest = false, + type: NotificationType = 'success' + ) { + return this._notificationService.inAppNotification( + orgId, + subject, + message, + sendEmail, + digest, + type + ); + } + + @ActivityMethod() + async globalPlugs(integration: Integration) { + return this._postService.checkPlugs( + integration.organizationId, + integration.providerIdentifier, + integration.id + ); + } + + @ActivityMethod() + async changeState(id: string, state: State, err?: any, body?: any) { + return this._postService.changeState(id, state, err, body); + } + + @ActivityMethod() + async internalPlugs(integration: Integration, settings: any) { + return this._postService.checkInternalPlug( + integration, + integration.organizationId, + integration.id, + settings + ); + } + + @ActivityMethod() + async sendWebhooks(postId: string, orgId: string, integrationId: string) { + const webhooks = (await this._webhookService.getWebhooks(orgId)).filter( + (f) => { + return ( + f.integrations.length === 0 || + f.integrations.some((i) => i.integration.id === integrationId) + ); + } + ); + + const post = await this._postService.getPostByForWebhookId(postId); + return Promise.all( + webhooks.map(async (webhook) => { + try { + await fetch(webhook.url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(post), + }); + } catch (e) { + /**empty**/ + } + }) + ); + } + @ActivityMethod() + async processPlug(data: { + plugId: string; + postId: string; + delay: number; + totalRuns: number; + currentRun: number; + }) { + return this._integrationService.processPlugs(data); + } + + @ActivityMethod() + async processInternalPlug(data: { + post: string; + originalIntegration: string; + integration: string; + plugName: string; + orgId: string; + delay: number; + information: any; + }) { + return this._integrationService.processInternalPlug(data); + } + + @ActivityMethod() + async refreshToken( + integration: Integration + ): Promise { + const getIntegration = this._integrationManager.getSocialIntegration( + integration.providerIdentifier + ); + + try { + const refresh = await this._refreshIntegrationService.refresh( + integration + ); + if (!refresh) { + return false; + } + + if (getIntegration.refreshWait) { + await timer(10000); + } + + return refresh; + } catch (err) { + return false; + } + } +} diff --git a/apps/orchestrator/src/app.module.ts b/apps/orchestrator/src/app.module.ts new file mode 100644 index 00000000..cf5115cb --- /dev/null +++ b/apps/orchestrator/src/app.module.ts @@ -0,0 +1,22 @@ +import { Module } from '@nestjs/common'; +import { PostActivity } from '@gitroom/orchestrator/activities/post.activity'; +import { getTemporalModule } from '@gitroom/nestjs-libraries/temporal/temporal.module'; +import { DatabaseModule } from '@gitroom/nestjs-libraries/database/prisma/database.module'; +import { BullMqModule } from '@gitroom/nestjs-libraries/bull-mq-transport-new/bull.mq.module'; + +const activities = [ + PostActivity, +]; +@Module({ + imports: [ + BullMqModule, + DatabaseModule, + getTemporalModule(true, require.resolve('./workflows'), activities), + ], + controllers: [], + providers: [...activities], + get exports() { + return [...this.providers, ...this.imports]; + }, +}) +export class AppModule {} diff --git a/apps/orchestrator/src/main.ts b/apps/orchestrator/src/main.ts new file mode 100644 index 00000000..03bf80a2 --- /dev/null +++ b/apps/orchestrator/src/main.ts @@ -0,0 +1,15 @@ +import 'source-map-support/register'; +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +dayjs.extend(utc); + +import { NestFactory } from '@nestjs/core'; +import { AppModule } from '@gitroom/orchestrator/app.module'; + +async function bootstrap() { + // some comment again + const app = await NestFactory.createApplicationContext(AppModule); + app.enableShutdownHooks(); +} + +bootstrap(); diff --git a/apps/orchestrator/src/workflows/index.ts b/apps/orchestrator/src/workflows/index.ts new file mode 100644 index 00000000..a64cfc6e --- /dev/null +++ b/apps/orchestrator/src/workflows/index.ts @@ -0,0 +1 @@ +export * from './post.workflow'; diff --git a/apps/orchestrator/src/workflows/post.workflow.ts b/apps/orchestrator/src/workflows/post.workflow.ts new file mode 100644 index 00000000..aef3e4a1 --- /dev/null +++ b/apps/orchestrator/src/workflows/post.workflow.ts @@ -0,0 +1,331 @@ +import { PostActivity } from '@gitroom/orchestrator/activities/post.activity'; +import { + ActivityFailure, + ApplicationFailure, + startChild, + proxyActivities, + sleep, +} from '@temporalio/workflow'; +import dayjs from 'dayjs'; +import { Integration } from '@prisma/client'; +import { capitalize, sortBy } from 'lodash'; +import { PostResponse } from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface'; +import { makeId } from '@gitroom/nestjs-libraries/services/make.is'; +import { TypedSearchAttributes } from '@temporalio/common'; +import { postId as postIdSearchParam } from '@gitroom/nestjs-libraries/temporal/temporal.search.attribute'; + +const { + getPostsList, + inAppNotification, + postSocial, + postComment, + refreshToken, + internalPlugs, + changeState, + globalPlugs, + updatePost, + processInternalPlug, + processPlug, + sendWebhooks, + isCommentable, +} = proxyActivities({ + startToCloseTimeout: '10 minute', + retry: { + maximumAttempts: 3, + backoffCoefficient: 1, + initialInterval: '2 minutes', + }, +}); + +export async function postWorkflow({ + postId, + organizationId, + postNow = false, +}: { + postId: string; + organizationId: string; + postNow?: boolean; +}) { + const startTime = new Date(); + // get all the posts and comments to post + const postsList = await getPostsList(organizationId, postId); + const [post] = postsList; + + // in case doesn't exists for some reason, fail it + if (!post) { + return; + } + + // if it's a repeatable post, we should ignore this + if (!postNow) { + await sleep(dayjs(post.publishDate).diff(dayjs(), 'millisecond')); + } + + // if refresh is needed from last time, let's inform the user + if (post.integration?.refreshNeeded) { + await inAppNotification( + post.organizationId, + `We couldn't post to ${post.integration?.providerIdentifier} for ${post?.integration?.name}`, + `We couldn't post to ${post.integration?.providerIdentifier} for ${post?.integration?.name} because you need to reconnect it. Please enable it and try again.`, + true, + false, + 'info' + ); + return; + } + + // if it's disabled, inform the user + if (post.integration?.disabled) { + await inAppNotification( + post.organizationId, + `We couldn't post to ${post.integration?.providerIdentifier} for ${post?.integration?.name}`, + `We couldn't post to ${post.integration?.providerIdentifier} for ${post?.integration?.name} because it's disabled. Please enable it and try again.`, + true, + false, + 'info' + ); + return; + } + + // Do we need to post comment for this social? + const toComment = + postsList.length === 1 ? false : await isCommentable(post.integration); + + // list of all the saved results + const postsResults: PostResponse[] = []; + + // iterate over the posts + for (let i = 0; i < postsList.length; i++) { + // this is a small trick to repeat an action in case of token refresh + while (true) { + try { + // first post the main post + if (i === 0) { + postsResults.push( + ...(await postSocial(post.integration as Integration, [ + postsList[i], + ])) + ); + + // then post the comments if any + } else { + if (!toComment) { + break; + } + postsResults.push( + ...(await postComment( + postsResults[0].postId, + postsResults.length === 1 ? undefined : postsResults[i - 1].id, + post.integration, + [postsList[i]] + )) + ); + } + + // mark post as successful + await updatePost( + postsList[i].id, + postsResults[i].postId, + postsResults[i].releaseURL + ); + + // break the current while to move to the next post + break; + } catch (err) { + // if token refresh is needed, do it and repeat + if ( + err instanceof ActivityFailure && + err.cause instanceof ApplicationFailure && + err.cause.type === 'refresh_token' + ) { + const refresh = await refreshToken(post.integration); + if (!refresh) { + return false; + } + + post.integration.token = refresh.accessToken; + continue; + } + + // for other errors, change state and inform the user if needed + await changeState(postsList[0].id, 'ERROR', err, postsList); + + // specific case for bad body errors + if ( + err instanceof ActivityFailure && + err.cause instanceof ApplicationFailure && + err.cause.type === 'bad_body' + ) { + await inAppNotification( + post.organizationId, + `Error posting on ${post.integration?.providerIdentifier} for ${post?.integration?.name}`, + `An error occurred while posting on ${ + post.integration?.providerIdentifier + }${err?.message ? `: ${err?.message}` : ``}`, + true, + false, + 'fail' + ); + return false; + } + + return false; + } + } + } + + // send notification on a sucessful post + await inAppNotification( + post.integration.organizationId, + `Your post has been published on ${capitalize( + post.integration.providerIdentifier + )}`, + `Your post has been published on ${capitalize( + post.integration.providerIdentifier + )} at ${postsResults[0].releaseURL}`, + true, + true + ); + + // send webhooks for the post + await sendWebhooks( + postsResults[0].postId, + post.organizationId, + post.integration.id + ); + + // load internal plugs like repost by other users + const internalPlugsList = await internalPlugs( + post.integration, + JSON.parse(post.settings) + ); + + // load global plugs, like repost a post if it gets to a certain number of likes + const globalPlugsList = (await globalPlugs(post.integration)).reduce( + (all, current) => { + for (let i = 1; i <= current.totalRuns; i++) { + all.push({ + ...current, + delay: current.delay * i, + }); + } + + return all; + }, + [] + ); + + // Check if the post is repeatable + const repeatPost = !post.intervalInDays + ? [] + : [ + { + type: 'repeat-post', + delay: + post.intervalInDays * 24 * 60 * 60 * 1000 - + (new Date().getTime() - startTime.getTime()), + }, + ]; + + // Sort all the actions by delay, so we can process them in order + const list = sortBy( + [...internalPlugsList, ...globalPlugsList, ...repeatPost], + 'delay' + ); + + // process all the plugs in order, we are using while because in some cases we need to remove items from the list + while (list.length > 0) { + // get the next to process + const todo = list.shift(); + + // wait for the delay + await sleep(todo.delay); + + // process internal plug + if (todo.type === 'internal-plug') { + while (true) { + try { + await processInternalPlug({ ...todo, post: postsResults[0].postId }); + } catch (err) { + if ( + err instanceof ActivityFailure && + err.cause instanceof ApplicationFailure && + err.cause.type === 'refresh_token' + ) { + const refresh = await refreshToken(post.integration); + if (!refresh) { + return false; + } + + post.integration.token = refresh.accessToken; + continue; + } + } + break; + } + } + + // process global plug + if (todo.type === 'global') { + while (true) { + try { + const process = await processPlug({ + ...todo, + postId: postsResults[0].postId, + }); + if (process) { + const toDelete = list + .reduce((all, current, index) => { + if (current.plugId === todo.plugId) { + all.push(index); + } + + return all; + }, []) + .reverse(); + + for (const index of toDelete) { + list.splice(index, 1); + } + } + } catch (err) { + if ( + err instanceof ActivityFailure && + err.cause instanceof ApplicationFailure && + err.cause.type === 'refresh_token' + ) { + const refresh = await refreshToken(post.integration); + if (!refresh) { + return false; + } + + post.integration.token = refresh.accessToken; + continue; + } + } + break; + } + } + + // process repeat post in a new workflow, this is important so the other plugs can keep running + if (todo.type === 'repeat-post') { + await startChild(postWorkflow, { + parentClosePolicy: 'ABANDON', + args: [ + { + postId, + organizationId, + postNow: true, + }, + ], + workflowId: `post_${post.id}_${makeId(10)}`, + typedSearchAttributes: new TypedSearchAttributes([ + { + key: postIdSearchParam, + value: postId, + }, + ]), + }); + } + } +} diff --git a/apps/orchestrator/tsconfig.build.json b/apps/orchestrator/tsconfig.build.json new file mode 100644 index 00000000..bf14cec5 --- /dev/null +++ b/apps/orchestrator/tsconfig.build.json @@ -0,0 +1,23 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"], + "compilerOptions": { + "module": "CommonJS", + "resolveJsonModule": true, + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false, + "outDir": "./dist" + } +} diff --git a/apps/orchestrator/tsconfig.json b/apps/orchestrator/tsconfig.json new file mode 100644 index 00000000..d2cc80fd --- /dev/null +++ b/apps/orchestrator/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "allowSyntheticDefaultImports": true, + "target": "es2017", + "sourceMap": true + } +} diff --git a/apps/workers/src/app/plugs.controller.ts b/apps/workers/src/app/plugs.controller.ts deleted file mode 100644 index a7d1134b..00000000 --- a/apps/workers/src/app/plugs.controller.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Controller } from '@nestjs/common'; -import { EventPattern, Transport } from '@nestjs/microservices'; -import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service'; - -@Controller() -export class PlugsController { - constructor(private _integrationService: IntegrationService) {} - - @EventPattern('plugs', Transport.REDIS) - async plug(data: { - plugId: string; - postId: string; - delay: number; - totalRuns: number; - currentRun: number; - }) { - try { - return await this._integrationService.processPlugs(data); - } catch (err) { - console.log( - "Unhandled error, let's avoid crashing the plug worker", - err - ); - } - } - - @EventPattern('internal-plugs', Transport.REDIS) - async internalPlug(data: { - post: string; - originalIntegration: string; - integration: string; - plugName: string; - orgId: string; - delay: number; - information: any; - }) { - try { - return await this._integrationService.processInternalPlug(data); - } catch (err) { - console.log( - "Unhandled error, let's avoid crashing the internal plugs worker", - err - ); - } - } -} diff --git a/apps/workers/src/app/posts.controller.ts b/apps/workers/src/app/posts.controller.ts index f41b77fc..2bfbaeb8 100644 --- a/apps/workers/src/app/posts.controller.ts +++ b/apps/workers/src/app/posts.controller.ts @@ -12,28 +12,6 @@ export class PostsController { private _autopostsService: AutopostService ) {} - @EventPattern('post', Transport.REDIS) - async post(data: { id: string }) { - console.log('processing', data); - try { - return await this._postsService.post(data.id); - } catch (err) { - console.log("Unhandled error, let's avoid crashing the post worker", err); - } - } - - @EventPattern('submit', Transport.REDIS) - async payout(data: { id: string; releaseURL: string }) { - try { - return await this._postsService.payout(data.id, data.releaseURL); - } catch (err) { - console.log( - "Unhandled error, let's avoid crashing the submit worker", - err - ); - } - } - @EventPattern('sendDigestEmail', Transport.REDIS) async sendDigestEmail(data: { subject: string; org: string; since: string }) { try { diff --git a/libraries/helpers/src/utils/concurrency.service.ts b/libraries/helpers/src/utils/concurrency.service.ts index 4f241493..bd2c02c1 100644 --- a/libraries/helpers/src/utils/concurrency.service.ts +++ b/libraries/helpers/src/utils/concurrency.service.ts @@ -1,6 +1,5 @@ import { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service'; import Bottleneck from 'bottleneck'; -import { timer } from '@gitroom/helpers/utils/timer'; import { BadBody } from '@gitroom/nestjs-libraries/integrations/social.abstract'; const connection = new Bottleneck.IORedisConnection({ @@ -35,11 +34,12 @@ export const concurrency = async ( async () => { try { return await func(); - } catch (err) {} + } catch (err) { + console.log(err); + } } ); } catch (err) { - console.log(err); throw new BadBody( identifier, JSON.stringify({}), diff --git a/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts b/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts index 5ab3c3c4..1ad9c831 100644 --- a/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts +++ b/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts @@ -1,4 +1,10 @@ -import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { + forwardRef, + HttpException, + HttpStatus, + Inject, + Injectable, +} from '@nestjs/common'; import { IntegrationRepository } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.repository'; import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager'; import { @@ -442,40 +448,15 @@ export class IntegrationService { getIntegration.providerIdentifier ); - if ( - dayjs(getIntegration?.tokenExpiration).isBefore(dayjs()) || - forceRefresh - ) { - const data = await this._refreshIntegrationService.refresh( - getIntegration - ); - if (!data) { - return; - } - const { accessToken } = data; + // @ts-ignore + await getSocialIntegration?.[getAllInternalPlugs.methodName]?.( + getIntegration, + originalIntegration, + data.post, + data.information + ); - getIntegration.token = accessToken; - - if (getSocialIntegration.refreshWait) { - await timer(10000); - } - } - - try { - // @ts-ignore - await getSocialIntegration?.[getAllInternalPlugs.methodName]?.( - getIntegration, - originalIntegration, - data.post, - data.information - ); - } catch (err) { - if (err instanceof RefreshToken) { - return this.processInternalPlug(data, true); - } - - return; - } + return; } async processPlugs(data: { @@ -487,19 +468,13 @@ export class IntegrationService { }) { const getPlugById = await this._integrationRepository.getPlug(data.plugId); if (!getPlugById) { - return; + return true; } const integration = this._integrationManager.getSocialIntegration( getPlugById.integration.providerIdentifier ); - const findPlug = this._integrationManager - .getAllPlugs() - .find( - (p) => p.identifier === getPlugById.integration.providerIdentifier - )!; - // @ts-ignore const process = await integration[getPlugById.plugFunction]( getPlugById.integration, @@ -511,26 +486,14 @@ export class IntegrationService { ); if (process) { - return; + return true; } if (data.totalRuns === data.currentRun) { - return; + return true; } - this._workerServiceProducer.emit('plugs', { - id: 'plug_' + data.postId + '_' + findPlug.identifier, - options: { - delay: data.delay, - }, - payload: { - plugId: data.plugId, - postId: data.postId, - delay: data.delay, - totalRuns: data.totalRuns, - currentRun: data.currentRun + 1, - }, - }); + return false; } async createOrUpdatePlug( diff --git a/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts b/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts index 96fccaff..5dba914f 100644 --- a/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts +++ b/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts @@ -694,6 +694,32 @@ export class PostsRepository { }); } + async getPostByForWebhookId(postId: string) { + return this._post.model.post.findMany({ + where: { + id: postId, + deletedAt: null, + parentPostId: null, + }, + select: { + id: true, + content: true, + publishDate: true, + releaseURL: true, + state: true, + integration: { + select: { + id: true, + name: true, + providerIdentifier: true, + picture: true, + type: true, + }, + }, + }, + }); + } + async getPostsSince(orgId: string, since: string) { return this._post.model.post.findMany({ where: { 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 ee82d18a..d20c7085 100644 --- a/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts +++ b/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts @@ -8,38 +8,29 @@ import { PostsRepository } from '@gitroom/nestjs-libraries/database/prisma/posts import { CreatePostDto } from '@gitroom/nestjs-libraries/dtos/posts/create.post.dto'; import dayjs from 'dayjs'; import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager'; -import { Integration, Post, Media, From } from '@prisma/client'; +import { Integration, Post, Media, From, State } from '@prisma/client'; import { GetPostsDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.dto'; import { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service'; -import { capitalize, shuffle, uniq } from 'lodash'; +import { shuffle } from 'lodash'; import { MessagesService } from '@gitroom/nestjs-libraries/database/prisma/marketplace/messages.service'; import { StripeService } from '@gitroom/nestjs-libraries/services/stripe.service'; import { CreateGeneratedPostsDto } from '@gitroom/nestjs-libraries/dtos/generator/create.generated.posts.dto'; import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service'; import { makeId } from '@gitroom/nestjs-libraries/services/make.is'; -import { - BadBody, - RefreshToken, -} from '@gitroom/nestjs-libraries/integrations/social.abstract'; -import { BullMqClient } from '@gitroom/nestjs-libraries/bull-mq-transport-new/client'; -import { timer } from '@gitroom/helpers/utils/timer'; -import { AuthTokenDetails } from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface'; import utc from 'dayjs/plugin/utc'; import { MediaService } from '@gitroom/nestjs-libraries/database/prisma/media/media.service'; 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'; import { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service'; -import { plainToInstance } from 'class-transformer'; -import { validate } from 'class-validator'; -import { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation'; dayjs.extend(utc); import * as Sentry from '@sentry/nestjs'; -import { RefreshIntegrationService } from '@gitroom/nestjs-libraries/integrations/refresh.integration.service'; +import { TemporalService } from 'nestjs-temporal-core'; +import { TypedSearchAttributes } from '@temporalio/common'; +import { postId as postIdSearchParam } from '@gitroom/nestjs-libraries/temporal/temporal.search.attribute'; type PostWithConditionals = Post & { integration?: Integration; @@ -51,7 +42,6 @@ export class PostsService { private storage = UploadFactory.createStorage(); constructor( private _postRepository: PostsRepository, - private _workerServiceProducer: BullMqClient, private _integrationManager: IntegrationManager, private _notificationService: NotificationService, private _messagesService: MessagesService, @@ -59,9 +49,8 @@ export class PostsService { private _integrationService: IntegrationService, private _mediaService: MediaService, private _shortLinkService: ShortLinkService, - private _webhookService: WebhooksService, private openaiService: OpenaiService, - private _refreshIntegrationService: RefreshIntegrationService + private _temporalService: TemporalService ) {} checkPending15minutesBack() { @@ -71,6 +60,10 @@ export class PostsService { return this._postRepository.searchForMissingThreeHoursPosts(); } + updatePost(id: string, postId: string, releaseURL: string) { + return this._postRepository.updatePost(id, postId, releaseURL); + } + async getStatistics(orgId: string, id: string) { const getPost = await this.getPostsRecursively(id, true, orgId, true); const content = getPost.map((p) => p.content); @@ -292,102 +285,7 @@ export class PostsService { return this._postRepository.getOldPosts(orgId, date); } - async post(id: string) { - const allPosts = await this.getPostsRecursively(id, true); - const [firstPost, ...morePosts] = allPosts; - if (!firstPost) { - return; - } - - if (firstPost.integration?.refreshNeeded) { - await this._notificationService.inAppNotification( - firstPost.organizationId, - `We couldn't post to ${firstPost.integration?.providerIdentifier} for ${firstPost?.integration?.name}`, - `We couldn't post to ${firstPost.integration?.providerIdentifier} for ${firstPost?.integration?.name} because you need to reconnect it. Please enable it and try again.`, - true, - false, - 'info' - ); - return; - } - - if (firstPost.integration?.disabled) { - await this._notificationService.inAppNotification( - firstPost.organizationId, - `We couldn't post to ${firstPost.integration?.providerIdentifier} for ${firstPost?.integration?.name}`, - `We couldn't post to ${firstPost.integration?.providerIdentifier} for ${firstPost?.integration?.name} because it's disabled. Please enable it and try again.`, - true, - false, - 'info' - ); - return; - } - - try { - const finalPost = await this.postSocial(firstPost.integration!, [ - firstPost, - ...morePosts, - ]); - - if (firstPost?.intervalInDays) { - this._workerServiceProducer.emit('post', { - id, - options: { - delay: firstPost.intervalInDays * 86400000, - }, - payload: { - id: id, - }, - }); - } - - if (!finalPost?.postId || !finalPost?.releaseURL) { - await this._postRepository.changeState(firstPost.id, 'ERROR'); - await this._notificationService.inAppNotification( - firstPost.organizationId, - `Error posting on ${firstPost.integration?.providerIdentifier} for ${firstPost?.integration?.name}`, - `An error occurred while posting on ${firstPost.integration?.providerIdentifier}`, - true, - false, - 'fail' - ); - - return; - } - } catch (err: any) { - await this._postRepository.changeState( - firstPost.id, - 'ERROR', - err, - allPosts - ); - if (err instanceof BadBody) { - await this._notificationService.inAppNotification( - firstPost.organizationId, - `Error posting on ${firstPost.integration?.providerIdentifier} for ${firstPost?.integration?.name}`, - `An error occurred while posting on ${ - firstPost.integration?.providerIdentifier - }${err?.message ? `: ${err?.message}` : ``}`, - true, - false, - 'fail' - ); - - console.error( - '[Error] posting on', - firstPost.integration?.providerIdentifier, - err.identifier, - err.json, - err.body, - err - ); - } - - return; - } - } - - private async updateTags(orgId: string, post: Post[]): Promise { + public async updateTags(orgId: string, post: Post[]): Promise { const plainText = JSON.stringify(post); const extract = Array.from( plainText.match(/\(post:[a-zA-Z0-9-_]+\)/g) || [] @@ -411,127 +309,7 @@ export class PostsService { return this.updateTags(orgId, JSON.parse(newPlainText) as Post[]); } - private async postSocial( - integration: Integration, - posts: Post[], - forceRefresh = false - ): Promise | undefined> { - const getIntegration = this._integrationManager.getSocialIntegration( - integration.providerIdentifier - ); - - if (!getIntegration) { - return {}; - } - - if (dayjs(integration?.tokenExpiration).isBefore(dayjs()) || forceRefresh) { - const data = await this._refreshIntegrationService.refresh(integration); - - if (!data) { - return undefined; - } - - integration.token = data.accessToken; - - if (getIntegration.refreshWait) { - await timer(10000); - } - } - - const newPosts = await this.updateTags(integration.organizationId, posts); - - try { - const publishedPosts = await getIntegration.post( - integration.internalId, - integration.token, - await Promise.all( - (newPosts || []).map(async (p) => ({ - id: p.id, - message: stripHtmlValidation( - getIntegration.editor, - p.content, - true, - false, - !/<\/?[a-z][\s\S]*>/i.test(p.content), - getIntegration.mentionFormat - ), - settings: JSON.parse(p.settings || '{}'), - media: await this.updateMedia( - p.id, - JSON.parse(p.image || '[]'), - getIntegration?.convertToJPEG || false - ), - })) - ), - integration - ); - - for (const post of publishedPosts) { - try { - await this._postRepository.updatePost( - post.id, - post.postId, - post.releaseURL - ); - } catch (err) {} - } - - try { - await this._notificationService.inAppNotification( - integration.organizationId, - `Your post has been published on ${capitalize( - integration.providerIdentifier - )}`, - `Your post has been published on ${capitalize( - integration.providerIdentifier - )} at ${publishedPosts[0].releaseURL}`, - true, - true - ); - - await this._webhookService.digestWebhooks( - integration.organizationId, - dayjs(newPosts[0].publishDate).format('YYYY-MM-DDTHH:mm:00') - ); - - await this.checkPlugs( - integration.organizationId, - getIntegration.identifier, - integration.id, - publishedPosts[0].postId - ); - - await this.checkInternalPlug( - integration, - integration.organizationId, - publishedPosts[0].postId, - JSON.parse(newPosts[0].settings || '{}') - ); - } catch (err) {} - - return { - postId: publishedPosts[0].postId, - releaseURL: publishedPosts[0].releaseURL, - }; - } catch (err) { - if (err instanceof RefreshToken) { - return this.postSocial(integration, posts, true); - } - - if (err instanceof BadBody) { - throw err; - } - - throw new BadBody( - integration.providerIdentifier, - JSON.stringify(err), - {} as any, - '' - ); - } - } - - private async checkInternalPlug( + public async checkInternalPlug( integration: Integration, orgId: string, id: string, @@ -542,7 +320,7 @@ export class PostsService { }); if (plugs.length === 0) { - return; + return []; } const parsePlugs = plugs.reduce((all, [key, value]) => { @@ -559,32 +337,24 @@ export class PostsService { active: boolean; }[] = Object.values(parsePlugs); - for (const trigger of list || []) { - for (const int of trigger?.integrations || []) { - this._workerServiceProducer.emit('internal-plugs', { - id: 'plug_' + id + '_' + trigger.name + '_' + int.id, - options: { - delay: +trigger.delay, - }, - payload: { - post: id, - originalIntegration: integration.id, - integration: int.id, - plugName: trigger.name, - orgId: orgId, - delay: +trigger.delay, - information: trigger, - }, - }); - } - } + return (list || []).flatMap((trigger) => { + return (trigger?.integrations || []).flatMap((int) => ({ + type: 'internal-plug', + post: id, + originalIntegration: integration.id, + integration: int.id, + plugName: trigger.name, + orgId: orgId, + delay: +trigger.delay, + information: trigger, + })); + }); } - private async checkPlugs( + public async checkPlugs( orgId: string, providerName: string, - integrationId: string, - postId: string + integrationId: string ) { const loadAllPlugs = this._integrationManager.getAllPlugs(); const getPlugs = await this._integrationService.getPlugs( @@ -594,35 +364,51 @@ export class PostsService { const currentPlug = loadAllPlugs.find((p) => p.identifier === providerName); - for (const plug of getPlugs) { - const runPlug = currentPlug?.plugs?.find( - (p: any) => p.methodName === plug.plugFunction - )!; - if (!runPlug) { - continue; - } - - this._workerServiceProducer.emit('plugs', { - id: 'plug_' + postId + '_' + runPlug.identifier, - options: { - delay: runPlug.runEveryMilliseconds, - }, - payload: { + return getPlugs + .filter((plug) => { + return currentPlug?.plugs?.some( + (p: any) => p.methodName === plug.plugFunction + ); + }) + .map((plug) => { + const runPlug = currentPlug?.plugs?.find( + (p: any) => p.methodName === plug.plugFunction + )!; + return { + type: 'global', plugId: plug.id, - postId, delay: runPlug.runEveryMilliseconds, totalRuns: runPlug.totalRuns, - currentRun: 1, - }, + }; }); - } } async deletePost(orgId: string, group: string) { const post = await this._postRepository.deletePost(orgId, group); + if (post?.id) { - await this._workerServiceProducer.delete('post', post.id); - return { id: post.id }; + try { + const workflows = this._temporalService.client + .getRawClient() + ?.workflow.list({ + query: `WorkflowType="postWorkflow" AND postId="${post.id}" AND ExecutionStatus="Running"`, + }); + + for await (const executionInfo of workflows) { + try { + const workflow = + await this._temporalService.client.getWorkflowHandle( + executionInfo.workflowId + ); + if ( + workflow && + (await workflow.describe()).status.name !== 'TERMINATED' + ) { + await workflow.terminate(); + } + } catch (err) {} + } + } catch (err) {} } return { error: true }; @@ -632,6 +418,9 @@ export class PostsService { return this._postRepository.countPostsFromDay(orgId, date); } + getPostByForWebhookId(id: string) { + return this._postRepository.getPostByForWebhookId(id); + } async createPost(orgId: string, body: CreatePostDto): Promise { const postList = []; for (const post of body.posts) { @@ -661,32 +450,42 @@ export class PostsService { return [] as any[]; } - await this._workerServiceProducer.delete( - 'post', - previousPost ? previousPost : posts?.[0]?.id - ); + try { + const workflows = this._temporalService.client + .getRawClient() + ?.workflow.list({ + query: `WorkflowType="postWorkflow" AND postId="${posts[0].id}" AND ExecutionStatus="Running"`, + }); - if ( - body.type === 'now' || - (body.type === 'schedule' && dayjs(body.date).isAfter(dayjs())) - ) { - this._workerServiceProducer.emit('post', { - id: posts[0].id, - options: { - delay: - body.type === 'now' - ? 0 - : dayjs(posts[0].publishDate).diff(dayjs(), 'millisecond'), - }, - payload: { - id: posts[0].id, - delay: - body.type === 'now' - ? 0 - : dayjs(posts[0].publishDate).diff(dayjs(), 'millisecond'), - }, + for await (const executionInfo of workflows) { + try { + const workflow = + await this._temporalService.client.getWorkflowHandle( + executionInfo.workflowId + ); + if ( + workflow && + (await workflow.describe()).status.name !== 'TERMINATED' + ) { + await workflow.terminate(); + } + } catch (err) {} + } + } catch (err) {} + + await this._temporalService.client + .getRawClient() + ?.workflow.start('postWorkflow', { + workflowId: `post_${posts[0].id}`, + taskQueue: 'main', + args: [{ postId: posts[0].id, organizationId: orgId }], + typedSearchAttributes: new TypedSearchAttributes([ + { + key: postIdSearchParam, + value: posts[0].id, + }, + ]), }); - } Sentry.metrics.count('post_created', 1); postList.push({ @@ -702,24 +501,51 @@ export class PostsService { return this.openaiService.separatePosts(content, len); } + async changeState(id: string, state: State, err?: any, body?: any) { + return this._postRepository.changeState(id, state, err, body); + } + async changeDate(orgId: string, id: string, date: string) { const getPostById = await this._postRepository.getPostById(id, orgId); + const newDate = await this._postRepository.changeDate(orgId, id, date); - await this._workerServiceProducer.delete('post', id); - if (getPostById?.state !== 'DRAFT') { - this._workerServiceProducer.emit('post', { - id: id, - options: { - delay: dayjs(date).diff(dayjs(), 'millisecond'), - }, - payload: { - id: id, - delay: dayjs(date).diff(dayjs(), 'millisecond'), - }, + try { + const workflows = this._temporalService.client + .getRawClient() + ?.workflow.list({ + query: `WorkflowType="postWorkflow" AND postId="${getPostById.id}" AND ExecutionStatus="Running"`, + }); + + for await (const executionInfo of workflows) { + try { + const workflow = await this._temporalService.client.getWorkflowHandle( + executionInfo.workflowId + ); + if ( + workflow && + (await workflow.describe()).status.name !== 'TERMINATED' + ) { + await workflow.terminate(); + } + } catch (err) {} + } + } catch (err) {} + + await this._temporalService.client + .getRawClient() + ?.workflow.start('postWorkflow', { + workflowId: `post_${getPostById.id}`, + taskQueue: 'main', + args: [{ postId: getPostById.id, organizationId: orgId }], + typedSearchAttributes: new TypedSearchAttributes([ + { + key: postIdSearchParam, + value: getPostById.id, + }, + ]), }); - } - return this._postRepository.changeDate(orgId, id, date); + return newDate; } async payout(id: string, url: string) { diff --git a/libraries/nestjs-libraries/src/integrations/social.abstract.ts b/libraries/nestjs-libraries/src/integrations/social.abstract.ts index 94bb51b0..22fe3a18 100644 --- a/libraries/nestjs-libraries/src/integrations/social.abstract.ts +++ b/libraries/nestjs-libraries/src/integrations/social.abstract.ts @@ -1,22 +1,30 @@ import { timer } from '@gitroom/helpers/utils/timer'; import { concurrency } from '@gitroom/helpers/utils/concurrency.service'; import { Integration } from '@prisma/client'; +import { ApplicationFailure } from '@temporalio/activity'; -export class RefreshToken { - constructor( - public identifier: string, - public json: string, - public body: BodyInit, - public message = '' - ) {} +export class RefreshToken extends ApplicationFailure { + constructor(identifier: string, json: string, body: BodyInit, message = '') { + super(message, 'refresh_token', true, [ + { + identifier, + json, + body, + }, + ]); + } } -export class BadBody { - constructor( - public identifier: string, - public json: string, - public body: BodyInit, - public message = '' - ) {} + +export class BadBody extends ApplicationFailure { + constructor(identifier: string, json: string, body: BodyInit, message = '') { + super(message, 'bad_body', true, [ + { + identifier, + json, + body, + }, + ]); + } } export class NotEnoughScopes { @@ -58,7 +66,6 @@ export abstract class SocialAbstract { try { return await func(); } catch (err) { - console.log(err); const handle = this.handleErrors(JSON.stringify(err)); return { err: true, ...(handle || {}) }; } @@ -109,34 +116,36 @@ export abstract class SocialAbstract { json.includes('Rate limit') ) { await timer(5000); - return this.fetch(url, options, identifier, totalRetries + 1, ignoreConcurrency); + 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, ignoreConcurrency); + return this.fetch( + url, + options, + identifier, + totalRetries + 1, + ignoreConcurrency + ); } if ( request.status === 401 && (handleError?.type === 'refresh-token' || !handleError) ) { - throw new RefreshToken( - identifier, - json, - options.body!, - handleError?.value - ); + throw new RefreshToken(identifier, json, options.body!, handleError?.value); } - throw new BadBody( - identifier, - json, - options.body!, - handleError?.value || '' - ); + throw new BadBody(identifier, json, options.body!, handleError?.value || ''); } checkScopes(required: string[], got: string | string[]) { diff --git a/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts b/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts index 4aa1c9d5..0e6f9872 100644 --- a/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts @@ -554,6 +554,17 @@ export class LinkedinProvider extends SocialAbstract implements SocialProvider { isPdf ); + console.log({ + method: 'POST', + headers: { + 'LinkedIn-Version': '202501', + 'X-Restli-Protocol-Version': '2.0.0', + 'Content-Type': 'application/json', + Authorization: `Bearer ${accessToken}`, + }, + body: JSON.stringify(postPayload), + }); + const response = await this.fetch('https://api.linkedin.com/rest/posts', { method: 'POST', headers: { @@ -723,7 +734,7 @@ export class LinkedinProvider extends SocialAbstract implements SocialProvider { headers: { 'X-Restli-Protocol-Version': '2.0.0', 'Content-Type': 'application/json', - 'LinkedIn-Version': '202504', + 'LinkedIn-Version': '202511', Authorization: `Bearer ${integration.token}`, }, }); @@ -739,7 +750,7 @@ export class LinkedinProvider extends SocialAbstract implements SocialProvider { headers: { 'X-Restli-Protocol-Version': '2.0.0', 'Content-Type': 'application/json', - 'LinkedIn-Version': '202504', + 'LinkedIn-Version': '202511', Authorization: `Bearer ${token}`, }, } 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 3653b97e..8ade3a81 100644 --- a/libraries/nestjs-libraries/src/integrations/social/social.integrations.interface.ts +++ b/libraries/nestjs-libraries/src/integrations/social/social.integrations.interface.ts @@ -77,6 +77,15 @@ export interface ISocialMediaIntegration { postDetails: PostDetails[], integration: Integration ): Promise; // Schedules a new post + + comment?( + id: string, + postId: string, + lastCommentId: string | undefined, + accessToken: string, + postDetails: PostDetails[], + integration: Integration + ): Promise; // Schedules a new post } export type PostResponse = { @@ -143,8 +152,14 @@ export interface SocialProvider url: string ) => Promise<{ client_id: string; client_secret: string }>; mention?: ( - token: string, data: { query: string }, id: string, integration: Integration - ) => Promise<{ id: string; label: string; image: string, doNotCache?: boolean }[] | {none: true}>; + token: string, + data: { query: string }, + id: string, + integration: Integration + ) => Promise< + | { id: string; label: string; image: string; doNotCache?: boolean }[] + | { none: true } + >; mentionFormat?(idOrHandle: string, name: string): string; fetchPageInformation?( accessToken: string, diff --git a/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts b/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts index 8682d1aa..bf4f7264 100644 --- a/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts @@ -13,7 +13,6 @@ import { TikTokDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settin import { timer } from '@gitroom/helpers/utils/timer'; import { Integration } from '@prisma/client'; import { Rules } from '@gitroom/nestjs-libraries/chat/rules.description.decorator'; -import { makeId } from '@gitroom/nestjs-libraries/services/make.is'; @Rules( 'TikTok can have one video or one picture or multiple pictures, it cannot be without an attachment' diff --git a/libraries/nestjs-libraries/src/temporal/temporal.module.ts b/libraries/nestjs-libraries/src/temporal/temporal.module.ts new file mode 100644 index 00000000..0e0225fb --- /dev/null +++ b/libraries/nestjs-libraries/src/temporal/temporal.module.ts @@ -0,0 +1,28 @@ +import { TemporalModule } from 'nestjs-temporal-core'; + +export const getTemporalModule = ( + isWorkers: boolean, + path?: string, + activityClasses?: any[], +) => { + return TemporalModule.register({ + isGlobal: true, + connection: { + address: process.env.TEMPORAL_ADDRESS || 'localhost:7233', + namespace: process.env.TEMPORAL_NAMESPACE || 'default', + }, + taskQueue: 'main', + ...(isWorkers + ? { + worker: { + workflowsPath: path!, + activityClasses: activityClasses!, + autoStart: true, + workerOptions: { + maxConcurrentActivityTaskExecutions: 24, + }, + }, + } + : {}), + }); +}; diff --git a/libraries/nestjs-libraries/src/temporal/temporal.register.ts b/libraries/nestjs-libraries/src/temporal/temporal.register.ts new file mode 100644 index 00000000..e8090aad --- /dev/null +++ b/libraries/nestjs-libraries/src/temporal/temporal.register.ts @@ -0,0 +1,50 @@ +import { + Global, + Injectable, + Module, + OnModuleInit, +} from '@nestjs/common'; +import { TemporalService } from 'nestjs-temporal-core'; +import { Connection } from '@temporalio/client'; + +@Injectable() +export class TemporalRegister implements OnModuleInit { + constructor(private _client: TemporalService) {} + + async onModuleInit(): Promise { + const connection = this._client?.client?.getRawClient() + ?.connection as Connection; + + const { customAttributes } = + await connection.operatorService.listSearchAttributes({ + namespace: process.env.TEMPORAL_NAMESPACE || 'default', + }); + + const neededAttribute = ['organizationId', 'postId']; + const missingAttributes = neededAttribute.filter( + (attr) => !customAttributes[attr], + ); + + if (missingAttributes.length > 0) { + await connection.operatorService.addSearchAttributes({ + namespace: process.env.TEMPORAL_NAMESPACE || 'default', + searchAttributes: missingAttributes.reduce((all, current) => { + // @ts-ignore + all[current] = 1; + return all; + }, {}), + }); + } + } +} + +@Global() +@Module({ + imports: [], + controllers: [], + providers: [TemporalRegister], + get exports() { + return this.providers; + }, +}) +export class TemporalRegisterMissingSearchAttributesModule {} diff --git a/libraries/nestjs-libraries/src/temporal/temporal.search.attribute.ts b/libraries/nestjs-libraries/src/temporal/temporal.search.attribute.ts new file mode 100644 index 00000000..f037efd1 --- /dev/null +++ b/libraries/nestjs-libraries/src/temporal/temporal.search.attribute.ts @@ -0,0 +1,14 @@ +import { + defineSearchAttributeKey, + SearchAttributeType, +} from '@temporalio/common'; + +export const organizationId = defineSearchAttributeKey( + 'organizationId', + SearchAttributeType.TEXT +); + +export const postId = defineSearchAttributeKey( + 'postId', + SearchAttributeType.TEXT +); diff --git a/package.json b/package.json index 27219367..f7881301 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ }, "packageManager": "pnpm@10.6.1", "scripts": { - "dev": "pnpm run --filter ./apps/extension --filter ./apps/cron --filter ./apps/workers --filter ./apps/backend --filter ./apps/frontend --parallel dev", + "dev": "pnpm run --filter ./apps/extension --filter ./apps/cron --filter ./apps/orchestrator --filter ./apps/backend --filter ./apps/frontend --parallel dev", "pm2": "pnpm run pm2-run", "publish-sdk": "pnpm run --filter ./apps/sdk publish", "pm2-run": "pm2 delete all || true && pnpm run prisma-db-push && pnpm run --parallel pm2 && pm2 logs", @@ -20,11 +20,13 @@ "build:backend": "rm -rf apps/backend/dist && pnpm --filter ./apps/backend run build", "build:frontend": "rm -rf apps/frontend/dist && pnpm --filter ./apps/frontend run build", "build:workers": "rm -rf apps/workers/dist && pnpm --filter ./apps/workers run build", + "build:orchestrator": "rm -rf apps/orchestrator/dist && pnpm --filter ./apps/orchestrator run build", "build:cron": "rm -rf apps/cron/dist && pnpm --filter ./apps/cron run build", "build:extension": "rm -rf apps/extension/dist && pnpm --filter ./apps/extension run build", "dev:backend": "rm -rf apps/backend/dist && pnpm --filter ./apps/backend run dev", "dev:frontend": "rm -rf apps/frontend/dist && pnpm --filter ./apps/frontend run dev", "dev:workers": "rm -rf apps/workers/dist && pnpm --filter ./apps/workers run dev", + "dev:orchestrator": "rm -rf apps/orchestrator/dist && pnpm --filter ./apps/orchestrator run dev", "dev:cron": "rm -rf apps/cron/dist && pnpm --filter ./apps/cron run dev", "start:prod:backend": "pnpm --filter ./apps/backend run start", "start:prod:frontend": "pnpm --filter ./apps/frontend run start", @@ -92,6 +94,11 @@ "@swc/helpers": "0.5.13", "@sweetalert2/theme-dark": "^5.0.16", "@tailwindcss/postcss": "^4.1.7", + "@temporalio/activity": "^1.14.0", + "@temporalio/client": "^1.14.0", + "@temporalio/common": "^1.14.0", + "@temporalio/worker": "^1.14.0", + "@temporalio/workflow": "^1.14.0", "@tiptap/extension-bold": "^3.0.6", "@tiptap/extension-document": "^3.0.6", "@tiptap/extension-heading": "^3.0.7", @@ -181,6 +188,7 @@ "music-metadata": "^7.14.0", "nestjs-command": "^3.1.4", "nestjs-real-ip": "^3.0.1", + "nestjs-temporal-core": "^3.2.0", "next": "14.2.35", "next-plausible": "^3.12.0", "node-fetch": "^3.3.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8f900596..07c334b9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -156,6 +156,21 @@ importers: '@tailwindcss/postcss': specifier: ^4.1.7 version: 4.1.18 + '@temporalio/activity': + specifier: ^1.14.0 + version: 1.14.0 + '@temporalio/client': + specifier: ^1.14.0 + version: 1.14.0 + '@temporalio/common': + specifier: ^1.14.0 + version: 1.14.0 + '@temporalio/worker': + specifier: ^1.14.0 + version: 1.14.0(@swc/helpers@0.5.13)(esbuild@0.25.12) + '@temporalio/workflow': + specifier: ^1.14.0 + version: 1.14.0 '@tiptap/extension-bold': specifier: ^3.0.6 version: 3.14.0(@tiptap/core@3.14.0(@tiptap/pm@3.14.0)) @@ -423,6 +438,9 @@ importers: nestjs-real-ip: specifier: ^3.0.1 version: 3.0.1(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.1.14)(rxjs@7.8.2)) + nestjs-temporal-core: + specifier: ^3.2.0 + version: 3.2.0(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.20)(@temporalio/client@1.14.0)(@temporalio/common@1.14.0)(@temporalio/worker@1.14.0(@swc/helpers@0.5.13)(esbuild@0.25.12))(@temporalio/workflow@1.14.0)(reflect-metadata@0.1.14)(rxjs@7.8.2) next: specifier: 14.2.35 version: 14.2.35(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.97.1) @@ -792,6 +810,8 @@ importers: apps/frontend: {} + apps/orchestrator: {} + apps/sdk: dependencies: node-fetch: @@ -2787,6 +2807,42 @@ packages: '@jsdevtools/ono@7.1.3': resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + '@jsonjoy.com/base64@1.1.2': + resolution: {integrity: sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/buffers@1.2.1': + resolution: {integrity: sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/codegen@1.0.0': + resolution: {integrity: sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/json-pack@1.21.0': + resolution: {integrity: sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/json-pointer@1.0.2': + resolution: {integrity: sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/util@1.9.0': + resolution: {integrity: sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@juggle/resize-observer@3.4.0': resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} @@ -6954,6 +7010,38 @@ packages: '@tanstack/virtual-core@3.13.13': resolution: {integrity: sha512-uQFoSdKKf5S8k51W5t7b2qpfkyIbdHMzAn+AMQvHPxKUPeo1SsGaA4JRISQT87jm28b7z8OEqPcg1IOZagQHcA==} + '@temporalio/activity@1.14.0': + resolution: {integrity: sha512-ayGqfjqW8R1nhow54Y3A5ezoVwFr4SbB8VHaQA3seDFOB+6TyOVSlulYqGgFMxl/FXBkRa/VEswEDqS/xQq7aQ==} + engines: {node: '>= 18.0.0'} + + '@temporalio/client@1.14.0': + resolution: {integrity: sha512-kjzJ+7M2kHj32cTTSQT5WOjEIOxY0TNV5g6Sw9PzWmKWdtIZig+d7qUIA3VjDe/TieNozxjR2wNAX5sKzYFANA==} + engines: {node: '>= 18.0.0'} + + '@temporalio/common@1.14.0': + resolution: {integrity: sha512-jVmurBdFHdqw/wIehzVJikS8MhavL630p88TJ64P5PH0nP8S5V8R5vhkmHZ7n0sMRO+A0QFyWYyvnccu6MQZvw==} + engines: {node: '>= 18.0.0'} + + '@temporalio/core-bridge@1.14.0': + resolution: {integrity: sha512-62WRbESKVtCx1FafbikQB90EwKNF+mEAaOJKifUIU4lQnk9wlZPRfrf6pwyqr+Uqi7uZhD2YqHXWUNVYbmQU7w==} + engines: {node: '>= 18.0.0'} + + '@temporalio/nexus@1.14.0': + resolution: {integrity: sha512-0tgf+EBuz5vgYUukaYUzVHKr27XNQejXXO1i0x8+4sjR5zN6euNKraHfRzrDWRSm3nTZ6199rCTbR+CPrqaC/g==} + engines: {node: '>= 18.0.0'} + + '@temporalio/proto@1.14.0': + resolution: {integrity: sha512-duYVjt3x6SkuFzJr+5NlklEgookPqW065qdcvogmdfVjrgiwz4W/07AN3+fL4ufmqt1//0SyF6nyqv9RNADYNA==} + engines: {node: '>= 18.0.0'} + + '@temporalio/worker@1.14.0': + resolution: {integrity: sha512-wo5rgPSt83aT1hLYmh/0X4yOx/6uRbIvBa9LXqGo7s9s1GJkUyJpAahRt8aMoLm4qPsiZtu1gtU5KcASOmgqtg==} + engines: {node: '>= 18.0.0'} + + '@temporalio/workflow@1.14.0': + resolution: {integrity: sha512-hxUqCZTkdSwgy5nc/O1DIpYH0Z77cM57RfJvhK4ELmkkb1jh/Q4dshDannH1qQ1zYT0IKRBHSW7m1aMy1+dgDA==} + engines: {node: '>= 18.0.0'} + '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -8306,6 +8394,12 @@ packages: peerDependencies: acorn: ^8 + acorn-import-phases@1.0.4: + resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==} + engines: {node: '>=10.13.0'} + peerDependencies: + acorn: ^8.14.0 + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -8932,6 +9026,10 @@ packages: capital-case@1.0.4: resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} + cargo-cp-artifact@0.1.9: + resolution: {integrity: sha512-6F+UYzTaGB+awsTXg0uSJA1/b/B3DDJzpKVRu0UmyI7DmNeaAl2RFHuTGIN6fEgpadRxoXGb7gbC1xo4C3IdyA==} + hasBin: true + caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} @@ -9965,6 +10063,9 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -10776,6 +10877,12 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob-to-regex.js@1.2.0: + resolution: {integrity: sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} @@ -11029,6 +11136,10 @@ packages: header-case@2.0.4: resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} + heap-js@2.7.1: + resolution: {integrity: sha512-EQfezRg0NCZGNlhlDR3Evrw1FVL2G3LhU7EgPoxufQKruNBSYA8MiRPHeWbU+36o+Fhel0wMwM+sLEiBAlNLJA==} + engines: {node: '>=10.0.0'} + help-me@5.0.0: resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} @@ -11194,6 +11305,10 @@ packages: humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + hyperdyperid@1.2.0: + resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==} + engines: {node: '>=10.18'} + i18n-iso-countries@7.14.0: resolution: {integrity: sha512-nXHJZYtNrfsi1UQbyRqm3Gou431elgLjKl//CYlnBGt5aTWdRPH1PiS2T/p/n8Q8LnqYqzQJik3Q7mkwvLokeg==} engines: {node: '>= 12'} @@ -11623,6 +11738,10 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isexe@3.1.1: + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} + iso-3166-1@2.1.1: resolution: {integrity: sha512-RZxXf8cw5Y8LyHZIwIRvKw8sWTIHh2/txBT+ehO0QroesVfnz3JNFFX4i/OC/Yuv2bDIVYrHna5PMvjtpefq5w==} @@ -12611,6 +12730,9 @@ packages: resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} engines: {node: '>= 4.0.0'} + memfs@4.51.1: + resolution: {integrity: sha512-Eyt3XrufitN2ZL9c/uIRMyDwXanLI88h/L3MoWqNY747ha3dMR9dWqp8cRT5ntjZ0U1TNuq4U91ZXK0sMBjYOQ==} + memoize-one@5.2.1: resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} @@ -13007,6 +13129,10 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + ms@3.0.0-canary.1: + resolution: {integrity: sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g==} + engines: {node: '>=12.13'} + msgpackr-extract@3.0.3: resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} hasBin: true @@ -13095,6 +13221,19 @@ packages: peerDependencies: '@nestjs/common': '>=8' + nestjs-temporal-core@3.2.0: + resolution: {integrity: sha512-EIf1PZKBj9YR2ln8eDqBliGqo9IpWmetPbSbsO+X00s1qwewWNGGw7VX2Fq4Bw8/WPkbWeHgUHXwphkuOIIyaQ==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + peerDependencies: + '@nestjs/common': ^9.0.0 || ^10.0.0 || ^11.0.0 + '@nestjs/core': ^9.0.0 || ^10.0.0 || ^11.0.0 + '@temporalio/client': ^1.12.0 || ^1.13.0 + '@temporalio/common': ^1.12.0 || ^1.13.0 + '@temporalio/worker': ^1.12.0 || ^1.13.0 + '@temporalio/workflow': ^1.12.0 || ^1.13.0 + reflect-metadata: ^0.2.2 + rxjs: ^7.8.0 + netmask@2.0.2: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} @@ -13124,6 +13263,10 @@ packages: sass: optional: true + nexus-rpc@0.0.1: + resolution: {integrity: sha512-hAWn8Hh2eewpB5McXR5EW81R3pR/ziuGhKCF3wFyUVCklanPqrIgMNr7jKCbzXeNVad0nUDfWpFRqh2u+zxQtw==} + engines: {node: '>= 18.0.0'} + nice-grpc-client-middleware-retry@3.1.13: resolution: {integrity: sha512-Q9I/wm5lYkDTveKFirrTHBkBY137yavXZ4xQDXTPIycUp7aLXD8xPTHFhqtAFWUw05aS91uffZZRgdv3HS0y/g==} @@ -14068,6 +14211,10 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + proto3-json-serializer@2.0.2: + resolution: {integrity: sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==} + engines: {node: '>=14.0.0'} + protobufjs@7.5.4: resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} engines: {node: '>=12.0.0'} @@ -15187,6 +15334,12 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map-loader@4.0.2: + resolution: {integrity: sha512-oYwAqCuL0OZhBoSgmdrLa7mv9MjommVMiQIWgcztf+eS4+8BfcUee6nenFnDhKOhzAVnk5gpZdfnz1iiBv+5sg==} + engines: {node: '>= 14.15.0'} + peerDependencies: + webpack: ^5.72.1 + source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} @@ -15504,6 +15657,12 @@ packages: swagger-ui-dist@5.17.14: resolution: {integrity: sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw==} + swc-loader@0.2.6: + resolution: {integrity: sha512-9Zi9UP2YmDpgmQVbyOPJClY0dwf58JDyDMQ7uRc4krmc72twNI2fvlBWHLqVekBpPc7h5NJkGVT1zNDxFrqhvg==} + peerDependencies: + '@swc/core': ^1.2.147 + webpack: '>=2' + sweetalert2@11.4.8: resolution: {integrity: sha512-BDS/+E8RwaekGSxCPUbPnsRAyQ439gtXkTF/s98vY2l9DaVEOMjGj1FaQSorfGREKsbbxGSP7UXboibL5vgTMA==} @@ -15597,6 +15756,12 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + thingies@2.5.0: + resolution: {integrity: sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==} + engines: {node: '>=10.18'} + peerDependencies: + tslib: ^2 + thread-stream@0.15.2: resolution: {integrity: sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA==} @@ -15745,6 +15910,12 @@ packages: resolution: {integrity: sha512-FvhKs0EBiQufK29irGLM/4aMIrfU5S/TiHB3h+DcO2hjRnVVM2WC278UQJCrNO4L/REE8IKWx/mQzQW2MrrLsg==} engines: {node: '>= 10.0.0'} + tree-dump@1.1.0: + resolution: {integrity: sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -16071,6 +16242,9 @@ packages: unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + unionfs@4.6.0: + resolution: {integrity: sha512-fJAy3gTHjFi5S3TP5EGdjs/OUMFFvI/ady3T8qVuZfkv8Qi8prV/Q8BuFEgODJslhZTT2z2qdD2lGdee9qjEnA==} + unist-util-filter@5.0.1: resolution: {integrity: sha512-pHx7D4Zt6+TsfwylH9+lYhBhzyhEnCXs/lbq/Hstxno5z4gVdyc2WEW0asfjGKPyG4pEKrnBv5hdkO6+aRnQJw==} @@ -16560,6 +16734,16 @@ packages: webpack-virtual-modules@0.5.0: resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==} + webpack@5.104.1: + resolution: {integrity: sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + webpack@5.87.0: resolution: {integrity: sha512-GOu1tNbQ7p1bDEoFRs2YPcfyGs8xq52yyPBZ3m2VGnXGtV9MxjrkABHm4V9Ia280OefsSLzvbVoXcfLxjKY/Iw==} engines: {node: '>=10.13.0'} @@ -16632,6 +16816,11 @@ packages: engines: {node: '>= 8'} hasBin: true + which@4.0.0: + resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} + engines: {node: ^16.13.0 || >=18.0.0} + hasBin: true + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -19954,6 +20143,42 @@ snapshots: '@jsdevtools/ono@7.1.3': {} + '@jsonjoy.com/base64@1.1.2(tslib@2.8.1)': + dependencies: + tslib: 2.8.1 + + '@jsonjoy.com/buffers@1.2.1(tslib@2.8.1)': + dependencies: + tslib: 2.8.1 + + '@jsonjoy.com/codegen@1.0.0(tslib@2.8.1)': + dependencies: + tslib: 2.8.1 + + '@jsonjoy.com/json-pack@1.21.0(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/base64': 1.1.2(tslib@2.8.1) + '@jsonjoy.com/buffers': 1.2.1(tslib@2.8.1) + '@jsonjoy.com/codegen': 1.0.0(tslib@2.8.1) + '@jsonjoy.com/json-pointer': 1.0.2(tslib@2.8.1) + '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) + hyperdyperid: 1.2.0 + thingies: 2.5.0(tslib@2.8.1) + tree-dump: 1.1.0(tslib@2.8.1) + tslib: 2.8.1 + + '@jsonjoy.com/json-pointer@1.0.2(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/codegen': 1.0.0(tslib@2.8.1) + '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) + tslib: 2.8.1 + + '@jsonjoy.com/util@1.9.0(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/buffers': 1.2.1(tslib@2.8.1) + '@jsonjoy.com/codegen': 1.0.0(tslib@2.8.1) + tslib: 2.8.1 + '@juggle/resize-observer@3.4.0': {} '@keystonehq/alias-sampling@0.1.2': {} @@ -25035,6 +25260,86 @@ snapshots: '@tanstack/virtual-core@3.13.13': {} + '@temporalio/activity@1.14.0': + dependencies: + '@temporalio/client': 1.14.0 + '@temporalio/common': 1.14.0 + abort-controller: 3.0.0 + + '@temporalio/client@1.14.0': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@temporalio/common': 1.14.0 + '@temporalio/proto': 1.14.0 + abort-controller: 3.0.0 + long: 5.3.2 + uuid: 11.1.0 + + '@temporalio/common@1.14.0': + dependencies: + '@temporalio/proto': 1.14.0 + long: 5.3.2 + ms: 3.0.0-canary.1 + nexus-rpc: 0.0.1 + proto3-json-serializer: 2.0.2 + + '@temporalio/core-bridge@1.14.0': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@temporalio/common': 1.14.0 + arg: 5.0.2 + cargo-cp-artifact: 0.1.9 + which: 4.0.0 + + '@temporalio/nexus@1.14.0': + dependencies: + '@temporalio/client': 1.14.0 + '@temporalio/common': 1.14.0 + '@temporalio/proto': 1.14.0 + long: 5.3.2 + nexus-rpc: 0.0.1 + + '@temporalio/proto@1.14.0': + dependencies: + long: 5.3.2 + protobufjs: 7.5.4 + + '@temporalio/worker@1.14.0(@swc/helpers@0.5.13)(esbuild@0.25.12)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@swc/core': 1.5.7(@swc/helpers@0.5.13) + '@temporalio/activity': 1.14.0 + '@temporalio/client': 1.14.0 + '@temporalio/common': 1.14.0 + '@temporalio/core-bridge': 1.14.0 + '@temporalio/nexus': 1.14.0 + '@temporalio/proto': 1.14.0 + '@temporalio/workflow': 1.14.0 + abort-controller: 3.0.0 + heap-js: 2.7.1 + memfs: 4.51.1 + nexus-rpc: 0.0.1 + proto3-json-serializer: 2.0.2 + protobufjs: 7.5.4 + rxjs: 7.8.2 + source-map: 0.7.6 + source-map-loader: 4.0.2(webpack@5.104.1(@swc/core@1.5.7(@swc/helpers@0.5.13))(esbuild@0.25.12)) + supports-color: 8.1.1 + swc-loader: 0.2.6(@swc/core@1.5.7(@swc/helpers@0.5.13))(webpack@5.104.1(@swc/core@1.5.7(@swc/helpers@0.5.13))(esbuild@0.25.12)) + unionfs: 4.6.0 + webpack: 5.104.1(@swc/core@1.5.7(@swc/helpers@0.5.13))(esbuild@0.25.12) + transitivePeerDependencies: + - '@swc/helpers' + - esbuild + - uglify-js + - webpack-cli + + '@temporalio/workflow@1.14.0': + dependencies: + '@temporalio/common': 1.14.0 + '@temporalio/proto': 1.14.0 + nexus-rpc: 0.0.1 + '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.27.1 @@ -27145,6 +27450,10 @@ snapshots: dependencies: acorn: 8.15.0 + acorn-import-phases@1.0.4(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -27905,6 +28214,8 @@ snapshots: tslib: 2.8.1 upper-case-first: 2.0.2 + cargo-cp-artifact@0.1.9: {} + caseless@0.12.0: {} cbor-sync@1.0.4: {} @@ -29028,6 +29339,8 @@ snapshots: es-module-lexer@1.7.0: {} + es-module-lexer@2.0.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -30208,6 +30521,10 @@ snapshots: dependencies: is-glob: 4.0.3 + glob-to-regex.js@1.2.0(tslib@2.8.1): + dependencies: + tslib: 2.8.1 + glob-to-regexp@0.4.1: {} glob@10.5.0: @@ -30646,6 +30963,8 @@ snapshots: capital-case: 1.0.4 tslib: 2.8.1 + heap-js@2.7.1: {} + help-me@5.0.0: {} hermes-compiler@0.14.0: {} @@ -30803,6 +31122,8 @@ snapshots: dependencies: ms: 2.1.3 + hyperdyperid@1.2.0: {} + i18n-iso-countries@7.14.0: dependencies: diacritics: 1.3.0 @@ -31255,6 +31576,8 @@ snapshots: isexe@2.0.0: {} + isexe@3.1.1: {} + iso-3166-1@2.1.1: {} iso-datestring-validator@2.2.2: {} @@ -32642,6 +32965,15 @@ snapshots: dependencies: fs-monkey: 1.1.0 + memfs@4.51.1: + dependencies: + '@jsonjoy.com/json-pack': 1.21.0(tslib@2.8.1) + '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) + glob-to-regex.js: 1.2.0(tslib@2.8.1) + thingies: 2.5.0(tslib@2.8.1) + tree-dump: 1.1.0(tslib@2.8.1) + tslib: 2.8.1 + memoize-one@5.2.1: {} memoizerific@1.11.3: @@ -33298,6 +33630,8 @@ snapshots: ms@2.1.3: {} + ms@3.0.0-canary.1: {} + msgpackr-extract@3.0.3: dependencies: node-gyp-build-optional-packages: 5.2.2 @@ -33396,6 +33730,18 @@ snapshots: '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@supercharge/request-ip': 1.2.0 + nestjs-temporal-core@3.2.0(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.20)(@temporalio/client@1.14.0)(@temporalio/common@1.14.0)(@temporalio/worker@1.14.0(@swc/helpers@0.5.13)(esbuild@0.25.12))(@temporalio/workflow@1.14.0)(reflect-metadata@0.1.14)(rxjs@7.8.2): + dependencies: + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/core': 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.20)(@nestjs/platform-express@10.4.20)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@temporalio/client': 1.14.0 + '@temporalio/common': 1.14.0 + '@temporalio/worker': 1.14.0(@swc/helpers@0.5.13)(esbuild@0.25.12) + '@temporalio/workflow': 1.14.0 + ms: 2.1.3 + reflect-metadata: 0.1.14 + rxjs: 7.8.2 + netmask@2.0.2: {} next-plausible@3.12.5(next@14.2.35(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.97.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): @@ -33432,6 +33778,8 @@ snapshots: - '@babel/core' - babel-plugin-macros + nexus-rpc@0.0.1: {} + nice-grpc-client-middleware-retry@3.1.13: dependencies: abort-controller-x: 0.4.3 @@ -34524,6 +34872,10 @@ snapshots: proto-list@1.2.4: {} + proto3-json-serializer@2.0.2: + dependencies: + protobufjs: 7.5.4 + protobufjs@7.5.4: dependencies: '@protobufjs/aspromise': 1.1.2 @@ -36055,6 +36407,12 @@ snapshots: source-map-js@1.2.1: {} + source-map-loader@4.0.2(webpack@5.104.1(@swc/core@1.5.7(@swc/helpers@0.5.13))(esbuild@0.25.12)): + dependencies: + iconv-lite: 0.6.3 + source-map-js: 1.2.1 + webpack: 5.104.1(@swc/core@1.5.7(@swc/helpers@0.5.13))(esbuild@0.25.12) + source-map-support@0.5.13: dependencies: buffer-from: 1.1.2 @@ -36374,6 +36732,12 @@ snapshots: swagger-ui-dist@5.17.14: {} + swc-loader@0.2.6(@swc/core@1.5.7(@swc/helpers@0.5.13))(webpack@5.104.1(@swc/core@1.5.7(@swc/helpers@0.5.13))(esbuild@0.25.12)): + dependencies: + '@swc/core': 1.5.7(@swc/helpers@0.5.13) + '@swc/counter': 0.1.3 + webpack: 5.104.1(@swc/core@1.5.7(@swc/helpers@0.5.13))(esbuild@0.25.12) + sweetalert2@11.4.8: {} swr@2.3.8(react@18.3.1): @@ -36449,6 +36813,18 @@ snapshots: dependencies: memoizerific: 1.11.3 + terser-webpack-plugin@5.3.16(@swc/core@1.5.7(@swc/helpers@0.5.13))(esbuild@0.25.12)(webpack@5.104.1(@swc/core@1.5.7(@swc/helpers@0.5.13))(esbuild@0.25.12)): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.3 + serialize-javascript: 6.0.2 + terser: 5.44.1 + webpack: 5.104.1(@swc/core@1.5.7(@swc/helpers@0.5.13))(esbuild@0.25.12) + optionalDependencies: + '@swc/core': 1.5.7(@swc/helpers@0.5.13) + esbuild: 0.25.12 + terser-webpack-plugin@5.3.16(@swc/core@1.5.7(@swc/helpers@0.5.13))(esbuild@0.25.12)(webpack@5.87.0(@swc/core@1.5.7(@swc/helpers@0.5.13))(esbuild@0.25.12)): dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -36486,6 +36862,10 @@ snapshots: dependencies: any-promise: 1.3.0 + thingies@2.5.0(tslib@2.8.1): + dependencies: + tslib: 2.8.1 + thread-stream@0.15.2: dependencies: real-require: 0.1.0 @@ -36625,6 +37005,10 @@ snapshots: transitivePeerDependencies: - supports-color + tree-dump@1.1.0(tslib@2.8.1): + dependencies: + tslib: 2.8.1 + tree-kill@1.2.2: {} trim-lines@3.0.1: {} @@ -36963,6 +37347,10 @@ snapshots: trough: 2.2.0 vfile: 6.0.3 + unionfs@4.6.0: + dependencies: + fs-monkey: 1.1.0 + unist-util-filter@5.0.1: dependencies: '@types/unist': 3.0.3 @@ -37469,6 +37857,38 @@ snapshots: webpack-virtual-modules@0.5.0: {} + webpack@5.104.1(@swc/core@1.5.7(@swc/helpers@0.5.13))(esbuild@0.25.12): + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.15.0 + acorn-import-phases: 1.0.4(acorn@8.15.0) + browserslist: 4.28.1 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.18.4 + es-module-lexer: 2.0.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.1 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 4.3.3 + tapable: 2.3.0 + terser-webpack-plugin: 5.3.16(@swc/core@1.5.7(@swc/helpers@0.5.13))(esbuild@0.25.12)(webpack@5.104.1(@swc/core@1.5.7(@swc/helpers@0.5.13))(esbuild@0.25.12)) + watchpack: 2.4.4 + webpack-sources: 3.3.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + webpack@5.87.0(@swc/core@1.5.7(@swc/helpers@0.5.13))(esbuild@0.25.12): dependencies: '@types/eslint-scope': 3.7.7 @@ -37585,6 +38005,10 @@ snapshots: dependencies: isexe: 2.0.0 + which@4.0.0: + dependencies: + isexe: 3.1.1 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 diff --git a/tsconfig.base.json b/tsconfig.base.json index 585db7ce..90122b62 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -33,6 +33,7 @@ "@gitroom/react/*": ["libraries/react-shared-libraries/src/*"], "@gitroom/plugins/*": ["libraries/plugins/src/*"], "@gitroom/workers/*": ["apps/workers/src/*"], + "@gitroom/orchestrator/*": ["apps/orchestrator/src/*"], "@gitroom/extension/*": ["apps/extension/src/*"] } }, From da0045428ac3a9ce08658073d324ecef530631f4 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Mon, 5 Jan 2026 15:49:19 +0700 Subject: [PATCH 2/3] feat: temporal - huge refactor --- apps/backend/src/api/api.module.ts | 6 - .../src/api/routes/agencies.controller.ts | 37 - .../src/api/routes/analytics.controller.ts | 47 +- .../src/api/routes/marketplace.controller.ts | 242 ----- .../src/api/routes/messages.controller.ts | 50 - .../src/api/routes/monitor.controller.ts | 26 +- .../src/api/routes/posts.controller.ts | 17 - .../src/api/routes/settings.controller.ts | 87 -- .../src/api/routes/stripe.controller.ts | 37 - apps/backend/src/app.module.ts | 3 - apps/backend/src/main.ts | 8 +- apps/commands/src/command.module.ts | 6 +- apps/commands/src/tasks/check.stars.ts | 52 - apps/cron/.gitignore | 8 - apps/cron/nest-cli.json | 20 - apps/cron/package.json | 14 - apps/cron/src/cron.module.ts | 20 - apps/cron/src/main.ts | 12 - apps/cron/src/tasks/.gitkeep | 0 apps/cron/src/tasks/check.missing.queues.ts | 45 - .../cron/src/tasks/post.now.pending.queues.ts | 43 - apps/cron/tsconfig.build.json | 23 - apps/cron/tsconfig.json | 13 - .../components/layout/settings.component.tsx | 1 - .../components/marketplace/buyer.seller.tsx | 40 - .../src/components/marketplace/buyer.tsx | 568 ----------- .../marketplace/marketplace.provider.tsx | 36 - .../components/marketplace/marketplace.tsx | 3 - .../src/components/marketplace/order.list.tsx | 79 -- .../marketplace/order.top.actions.tsx | 387 -------- .../marketplace/preview.popup.dynamic.tsx | 19 - .../src/components/marketplace/seller.tsx | 241 ----- .../marketplace/special.message.tsx | 430 -------- .../src/components/messages/layout.tsx | 123 --- .../src/components/messages/messages.tsx | 285 ------ .../src/activities/autopost.activity.ts | 27 + .../src/activities/email.activity.ts | 23 + apps/orchestrator/src/app.module.ts | 8 +- apps/orchestrator/src/signals/email.signal.ts | 9 + .../src/workflows/autopost.workflow.ts | 30 + .../src/workflows/digest.email.workflow.ts | 69 ++ apps/orchestrator/src/workflows/index.ts | 2 + .../src/workflows/post.workflow.ts | 38 +- apps/workers/.gitignore | 8 - apps/workers/.swcrc | 38 - apps/workers/nest-cli.json | 20 - apps/workers/package.json | 14 - apps/workers/src/app/app.module.ts | 15 - apps/workers/src/app/posts.controller.ts | 54 -- apps/workers/src/main.ts | 25 - apps/workers/tsconfig.build.json | 23 - apps/workers/tsconfig.json | 11 - .../helpers/src/utils/concurrency.service.ts | 52 - .../bull-mq-transport-new/bull.mq.module.ts | 9 - .../src/bull-mq-transport-new/client.ts | 121 --- .../src/bull-mq-transport-new/strategy.ts | 61 -- .../prisma/autopost/autopost.service.ts | 48 +- .../src/database/prisma/database.module.ts | 12 - .../integrations/integration.service.ts | 11 +- .../marketplace/item.user.repository.ts | 41 - .../prisma/marketplace/item.user.service.ts | 15 - .../prisma/marketplace/messages.repository.ts | 915 ------------------ .../prisma/marketplace/messages.service.ts | 252 ----- .../database/prisma/marketplace/tags.list.ts | 138 --- .../notifications/notification.service.ts | 64 +- .../organizations/organization.repository.ts | 2 + .../database/prisma/posts/posts.service.ts | 168 +--- .../src/database/prisma/schema.prisma | 1 - .../database/prisma/stars/stars.repository.ts | 198 ---- .../database/prisma/stars/stars.service.ts | 485 ---------- .../subscriptions/subscription.service.ts | 33 - .../database/prisma/users/users.repository.ts | 92 -- .../database/prisma/users/users.service.ts | 13 - .../prisma/webhooks/webhooks.service.ts | 78 +- .../dtos/marketplace/add.remove.item.dto.ts | 11 - .../src/dtos/marketplace/audience.dto.ts | 8 - .../src/dtos/marketplace/change.active.dto.ts | 6 - .../src/dtos/marketplace/create.offer.dto.ts | 27 - .../src/dtos/marketplace/items.dto.ts | 12 - .../dtos/marketplace/new.conversation.dto.ts | 10 - .../src/dtos/messages/add.message.ts | 7 - .../src/integrations/integration.manager.ts | 3 +- .../src/integrations/social.abstract.ts | 42 +- .../integrations/social/bluesky.provider.ts | 270 +++--- .../integrations/social/discord.provider.ts | 153 ++- .../integrations/social/facebook.provider.ts | 74 +- .../integrations/social/instagram.provider.ts | 101 +- .../social/instagram.standalone.provider.ts | 19 + .../social/linkedin.page.provider.ts | 19 + .../integrations/social/linkedin.provider.ts | 43 +- .../social/mastodon.custom.provider.ts | 19 + .../integrations/social/mastodon.provider.ts | 134 ++- .../integrations/social/reddit.provider.ts | 93 +- .../integrations/social/threads.provider.ts | 91 +- .../src/integrations/social/x.provider.ts | 193 ++-- .../src/services/email.service.ts | 1 - .../src/services/stripe.service.ts | 144 --- .../src/services/trending.service.ts | 31 - .../nestjs-libraries/src/services/trending.ts | 217 ----- .../src/temporal/temporal.module.ts | 31 +- package.json | 13 +- pnpm-lock.yaml | 19 +- tsconfig.base.json | 2 - 103 files changed, 1108 insertions(+), 6936 deletions(-) delete mode 100644 apps/backend/src/api/routes/agencies.controller.ts delete mode 100644 apps/backend/src/api/routes/marketplace.controller.ts delete mode 100644 apps/backend/src/api/routes/messages.controller.ts delete mode 100644 apps/commands/src/tasks/check.stars.ts delete mode 100644 apps/cron/.gitignore delete mode 100644 apps/cron/nest-cli.json delete mode 100644 apps/cron/package.json delete mode 100644 apps/cron/src/cron.module.ts delete mode 100644 apps/cron/src/main.ts delete mode 100644 apps/cron/src/tasks/.gitkeep delete mode 100644 apps/cron/src/tasks/check.missing.queues.ts delete mode 100644 apps/cron/src/tasks/post.now.pending.queues.ts delete mode 100644 apps/cron/tsconfig.build.json delete mode 100644 apps/cron/tsconfig.json delete mode 100644 apps/frontend/src/components/marketplace/buyer.seller.tsx delete mode 100644 apps/frontend/src/components/marketplace/buyer.tsx delete mode 100644 apps/frontend/src/components/marketplace/marketplace.provider.tsx delete mode 100644 apps/frontend/src/components/marketplace/marketplace.tsx delete mode 100644 apps/frontend/src/components/marketplace/order.list.tsx delete mode 100644 apps/frontend/src/components/marketplace/order.top.actions.tsx delete mode 100644 apps/frontend/src/components/marketplace/preview.popup.dynamic.tsx delete mode 100644 apps/frontend/src/components/marketplace/seller.tsx delete mode 100644 apps/frontend/src/components/marketplace/special.message.tsx delete mode 100644 apps/frontend/src/components/messages/layout.tsx delete mode 100644 apps/frontend/src/components/messages/messages.tsx create mode 100644 apps/orchestrator/src/activities/autopost.activity.ts create mode 100644 apps/orchestrator/src/activities/email.activity.ts create mode 100644 apps/orchestrator/src/signals/email.signal.ts create mode 100644 apps/orchestrator/src/workflows/autopost.workflow.ts create mode 100644 apps/orchestrator/src/workflows/digest.email.workflow.ts delete mode 100644 apps/workers/.gitignore delete mode 100644 apps/workers/.swcrc delete mode 100644 apps/workers/nest-cli.json delete mode 100644 apps/workers/package.json delete mode 100644 apps/workers/src/app/app.module.ts delete mode 100644 apps/workers/src/app/posts.controller.ts delete mode 100644 apps/workers/src/main.ts delete mode 100644 apps/workers/tsconfig.build.json delete mode 100644 apps/workers/tsconfig.json delete mode 100644 libraries/helpers/src/utils/concurrency.service.ts delete mode 100644 libraries/nestjs-libraries/src/bull-mq-transport-new/bull.mq.module.ts delete mode 100644 libraries/nestjs-libraries/src/bull-mq-transport-new/client.ts delete mode 100644 libraries/nestjs-libraries/src/bull-mq-transport-new/strategy.ts delete mode 100644 libraries/nestjs-libraries/src/database/prisma/marketplace/item.user.repository.ts delete mode 100644 libraries/nestjs-libraries/src/database/prisma/marketplace/item.user.service.ts delete mode 100644 libraries/nestjs-libraries/src/database/prisma/marketplace/messages.repository.ts delete mode 100644 libraries/nestjs-libraries/src/database/prisma/marketplace/messages.service.ts delete mode 100644 libraries/nestjs-libraries/src/database/prisma/marketplace/tags.list.ts delete mode 100644 libraries/nestjs-libraries/src/database/prisma/stars/stars.repository.ts delete mode 100644 libraries/nestjs-libraries/src/database/prisma/stars/stars.service.ts delete mode 100644 libraries/nestjs-libraries/src/dtos/marketplace/add.remove.item.dto.ts delete mode 100644 libraries/nestjs-libraries/src/dtos/marketplace/audience.dto.ts delete mode 100644 libraries/nestjs-libraries/src/dtos/marketplace/change.active.dto.ts delete mode 100644 libraries/nestjs-libraries/src/dtos/marketplace/create.offer.dto.ts delete mode 100644 libraries/nestjs-libraries/src/dtos/marketplace/items.dto.ts delete mode 100644 libraries/nestjs-libraries/src/dtos/marketplace/new.conversation.dto.ts delete mode 100644 libraries/nestjs-libraries/src/dtos/messages/add.message.ts delete mode 100644 libraries/nestjs-libraries/src/services/trending.service.ts delete mode 100644 libraries/nestjs-libraries/src/services/trending.ts diff --git a/apps/backend/src/api/api.module.ts b/apps/backend/src/api/api.module.ts index 586dcd2f..291479c7 100644 --- a/apps/backend/src/api/api.module.ts +++ b/apps/backend/src/api/api.module.ts @@ -16,13 +16,10 @@ import { MediaController } from '@gitroom/backend/api/routes/media.controller'; import { UploadModule } from '@gitroom/nestjs-libraries/upload/upload.module'; import { BillingController } from '@gitroom/backend/api/routes/billing.controller'; import { NotificationsController } from '@gitroom/backend/api/routes/notifications.controller'; -import { MarketplaceController } from '@gitroom/backend/api/routes/marketplace.controller'; -import { MessagesController } from '@gitroom/backend/api/routes/messages.controller'; import { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service'; import { ExtractContentService } from '@gitroom/nestjs-libraries/openai/extract.content.service'; import { CodesService } from '@gitroom/nestjs-libraries/services/codes.service'; import { CopilotController } from '@gitroom/backend/api/routes/copilot.controller'; -import { AgenciesController } from '@gitroom/backend/api/routes/agencies.controller'; import { PublicController } from '@gitroom/backend/api/routes/public.controller'; import { RootController } from '@gitroom/backend/api/routes/root.controller'; import { TrackService } from '@gitroom/nestjs-libraries/track/track.service'; @@ -44,10 +41,7 @@ const authenticatedController = [ MediaController, BillingController, NotificationsController, - MarketplaceController, - MessagesController, CopilotController, - AgenciesController, WebhookController, SignatureController, AutopostController, diff --git a/apps/backend/src/api/routes/agencies.controller.ts b/apps/backend/src/api/routes/agencies.controller.ts deleted file mode 100644 index e2849f96..00000000 --- a/apps/backend/src/api/routes/agencies.controller.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Body, Controller, Get, Param, Post } from '@nestjs/common'; -import { User } from '@prisma/client'; -import { ApiTags } from '@nestjs/swagger'; -import { AgenciesService } from '@gitroom/nestjs-libraries/database/prisma/agencies/agencies.service'; -import { GetUserFromRequest } from '@gitroom/nestjs-libraries/user/user.from.request'; -import { CreateAgencyDto } from '@gitroom/nestjs-libraries/dtos/agencies/create.agency.dto'; - -@ApiTags('Agencies') -@Controller('/agencies') -export class AgenciesController { - constructor(private _agenciesService: AgenciesService) {} - @Get('/') - async getAgencyByUser(@GetUserFromRequest() user: User) { - return (await this._agenciesService.getAgencyByUser(user)) || {}; - } - - @Post('/') - async createAgency( - @GetUserFromRequest() user: User, - @Body() body: CreateAgencyDto - ) { - return this._agenciesService.createAgency(user, body); - } - - @Post('/action/:action/:id') - async updateAgency( - @GetUserFromRequest() user: User, - @Param('action') action: string, - @Param('id') id: string - ) { - if (!user.isSuperAdmin) { - return 400; - } - - return this._agenciesService.approveOrDecline(user.email, action, id); - } -} diff --git a/apps/backend/src/api/routes/analytics.controller.ts b/apps/backend/src/api/routes/analytics.controller.ts index 98caae8c..8c3201ef 100644 --- a/apps/backend/src/api/routes/analytics.controller.ts +++ b/apps/backend/src/api/routes/analytics.controller.ts @@ -1,56 +1,13 @@ -import { - Body, - Controller, - Get, - Inject, - Param, - Post, - Query, -} from '@nestjs/common'; +import { Controller, Get, Param, Query } from '@nestjs/common'; import { Organization } from '@prisma/client'; import { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request'; -import { StarsService } from '@gitroom/nestjs-libraries/database/prisma/stars/stars.service'; -import dayjs from 'dayjs'; -import { StarsListDto } from '@gitroom/nestjs-libraries/dtos/analytics/stars.list.dto'; import { ApiTags } from '@nestjs/swagger'; import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service'; -import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager'; @ApiTags('Analytics') @Controller('/analytics') export class AnalyticsController { - constructor( - private _starsService: StarsService, - private _integrationService: IntegrationService - ) {} - @Get('/') - async getStars(@GetOrgFromRequest() org: Organization) { - return this._starsService.getStars(org.id); - } - - @Get('/trending') - async getTrending() { - const todayTrending = dayjs(dayjs().format('YYYY-MM-DDT12:00:00')); - const last = todayTrending.isAfter(dayjs()) - ? todayTrending.subtract(1, 'day') - : todayTrending; - const nextTrending = last.add(1, 'day'); - - return { - last: last.format('YYYY-MM-DD HH:mm:ss'), - predictions: nextTrending.format('YYYY-MM-DD HH:mm:ss'), - }; - } - - @Post('/stars') - async getStarsFilter( - @GetOrgFromRequest() org: Organization, - @Body() starsFilter: StarsListDto - ) { - return { - stars: await this._starsService.getStarsFilter(org.id, starsFilter), - }; - } + constructor(private _integrationService: IntegrationService) {} @Get('/:integration') async getIntegration( diff --git a/apps/backend/src/api/routes/marketplace.controller.ts b/apps/backend/src/api/routes/marketplace.controller.ts deleted file mode 100644 index afb961e9..00000000 --- a/apps/backend/src/api/routes/marketplace.controller.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common'; -import { Organization, User } from '@prisma/client'; -import { ApiTags } from '@nestjs/swagger'; -import { GetUserFromRequest } from '@gitroom/nestjs-libraries/user/user.from.request'; -import { ItemUserService } from '@gitroom/nestjs-libraries/database/prisma/marketplace/item.user.service'; -import { AddRemoveItemDto } from '@gitroom/nestjs-libraries/dtos/marketplace/add.remove.item.dto'; -import { StripeService } from '@gitroom/nestjs-libraries/services/stripe.service'; -import { UsersService } from '@gitroom/nestjs-libraries/database/prisma/users/users.service'; -import { ChangeActiveDto } from '@gitroom/nestjs-libraries/dtos/marketplace/change.active.dto'; -import { ItemsDto } from '@gitroom/nestjs-libraries/dtos/marketplace/items.dto'; -import { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request'; -import { AudienceDto } from '@gitroom/nestjs-libraries/dtos/marketplace/audience.dto'; -import { NewConversationDto } from '@gitroom/nestjs-libraries/dtos/marketplace/new.conversation.dto'; -import { MessagesService } from '@gitroom/nestjs-libraries/database/prisma/marketplace/messages.service'; -import { CreateOfferDto } from '@gitroom/nestjs-libraries/dtos/marketplace/create.offer.dto'; -import { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service'; - -@ApiTags('Marketplace') -@Controller('/marketplace') -export class MarketplaceController { - constructor( - private _itemUserService: ItemUserService, - private _stripeService: StripeService, - private _userService: UsersService, - private _messagesService: MessagesService, - private _postsService: PostsService - ) {} - - @Post('/') - getInfluencers( - @GetOrgFromRequest() organization: Organization, - @GetUserFromRequest() user: User, - @Body() body: ItemsDto - ) { - return this._userService.getMarketplacePeople( - organization.id, - user.id, - body - ); - } - - @Post('/conversation') - createConversation( - @GetUserFromRequest() user: User, - @GetOrgFromRequest() organization: Organization, - @Body() body: NewConversationDto - ) { - return this._messagesService.createConversation( - user.id, - organization.id, - body - ); - } - - @Get('/bank') - connectBankAccount( - @GetUserFromRequest() user: User, - @Query('country') country: string - ) { - return this._stripeService.createAccountProcess( - user.id, - user.email, - country - ); - } - - @Post('/item') - async addItems( - @GetUserFromRequest() user: User, - @Body() body: AddRemoveItemDto - ) { - return this._itemUserService.addOrRemoveItem(body.state, user.id, body.key); - } - - @Post('/active') - async changeActive( - @GetUserFromRequest() user: User, - @Body() body: ChangeActiveDto - ) { - await this._userService.changeMarketplaceActive(user.id, body.active); - } - - @Post('/audience') - async changeAudience( - @GetUserFromRequest() user: User, - @Body() body: AudienceDto - ) { - await this._userService.changeAudienceSize(user.id, body.audience); - } - - @Get('/item') - async getItems(@GetUserFromRequest() user: User) { - return this._itemUserService.getItems(user.id); - } - - @Get('/orders') - async getOrders( - @GetUserFromRequest() user: User, - @GetOrgFromRequest() organization: Organization, - @Query('type') type: 'seller' | 'buyer' - ) { - return this._messagesService.getOrders(user.id, organization.id, type); - } - - @Get('/account') - async getAccount(@GetUserFromRequest() user: User) { - const { account, marketplace, connectedAccount, name, picture, audience } = - await this._userService.getUserByEmail(user.email); - return { - account, - marketplace, - connectedAccount, - fullname: name, - audience, - picture, - }; - } - - @Post('/offer') - async createOffer( - @GetUserFromRequest() user: User, - @Body() body: CreateOfferDto - ) { - return this._messagesService.createOffer(user.id, body); - } - - @Get('/posts/:id') - async post( - @GetUserFromRequest() user: User, - @GetOrgFromRequest() organization: Organization, - @Param('id') id: string - ) { - const getPost = await this._messagesService.getPost( - user.id, - organization.id, - id - ); - if (!getPost) { - return; - } - - return { - ...(await this._postsService.getPost(getPost.organizationId, id)), - providerId: getPost.integration.providerIdentifier, - }; - } - - @Post('/posts/:id/revision') - async revision( - @GetUserFromRequest() user: User, - @GetOrgFromRequest() organization: Organization, - @Param('id') id: string, - @Body('message') message: string - ) { - return this._messagesService.requestRevision( - user.id, - organization.id, - id, - message - ); - } - - @Post('/posts/:id/approve') - async approve( - @GetUserFromRequest() user: User, - @GetOrgFromRequest() organization: Organization, - @Param('id') id: string, - @Body('message') message: string - ) { - return this._messagesService.requestApproved( - user.id, - organization.id, - id, - message - ); - } - - @Post('/posts/:id/cancel') - async cancel( - @GetOrgFromRequest() organization: Organization, - @Param('id') id: string - ) { - return this._messagesService.requestCancel(organization.id, id); - } - - @Post('/offer/:id/complete') - async completeOrder( - @GetOrgFromRequest() organization: Organization, - @Param('id') id: string - ) { - const order = await this._messagesService.completeOrderAndPay( - organization.id, - id - ); - - if (!order) { - return; - } - - try { - await this._stripeService.payout( - id, - order.charge, - order.account, - order.price - ); - } catch (e) { - await this._messagesService.payoutProblem( - id, - order.sellerId, - order.price - ); - } - await this._messagesService.completeOrder(id); - } - - @Post('/orders/:id/payment') - async payOrder( - @GetUserFromRequest() user: User, - @GetOrgFromRequest() organization: Organization, - @Param('id') id: string - ) { - const orderDetails = await this._messagesService.getOrderDetails( - user.id, - organization.id, - id - ); - const payment = await this._stripeService.payAccountStepOne( - user.id, - organization, - orderDetails.seller, - orderDetails.order.id, - orderDetails.order.ordersItems.map((p) => ({ - quantity: p.quantity, - integrationType: p.integration.providerIdentifier, - price: p.price, - })), - orderDetails.order.messageGroupId - ); - return payment; - } -} diff --git a/apps/backend/src/api/routes/messages.controller.ts b/apps/backend/src/api/routes/messages.controller.ts deleted file mode 100644 index 2af0d9b0..00000000 --- a/apps/backend/src/api/routes/messages.controller.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Body, Controller, Get, Param, Post } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { MessagesService } from '@gitroom/nestjs-libraries/database/prisma/marketplace/messages.service'; -import { GetUserFromRequest } from '@gitroom/nestjs-libraries/user/user.from.request'; -import { Organization, User } from '@prisma/client'; -import { AddMessageDto } from '@gitroom/nestjs-libraries/dtos/messages/add.message'; -import { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request'; - -@ApiTags('Messages') -@Controller('/messages') -export class MessagesController { - constructor(private _messagesService: MessagesService) {} - - @Get('/') - getMessagesGroup( - @GetUserFromRequest() user: User, - @GetOrgFromRequest() organization: Organization - ) { - return this._messagesService.getMessagesGroup(user.id, organization.id); - } - - @Get('/:groupId/:page') - getMessages( - @GetUserFromRequest() user: User, - @GetOrgFromRequest() organization: Organization, - @Param('groupId') groupId: string, - @Param('page') page: string - ) { - return this._messagesService.getMessages( - user.id, - organization.id, - groupId, - +page - ); - } - @Post('/:groupId') - createMessage( - @GetUserFromRequest() user: User, - @GetOrgFromRequest() organization: Organization, - @Param('groupId') groupId: string, - @Body() message: AddMessageDto - ) { - return this._messagesService.createMessage( - user.id, - organization.id, - groupId, - message - ); - } -} diff --git a/apps/backend/src/api/routes/monitor.controller.ts b/apps/backend/src/api/routes/monitor.controller.ts index 6409def7..df4a857c 100644 --- a/apps/backend/src/api/routes/monitor.controller.ts +++ b/apps/backend/src/api/routes/monitor.controller.ts @@ -1,30 +1,14 @@ -import { Controller, Get, HttpException, Param } from '@nestjs/common'; +import { Controller, Get, Param } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { BullMqClient } from '@gitroom/nestjs-libraries/bull-mq-transport-new/client'; @ApiTags('Monitor') @Controller('/monitor') export class MonitorController { - constructor(private _workerServiceProducer: BullMqClient) {} - @Get('/queue/:name') async getMessagesGroup(@Param('name') name: string) { - const { valid } = - await this._workerServiceProducer.checkForStuckWaitingJobs(name); - - if (valid) { - return { - status: 'success', - message: `Queue ${name} is healthy.`, - }; - } - - throw new HttpException( - { - status: 'error', - message: `Queue ${name} has stuck waiting jobs.`, - }, - 503 - ); + return { + status: 'success', + message: `Queue ${name} is healthy.`, + }; } } diff --git a/apps/backend/src/api/routes/posts.controller.ts b/apps/backend/src/api/routes/posts.controller.ts index 18cd1bfa..c51852ee 100644 --- a/apps/backend/src/api/routes/posts.controller.ts +++ b/apps/backend/src/api/routes/posts.controller.ts @@ -13,10 +13,8 @@ import { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/po import { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request'; import { Organization, User } from '@prisma/client'; import { GetPostsDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.dto'; -import { StarsService } from '@gitroom/nestjs-libraries/database/prisma/stars/stars.service'; import { CheckPolicies } from '@gitroom/backend/services/auth/permissions/permissions.ability'; import { ApiTags } from '@nestjs/swagger'; -import { MessagesService } from '@gitroom/nestjs-libraries/database/prisma/marketplace/messages.service'; import { GeneratorDto } from '@gitroom/nestjs-libraries/dtos/generator/generator.dto'; import { CreateGeneratedPostsDto } from '@gitroom/nestjs-libraries/dtos/generator/create.generated.posts.dto'; import { AgentGraphService } from '@gitroom/nestjs-libraries/agent/agent.graph.service'; @@ -31,8 +29,6 @@ import { AuthorizationActions, Sections } from '@gitroom/backend/services/auth/p export class PostsController { constructor( private _postsService: PostsService, - private _starsService: StarsService, - private _messagesService: MessagesService, private _agentGraphService: AgentGraphService, private _shortLinkService: ShortLinkService ) {} @@ -50,14 +46,6 @@ export class PostsController { return { ask: this._shortLinkService.askShortLinkedin(body.messages) }; } - @Get('/marketplace/:id') - async getMarketplacePosts( - @GetOrgFromRequest() org: Organization, - @Param('id') id: string - ) { - return this._messagesService.getMarketplaceAvailableOffers(org.id, id); - } - @Post('/:id/comments') async createComment( @GetOrgFromRequest() org: Organization, @@ -115,11 +103,6 @@ export class PostsController { return { date: await this._postsService.findFreeDateTime(org.id, id) }; } - @Get('/predict-trending') - predictTrending() { - return this._starsService.predictTrending(); - } - @Get('/old') oldPosts( @GetOrgFromRequest() org: Organization, diff --git a/apps/backend/src/api/routes/settings.controller.ts b/apps/backend/src/api/routes/settings.controller.ts index bcc5044e..c92537b4 100644 --- a/apps/backend/src/api/routes/settings.controller.ts +++ b/apps/backend/src/api/routes/settings.controller.ts @@ -1,7 +1,6 @@ import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common'; import { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request'; import { Organization } from '@prisma/client'; -import { StarsService } from '@gitroom/nestjs-libraries/database/prisma/stars/stars.service'; import { CheckPolicies } from '@gitroom/backend/services/auth/permissions/permissions.ability'; import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service'; import { AddTeamMemberDto } from '@gitroom/nestjs-libraries/dtos/settings/add.team.member.dto'; @@ -12,95 +11,9 @@ import { AuthorizationActions, Sections } from '@gitroom/backend/services/auth/p @Controller('/settings') export class SettingsController { constructor( - private _starsService: StarsService, private _organizationService: OrganizationService ) {} - @Get('/github') - @CheckPolicies([AuthorizationActions.Create, Sections.ADMIN]) - async getConnectedGithubAccounts(@GetOrgFromRequest() org: Organization) { - return { - github: ( - await this._starsService.getGitHubRepositoriesByOrgId(org.id) - ).map((repo) => ({ - id: repo.id, - login: repo.login, - })), - }; - } - - @Post('/github') - @CheckPolicies([AuthorizationActions.Create, Sections.ADMIN]) - async addGitHub( - @GetOrgFromRequest() org: Organization, - @Body('code') code: string - ) { - if (!code) { - throw new Error('No code provided'); - } - await this._starsService.addGitHub(org.id, code); - } - - @Get('/github/url') - @CheckPolicies([AuthorizationActions.Create, Sections.ADMIN]) - authUrl() { - return { - url: `https://github.com/login/oauth/authorize?client_id=${ - process.env.GITHUB_CLIENT_ID - }&scope=${encodeURIComponent( - 'user:email' - )}&redirect_uri=${encodeURIComponent( - `${process.env.FRONTEND_URL}/settings` - )}`, - }; - } - - @Get('/organizations/:id') - @CheckPolicies([AuthorizationActions.Create, Sections.ADMIN]) - async getOrganizations( - @GetOrgFromRequest() org: Organization, - @Param('id') id: string - ) { - return { - organizations: await this._starsService.getOrganizations(org.id, id), - }; - } - - @Get('/organizations/:id/:github') - @CheckPolicies([AuthorizationActions.Create, Sections.ADMIN]) - async getRepositories( - @GetOrgFromRequest() org: Organization, - @Param('id') id: string, - @Param('github') github: string - ) { - return { - repositories: await this._starsService.getRepositoriesOfOrganization( - org.id, - id, - github - ), - }; - } - - @Post('/organizations/:id') - @CheckPolicies([AuthorizationActions.Create, Sections.ADMIN]) - async updateGitHubLogin( - @GetOrgFromRequest() org: Organization, - @Param('id') id: string, - @Body('login') login: string - ) { - return this._starsService.updateGitHubLogin(org.id, id, login); - } - - @Delete('/repository/:id') - @CheckPolicies([AuthorizationActions.Create, Sections.ADMIN]) - async deleteRepository( - @GetOrgFromRequest() org: Organization, - @Param('id') id: string - ) { - return this._starsService.deleteRepository(org.id, id); - } - @Get('/team') @CheckPolicies( [AuthorizationActions.Create, Sections.TEAM_MEMBERS], diff --git a/apps/backend/src/api/routes/stripe.controller.ts b/apps/backend/src/api/routes/stripe.controller.ts index 12c6a40a..37aadf24 100644 --- a/apps/backend/src/api/routes/stripe.controller.ts +++ b/apps/backend/src/api/routes/stripe.controller.ts @@ -1,47 +1,19 @@ import { Controller, - Get, - Header, HttpException, - Param, Post, RawBodyRequest, Req, } from '@nestjs/common'; import { StripeService } from '@gitroom/nestjs-libraries/services/stripe.service'; import { ApiTags } from '@nestjs/swagger'; -import { CodesService } from '@gitroom/nestjs-libraries/services/codes.service'; @ApiTags('Stripe') @Controller('/stripe') export class StripeController { constructor( private readonly _stripeService: StripeService, - private readonly _codesService: CodesService ) {} - @Post('/connect') - stripeConnect(@Req() req: RawBodyRequest) { - const event = this._stripeService.validateRequest( - req.rawBody, - // @ts-ignore - req.headers['stripe-signature'], - process.env.STRIPE_SIGNING_KEY_CONNECT - ); - - // Maybe it comes from another stripe webhook - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - if (event?.data?.object?.metadata?.service !== 'gitroom') { - return { ok: true }; - } - - switch (event.type) { - case 'account.updated': - return this._stripeService.updateAccount(event); - default: - return { ok: true }; - } - } @Post('/') stripe(@Req() req: RawBodyRequest) { @@ -66,8 +38,6 @@ export class StripeController { switch (event.type) { case 'invoice.payment_succeeded': return this._stripeService.paymentSucceeded(event); - case 'account.updated': - return this._stripeService.updateAccount(event); case 'customer.subscription.created': return this._stripeService.createSubscription(event); case 'customer.subscription.updated': @@ -81,11 +51,4 @@ export class StripeController { throw new HttpException(e, 500); } } - - @Get('/lifetime-deal-codes/:provider') - @Header('Content-disposition', 'attachment; filename=codes.csv') - @Header('Content-type', 'text/csv') - async getStripeCodes(@Param('provider') providerToken: string) { - return this._codesService.generateCodes(providerToken); - } } diff --git a/apps/backend/src/app.module.ts b/apps/backend/src/app.module.ts index d4f800d4..1a5f89b0 100644 --- a/apps/backend/src/app.module.ts +++ b/apps/backend/src/app.module.ts @@ -3,7 +3,6 @@ import { DatabaseModule } from '@gitroom/nestjs-libraries/database/prisma/databa import { ApiModule } from '@gitroom/backend/api/api.module'; import { APP_GUARD } from '@nestjs/core'; import { PoliciesGuard } from '@gitroom/backend/services/auth/permissions/permissions.guard'; -import { BullMqModule } from '@gitroom/nestjs-libraries/bull-mq-transport-new/bull.mq.module'; import { PublicApiModule } from '@gitroom/backend/public-api/public.api.module'; import { ThrottlerBehindProxyGuard } from '@gitroom/nestjs-libraries/throttler/throttler.provider'; import { ThrottlerModule } from '@nestjs/throttler'; @@ -20,7 +19,6 @@ import { TemporalRegisterMissingSearchAttributesModule } from '@gitroom/nestjs-l @Module({ imports: [ SentryModule.forRoot(), - BullMqModule, DatabaseModule, ApiModule, PublicApiModule, @@ -50,7 +48,6 @@ import { TemporalRegisterMissingSearchAttributesModule } from '@gitroom/nestjs-l }, ], exports: [ - BullMqModule, DatabaseModule, ApiModule, PublicApiModule, diff --git a/apps/backend/src/main.ts b/apps/backend/src/main.ts index d1ee6fb7..3f1c1ae5 100644 --- a/apps/backend/src/main.ts +++ b/apps/backend/src/main.ts @@ -3,6 +3,8 @@ initializeSentry('backend', true); import { loadSwagger } from '@gitroom/helpers/swagger/load.swagger'; import { json } from 'express'; +import { Runtime } from '@temporalio/worker'; +Runtime.install({ shutdownSignals: [] }); process.env.TZ = 'UTC'; @@ -21,7 +23,11 @@ async function start() { rawBody: true, cors: { ...(!process.env.NOT_SECURED ? { credentials: true } : {}), - allowedHeaders: ['Content-Type', 'Authorization', 'x-copilotkit-runtime-client-gql-version'], + allowedHeaders: [ + 'Content-Type', + 'Authorization', + 'x-copilotkit-runtime-client-gql-version', + ], exposedHeaders: [ 'reload', 'onboarding', diff --git a/apps/commands/src/command.module.ts b/apps/commands/src/command.module.ts index 724182b5..6320bd30 100644 --- a/apps/commands/src/command.module.ts +++ b/apps/commands/src/command.module.ts @@ -1,17 +1,15 @@ import { Module } from '@nestjs/common'; import { CommandModule as ExternalCommandModule } from 'nestjs-command'; -import { CheckStars } from './tasks/check.stars'; import { DatabaseModule } from '@gitroom/nestjs-libraries/database/prisma/database.module'; import { RefreshTokens } from './tasks/refresh.tokens'; -import { BullMqModule } from '@gitroom/nestjs-libraries/bull-mq-transport-new/bull.mq.module'; import { ConfigurationTask } from './tasks/configuration'; import { AgentRun } from './tasks/agent.run'; import { AgentModule } from '@gitroom/nestjs-libraries/agent/agent.module'; @Module({ - imports: [ExternalCommandModule, DatabaseModule, BullMqModule, AgentModule], + imports: [ExternalCommandModule, DatabaseModule, AgentModule], controllers: [], - providers: [CheckStars, RefreshTokens, ConfigurationTask, AgentRun], + providers: [RefreshTokens, ConfigurationTask, AgentRun], get exports() { return [...this.imports, ...this.providers]; }, diff --git a/apps/commands/src/tasks/check.stars.ts b/apps/commands/src/tasks/check.stars.ts deleted file mode 100644 index b80ce434..00000000 --- a/apps/commands/src/tasks/check.stars.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Command, Positional } from 'nestjs-command'; -import { Injectable } from '@nestjs/common'; -import { BullMqClient } from '@gitroom/nestjs-libraries/bull-mq-transport-new/client'; - -@Injectable() -export class CheckStars { - constructor(private _workerServiceProducer: BullMqClient) {} - @Command({ - command: 'sync:stars ', - describe: 'Sync stars for a login', - }) - async create( - @Positional({ - name: 'login', - describe: 'login {owner}/{repo}', - type: 'string', - }) - login: string - ) { - this._workerServiceProducer - .emit('check_stars', { payload: { login } }) - .subscribe(); - return true; - } - - @Command({ - command: 'sync:all_stars ', - describe: 'Sync all stars for a login', - }) - async syncAllStars( - @Positional({ - name: 'login', - describe: 'login {owner}/{repo}', - type: 'string', - }) - login: string - ) { - this._workerServiceProducer - .emit('sync_all_stars', { payload: { login } }) - .subscribe(); - return true; - } - - @Command({ - command: 'sync:trending', - describe: 'Sync trending', - }) - async syncTrending() { - this._workerServiceProducer.emit('sync_trending', {}).subscribe(); - return true; - } -} diff --git a/apps/cron/.gitignore b/apps/cron/.gitignore deleted file mode 100644 index 0dff6fb6..00000000 --- a/apps/cron/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -dist/ -node_modules/ -[._]*.s[a-v][a-z] -[._]*.sw[a-p] -[._]s[a-rt-v][a-z] -[._]ss[a-gi-z] -[._]sw[a-p] - diff --git a/apps/cron/nest-cli.json b/apps/cron/nest-cli.json deleted file mode 100644 index 1fcd4a33..00000000 --- a/apps/cron/nest-cli.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/nest-cli", - "collection": "@nestjs/schematics", - "monorepo": false, - "sourceRoot": "src", - "entryFile": "../../dist/cron/apps/cron/src/main", - "language": "ts", - "generateOptions": { - "spec": false - }, - "compilerOptions": { - "manualRestart": true, - "tsConfigPath": "./tsconfig.build.json", - "webpack": false, - "deleteOutDir": true, - "assets": [], - "watchAssets": false, - "plugins": [] - } -} diff --git a/apps/cron/package.json b/apps/cron/package.json deleted file mode 100644 index 6276445d..00000000 --- a/apps/cron/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "postiz-cron", - "version": "1.0.0", - "description": "", - "scripts": { - "dev": "dotenv -e ../../.env -- nest start --watch --entryFile=./apps/cron/src/main", - "build": "cross-env NODE_ENV=production nest build", - "start": "dotenv -e ../../.env -- node --experimental-require-module ./dist/apps/cron/src/main.js", - "pm2": "pm2 start pnpm --name cron -- start" - }, - "keywords": [], - "author": "", - "license": "ISC" -} diff --git a/apps/cron/src/cron.module.ts b/apps/cron/src/cron.module.ts deleted file mode 100644 index 01eb6ed7..00000000 --- a/apps/cron/src/cron.module.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Module } from '@nestjs/common'; -import { ScheduleModule } from '@nestjs/schedule'; -import { DatabaseModule } from '@gitroom/nestjs-libraries/database/prisma/database.module'; -import { BullMqModule } from '@gitroom/nestjs-libraries/bull-mq-transport-new/bull.mq.module'; -import { SentryModule } from '@sentry/nestjs/setup'; -import { FILTER } from '@gitroom/nestjs-libraries/sentry/sentry.exception'; -import { CheckMissingQueues } from '@gitroom/cron/tasks/check.missing.queues'; -import { PostNowPendingQueues } from '@gitroom/cron/tasks/post.now.pending.queues'; - -@Module({ - imports: [ - SentryModule.forRoot(), - DatabaseModule, - ScheduleModule.forRoot(), - BullMqModule, - ], - controllers: [], - providers: [FILTER, CheckMissingQueues, PostNowPendingQueues], -}) -export class CronModule {} diff --git a/apps/cron/src/main.ts b/apps/cron/src/main.ts deleted file mode 100644 index cc2684f9..00000000 --- a/apps/cron/src/main.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { initializeSentry } from '@gitroom/nestjs-libraries/sentry/initialize.sentry'; -initializeSentry('cron'); - -import { NestFactory } from '@nestjs/core'; -import { CronModule } from './cron.module'; - -async function bootstrap() { - // some comment again - await NestFactory.createApplicationContext(CronModule); -} - -bootstrap(); diff --git a/apps/cron/src/tasks/.gitkeep b/apps/cron/src/tasks/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/cron/src/tasks/check.missing.queues.ts b/apps/cron/src/tasks/check.missing.queues.ts deleted file mode 100644 index 0664676c..00000000 --- a/apps/cron/src/tasks/check.missing.queues.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Cron } from '@nestjs/schedule'; -import { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service'; -import { BullMqClient } from '@gitroom/nestjs-libraries/bull-mq-transport-new/client'; -import dayjs from 'dayjs'; - -@Injectable() -export class CheckMissingQueues { - constructor( - private _postService: PostsService, - private _workerServiceProducer: BullMqClient - ) {} - @Cron('0 * * * *') - async handleCron() { - const list = await this._postService.searchForMissingThreeHoursPosts(); - const notExists = ( - await Promise.all( - list.map(async (p) => ({ - id: p.id, - publishDate: p.publishDate, - isJob: - ['delayed', 'waiting'].indexOf( - await this._workerServiceProducer - .getQueue('post') - .getJobState(p.id) - ) > -1, - })) - ) - ).filter((p) => !p.isJob); - - - for (const job of notExists) { - this._workerServiceProducer.emit('post', { - id: job.id, - options: { - delay: dayjs(job.publishDate).diff(dayjs(), 'millisecond'), - }, - payload: { - id: job.id, - delay: dayjs(job.publishDate).diff(dayjs(), 'millisecond'), - }, - }); - } - } -} diff --git a/apps/cron/src/tasks/post.now.pending.queues.ts b/apps/cron/src/tasks/post.now.pending.queues.ts deleted file mode 100644 index 69105304..00000000 --- a/apps/cron/src/tasks/post.now.pending.queues.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Cron } from '@nestjs/schedule'; -import { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service'; -import { BullMqClient } from '@gitroom/nestjs-libraries/bull-mq-transport-new/client'; - -@Injectable() -export class PostNowPendingQueues { - constructor( - private _postService: PostsService, - private _workerServiceProducer: BullMqClient - ) {} - @Cron('*/16 * * * *') - async handleCron() { - const list = await this._postService.checkPending15minutesBack(); - const notExists = ( - await Promise.all( - list.map(async (p) => ({ - id: p.id, - publishDate: p.publishDate, - isJob: - ['delayed', 'waiting'].indexOf( - await this._workerServiceProducer - .getQueue('post') - .getJobState(p.id) - ) > -1, - })) - ) - ).filter((p) => !p.isJob); - - for (const job of notExists) { - this._workerServiceProducer.emit('post', { - id: job.id, - options: { - delay: 0, - }, - payload: { - id: job.id, - delay: 0, - }, - }); - } - } -} diff --git a/apps/cron/tsconfig.build.json b/apps/cron/tsconfig.build.json deleted file mode 100644 index bf14cec5..00000000 --- a/apps/cron/tsconfig.build.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "extends": "./tsconfig.json", - "exclude": ["node_modules", "test", "dist", "**/*spec.ts"], - "compilerOptions": { - "module": "CommonJS", - "resolveJsonModule": true, - "declaration": true, - "removeComments": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "allowSyntheticDefaultImports": true, - "target": "ES2021", - "sourceMap": true, - "incremental": true, - "skipLibCheck": true, - "strictNullChecks": false, - "noImplicitAny": false, - "strictBindCallApply": false, - "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false, - "outDir": "./dist" - } -} diff --git a/apps/cron/tsconfig.json b/apps/cron/tsconfig.json deleted file mode 100644 index 77fa915a..00000000 --- a/apps/cron/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "module": "commonjs", - "declaration": true, - "removeComments": true, - "allowSyntheticDefaultImports": true, - "noLib": false, - "target": "ES2021", - "sourceMap": true, - "esModuleInterop": true, - } -} diff --git a/apps/frontend/src/components/layout/settings.component.tsx b/apps/frontend/src/components/layout/settings.component.tsx index 90c16e4e..fc1f6a28 100644 --- a/apps/frontend/src/components/layout/settings.component.tsx +++ b/apps/frontend/src/components/layout/settings.component.tsx @@ -77,7 +77,6 @@ export const SettingsPopup: FC<{ return; } toast.show(t('profile_updated', 'Profile updated')); - swr.mutate('/marketplace/account'); close(); }, []); diff --git a/apps/frontend/src/components/marketplace/buyer.seller.tsx b/apps/frontend/src/components/marketplace/buyer.seller.tsx deleted file mode 100644 index 1e910ce7..00000000 --- a/apps/frontend/src/components/marketplace/buyer.seller.tsx +++ /dev/null @@ -1,40 +0,0 @@ -'use client'; - -import { FC } from 'react'; -import { usePathname } from 'next/navigation'; -import clsx from 'clsx'; -import Link from 'next/link'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -export const BuyerSeller: FC = () => { - const path = usePathname(); - const t = useT(); - const pathComputed = path === '/marketplace' ? '/marketplace/seller' : path; - return ( -
-
-
- -1 && - 'bg-forth text-white' - )} - > - {t('seller', 'Seller')} - - -1 && - 'bg-forth text-white' - )} - > - {t('buyer', 'Buyer')} - -
-
-
- ); -}; diff --git a/apps/frontend/src/components/marketplace/buyer.tsx b/apps/frontend/src/components/marketplace/buyer.tsx deleted file mode 100644 index 064c2faf..00000000 --- a/apps/frontend/src/components/marketplace/buyer.tsx +++ /dev/null @@ -1,568 +0,0 @@ -'use client'; - -import React, { - FC, - Fragment, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; -import { Checkbox } from '@gitroom/react/form/checkbox'; -import { useRouter, useSearchParams } from 'next/navigation'; -import clsx from 'clsx'; -import { Button } from '@gitroom/react/form/button'; -import { - allTagsOptions, - tagsList, -} from '@gitroom/nestjs-libraries/database/prisma/marketplace/tags.list'; -import { capitalize, chunk, fill } from 'lodash'; -import useSWR from 'swr'; -import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; -import { useModals } from '@gitroom/frontend/components/layout/new-modal'; -import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component'; -import { Textarea } from '@gitroom/react/form/textarea'; -import { FormProvider, SubmitHandler, useForm } from 'react-hook-form'; -import { classValidatorResolver } from '@hookform/resolvers/class-validator'; -import { NewConversationDto } from '@gitroom/nestjs-libraries/dtos/marketplace/new.conversation.dto'; -import { OrderList } from '@gitroom/frontend/components/marketplace/order.list'; -import { useT } from '@gitroom/react/translation/get.transation.service.client'; -export interface Root { - list: List[]; - count: number; -} -export interface List { - id: string; - name: any; - bio: string; - audience: number; - picture: { - id: string; - path: string; - }; - organizations: Organization[]; - items: Item[]; -} -export interface Organization { - organization: Organization2; -} -export interface Organization2 { - Integration: Integration[]; -} -export interface Integration { - providerIdentifier: string; -} -export interface Item { - key: string; -} -export const LabelCheckbox: FC<{ - label: string; - name: string; - value: string; - checked: boolean; - onChange: (value: string, status: boolean) => void; -}> = (props) => { - const { label, name, value, checked, onChange } = props; - const ref = useRef(null); - const [innerCheck, setInnerCheck] = useState(checked); - const change = useCallback(() => { - setInnerCheck(!innerCheck); - onChange(value, !innerCheck); - }, [innerCheck]); - return ( -
- - -
- ); -}; -const Pagination: FC<{ - results: number; -}> = (props) => { - const { results } = props; - const router = useRouter(); - const search = useSearchParams(); - const page = +(parseInt(search.get('page')!) || 1) - 1; - const t = useT(); - const from = page * 8; - const to = (page + 1) * 8; - const pagesArray = useMemo(() => { - return Array.from( - { - length: Math.ceil(results / 8), - }, - (_, i) => i + 1 - ); - }, [results]); - const changePage = useCallback( - (newPage: number) => () => { - const params = new URLSearchParams(window.location.search); - params.set('page', String(newPage)); - router.replace('?' + params.toString(), { - scroll: true, - }); - }, - [page] - ); - if (results < 8) { - return null; - } - return ( -
-
- {t('showing', 'Showing')} - {from + 1} - {t('to', 'to')} - {to > results ? results : to} - {t('from', 'from')} - {results} - {t('results', 'Results')} -
-
- {page > 0 && ( -
- - - - - - - - - - -
- )} - {pagesArray.map((p) => ( -
- {p} -
- ))} - {page + 1 < pagesArray[pagesArray.length - 1] && ( - - - - )} -
-
- ); -}; -export const Options: FC<{ - title: string; - options: Array<{ - key: string; - value: string; - }>; - onChange?: (key: string, value: boolean) => void; - preSelected?: string[]; - rows?: number; - search: boolean; -}> = (props) => { - const { title, onChange, search, preSelected } = props; - const query = 'services'; - const [selected, setPreSelected] = useState( - preSelected?.slice(0) || [] - ); - const rows = props.rows || 1; - const optionsGroupList = chunk( - props.options, - Math.ceil(props.options.length / rows) - ); - const optionsGroup = - optionsGroupList.length < rows - ? [ - ...optionsGroupList, - ...fill(Array(rows - optionsGroupList.length), []), - ] - : optionsGroupList; - const router = useRouter(); - const searchParams = (useSearchParams().get(query) || '')?.split(',') || []; - const change = (value: string, state: boolean) => { - if (onChange) { - onChange(value, state); - } - if (!search) { - return; - } - const getAll = new URLSearchParams(window.location.search).get(query); - const splitAll = (getAll?.split(',') || []).filter((f) => f); - if (state) { - splitAll?.push(value); - } else { - splitAll?.splice(splitAll.indexOf(value), 1); - } - const params = new URLSearchParams(window.location.search); - if (!splitAll?.length) { - params.delete(query); - } else { - params.set(query, splitAll?.join(',') || ''); - } - router.replace('?' + params.toString()); - return params.toString(); - }; - return ( - <> -
- {title} -
-
- {optionsGroup.map((options, key) => ( -
- {options.map((option) => ( -
- -1 || - searchParams.indexOf(option.key) > -1 - } - name={query} - onChange={change} - /> -
- ))} -
- ))} -
- - ); -}; -export const RequestService: FC<{ - toId: string; - name: string; -}> = (props) => { - const { toId, name } = props; - const router = useRouter(); - const fetch = useFetch(); - const modal = useModals(); - const resolver = useMemo(() => { - return classValidatorResolver(NewConversationDto); - }, []); - const form = useForm({ - resolver, - values: { - to: toId, - message: '', - }, - }); - const close = useCallback(() => { - return modal.closeAll(); - }, []); - - const t = useT(); - - const createConversation: SubmitHandler = useCallback( - async (data) => { - const { id } = await ( - await fetch('/marketplace/conversation', { - method: 'POST', - body: JSON.stringify(data), - }) - ).json(); - close(); - router.push(`/messages/${id}`); - }, - [] - ); - return ( -
- -
- -
- -