UNPKG

@cocalc/server

Version:

CoCalc server functionality: functions used by either the hub and the next.js server

117 lines (105 loc) 3.41 kB
/* Handle all mentions that haven't yet been handled. */ import getPool from "@cocalc/database/pool"; import { delay } from "awaiting"; import { getLogger } from "@cocalc/backend/logger"; import type { Action, Key } from "./types"; import notify from "./notify"; const logger = getLogger("mentions - handle"); // TODO: should be in the database server settings; also should be // user configurable, and this is just a default const minEmailInterval = "6 hours"; const maxPerInterval = 50; // up to 50 emails for a given chatroom every 6 hours. // We check for new notifications this frequently. const polIntervalSeconds = 15; // Handle all notification, then wait for the given time, then again // handle all unhandled notifications. export default async function init(): Promise<void> { while (true) { try { await handleAllMentions(); } catch (err) { logger.warn(`WARNING -- error handling mentions -- ${err}`); } await delay(polIntervalSeconds * 1000); } } async function handleAllMentions(): Promise<void> { const pool = getPool(); const { rows } = await pool.query( "SELECT time, project_id, path, source, target, description, fragment_id FROM mentions WHERE action IS null" ); for (const row of rows) { const { time, project_id, path, source, target, description, fragment_id } = row; try { await handleMention( { project_id, path, time, target, fragment_id }, source, description ?? "" ); } catch (err) { logger.warn( `WARNING -- error handling mention (will try later) -- ${err}` ); } } } async function handleMention( key: Key, source: string, description: string ): Promise<void> { // Check that source and target are both currently // collaborators on the project. const action: Action = await determineAction(key); try { switch (action) { case "ignore": // already recently notified about this chatroom. await setAction(key, action); return; case "notify": let whatDid = await notify(key, source, description); // record what we did. await setAction(key, whatDid); return; default: throw Error(`BUG: unknown action "${action}"`); } } catch (err) { await setError(key, action, `${err}`); } } async function determineAction(key: Key): Promise<Action> { const pool = getPool(); const { rows } = await pool.query( `SELECT COUNT(*)::INT FROM mentions WHERE project_id=$1 AND path=$2 AND target=$3 AND action = 'email' AND time >= NOW() - INTERVAL '${parseInt( minEmailInterval )}'`, [key.project_id, key.path, key.target] ); const count: number = rows[0]?.count ?? 0; if (count >= maxPerInterval) { return "ignore"; } return "notify"; } async function setAction(key: Key, action: Action): Promise<void> { const pool = getPool(); await pool.query( "UPDATE mentions SET action=$1 WHERE project_id=$2 AND path=$3 AND time=$4 AND target=$5", [action, key.project_id, key.path, key.time, key.target] ); } async function setError( key: Key, action: Action, error: string ): Promise<void> { const pool = getPool(); await pool.query( "UPDATE mentions SET action=$1, error=$2 WHERE project_id=$3 AND path=$4 AND time=$5 AND target=$6", [action, error, key.project_id, key.path, key.time, key.target] ); }