Skip to content

Commit 8516dc8

Browse files
author
Deathgiver
committed
feat: Update APIs
1 parent 7501266 commit 8516dc8

27 files changed

Lines changed: 678 additions & 150 deletions

File tree

apps/builder/src/app/api/[[...rest]]/route.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ const openAPIHandler = new OpenAPIHandler(router, {
2020
commonSchemas: {
2121
UndefinedError: { error: "UndefinedError" },
2222
},
23-
security: [{ bearerAuth: [] }, { developerAccessToken: [] }],
23+
security: [
24+
{ bearerAuth: [] },
25+
{ developerAccessToken: [] },
26+
{ apiKeyInUrl: [] },
27+
],
2428
components: {
2529
securitySchemes: {
2630
bearerAuth: {
@@ -31,6 +35,11 @@ const openAPIHandler = new OpenAPIHandler(router, {
3135
type: "http",
3236
scheme: "bearer",
3337
},
38+
apiKeyInUrl: {
39+
type: "apiKey",
40+
in: "query",
41+
name: "api_key",
42+
},
3443
},
3544
},
3645
},
@@ -53,7 +62,7 @@ const openAPIHandler = new OpenAPIHandler(router, {
5362
export async function handleRequest(request: Request) {
5463
const { response } = await openAPIHandler.handle(request, {
5564
prefix: "/api",
56-
context: { headers: request.headers },
65+
context: { headers: request.headers, url: request.url },
5766
})
5867

5968
return response ?? new Response("Not found", { status: 404 })

apps/builder/src/app/api/public-spec.json/route.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,18 @@ async function handleRequest(request: Request) {
1818
commonSchemas: {
1919
UndefinedError: { error: "UndefinedError" },
2020
},
21-
security: [{ developerAccessToken: [] }],
21+
security: [{ developerAccessToken: [] }, { apiKeyInUrl: [] }],
2222
components: {
2323
securitySchemes: {
2424
developerAccessToken: {
2525
type: "http",
2626
scheme: "bearer",
2727
},
28+
apiKeyInUrl: {
29+
type: "apiKey",
30+
in: "query",
31+
name: "api_key",
32+
},
2833
},
2934
},
3035
servers: [

apps/builder/src/app/api/scalar-ui/route.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,6 @@ export async function GET() {
1515
<script>
1616
Scalar.createApiReference('#app', {
1717
url: '/api/public-spec.json',
18-
authentication: {
19-
securitySchemes: {
20-
bearerAuth: {
21-
token: 'default-token',
22-
},
23-
developerAccessToken: {
24-
token: 'default-workspace-token',
25-
},
26-
},
27-
},
2818
})
2919
</script>
3020
</body>

apps/builder/src/app/rpc/[[...rest]]/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const rpcHandler = new RPCHandler(router, {
1616
async function handleRequest(request: Request) {
1717
const { response } = await rpcHandler.handle(request, {
1818
prefix: "/rpc",
19-
context: { headers: request.headers },
19+
context: { headers: request.headers, url: request.url },
2020
})
2121

2222
return response ?? new Response("Not found", { status: 404 })

apps/builder/src/app/space/[workspaceId]/contacts/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { getTranslations } from "next-intl/server"
44
import type { SearchParams } from "nuqs/server"
55
import { Suspense } from "react"
66
import { ContactsTable } from "@/features/contacts/contacts-table"
7-
import { listContacts } from "@/features/contacts/queries/list-contacts.queries"
7+
import { listContactsRSC } from "@/features/contacts/queries/list-contacts.queries"
88
import { listContactsRequest } from "@/features/contacts/schemas/query"
99
import { CustomFieldStoreProvider } from "@/features/custom-fields/provider/custom-field-store-context"
1010
import { InboxStoreProvider } from "@/features/inboxes/provider/inbox-store-context"
@@ -25,7 +25,7 @@ export default async function ContactsPage(props: {
2525
const { data: search } = listContactsRequest.safeParse(searchParams)
2626

2727
const promises = Promise.all([
28-
listContacts({
28+
listContactsRSC({
2929
...search,
3030
workspaceId,
3131
}),

apps/builder/src/features/bot-fields/api/workspace-token.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { botFieldService } from "@chatbotx.io/business"
22
import z from "zod"
33
import {
4-
posibleErrorsOnCreatingResource,
5-
posibleErrorsOnDeletingResource,
6-
posibleErrorsOnFindingResource,
4+
possibleErrorsOnCreatingResource,
5+
possibleErrorsOnDeletingResource,
6+
possibleErrorsOnFindingResource,
77
} from "@/lib/orpc/orpc-error-helper"
88
import { maxPerPage } from "@/lib/shared-request"
99
import { workspaceTokenAuthAPI } from "@/orpc"
@@ -21,7 +21,7 @@ const botFieldWorkspaceTokenAPIs = {
2121
})
2222
.input(z.object({}))
2323
.output(publicListBotFieldsResponse)
24-
.errors(posibleErrorsOnFindingResource)
24+
.errors(possibleErrorsOnFindingResource)
2525
.handler(async ({ context }) => {
2626
const result = await botFieldService.list({
2727
workspaceId: context.workspace.id,
@@ -44,7 +44,7 @@ const botFieldWorkspaceTokenAPIs = {
4444
})
4545
.input(createBotFieldRequest)
4646
.output(publicBotFieldResource)
47-
.errors(posibleErrorsOnCreatingResource)
47+
.errors(possibleErrorsOnCreatingResource)
4848
.handler(
4949
async ({ context, input }) =>
5050
await botFieldService.create({
@@ -62,7 +62,7 @@ const botFieldWorkspaceTokenAPIs = {
6262
})
6363
.input(z.object({ key: z.string().max(255) }))
6464
.output(publicBotFieldResource)
65-
.errors(posibleErrorsOnFindingResource)
65+
.errors(possibleErrorsOnFindingResource)
6666
.handler(
6767
async ({ context, input }) =>
6868
await botFieldService.findByKeyOrFail({
@@ -80,7 +80,7 @@ const botFieldWorkspaceTokenAPIs = {
8080
})
8181
.input(z.object({ key: z.string().max(255), value: z.string().max(255) }))
8282
.output(publicBotFieldResource)
83-
.errors(posibleErrorsOnCreatingResource)
83+
.errors(possibleErrorsOnCreatingResource)
8484
.handler(async ({ context, input }) => {
8585
const { key, ...rest } = input
8686
return await botFieldService.updateByKey({
@@ -99,7 +99,7 @@ const botFieldWorkspaceTokenAPIs = {
9999
tags: ["Bot Fields"],
100100
})
101101
.input(z.object({ key: z.string().max(255) }))
102-
.errors(posibleErrorsOnDeletingResource)
102+
.errors(possibleErrorsOnDeletingResource)
103103
.handler(
104104
async ({ context, input }) =>
105105
await botFieldService.deleteByKey({
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { db, relationsFilterToSQL } from "@chatbotx.io/database/client"
2+
import { rootFolderId } from "@chatbotx.io/database/partials"
3+
import { customFieldModel } from "@chatbotx.io/database/schema"
4+
import {
5+
parseOrderByAsObject,
6+
parsePagination,
7+
} from "@chatbotx.io/database/utils"
8+
import type { PaginatedResponse } from "@/features/common/schemas/pagination"
9+
import { assertCurrentUserCanAccessChatbot } from "@/lib/auth/utils"
10+
import type {
11+
FindBotFieldByKeyRequest,
12+
FindBotFieldRequest,
13+
ListBotFieldsSearchParams,
14+
} from "../schemas/query"
15+
import type { BotFieldResource } from "../schemas/resource"
16+
17+
const REGEX_BOT_FIELD_ID = /^\d+$/
18+
19+
export async function listBotFields(
20+
input: ListBotFieldsSearchParams,
21+
): Promise<PaginatedResponse<BotFieldResource>> {
22+
await assertCurrentUserCanAccessChatbot(input.workspaceId)
23+
24+
const where = {
25+
workspaceId: input.workspaceId,
26+
folderId: input.folderId
27+
? // biome-ignore lint/style/noNestedTernary: allow nested ternary
28+
input.folderId === rootFolderId
29+
? { isNull: true as const }
30+
: input.folderId
31+
: undefined,
32+
name: input.name
33+
? {
34+
ilike: `%${input.name.toLowerCase()}%`,
35+
}
36+
: undefined,
37+
}
38+
39+
const orderBy = parseOrderByAsObject(customFieldModel, input)
40+
41+
const pagination = parsePagination(input)
42+
const [data, total] = await Promise.all([
43+
db.query.botFieldModel.findMany({
44+
where,
45+
orderBy,
46+
...pagination,
47+
}),
48+
db.$count(customFieldModel, relationsFilterToSQL(customFieldModel, where)),
49+
])
50+
51+
const pageCount = pagination?.limit ? Math.ceil(total / pagination.limit) : 1
52+
53+
return { data, pageCount }
54+
}
55+
56+
export const findBotField = async (
57+
input: FindBotFieldRequest,
58+
): Promise<BotFieldResource | undefined> =>
59+
await db.query.botFieldModel.findFirst({
60+
where: input,
61+
})
62+
63+
export const findBotFieldByKey = async (
64+
input: FindBotFieldByKeyRequest,
65+
): Promise<BotFieldResource | undefined> =>
66+
await db.query.botFieldModel.findFirst({
67+
where: {
68+
[REGEX_BOT_FIELD_ID.test(input.key) ? "id" : "name"]: input.key,
69+
workspaceId: input.workspaceId,
70+
},
71+
})

apps/builder/src/features/bot-fields/schemas/query.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
} from "nuqs/server"
77
import z from "zod"
88
import { parseAsBigInt } from "@/lib/nuqs"
9-
import { type BotFieldResource, publicBotFieldResource } from "./resource"
9+
import { type BotFieldResource, botFieldResource, publicBotFieldResource } from "./resource"
1010

1111
export const listBotFieldsSearchParams = createSearchParamsCache({
1212
page: parseAsInteger.withDefault(1),
@@ -27,3 +27,14 @@ export type ListBotFieldsSearchParams = Awaited<
2727
export const publicListBotFieldsResponse = z.object({
2828
data: z.array(publicBotFieldResource),
2929
})
30+
31+
export const findBotFieldRequest = botFieldResource
32+
.pick({ id: true, workspaceId: true, name: true })
33+
.partial()
34+
export type FindBotFieldRequest = z.infer<typeof findBotFieldRequest>
35+
36+
export const findBotFieldByKeyRequest = z.object({
37+
key: z.string(),
38+
workspaceId: z.string(),
39+
})
40+
export type FindBotFieldByKeyRequest = z.infer<typeof findBotFieldByKeyRequest>

apps/builder/src/features/contacts/actions/add-contact-custom-field.action.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,85 @@ export const setContactCustomFieldValue = async ({
202202

203203
revalidateCacheTags(`workspaces:${workspaceId}#contacts`)
204204
}
205+
206+
export const setContactCustomFieldValues = async ({
207+
workspaceId,
208+
contactId,
209+
fields,
210+
}: {
211+
workspaceId: string
212+
contactId: string
213+
fields: Array<{ customFieldId: string; value: string }>
214+
}) => {
215+
try {
216+
const customFieldIds = fields.map((f) => f.customFieldId)
217+
218+
const customFields = await db.query.customFieldModel.findMany({
219+
where: { workspaceId, id: { in: customFieldIds } },
220+
columns: { id: true, name: true },
221+
})
222+
223+
if (customFields.length === 0) {
224+
return
225+
}
226+
227+
const existingValues = await db.query.contactCustomFieldModel.findMany({
228+
where: {
229+
contactId,
230+
customFieldId: { in: customFieldIds },
231+
},
232+
})
233+
234+
await db.transaction(async (tx) => {
235+
const matchedFields = customFields.flatMap((customField) => {
236+
const field = fields.find((f) => f.customFieldId === customField.id)
237+
return field ? [{ customField, field }] : []
238+
})
239+
240+
await Promise.all(
241+
matchedFields.map(({ customField, field }) => {
242+
const existing = existingValues.find(
243+
(v) => v.customFieldId === customField.id,
244+
)
245+
246+
if (existing) {
247+
return tx
248+
.update(contactCustomFieldModel)
249+
.set({ value: field.value })
250+
.where(eq(contactCustomFieldModel.id, existing.id))
251+
}
252+
253+
return tx.insert(contactCustomFieldModel).values({
254+
id: createId(),
255+
contactId,
256+
customFieldId: customField.id,
257+
value: field.value,
258+
})
259+
}),
260+
)
261+
})
262+
263+
for (const customField of customFields) {
264+
const field = fields.find((f) => f.customFieldId === customField.id)
265+
if (!field) {
266+
continue
267+
}
268+
try {
269+
await emitCustomFieldChanged(
270+
workspaceId,
271+
contactId,
272+
customField.id,
273+
customField.name,
274+
null,
275+
field.value,
276+
)
277+
} catch (error) {
278+
console.error("Failed to emit customFieldChanged event:", error)
279+
}
280+
}
281+
282+
revalidateCacheTags(`workspaces:${workspaceId}#contacts`)
283+
} catch (error) {
284+
console.error("[setContactCustomFieldValues] Error:", error)
285+
}
286+
}

0 commit comments

Comments
 (0)