Skip to content

Commit

Permalink
Revert "Remove misplaced v1 messages endpoints (#1848)" (#1849)
Browse files Browse the repository at this point in the history
This reverts commit 7bf8f76.
  • Loading branch information
spolu authored Sep 28, 2023
1 parent 7bf8f76 commit ff5c1c8
Show file tree
Hide file tree
Showing 3 changed files with 248 additions and 1 deletion.
135 changes: 135 additions & 0 deletions front/pages/api/v1/w/[wId]/assistant/[cId]/messages/[mId]/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { NextApiRequest, NextApiResponse } from "next";

import { getConversation } from "@app/lib/api/assistant/conversation";
import { getMessagesEvents } from "@app/lib/api/assistant/pubsub";
import { Authenticator, getAPIKey } from "@app/lib/auth";
import { ReturnedAPIErrorType } from "@app/lib/error";
import { apiError, withLogging } from "@app/logger/withlogging";
import { isAgentMessageType } from "@app/types/assistant/conversation";

async function handler(
req: NextApiRequest,
res: NextApiResponse<ReturnedAPIErrorType>
): Promise<void> {
const keyRes = await getAPIKey(req);
if (keyRes.isErr()) {
return apiError(req, res, keyRes.error);
}

const { auth, keyWorkspaceId } = await Authenticator.fromKey(
keyRes.value,
req.query.wId as string
);

if (!auth.isBuilder() || keyWorkspaceId !== req.query.wId) {
return apiError(req, res, {
status_code: 400,
api_error: {
type: "invalid_request_error",
message: "The Assistant API is only available on your own workspace.",
},
});
}

const owner = auth.workspace();
if (!owner) {
return apiError(req, res, {
status_code: 404,
api_error: {
type: "workspace_not_found",
message: "The workspace you're trying to modify was not found.",
},
});
}

const conversation = await getConversation(auth, req.query.cId as string);

if (!conversation) {
return apiError(req, res, {
status_code: 404,
api_error: {
type: "conversation_not_found",
message: "The conversation you're trying to access was not found.",
},
});
}

if (!(typeof req.query.mId === "string")) {
return apiError(req, res, {
status_code: 400,
api_error: {
type: "invalid_request_error",
message: "Invalid query parameters, `cId` (string) is required.",
},
});
}

const messageId = req.query.mId;

const message = conversation.content
.flat()
.find((message) => message.sId === messageId);

if (!message) {
return apiError(req, res, {
status_code: 404,
api_error: {
type: "message_not_found",
message: "The message you're trying to access was not found.",
},
});
}
if (!isAgentMessageType(message)) {
return apiError(req, res, {
status_code: 400,
api_error: {
type: "invalid_request_error",
message: "Events are only available for agent messages.",
},
});
}

const lastEventId = req.query.lastEventId || null;
if (lastEventId && typeof lastEventId !== "string") {
return apiError(req, res, {
status_code: 400,
api_error: {
type: "invalid_request_error",
message:
"Invalid query parameters, `lastEventId` should be string if specified.",
},
});
}

switch (req.method) {
case "GET":
const eventStream = getMessagesEvents(messageId, lastEventId);

res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
res.flushHeaders();

for await (const event of eventStream) {
res.write(`data: ${JSON.stringify(event)}\n\n`);
// @ts-expect-error - We need it for streaming but it does not exists in the types.
res.flush();
}

res.status(200).end();
return;

default:
return apiError(req, res, {
status_code: 405,
api_error: {
type: "method_not_supported_error",
message: "The method passed is not supported, GET is expected.",
},
});
}
}

export default withLogging(handler, true);
112 changes: 112 additions & 0 deletions front/pages/api/v1/w/[wId]/assistant/[cId]/messages/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { isLeft } from "fp-ts/lib/Either";
import * as t from "io-ts";
import * as reporter from "io-ts-reporters";
import { NextApiRequest, NextApiResponse } from "next";

import { getConversation } from "@app/lib/api/assistant/conversation";
import { postUserMessageWithPubSub } from "@app/lib/api/assistant/pubsub";
import { Authenticator, getAPIKey } from "@app/lib/auth";
import { ReturnedAPIErrorType } from "@app/lib/error";
import { apiError, withLogging } from "@app/logger/withlogging";
import { UserMessageType } from "@app/types/assistant/conversation";

export type PostMessagesResponseBody = {
message: UserMessageType;
};

export const PostMessagesRequestBodySchema = t.type({
content: t.string,
mentions: t.array(
t.union([
t.type({ configurationId: t.string }),
t.type({
provider: t.string,
providerId: t.string,
}),
])
),
context: t.type({
timezone: t.string,
username: t.string,
fullName: t.union([t.string, t.null]),
email: t.union([t.string, t.null]),
profilePictureUrl: t.union([t.string, t.null]),
}),
});

async function handler(
req: NextApiRequest,
res: NextApiResponse<{ message: UserMessageType } | ReturnedAPIErrorType>
): Promise<void> {
const keyRes = await getAPIKey(req);
if (keyRes.isErr()) {
return apiError(req, res, keyRes.error);
}

const { auth, keyWorkspaceId } = await Authenticator.fromKey(
keyRes.value,
req.query.wId as string
);

if (!auth.isBuilder() || keyWorkspaceId !== req.query.wId) {
return apiError(req, res, {
status_code: 400,
api_error: {
type: "invalid_request_error",
message: "The Assistant API is only available on your own workspace.",
},
});
}

const conversation = await getConversation(auth, req.query.cId as string);
if (!conversation) {
return apiError(req, res, {
status_code: 404,
api_error: {
type: "conversation_not_found",
message: "Conversation not found.",
},
});
}

switch (req.method) {
case "POST":
const bodyValidation = PostMessagesRequestBodySchema.decode(req.body);
if (isLeft(bodyValidation)) {
const pathError = reporter.formatValidationErrors(bodyValidation.left);
return apiError(req, res, {
status_code: 400,
api_error: {
type: "invalid_request_error",
message: `Invalid request body: ${pathError}`,
},
});
}

const { content, context, mentions } = bodyValidation.right;

const messageRes = await postUserMessageWithPubSub(auth, {
conversation,
content,
mentions,
context,
});
if (messageRes.isErr()) {
return apiError(req, res, messageRes.error);
}

res.status(200).json({ message: messageRes.value });
return;

default:
return apiError(req, res, {
status_code: 405,
api_error: {
type: "method_not_supported_error",
message: "The method passed is not supported, POST is expected.",
},
});
}
}

export default withLogging(handler);
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { postUserMessageWithPubSub } from "@app/lib/api/assistant/pubsub";
import { Authenticator, getAPIKey } from "@app/lib/auth";
import { ReturnedAPIErrorType } from "@app/lib/error";
import { apiError, withLogging } from "@app/logger/withlogging";
import { PostMessagesRequestBodySchema } from "@app/pages/api/v1/w/[wId]/assistant/conversations/[cId]/messages";
import { PostMessagesRequestBodySchema } from "@app/pages/api/v1/w/[wId]/assistant/[cId]/messages";
import { ConversationType } from "@app/types/assistant/conversation";

const PostConversationsRequestBodySchema = t.type({
Expand Down

0 comments on commit ff5c1c8

Please sign in to comment.