feat: before split

This commit is contained in:
Nevo David 2026-01-05 11:35:15 +07:00
parent 9e0eff7e5a
commit d8a6215155
28 changed files with 1518 additions and 476 deletions

View File

@ -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,

8
apps/orchestrator/.gitignore vendored Normal file
View File

@ -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]

38
apps/orchestrator/.swcrc Normal file
View File

@ -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
}

View File

@ -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": []
}
}

View File

@ -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"
}

View File

@ -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<false | AuthTokenDetails> {
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;
}
}
}

View File

@ -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 {}

View File

@ -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();

View File

@ -0,0 +1 @@
export * from './post.workflow';

View File

@ -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<PostActivity>({
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,
},
]),
});
}
}
}

View File

@ -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"
}
}

View File

@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"allowSyntheticDefaultImports": true,
"target": "es2017",
"sourceMap": true
}
}

View File

@ -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
);
}
}
}

View File

@ -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 {

View File

@ -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 <T>(
async () => {
try {
return await func();
} catch (err) {}
} catch (err) {
console.log(err);
}
}
);
} catch (err) {
console.log(err);
throw new BadBody(
identifier,
JSON.stringify({}),

View File

@ -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(

View File

@ -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: {

View File

@ -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<Post[]> {
public async updateTags(orgId: string, post: Post[]): Promise<Post[]> {
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<Partial<{ postId: string; releaseURL: string }> | 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<any[]> {
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) {

View File

@ -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[]) {

View File

@ -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}`,
},
}

View File

@ -77,6 +77,15 @@ export interface ISocialMediaIntegration {
postDetails: PostDetails[],
integration: Integration
): Promise<PostResponse[]>; // Schedules a new post
comment?(
id: string,
postId: string,
lastCommentId: string | undefined,
accessToken: string,
postDetails: PostDetails[],
integration: Integration
): Promise<PostResponse[]>; // 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,

View File

@ -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'

View File

@ -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,
},
},
}
: {}),
});
};

View File

@ -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<void> {
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 {}

View File

@ -0,0 +1,14 @@
import {
defineSearchAttributeKey,
SearchAttributeType,
} from '@temporalio/common';
export const organizationId = defineSearchAttributeKey(
'organizationId',
SearchAttributeType.TEXT
);
export const postId = defineSearchAttributeKey(
'postId',
SearchAttributeType.TEXT
);

View File

@ -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",

View File

@ -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

View File

@ -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/*"]
}
},