Files
cobble/src/functions/backend/db/users/grab-users.ts
T
tbenandClaude Opus 5.5 3286b80029 Build Cobble project structure from the handoff
- Remove the preset document suite (letters, emails, invoices, quotes,
  PDF generation, line items) with its routes, API handlers and tables
- Rebrand to Cobble: site data, Lato, handoff design tokens (light and
  dark) in main.css, brand assets in public/brand, ember stone logo and
  regenerated favicons
- Add bots, bot_messages, bot_tasks and bot_task_steps tables, regenerate
  DB types (BUN_SQLITE_COBBLE_*) and add a sample crew seed (seed:bots)
- Add the chat shell at /admin/bots: rail with pinned coordinator and
  user popover, bottom-anchored conversation with composer, working-on
  panel, and a send-message API scoped to the session user
- Add BotStone and Wordmark components, crew status and relative time
  utils with tests, and update AGENTS.md

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-18 17:10:36 +01:00

148 lines
5.3 KiB
TypeScript

import { AppData } from "@/src/data/app-data";
import type { ApiReqParams, SQLInsertGenValueType } from "@/src/types";
import type { BUN_SQLITE_COBBLE_USERS_JOIN } from "@/src/types/sql-joins";
import BunSQLite from "@moduletrace/bun-sqlite";
import type { APIResponseObject } from "@moduletrace/bunext/types";
import _ from "lodash";
type Params = {
query?: ApiReqParams;
/**
* For search terms (ie `search_term_*`) parameters.
* This forces all searches to `=` instead of `LIKE`
*/
exact_match?: boolean;
user_id?: string | number;
};
export default async function grabUsers(
params?: Params,
): Promise<APIResponseObject<BUN_SQLITE_COBBLE_USERS_JOIN>> {
const { query } = params || {};
try {
const LIMIT = AppData["DefaultQueryLimit"];
let sql = ``;
let values: SQLInsertGenValueType[] = [];
sql += `SELECT\n`;
sql += ` u.*,\n`;
sql += ` ut_agg.user_types,\n`;
sql += ` m.id AS profile_media_id\n`;
sql += `FROM users u\n`;
sql += `LEFT JOIN (\n`;
sql += ` SELECT user_id, GROUP_CONCAT(DISTINCT user_type) AS user_types\n`;
sql += ` FROM user_types\n`;
sql += ` GROUP BY user_id\n`;
sql += `) ut_agg ON u.id = ut_agg.user_id\n`;
sql += `LEFT JOIN media m ON (\n`;
sql += ` u.id = m.user_id\n`;
sql += ` AND m.is_primary = 1\n`;
sql += `)\n`;
sql += `LEFT JOIN media_paradigms mp ON (\n`;
sql += ` m.id = mp.media_id\n`;
sql += ` AND mp.media_paradigm = 'user-profile-image'\n`;
sql += `)\n`;
sql += `WHERE COALESCE(u.archived, 0) <> 1\n`;
const target_user_id = params?.user_id || query?.user_id;
if (target_user_id) {
sql += ` AND u.id=?\n`;
values.push(target_user_id);
}
if (query?.search_term_user_type) {
sql += ` AND EXISTS (\n`;
sql += ` SELECT 1 FROM user_types WHERE user_id = u.id AND user_type LIKE`;
if (Array.isArray(query.search_term_user_type)) {
sql += `(`;
for (let i = 0; i < query.search_term_user_type.length; i++) {
const ut = query.search_term_user_type[i];
sql += ` ?`;
values.push(`%${ut}%`);
}
sql += `)`;
} else {
sql += ` ?`;
values.push(`%${query.search_term_user_type}%`);
}
sql += ` )\n`;
} else if (query?.target_user_type) {
sql += ` AND EXISTS (
SELECT 1 FROM user_types WHERE user_id = u.id AND user_type = ?
)\n`;
values.push(query.target_user_type);
}
let search_terms = [
{ field: "first_name", value: query?.search_term_first_name },
{ field: "last_name", value: query?.search_term_last_name },
{ field: "email", value: query?.search_term_email },
{ field: "id", value: query?.search_term_id },
] as const;
for (const term of search_terms) {
if (term.value) {
const isId = term.field === "id";
const match_value = params?.exact_match ? "=" : "LIKE";
if (Array.isArray(term.value)) {
sql += ` AND (`;
for (let i = 0; i < term.value.length; i++) {
const term_value = term.value[i];
if (!term_value) continue;
const match_value_param = params?.exact_match
? term_value
: `%${term_value.replace(/ /g, "%")}%`;
sql += ` u.${term.field} ${isId ? "=" : match_value} ?`;
if (i < term.value.length - 1) {
sql += ` OR`;
}
values.push(isId ? term_value : match_value_param);
}
sql += ` )\n`;
} else {
const match_value_param = params?.exact_match
? term.value
: `%${term.value.replace(/ /g, "%")}%`;
sql += ` AND u.${term.field} ${isId ? "=" : match_value} ?\n`;
values.push(isId ? term.value : match_value_param);
}
}
}
let sql_limit = ``;
sql_limit += `LIMIT ${LIMIT}\n`;
if (query?.page) {
const offset = LIMIT * (query.page - 1);
sql_limit += `OFFSET ${offset}\n`;
}
const res = await BunSQLite.sql<BUN_SQLITE_COBBLE_USERS_JOIN>({
sql: `${sql}\n${sql_limit}`,
values,
});
if (query?.count) {
const count_sql = `SELECT COUNT(*) AS count FROM (${sql}) x`;
// const count_sql = `SELECT COUNT(*) AS count FROM users u WHERE u.archived IS NOT 1 ${
// query?.user_id ? "AND u.id = ?" : ""
// }`;
const count_res = await BunSQLite.sql({ sql: count_sql, values });
res.count = count_res.singleRes?.count;
}
return res;
} catch (error: any) {
return {
success: false,
msg: error.message,
};
}
}