Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .agents/skills/feature-scaffold/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,28 @@ Use `err: error` (not `error: error`) — pino's serializer is keyed on `err`.

Client components may use `console` only for local development debugging that is removed before merge.

## Services — Business Logic Belongs in @chatbotx.io/business

Business logic services (DB queries, domain mutations, cache management) **MUST NOT** be placed inside `features/<name>/`. They belong in `packages/business/src/<domain>/service.ts`.

### Pattern
- `packages/business/src/<domain>/service.ts` — class extending `BaseService`, singleton export
- `packages/business/src/<domain>/index.ts` — `export * from "./service"`
- `packages/business/src/index.ts` — add `export * from "./<domain>"`

### Feature folders only contain
- `queries/` — RSC wrappers that call business services + add auth checks
- `actions/` — next-safe-action handlers that call business services
- `api/` — oRPC handlers
- `schema/` — Zod validation schemas (NOT imported by business package)
- `components/`, `hooks/`, `provider/` — UI concerns

**Never** create a `*.service.ts` inside a feature folder for new work. If one already exists, move it to `@chatbotx.io/business` before extending it.

```typescript
import { integrationService, webhookService } from "@chatbotx.io/business"
```

## Checklist for New Feature

1. Create feature directory under `src/features/<name>/`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { listInboxTeams } from "../queries"
import { listInboxTeamsResponse } from "../schema/action"

export const inboxTeamsWorkspaceTokenAPIs = {
listInboxTeamsWorkspaceTokenAPI: workspaceTokenAuthAPI
listTeamsWorkspaceTokenAPI: workspaceTokenAuthAPI
.route({
method: "GET",
path: "/v1/inbox-teams",
summary: "List inbox teams",
tags: ["Inbox Teams"],
path: "/v1/teams",
summary: "List teams",
tags: ["Teams"],
})
.output(listInboxTeamsResponse)
.handler(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"use server"

import { aiAgentService } from "@chatbotx.io/business"
import { createAIAgentRequest } from "@/features/ai-agents/schemas/action"
import { workspaceIdrequestParams } from "@/features/common/schemas"
import { workspaceActionClient } from "@/lib/safe-action"
import { aiAgentService } from "../ai-agent.service"

export const createAIAgentAction = workspaceActionClient
.bindArgsSchemas(workspaceIdrequestParams)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"use server"

import { aiAgentService } from "@chatbotx.io/business"
import {
bulkUpdateIdsRequest,
workspaceIdrequestParams,
} from "@/features/common/schemas"
import { authActionClient } from "@/lib/safe-action"
import { aiAgentService } from "../ai-agent.service"

export const deleteAIAgentAction = authActionClient
.bindArgsSchemas(workspaceIdrequestParams)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"use server"

import { aiAgentService } from "@chatbotx.io/business"
import { zodBigintAsString } from "@chatbotx.io/utils"
import { updateAIAgentRequest } from "@/features/ai-agents/schemas/action"
import { workspaceActionClient } from "@/lib/safe-action"
import { aiAgentService } from "../ai-agent.service"

export const updateAIAgentAction = workspaceActionClient
.bindArgsSchemas([zodBigintAsString(), zodBigintAsString()])
Expand Down
21 changes: 21 additions & 0 deletions apps/builder/src/features/ai-agents/api/authenticated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { withWorkspaceIdSchema } from "@/features/workspaces/schema/resource"
import { workspaceAuthorizedMidddleware } from "@/middlewares/auth"
import { authorizedAPI } from "@/orpc"
import { listAIAgents } from "../queries"
import { listAIAgentsRequest, listAIAgentsResponse } from "../schemas/query"

const listAIAgentsAPI = authorizedAPI
.route({
method: "GET",
path: "/workspaces/{workspaceId}/ai-agents",
summary: "List AI agents",
tags: ["AI"],
})
.input(listAIAgentsRequest.and(withWorkspaceIdSchema))
.use(workspaceAuthorizedMidddleware, (input) => input.workspaceId)
.output(listAIAgentsResponse)
.handler(async ({ input }) => await listAIAgents(input))

export const aiAgentsAuthenticatedAPI = {
listAIAgentsAPI,
}
22 changes: 4 additions & 18 deletions apps/builder/src/features/ai-agents/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,7 @@
import { withWorkspaceIdSchema } from "@/features/workspaces/schema/resource"
import { workspaceAuthorizedMidddleware } from "@/middlewares/auth"
import { authorizedAPI } from "@/orpc"
import { listAIAgents } from "../queries"
import { listAIAgentsRequest, listAIAgentsResponse } from "../schemas/query"

const listAIAgentsAPI = authorizedAPI
.route({
method: "GET",
path: "/workspaces/{workspaceId}/ai-agents",
summary: "List AI agents",
tags: ["AI"],
})
.input(listAIAgentsRequest.and(withWorkspaceIdSchema))
.use(workspaceAuthorizedMidddleware, (input) => input.workspaceId)
.output(listAIAgentsResponse)
.handler(async ({ input }) => await listAIAgents(input))
import { aiAgentsAuthenticatedAPI } from "./authenticated"
import aiAgentsWorkspaceTokenAPIs from "./workspace-token"

export const aiAgentsAPI = {
listAIAgentsAPI,
...aiAgentsAuthenticatedAPI,
...aiAgentsWorkspaceTokenAPIs,
}
27 changes: 27 additions & 0 deletions apps/builder/src/features/ai-agents/api/workspace-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { aiAgentService } from "@chatbotx.io/business"
import { workspaceTokenAuthAPI } from "@/orpc"
import { listAIAgentsResponse } from "../schemas/query"

const listAIAgentsWorkspaceTokenAPI = workspaceTokenAuthAPI
.route({
method: "GET",
path: "/v1/ai-agents",
summary: "List AI agents",
tags: ["AI Agents"],
})
.output(listAIAgentsResponse)
.handler(
async ({ context }) =>
await aiAgentService.listAIAgents({
workspaceId: context.workspace.id,
page: 1,
perPage: 100,
sort: [{ id: "createdAt", desc: true }],
}),
)

export const aiAgentsWorkspaceTokenAPIs = {
listAIAgentsWorkspaceTokenAPI,
}

export default aiAgentsWorkspaceTokenAPIs
2 changes: 1 addition & 1 deletion apps/builder/src/features/ai-agents/queries/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"use server"

import { aiAgentService } from "@chatbotx.io/business"
import type { AIAgentModel } from "@chatbotx.io/database/types"
import type { ListAIAgentsRequest } from "@/features/ai-agents/schemas/query"
import type { PaginatedResponse } from "@/features/common/schemas/pagination"
import { aiAgentService } from "../ai-agent.service"

export async function listAIAgents(
input: ListAIAgentsRequest,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"use server"

import { aiFunctionService } from "@chatbotx.io/business"
import { getTranslations } from "next-intl/server"
import { returnValidationErrors } from "next-safe-action"
import { workspaceIdrequestParams } from "@/features/common/schemas"
import { workspaceActionClient } from "@/lib/safe-action"
import { aiFunctionService } from "../ai-function.service"
import { createAIFunctionRequest } from "../schemas/action"

export const createAIFunctionAction = workspaceActionClient
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"use server"

import { aiFunctionService } from "@chatbotx.io/business"
import { zodBigintAsString } from "@chatbotx.io/utils"
import { getTranslations } from "next-intl/server"
import { workspaceActionClient } from "@/lib/safe-action"
import { aiFunctionService } from "../ai-function.service"

export const deleteAIFunctionAction = workspaceActionClient
.bindArgsSchemas([zodBigintAsString(), zodBigintAsString()])
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"use server"

import { aiFunctionService } from "@chatbotx.io/business"
import { zodBigintAsString } from "@chatbotx.io/utils"
import { getTranslations } from "next-intl/server"
import { returnValidationErrors } from "next-safe-action"
import { workspaceActionClient } from "@/lib/safe-action"
import { aiFunctionService } from "../ai-function.service"
import { updateAIFunctionRequest } from "../schemas/action"

export const updateAIFunctionAction = workspaceActionClient
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"use server"

import { aiMcpServerService } from "@chatbotx.io/business"
import { getTranslations } from "next-intl/server"
import { returnValidationErrors } from "next-safe-action"
import { workspaceIdrequestParams } from "@/features/common/schemas"
import { workspaceActionClient } from "@/lib/safe-action"
import { aiMcpServerService } from "../ai-mcp-server.service"
import { createAIMcpServerRequest } from "../schemas/action"

export const createAIMcpServerAction = workspaceActionClient
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"use server"

import { aiMcpServerService } from "@chatbotx.io/business"
import { notFoundException } from "@chatbotx.io/business/errors"
import { zodBigintAsString } from "@chatbotx.io/utils"
import { getTranslations } from "next-intl/server"
import { workspaceActionClient } from "@/lib/safe-action"
import { aiMcpServerService } from "../ai-mcp-server.service"

export const deleteAIMcpServerAction = workspaceActionClient
.bindArgsSchemas([zodBigintAsString(), zodBigintAsString()])
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
"use server"

import {
aiMcpServerService,
type UpdateAIMcpServerRequest,
} from "@chatbotx.io/business"
import { notFoundException } from "@chatbotx.io/business/errors"
import { zodBigintAsString } from "@chatbotx.io/utils"
import { getTranslations } from "next-intl/server"
import { returnValidationErrors } from "next-safe-action"
import { workspaceActionClient } from "@/lib/safe-action"
import { aiMcpServerService } from "../ai-mcp-server.service"
import {
type UpdateAIMcpServerRequest,
updateAIMcpServerRequest,
} from "../schemas/action"
import { updateAIMcpServerRequest } from "../schemas/action"

export const updateAIMcpServerAction = workspaceActionClient
.bindArgsSchemas([zodBigintAsString(), zodBigintAsString()])
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
"use server"

import { automatedResponseService, flowService } from "@chatbotx.io/business"
import { returnValidationErrors } from "next-safe-action"
import { workspaceIdrequestParams } from "@/features/common/schemas"
import { flowService } from "@/features/flows/flow.service"
import { ensureFolderIsExists } from "@/features/folders/actions/utils"
import { workspaceActionClient } from "@/lib/safe-action"
import { automatedResponseService } from "../automated-response.service"
import { createAutomatedResponseRequest } from "../schema/action"

export const createAutomatedResponseAction = workspaceActionClient
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"use server"

import { automatedResponseService } from "@chatbotx.io/business"
import {
bulkUpdateIdsRequest,
workspaceIdrequestParams,
} from "@/features/common/schemas"
import { workspaceActionClient } from "@/lib/safe-action"
import { automatedResponseService } from "../automated-response.service"

export const deleteAutomatedResponseAction = workspaceActionClient
.bindArgsSchemas(workspaceIdrequestParams)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"use server"

import { automatedResponseService } from "@chatbotx.io/business"
import { zodBigintAsString } from "@chatbotx.io/utils"
import z from "zod"
import { workspaceActionClient } from "@/lib/safe-action"
import { automatedResponseService } from "../automated-response.service"

const enableRequest = z.object({
status: z.boolean(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"use server"

import {
automatedResponseService,
flowService,
type UpdateAutomatedResponseRequest,
} from "@chatbotx.io/business"
import { zodBigintAsString } from "@chatbotx.io/utils"
import { returnValidationErrors } from "next-safe-action"
import { flowService } from "@/features/flows/flow.service"
import { workspaceActionClient } from "@/lib/safe-action"
import { automatedResponseService } from "../automated-response.service"
import {
type UpdateAutomatedResponseRequest,
updateAutomatedResponseRequest,
} from "../schema/action"
import { updateAutomatedResponseRequest } from "../schema/action"

export const updateAutomatedResponseAction = workspaceActionClient
.bindArgsSchemas([zodBigintAsString(), zodBigintAsString()])
Expand Down
5 changes: 5 additions & 0 deletions apps/builder/src/features/automated-response/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import keywordsWorkspaceTokenAPIs from "./workspace-token"

export const keywordsAPI = {
...keywordsWorkspaceTokenAPIs,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { automatedResponseService } from "@chatbotx.io/business"
import {
automatedResponseModel,
createSelectSchema,
} from "@chatbotx.io/database/schema"
import z from "zod"
import { maxPerPage } from "@/lib/shared-request"
import { workspaceTokenAuthAPI } from "@/orpc"

const keywordResource = createSelectSchema(automatedResponseModel, {
id: z.string(),
workspaceId: z.string(),
folderId: z.string().nullable(),
flowId: z.string().nullable(),
})

const listKeywordsWorkspaceTokenAPI = workspaceTokenAuthAPI
.route({
method: "GET",
path: "/v1/keywords",
summary: "List keywords (automated responses)",
tags: ["Keywords"],
})
.output(z.object({ data: z.array(keywordResource) }))
.handler(async ({ context }) => {
const { data } = await automatedResponseService.list({
workspaceId: context.workspace.id,
page: 1,
perPage: maxPerPage,
sort: [{ id: "createdAt", desc: true }],
keyword: null,
folderId: null,
})

return { data }
})

export const keywordsWorkspaceTokenAPIs = {
listKeywordsWorkspaceTokenAPI,
}

export default keywordsWorkspaceTokenAPIs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { automatedResponseService } from "@chatbotx.io/business"
import type { PaginatedResponse } from "@/features/common/schemas/pagination"
import { assertCurrentUserCanAccessChatbot } from "@/lib/auth/utils"
import { automatedResponseService } from "../automated-response.service"
import type {
FindAutomatedResponseRequest,
ListAutomatedResponsesRequest,
Expand Down
Loading
Loading