Fixes slack verification and timeout errors (#2972)
* Fixing slack create event * Fixes verify working 90% of the time :S * Verify V2 * Fixing verify - not needed on interaction payload * Fixing verifcation + response * Fix verifcation * DM user error/success * Remove random 200 * Timeout return * Adding Zod schema verification * Added validations, cleanup * Type fixes * Update event.ts Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: zomars <zomars@me.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Peer Richelsen
zomars
kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
parent
8fa0d0f8bb
commit
c03ff0a543
@@ -237,7 +237,8 @@ async function handler(req: NextApiRequest) {
|
||||
throw new HttpError({ statusCode: 400, message: error.message });
|
||||
}
|
||||
|
||||
const eventType = !eventTypeId ? getDefaultEvent(eventTypeSlug) : await getEventTypesFromDB(eventTypeId);
|
||||
const eventType =
|
||||
!eventTypeId && !!eventTypeSlug ? getDefaultEvent(eventTypeSlug) : await getEventTypesFromDB(eventTypeId);
|
||||
if (!eventType) throw new HttpError({ statusCode: 404, message: "eventType.notFound" });
|
||||
|
||||
let users = !eventTypeId
|
||||
|
||||
@@ -1,45 +1,41 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import type { NextApiRequest } from "next";
|
||||
import { stringify } from "querystring";
|
||||
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||
import { getSlackAppKeys } from "../lib/utils";
|
||||
|
||||
let client_id = "";
|
||||
const scopes = ["commands", "users:read", "users:read.email", "chat:write", "chat:write.public"];
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
async function handler(req: NextApiRequest) {
|
||||
if (!req.session?.user?.id) {
|
||||
return res.status(401).json({ message: "You must be logged in to do this" });
|
||||
throw new HttpError({ statusCode: 401, message: "You must be logged in to do this" });
|
||||
}
|
||||
|
||||
if (req.method === "GET") {
|
||||
if (!req.session?.user?.id) {
|
||||
return res.status(401).json({ message: "You must be logged in to do this" });
|
||||
}
|
||||
|
||||
const appKeys = await getAppKeysFromSlug("slack");
|
||||
if (typeof appKeys.client_id === "string") client_id = appKeys.client_id;
|
||||
if (!client_id) return res.status(400).json({ message: "Slack client_id missing" });
|
||||
// Get user
|
||||
await prisma.user.findFirst({
|
||||
rejectOnNotFound: true,
|
||||
where: {
|
||||
id: req.session.user.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
const params = {
|
||||
client_id,
|
||||
scope: scopes.join(","),
|
||||
};
|
||||
const query = stringify(params);
|
||||
const url = `https://slack.com/oauth/v2/authorize?${query}&user_`;
|
||||
// const url =
|
||||
// "https://slack.com/oauth/v2/authorize?client_id=3194129032064.3178385871204&scope=chat:write,commands&user_scope=";
|
||||
return res.status(200).json({ url });
|
||||
}
|
||||
return res.status(404).json({ error: "Not Found" });
|
||||
const { client_id } = await getSlackAppKeys();
|
||||
// Get user
|
||||
await prisma.user.findFirst({
|
||||
rejectOnNotFound: true,
|
||||
where: {
|
||||
id: req.session.user.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
const params = {
|
||||
client_id,
|
||||
scope: scopes.join(","),
|
||||
};
|
||||
const query = stringify(params);
|
||||
const url = `https://slack.com/oauth/v2/authorize?${query}&user_`;
|
||||
// const url =
|
||||
// "https://slack.com/oauth/v2/authorize?client_id=3194129032064.3178385871204&scope=chat:write,commands&user_scope=";
|
||||
return { url };
|
||||
}
|
||||
|
||||
export default defaultHandler({
|
||||
GET: Promise.resolve({ default: defaultResponder(handler) }),
|
||||
});
|
||||
|
||||
@@ -1,57 +1,60 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { stringify } from "querystring";
|
||||
import { z } from "zod";
|
||||
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||
import { getSlackAppKeys } from "../lib/utils";
|
||||
|
||||
let client_id = "";
|
||||
let client_secret = "";
|
||||
const callbackQuerySchema = z.object({
|
||||
code: z.string().min(1),
|
||||
});
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!req.session?.user?.id) {
|
||||
return res.status(401).json({ message: "You must be logged in to do this" });
|
||||
throw new HttpError({ statusCode: 401, message: "You must be logged in to do this" });
|
||||
}
|
||||
|
||||
if (req.method === "GET") {
|
||||
// Get user
|
||||
const { code } = req.query;
|
||||
// Get user
|
||||
const parsedCallbackQuery = callbackQuerySchema.safeParse(req.query);
|
||||
|
||||
if (!code) {
|
||||
res.redirect("/apps/installed"); // Redirect to where the user was if they cancel the signup or if the oauth fails
|
||||
}
|
||||
if (!parsedCallbackQuery.success) {
|
||||
return res.redirect("/apps/installed"); // Redirect to where the user was if they cancel the signup or if the oauth fails
|
||||
}
|
||||
|
||||
const appKeys = await getAppKeysFromSlug("slack");
|
||||
if (typeof appKeys.client_id === "string") client_id = appKeys.client_id;
|
||||
if (typeof appKeys.client_secret === "string") client_secret = appKeys.client_secret;
|
||||
if (!client_id) return res.status(400).json({ message: "Slack client_id missing" });
|
||||
if (!client_secret) return res.status(400).json({ message: "Slack client_secret missing" });
|
||||
const { code } = parsedCallbackQuery.data;
|
||||
const { client_id, client_secret } = await getSlackAppKeys();
|
||||
|
||||
const query = {
|
||||
client_secret,
|
||||
client_id,
|
||||
code,
|
||||
};
|
||||
const params = stringify(query);
|
||||
console.log("params", params);
|
||||
const query = {
|
||||
client_secret,
|
||||
client_id,
|
||||
code,
|
||||
};
|
||||
const params = stringify(query);
|
||||
const url = `https://slack.com/api/oauth.v2.access?${params}`;
|
||||
const result = await fetch(url);
|
||||
const responseBody = await result.json();
|
||||
|
||||
const url = `https://slack.com/api/oauth.v2.access?${params}`;
|
||||
const result = await fetch(url);
|
||||
const responseBody = await result.json();
|
||||
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
id: req.session.user.id,
|
||||
},
|
||||
data: {
|
||||
credentials: {
|
||||
create: {
|
||||
type: "slack_app",
|
||||
key: responseBody,
|
||||
},
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
id: req.session.user.id,
|
||||
},
|
||||
data: {
|
||||
credentials: {
|
||||
create: {
|
||||
type: "slack_app",
|
||||
key: responseBody,
|
||||
appId: "slack",
|
||||
},
|
||||
},
|
||||
});
|
||||
return res.redirect("/apps/installed");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return res.redirect("/apps/installed");
|
||||
}
|
||||
|
||||
export default defaultHandler({
|
||||
GET: Promise.resolve({ default: defaultResponder(handler) }),
|
||||
});
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
|
||||
|
||||
import { showCreateEventMessage, showTodayMessage } from "../lib";
|
||||
import showLinksMessage from "../lib/showLinksMessage";
|
||||
import slackVerify from "../lib/slackVerify";
|
||||
|
||||
export enum SlackAppCommands {
|
||||
CREATE_EVENT = "create-event",
|
||||
@@ -10,20 +12,28 @@ export enum SlackAppCommands {
|
||||
LINKS = "links",
|
||||
}
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method === "POST") {
|
||||
const command = req.body.command.split("/").pop();
|
||||
await slackVerify(req, res);
|
||||
switch (command) {
|
||||
case SlackAppCommands.CREATE_EVENT:
|
||||
return await showCreateEventMessage(req, res);
|
||||
case SlackAppCommands.TODAY:
|
||||
return await showTodayMessage(req, res);
|
||||
case SlackAppCommands.LINKS:
|
||||
return await showLinksMessage(req, res);
|
||||
default:
|
||||
return res.status(404).json({ message: `Command not found` });
|
||||
}
|
||||
const commandHandlerBodySchema = z.object({
|
||||
command: z.string().min(1),
|
||||
user_id: z.string(),
|
||||
trigger_id: z.string(),
|
||||
channel_id: z.string().optional(),
|
||||
});
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const body = commandHandlerBodySchema.parse(req.body);
|
||||
const command = body.command.split("/").pop();
|
||||
switch (command) {
|
||||
case SlackAppCommands.CREATE_EVENT:
|
||||
return await showCreateEventMessage(req, res);
|
||||
case SlackAppCommands.TODAY:
|
||||
return await showTodayMessage(req, res);
|
||||
case SlackAppCommands.LINKS:
|
||||
return await showLinksMessage(req, res);
|
||||
default:
|
||||
return res.status(404).json({ message: `Command not found` });
|
||||
}
|
||||
return res.status(400).json({ message: "Invalid request" });
|
||||
}
|
||||
|
||||
export default defaultHandler({
|
||||
POST: Promise.resolve({ default: defaultResponder(handler) }),
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import createEvent from "../lib/actions/createEvent";
|
||||
import slackVerify from "../lib/slackVerify";
|
||||
|
||||
enum InteractionEvents {
|
||||
CREATE_EVENT = "cal.event.create",
|
||||
@@ -9,14 +8,11 @@ enum InteractionEvents {
|
||||
|
||||
export default async function interactiveHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method === "POST") {
|
||||
await slackVerify(req, res);
|
||||
const payload = JSON.parse(req.body.payload);
|
||||
const actions = payload.view.callback_id;
|
||||
|
||||
// I've not found a case where actions is ever > than 1 when this function is called.
|
||||
switch (actions) {
|
||||
case InteractionEvents.CREATE_EVENT:
|
||||
return await createEvent(req, res);
|
||||
await createEvent(req, res);
|
||||
default:
|
||||
return res.status(200).end(); // Techincally an invalid request but we don't want to return an throw an error to slack - 200 just does nothing
|
||||
}
|
||||
|
||||
@@ -1,46 +1,24 @@
|
||||
import { WebClient } from "@slack/web-api";
|
||||
import dayjs from "dayjs";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import db from "@calcom/prisma";
|
||||
import type { BookingCreateBody } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { WhereCredsEqualsId } from "../WhereCredsEqualsID";
|
||||
import { getUserEmail } from "../utils";
|
||||
import BookingSuccess from "../views/BookingSuccess";
|
||||
|
||||
// TODO: Move this type to a shared location - being used in more than one package.
|
||||
export type BookingCreateBody = {
|
||||
email: string;
|
||||
end: string;
|
||||
web3Details?: {
|
||||
userWallet: string;
|
||||
userSignature: unknown;
|
||||
};
|
||||
eventTypeId: number;
|
||||
guests?: string[];
|
||||
location: string;
|
||||
name: string;
|
||||
notes?: string;
|
||||
rescheduleUid?: string;
|
||||
start: string;
|
||||
timeZone: string;
|
||||
user?: string | string[];
|
||||
language: string;
|
||||
customInputs: { label: string; value: string }[];
|
||||
metadata: {
|
||||
[key: string]: string;
|
||||
};
|
||||
};
|
||||
|
||||
export default async function createEvent(req: NextApiRequest, res: NextApiResponse) {
|
||||
const {
|
||||
user,
|
||||
view: {
|
||||
state: { values },
|
||||
id: view_id,
|
||||
},
|
||||
response_url,
|
||||
} = JSON.parse(req.body.payload);
|
||||
|
||||
// This is a mess I have no idea why slack makes getting infomation this hard.
|
||||
const {
|
||||
eventName: {
|
||||
@@ -90,9 +68,11 @@ export default async function createEvent(req: NextApiRequest, res: NextApiRespo
|
||||
},
|
||||
});
|
||||
|
||||
const slackCredentials = foundUser?.credentials[0].key; // Only one slack credential for user
|
||||
const SlackCredentialsSchema = z.object({
|
||||
access_token: z.string(),
|
||||
});
|
||||
|
||||
// @ts-ignore access_token must exist on slackCredentials otherwise we have wouldnt have reached this endpoint
|
||||
const slackCredentials = SlackCredentialsSchema.parse(foundUser?.credentials[0].key); // Only one slack credential for user
|
||||
|
||||
const access_token = slackCredentials?.access_token;
|
||||
// https://api.slack.com/authentication/best-practices#verifying since we verify the request is coming from slack we can store the access_token in the DB.
|
||||
@@ -100,9 +80,7 @@ export default async function createEvent(req: NextApiRequest, res: NextApiRespo
|
||||
// This could get a bit weird as there is a 3 second limit until the post times ou
|
||||
|
||||
// Compute all users that have been selected and get their email.
|
||||
const invitedGuestsEmails = selected_users.map(
|
||||
async (userId: string) => await getUserEmail(client, userId)
|
||||
);
|
||||
const invitedGuestsEmails = selected_users.map((userId: string) => getUserEmail(client, userId));
|
||||
|
||||
const startDate = dayjs(`${selected_date} ${selected_time}`, "YYYY-MM-DD HH:mm");
|
||||
|
||||
@@ -124,36 +102,18 @@ export default async function createEvent(req: NextApiRequest, res: NextApiRespo
|
||||
notes: "This event was created with slack.",
|
||||
};
|
||||
|
||||
if (startDate < dayjs()) {
|
||||
client.chat.postMessage({
|
||||
token: access_token,
|
||||
channel: user.id,
|
||||
text: `Error: Day must not be in the past`,
|
||||
});
|
||||
return res.status(200).send("");
|
||||
}
|
||||
|
||||
fetch(`${WEBAPP_URL}/api/book/event`, {
|
||||
const response = await fetch(`${WEBAPP_URL}/api/book/event`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(PostData),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
client.chat.postMessage({
|
||||
token: access_token,
|
||||
channel: user.id, // We just dm the user here as there is no point posting this message publicly - In future it might be worth pinging all the members of the invite also?
|
||||
text: "Booking has been created.",
|
||||
});
|
||||
return res.status(200).send(""); // Slack requires a 200 to be sent to clear the modal. This makes it massive pain to update the user that the event has been created.
|
||||
})
|
||||
.catch((e) => {
|
||||
client.chat.postMessage({
|
||||
token: access_token,
|
||||
channel: user.id,
|
||||
text: `Error: ${e}`,
|
||||
});
|
||||
return res.status(200).send("");
|
||||
});
|
||||
});
|
||||
const body = await response.json();
|
||||
client.chat.postMessage({
|
||||
token: access_token,
|
||||
channel: user.id,
|
||||
text: body.errorCode ? `Error: ${body.errorCode}` : "Booking has been created.",
|
||||
});
|
||||
return res.status(200).send("");
|
||||
}
|
||||
|
||||
@@ -5,10 +5,12 @@ import { NextApiRequest, NextApiResponse } from "next";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { WhereCredsEqualsId } from "./WhereCredsEqualsID";
|
||||
import slackVerify from "./slackVerify";
|
||||
import { CreateEventModal, NoUserMessage } from "./views";
|
||||
|
||||
export default async function showCreateEventMessage(req: NextApiRequest, res: NextApiResponse) {
|
||||
const body = req.body;
|
||||
await slackVerify(req, res);
|
||||
|
||||
const data = await prisma.credential.findFirst({
|
||||
...WhereCredsEqualsId(body.user_id),
|
||||
@@ -35,5 +37,5 @@ export default async function showCreateEventMessage(req: NextApiRequest, res: N
|
||||
trigger_id: body.trigger_id,
|
||||
view: CreateEventModal(data),
|
||||
});
|
||||
res.status(200).end();
|
||||
return res.status(200).end();
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { KnownBlock, WebClient } from "@slack/web-api";
|
||||
import { WebClient } from "@slack/web-api";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { WhereCredsEqualsId } from "./WhereCredsEqualsID";
|
||||
import { CreateEventModal, NoUserMessage } from "./views";
|
||||
import slackVerify from "./slackVerify";
|
||||
import { NoUserMessage } from "./views";
|
||||
import ShowLinks from "./views/ShowLinks";
|
||||
|
||||
export default async function showLinksMessage(req: NextApiRequest, res: NextApiResponse) {
|
||||
const body = req.body;
|
||||
await slackVerify(req, res);
|
||||
|
||||
const data = await prisma.credential.findFirst({
|
||||
...WhereCredsEqualsId(body.user_id),
|
||||
@@ -40,9 +42,8 @@ export default async function showLinksMessage(req: NextApiRequest, res: NextApi
|
||||
slackClient.chat.postMessage({
|
||||
channel: body.channel_id,
|
||||
text: `${data.user?.username}'s Cal.com Links`,
|
||||
//@ts-ignore this doesnt need to be of type Block[] - an object works completely fine
|
||||
blocks,
|
||||
});
|
||||
|
||||
res.status(200).end();
|
||||
return res.status(200).end();
|
||||
}
|
||||
|
||||
@@ -5,11 +5,12 @@ import { NextApiRequest, NextApiResponse } from "next";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { WhereCredsEqualsId } from "./WhereCredsEqualsID";
|
||||
import slackVerify from "./slackVerify";
|
||||
import { NoUserMessage, TodayMessage } from "./views";
|
||||
|
||||
export default async function showCreateEventMessage(req: NextApiRequest, res: NextApiResponse) {
|
||||
const body = req.body;
|
||||
|
||||
await slackVerify(req, res);
|
||||
const foundUser = await prisma.credential.findFirst({
|
||||
...WhereCredsEqualsId(body.user_id),
|
||||
include: {
|
||||
@@ -50,5 +51,5 @@ export default async function showCreateEventMessage(req: NextApiRequest, res: N
|
||||
},
|
||||
});
|
||||
|
||||
res.status(200).json(TodayMessage(bookings));
|
||||
return res.status(200).json(TodayMessage(bookings));
|
||||
}
|
||||
|
||||
@@ -3,17 +3,14 @@ import dayjs from "dayjs";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { stringify } from "querystring";
|
||||
|
||||
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||
|
||||
let signingSecret = "";
|
||||
import { getSlackAppKeys } from "./utils";
|
||||
|
||||
export default async function slackVerify(req: NextApiRequest, res: NextApiResponse) {
|
||||
const body = req.body;
|
||||
const timeStamp = req.headers["x-slack-request-timestamp"] as string; // Always returns a string and not a string[]
|
||||
const slackSignature = req.headers["x-slack-signature"] as string;
|
||||
const currentTime = dayjs().unix();
|
||||
let { signing_secret } = await getAppKeysFromSlug("slack");
|
||||
if (typeof signing_secret === "string") signingSecret = signing_secret;
|
||||
const { signing_secret: signingSecret } = await getSlackAppKeys();
|
||||
const [version, hash] = slackSignature.split("=");
|
||||
|
||||
if (!timeStamp) {
|
||||
return res.status(400).json({ message: "Missing X-Slack-Request-Timestamp header" });
|
||||
@@ -27,10 +24,13 @@ export default async function slackVerify(req: NextApiRequest, res: NextApiRespo
|
||||
return res.status(400).json({ message: "Request is too old" });
|
||||
}
|
||||
|
||||
const signature_base = `v0:${timeStamp}:${stringify(body)}`;
|
||||
const signed_sig = "v0=" + createHmac("sha256", signingSecret).update(signature_base).digest("hex");
|
||||
const hmac = createHmac("sha256", signingSecret);
|
||||
|
||||
if (signed_sig !== slackSignature) {
|
||||
return res.status(400).json({ message: "Invalid signature" });
|
||||
hmac.update(`${version}:${timeStamp}:${stringify(req.body)}`);
|
||||
|
||||
const signed_sig = hmac.digest("hex");
|
||||
console.log({ signed_sig, hash, match: signed_sig === hash });
|
||||
if (signed_sig !== hash) {
|
||||
throw new Error("Hashes do not match ");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
import { WebClient } from "@slack/web-api";
|
||||
import { z } from "zod";
|
||||
|
||||
const getUserEmail = async (client: WebClient, userId: string) =>
|
||||
await (
|
||||
await client.users.info({ user: userId })
|
||||
).user?.profile?.email;
|
||||
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||
|
||||
export { getUserEmail };
|
||||
export const getUserEmail = async (client: WebClient, userId: string) =>
|
||||
(await client.users.info({ user: userId })).user?.profile?.email;
|
||||
|
||||
const slackAppKeysSchema = z.object({
|
||||
client_id: z.string(),
|
||||
client_secret: z.string(),
|
||||
signing_secret: z.string(),
|
||||
});
|
||||
|
||||
export const getSlackAppKeys = async () => {
|
||||
const appKeys = await getAppKeysFromSlug("slack");
|
||||
return slackAppKeysSchema.parse(appKeys);
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ const CreateEventModal = (
|
||||
} | null;
|
||||
})
|
||||
| null,
|
||||
invalidInput: boolean = false
|
||||
invalidInput = false
|
||||
) => {
|
||||
return Modal({ title: "Create Booking", submit: "Create", callbackId: "cal.event.create" })
|
||||
.blocks(
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { Booking } from "@prisma/client";
|
||||
import dayjs from "dayjs";
|
||||
import { Modal, Blocks, Elements, Bits, Message } from "slack-block-builder";
|
||||
import { Blocks, Elements, Message } from "slack-block-builder";
|
||||
|
||||
import { BASE_URL } from "@calcom/lib/constants";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
|
||||
const TodayMessage = (bookings: Booking[]) => {
|
||||
if (bookings.length === 0) {
|
||||
return Message()
|
||||
.blocks(Blocks.Section({ text: "You do not have any bookings for today." }))
|
||||
.asUser()
|
||||
.buildToJSON();
|
||||
.buildToObject();
|
||||
}
|
||||
return Message()
|
||||
.blocks(
|
||||
@@ -18,7 +18,7 @@ const TodayMessage = (bookings: Booking[]) => {
|
||||
bookings.map((booking) =>
|
||||
Blocks.Section({
|
||||
text: `${booking.title} | ${dayjs(booking.startTime).format("HH:mm")}`,
|
||||
}).accessory(Elements.Button({ text: "Cancel", url: `${BASE_URL}/cancel/${booking.uid}` }))
|
||||
}).accessory(Elements.Button({ text: "Cancel", url: `${WEBAPP_URL}/cancel/${booking.uid}` }))
|
||||
)
|
||||
)
|
||||
.buildToObject();
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"dependencies": {
|
||||
"@calcom/prisma": "*",
|
||||
"@slack/web-api": "^6.7.0",
|
||||
"slack-block-builder": "^2.5.0"
|
||||
"slack-block-builder": "^2.5.0",
|
||||
"zod": "^3.17.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/types": "*"
|
||||
|
||||
@@ -67,7 +67,7 @@ export const bookingCreateBodySchema = z.object({
|
||||
})
|
||||
.optional(),
|
||||
eventTypeId: z.number(),
|
||||
eventTypeSlug: z.string(),
|
||||
eventTypeSlug: z.string().optional(),
|
||||
guests: z.array(z.string()).optional(),
|
||||
location: z.string(),
|
||||
name: z.string(),
|
||||
@@ -81,10 +81,12 @@ export const bookingCreateBodySchema = z.object({
|
||||
bookingUid: z.string().optional(),
|
||||
customInputs: z.array(z.object({ label: z.string(), value: z.union([z.string(), z.boolean()]) })),
|
||||
metadata: z.record(z.string()),
|
||||
hasHashedBookingLink: z.boolean(),
|
||||
hasHashedBookingLink: z.boolean().optional(),
|
||||
hashedLink: z.string().nullish(),
|
||||
});
|
||||
|
||||
export type BookingCreateBody = z.input<typeof bookingCreateBodySchema>;
|
||||
|
||||
export const extendedBookingCreateBody = bookingCreateBodySchema.merge(
|
||||
z.object({
|
||||
noEmail: z.boolean().optional(),
|
||||
|
||||
Reference in New Issue
Block a user