From e153ab0a9bbf557aaadec2ba2419cdf890d9252d Mon Sep 17 00:00:00 2001 From: Santosh Bhandari Date: Tue, 12 May 2026 14:41:59 +0545 Subject: [PATCH 1/4] feat: track post creation method (WEB/API/MCP/AUTOPOST) --- .../src/api/routes/posts.controller.ts | 2 +- .../v1/public.integrations.controller.ts | 2 +- .../src/app/(app)/(preview)/p/[id]/page.tsx | 7 ++ .../src/components/launches/calendar.tsx | 11 +++ .../launches/creation.method.badge.tsx | 49 ++++++++++ .../components/new-launch/manage.modal.tsx | 7 +- .../components/preview/preview.wrapper.tsx | 2 + .../helpers/src/utils/posts.list.minify.ts | 1 + .../chat/tools/integration.schedule.post.ts | 2 +- .../prisma/autopost/autopost.service.ts | 2 +- .../database/prisma/posts/posts.repository.ts | 11 ++- .../database/prisma/posts/posts.service.ts | 92 +++++++++++-------- .../src/database/prisma/schema.prisma | 10 ++ 13 files changed, 154 insertions(+), 44 deletions(-) create mode 100644 apps/frontend/src/components/launches/creation.method.badge.tsx diff --git a/apps/backend/src/api/routes/posts.controller.ts b/apps/backend/src/api/routes/posts.controller.ts index 790df94f..14756ce2 100644 --- a/apps/backend/src/api/routes/posts.controller.ts +++ b/apps/backend/src/api/routes/posts.controller.ts @@ -175,7 +175,7 @@ export class PostsController { ) { console.log(JSON.stringify(rawBody, null, 2)); const body = await this._postsService.mapTypeToPost(rawBody, org.id); - return this._postsService.createPost(org.id, body); + return this._postsService.createPost(org.id, body, 'WEB'); } @Post('/generator/draft') diff --git a/apps/backend/src/public-api/routes/v1/public.integrations.controller.ts b/apps/backend/src/public-api/routes/v1/public.integrations.controller.ts index 38ce060b..9eef59f1 100644 --- a/apps/backend/src/public-api/routes/v1/public.integrations.controller.ts +++ b/apps/backend/src/public-api/routes/v1/public.integrations.controller.ts @@ -189,7 +189,7 @@ export class PublicIntegrationsController { } console.log(JSON.stringify(body, null, 2)); - return this._postsService.createPost(org.id, body); + return this._postsService.createPost(org.id, body, 'API'); } @Delete('/posts/:id') diff --git a/apps/frontend/src/app/(app)/(preview)/p/[id]/page.tsx b/apps/frontend/src/app/(app)/(preview)/p/[id]/page.tsx index fb12181d..f5dc7b46 100644 --- a/apps/frontend/src/app/(app)/(preview)/p/[id]/page.tsx +++ b/apps/frontend/src/app/(app)/(preview)/p/[id]/page.tsx @@ -12,6 +12,7 @@ import { VideoOrImage } from '@gitroom/react/helpers/video.or.image'; import { CopyClient } from '@gitroom/frontend/components/preview/copy.client'; import { getT } from '@gitroom/react/translation/get.translation.service.backend'; import { RenderPreviewDateClient } from '@gitroom/frontend/components/preview/render.preview.date.client'; +import { CreationMethodBadge } from '@gitroom/frontend/components/launches/creation.method.badge'; dayjs.extend(utc); export const metadata: Metadata = { @@ -142,6 +143,12 @@ export default async function Auth( @{post[0].integration.profile} + {index === 0 && ( + + )}
{ window.open(`/p/` + post.id + '?share=true', '_blank'); }, [post]); @@ -1044,6 +1047,14 @@ const CalendarItem: FC<{ !
)} + {showCreationMethodBadge && ( +
+ +
+ )}
+ m === 'AUTOPOST' ? 'Auto-posted by system' : `Created via ${m}`; + +export const CreationMethodBadge: FC = ({ + creationMethod, + size = 'xs', + className, + ringColor, +}) => { + if (!creationMethod || creationMethod === 'UNKNOWN') return null; + + const sizeClasses = + size === 'xs' + ? 'h-[12px] px-[4px] text-[7px]' + : size === 'md' + ? 'h-[22px] px-[10px] text-[12px]' + : 'h-[18px] px-[8px] text-[10px]'; + + return ( +
+ {creationMethod} +
+ ); +}; diff --git a/apps/frontend/src/components/new-launch/manage.modal.tsx b/apps/frontend/src/components/new-launch/manage.modal.tsx index 0af6af15..becc74b7 100644 --- a/apps/frontend/src/components/new-launch/manage.modal.tsx +++ b/apps/frontend/src/components/new-launch/manage.modal.tsx @@ -33,6 +33,7 @@ import { SelectCustomer } from '@gitroom/frontend/components/launches/select.cus import { CopilotPopup } from '@copilotkit/react-ui'; import { DummyCodeComponent } from '@gitroom/frontend/components/new-launch/dummy.code.component'; import { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation'; +import { CreationMethodBadge } from '@gitroom/frontend/components/launches/creation.method.badge'; import { SettingsIcon, ChevronDownIcon, @@ -447,8 +448,12 @@ export const ManageModal: FC = (props) => {
-
+
{t('create_post_title', 'Create Post')} +
{ const fetch = useFetch(); const { backendUrl } = useVariables(); @@ -30,6 +31,7 @@ export const PreviewWrapper = ({ children }: { children: ReactNode }) => { > + {children} diff --git a/libraries/helpers/src/utils/posts.list.minify.ts b/libraries/helpers/src/utils/posts.list.minify.ts index cebe1497..f5847b9d 100644 --- a/libraries/helpers/src/utils/posts.list.minify.ts +++ b/libraries/helpers/src/utils/posts.list.minify.ts @@ -25,6 +25,7 @@ const POST_ITEM_KEYS: Record = { integration: 'n', intervalInDays: 'iv', actualDate: 'ad', + creationMethod: 'cm', }; const INTEGRATION_KEYS: Record = { diff --git a/libraries/nestjs-libraries/src/chat/tools/integration.schedule.post.ts b/libraries/nestjs-libraries/src/chat/tools/integration.schedule.post.ts index dc3c3f52..b2983de7 100644 --- a/libraries/nestjs-libraries/src/chat/tools/integration.schedule.post.ts +++ b/libraries/nestjs-libraries/src/chat/tools/integration.schedule.post.ts @@ -223,7 +223,7 @@ If the tools return errors, you would need to rerun it with the right parameters })), }, ], - }); + }, 'MCP'); finalOutput.push(...output); } diff --git a/libraries/nestjs-libraries/src/database/prisma/autopost/autopost.service.ts b/libraries/nestjs-libraries/src/database/prisma/autopost/autopost.service.ts index 5964983a..c8132847 100644 --- a/libraries/nestjs-libraries/src/database/prisma/autopost/autopost.service.ts +++ b/libraries/nestjs-libraries/src/database/prisma/autopost/autopost.service.ts @@ -303,7 +303,7 @@ export class AutopostService { }, ], })), - }); + }, 'AUTOPOST'); } async updateUrl(state: WorkflowChannelsState) { 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 be031e24..e131574e 100644 --- a/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts +++ b/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts @@ -1,7 +1,12 @@ import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service'; import { Injectable } from '@nestjs/common'; import { Post as PostBody } from '@gitroom/nestjs-libraries/dtos/posts/create.post.dto'; -import { APPROVED_SUBMIT_FOR_ORDER, Post, State } from '@prisma/client'; +import { + APPROVED_SUBMIT_FOR_ORDER, + CreationMethod, + Post, + State, +} from '@prisma/client'; import { GetPostsDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.dto'; import { GetPostsListDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.list.dto'; import dayjs from 'dayjs'; @@ -173,6 +178,7 @@ export class PostsRepository { state: true, intervalInDays: true, group: true, + creationMethod: true, tags: { select: { tag: true, @@ -260,6 +266,7 @@ export class PostsRepository { releaseId: true, state: true, group: true, + creationMethod: true, tags: { select: { tag: true, @@ -483,6 +490,7 @@ export class PostsRepository { date: string, body: PostBody, tags: { value: string; label: string }[], + creationMethod: CreationMethod, inter?: number ) { const posts: Post[] = []; @@ -517,6 +525,7 @@ export class PostsRepository { group: uuid, intervalInDays: inter ? +inter : null, approvedSubmitForOrder: APPROVED_SUBMIT_FOR_ORDER.NO, + ...(type === 'create' ? { creationMethod } : {}), ...(state === 'update' ? {} : { 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 2ca5c875..a4896849 100644 --- a/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts +++ b/libraries/nestjs-libraries/src/database/prisma/posts/posts.service.ts @@ -7,7 +7,14 @@ 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, State } from '@prisma/client'; +import { + Integration, + Post, + Media, + From, + CreationMethod, + State, +} from '@prisma/client'; import { GetPostsDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.dto'; import { GetPostsListDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.list.dto'; import { shuffle } from 'lodash'; @@ -731,7 +738,11 @@ export class PostsService { } catch (err) {} } - async createPost(orgId: string, body: CreatePostDto): Promise { + async createPost( + orgId: string, + body: CreatePostDto, + creationMethod: CreationMethod + ): Promise { const postList = []; for (const post of body.posts) { const messages = (post.value || []).map((p) => p.content); @@ -750,6 +761,7 @@ export class PostsService { body.type === 'now' ? dayjs().format('YYYY-MM-DDTHH:mm:00') : body.date, post, body.tags, + creationMethod, body.inter ); @@ -881,43 +893,47 @@ export class PostsService { const group = makeId(10); const randomDate = findTime(); - await this.createPost(orgId, { - type: 'draft', - date: randomDate, - order: '', - shortLink: false, - tags: [], - posts: [ - { - group, - integration: { - id: integration.id, - }, - settings: { - __type: integration.providerIdentifier as any, - title: '', - tags: [], - subreddit: [], - }, - value: [ - ...toPost.list.map((l) => ({ - id: '', - content: l.post, - delay: 0, - image: [], - })), - { - id: '', - delay: 0, - content: `Check out the full story here:\n${ - body.postId || body.url - }`, - image: [], + await this.createPost( + orgId, + { + type: 'draft', + date: randomDate, + order: '', + shortLink: false, + tags: [], + posts: [ + { + group, + integration: { + id: integration.id, }, - ], - }, - ], - }); + settings: { + __type: integration.providerIdentifier as any, + title: '', + tags: [], + subreddit: [], + }, + value: [ + ...toPost.list.map((l) => ({ + id: '', + content: l.post, + delay: 0, + image: [], + })), + { + id: '', + delay: 0, + content: `Check out the full story here:\n${ + body.postId || body.url + }`, + image: [], + }, + ], + }, + ], + }, + 'WEB' + ); } } } diff --git a/libraries/nestjs-libraries/src/database/prisma/schema.prisma b/libraries/nestjs-libraries/src/database/prisma/schema.prisma index a005ed80..03335f30 100644 --- a/libraries/nestjs-libraries/src/database/prisma/schema.prisma +++ b/libraries/nestjs-libraries/src/database/prisma/schema.prisma @@ -409,6 +409,7 @@ model Post { submittedForOrderId String? submittedForOrganizationId String? approvedSubmitForOrder APPROVED_SUBMIT_FOR_ORDER @default(NO) + creationMethod CreationMethod @default(UNKNOWN) lastMessageId String? intervalInDays Int? error String? @@ -436,6 +437,7 @@ model Post { @@index([submittedForOrderId]) @@index([intervalInDays]) @@index([approvedSubmitForOrder]) + @@index([creationMethod]) @@index([lastMessageId]) @@index([createdAt]) @@index([updatedAt]) @@ -938,6 +940,14 @@ enum APPROVED_SUBMIT_FOR_ORDER { YES } +enum CreationMethod { + UNKNOWN + WEB + MCP + API + AUTOPOST +} + enum ShortLinkPreference { ASK YES From 510f3963895eebd5ff5414257b4d8d03a974b36a Mon Sep 17 00:00:00 2001 From: Santosh Bhandari Date: Tue, 12 May 2026 17:06:29 +0545 Subject: [PATCH 2/4] feat: show creation method badge only when impersonating --- apps/frontend/src/components/launches/calendar.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/frontend/src/components/launches/calendar.tsx b/apps/frontend/src/components/launches/calendar.tsx index b137e1c1..1cb3f40b 100644 --- a/apps/frontend/src/components/launches/calendar.tsx +++ b/apps/frontend/src/components/launches/calendar.tsx @@ -1006,8 +1006,11 @@ const CalendarItem: FC<{ missingRelease, } = props; const { disableXAnalytics } = useVariables(); + const user = useUser(); const showCreationMethodBadge = - post.creationMethod && post.creationMethod !== 'UNKNOWN'; + user?.impersonate && + post.creationMethod && + post.creationMethod !== 'UNKNOWN'; const preview = useCallback(() => { window.open(`/p/` + post.id + '?share=true', '_blank'); }, [post]); @@ -1048,7 +1051,7 @@ const CalendarItem: FC<{
)} {showCreationMethodBadge && ( -
+
Date: Tue, 12 May 2026 17:25:10 +0545 Subject: [PATCH 3/4] feat: accept CLI creation method via public API --- .../routes/v1/public.integrations.controller.ts | 9 ++++++++- .../src/components/launches/creation.method.badge.tsx | 3 ++- .../nestjs-libraries/src/database/prisma/schema.prisma | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/backend/src/public-api/routes/v1/public.integrations.controller.ts b/apps/backend/src/public-api/routes/v1/public.integrations.controller.ts index 9eef59f1..d5d7f26e 100644 --- a/apps/backend/src/public-api/routes/v1/public.integrations.controller.ts +++ b/apps/backend/src/public-api/routes/v1/public.integrations.controller.ts @@ -188,8 +188,15 @@ export class PublicIntegrationsController { ); } + const allowedCreationMethods = ['WEB', 'CLI'] as const; + const creationMethod = allowedCreationMethods.includes( + rawBody.creationMethod + ) + ? (rawBody.creationMethod as 'WEB' | 'CLI') + : 'API'; + console.log(JSON.stringify(body, null, 2)); - return this._postsService.createPost(org.id, body, 'API'); + return this._postsService.createPost(org.id, body, creationMethod); } @Delete('/posts/:id') diff --git a/apps/frontend/src/components/launches/creation.method.badge.tsx b/apps/frontend/src/components/launches/creation.method.badge.tsx index e9ba1ab1..dbe19271 100644 --- a/apps/frontend/src/components/launches/creation.method.badge.tsx +++ b/apps/frontend/src/components/launches/creation.method.badge.tsx @@ -1,7 +1,7 @@ import { FC } from 'react'; import clsx from 'clsx'; -type CreationMethod = 'UNKNOWN' | 'WEB' | 'API' | 'MCP' | 'AUTOPOST'; +type CreationMethod = 'UNKNOWN' | 'WEB' | 'API' | 'MCP' | 'AUTOPOST' | 'CLI'; interface Props { creationMethod?: CreationMethod | string | null; @@ -37,6 +37,7 @@ export const CreationMethodBadge: FC = ({ creationMethod === 'API' && 'bg-[#2563eb]', creationMethod === 'MCP' && 'bg-[#9333ea]', creationMethod === 'AUTOPOST' && 'bg-[#d97706]', + creationMethod === 'CLI' && 'bg-[#0f766e]', className )} style={ringColor ? { boxShadow: `0 0 0 2px ${ringColor}` } : undefined} diff --git a/libraries/nestjs-libraries/src/database/prisma/schema.prisma b/libraries/nestjs-libraries/src/database/prisma/schema.prisma index 03335f30..7f48b7bf 100644 --- a/libraries/nestjs-libraries/src/database/prisma/schema.prisma +++ b/libraries/nestjs-libraries/src/database/prisma/schema.prisma @@ -946,6 +946,7 @@ enum CreationMethod { MCP API AUTOPOST + CLI } enum ShortLinkPreference { From e63d6d2cf28d720a40ba5f61b24ef4acd1796064 Mon Sep 17 00:00:00 2001 From: Santosh Bhandari Date: Thu, 14 May 2026 10:05:46 +0545 Subject: [PATCH 4/4] feat: restrict public API creation methods to CLI and API --- .../public-api/routes/v1/public.integrations.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/backend/src/public-api/routes/v1/public.integrations.controller.ts b/apps/backend/src/public-api/routes/v1/public.integrations.controller.ts index d5d7f26e..0b6d8338 100644 --- a/apps/backend/src/public-api/routes/v1/public.integrations.controller.ts +++ b/apps/backend/src/public-api/routes/v1/public.integrations.controller.ts @@ -188,11 +188,11 @@ export class PublicIntegrationsController { ); } - const allowedCreationMethods = ['WEB', 'CLI'] as const; + const allowedCreationMethods = ['CLI', 'API'] as const; const creationMethod = allowedCreationMethods.includes( rawBody.creationMethod ) - ? (rawBody.creationMethod as 'WEB' | 'CLI') + ? (rawBody.creationMethod as 'CLI' | 'API') : 'API'; console.log(JSON.stringify(body, null, 2));