echos-ocr/src/lib/monday.ts

238 lines
6.4 KiB
TypeScript
Raw Normal View History

const MONDAY_API = "https://api.monday.com/v2";
const MONDAY_FILE_API = "https://api.monday.com/v2/file";
type MondayColumn = { id: string; title: string; type: string };
async function gql(token: string, query: string, variables?: Record<string, unknown>) {
const body = JSON.stringify({ query, variables });
console.log("[monday gql] request:", body.slice(0, 500));
const res = await fetch(MONDAY_API, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: token,
"API-Version": "2024-10",
},
body,
});
if (!res.ok) {
const text = await res.text().catch(() => "");
throw new Error(`Monday.com API ${res.status}: ${text}`);
}
const json = await res.json();
if (json.errors?.length) {
const err = json.errors[0];
const detail = err.extensions ? ` (${JSON.stringify(err.extensions)})` : "";
console.error("[monday gql] GraphQL error:", JSON.stringify(json.errors));
throw new Error(`Monday.com GraphQL: ${err.message}${detail}`);
}
if (json.error_message) {
console.error("[monday gql] API error:", json.error_message, json.error_code);
throw new Error(`Monday.com: ${json.error_message} (${json.error_code || "unknown"})`);
}
return json.data;
}
export async function fetchBoardColumns(token: string, boardId: string): Promise<MondayColumn[]> {
const data = await gql(token, `
query ($boardId: [ID!]!) {
boards(ids: $boardId) {
columns { id title type }
}
}
`, { boardId: [boardId] });
return data?.boards?.[0]?.columns ?? [];
}
export async function createItem(
token: string,
boardId: string,
itemName: string,
columnValues: Record<string, unknown>
): Promise<string> {
const data = await gql(token, `
mutation ($boardId: ID!, $itemName: String!, $columnValues: JSON!) {
create_item(
board_id: $boardId,
item_name: $itemName,
column_values: $columnValues,
create_labels_if_missing: true
) { id }
}
`, {
boardId,
itemName,
columnValues: JSON.stringify(columnValues),
});
return String(data.create_item.id);
}
export async function updateItem(
token: string,
boardId: string,
itemId: string,
columnValues: Record<string, unknown>
): Promise<void> {
await gql(token, `
mutation ($boardId: ID!, $itemId: ID!, $columnValues: JSON!) {
change_multiple_column_values(
board_id: $boardId,
item_id: $itemId,
column_values: $columnValues,
create_labels_if_missing: true
) { id }
}
`, {
boardId,
itemId,
columnValues: JSON.stringify(columnValues),
});
}
export async function uploadFileToItem(
token: string,
itemId: string,
columnId: string,
fileBuffer: Buffer,
fileName: string
): Promise<void> {
const query = `mutation ($file: File!) { add_file_to_column(file: $file, item_id: ${itemId}, column_id: "${columnId}") { id } }`;
const form = new FormData();
form.append("query", query);
form.append("variables[file]", new Blob([new Uint8Array(fileBuffer)]), fileName);
const res = await fetch(MONDAY_FILE_API, {
method: "POST",
headers: { Authorization: token },
body: form,
});
if (!res.ok) {
const text = await res.text().catch(() => "");
throw new Error(`Monday.com file upload ${res.status}: ${text}`);
}
}
type ItemColumnValue = { id: string; text: string; value: string | null };
export async function readItem(
token: string,
itemId: string
): Promise<{ name: string; columnValues: ItemColumnValue[] } | null> {
const data = await gql(token, `
query ($itemId: [ID!]!) {
items(ids: $itemId) {
name
column_values { id text value }
}
}
`, { itemId: [itemId] });
const item = data?.items?.[0];
if (!item) return null;
return {
name: item.name,
columnValues: item.column_values.map((cv: { id: string; text: string; value: string | null }) => ({
id: cv.id,
text: cv.text,
value: cv.value,
})),
};
}
const BOOLEAN_CARD_FIELDS = new Set([
"iSaidYesBookSent",
"ftGuestLetterSent",
"prayerForTeam",
"prayerConfidential",
]);
function formatValue(cardField: string, val: unknown): unknown | null {
if (BOOLEAN_CARD_FIELDS.has(cardField) || typeof val === "boolean") {
return { checked: val === true || val === "true" ? "true" : "false" };
}
if (Array.isArray(val)) {
const joined = val.filter(Boolean).join(", ");
return joined || null;
}
if (typeof val === "object") {
const s = JSON.stringify(val);
return s === "{}" || s === "[]" ? null : s;
}
const strVal = String(val).trim();
if (strVal === "" || strVal === "null" || strVal === "undefined" || strVal === "[object Object]") return null;
return strVal;
}
export function mapCardToColumnValues(
card: Record<string, unknown>,
columnMap: Record<string, string>
): Record<string, unknown> {
const values: Record<string, unknown> = {};
for (const [cardField, colId] of Object.entries(columnMap)) {
if (!colId || !cardField || cardField.startsWith("_")) continue;
const val = card[cardField];
if (val === null || val === undefined) continue;
const formatted = formatValue(cardField, val);
if (formatted !== null) {
values[colId] = formatted;
}
}
return values;
}
export function mapItemToCardFields(
columnValues: ItemColumnValue[],
columnMap: Record<string, string>
): Record<string, string> {
const reverseMap: Record<string, string> = {};
for (const [cardField, colId] of Object.entries(columnMap)) {
reverseMap[colId] = cardField;
}
const cardData: Record<string, string> = {};
for (const cv of columnValues) {
const cardField = reverseMap[cv.id];
if (cardField && cv.text) {
cardData[cardField] = cv.text;
}
}
return cardData;
}
export async function createWebhookSubscription(
token: string,
boardId: string,
callbackUrl: string
): Promise<string> {
const escapedUrl = callbackUrl.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
const data = await gql(token, `
mutation {
create_webhook(board_id: ${boardId}, url: "${escapedUrl}", event: change_column_value) {
id
board_id
}
}
`);
return String(data.create_webhook.id);
}
export async function deleteWebhookSubscription(
token: string,
webhookId: string
): Promise<void> {
await gql(token, `
mutation {
delete_webhook(id: ${webhookId}) {
id
}
}
`);
}