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]>
This commit is contained in:
2026-10-18 17:10:36 +01:00
co-authored by Claude Opus 5.5
parent 81e4a8aecc
commit 3286b80029
340 changed files with 3161 additions and 12060 deletions
@@ -0,0 +1,35 @@
import type {
BUN_SQLITE_COBBLE_BOT_TASK_STEPS,
BUN_SQLITE_COBBLE_BOT_TASKS,
} from "@/db/types/db";
import type { BotTaskWithSteps } from "@/src/types";
import BunSQLite from "@moduletrace/bun-sqlite";
type Params = {
bot_id: number | string;
};
/**
* The bot's most recent active task and its steps, for the working-on panel.
*/
export default async function grabActiveBotTask({
bot_id,
}: Params): Promise<BotTaskWithSteps | null> {
const task_res = await BunSQLite.sql<BUN_SQLITE_COBBLE_BOT_TASKS>({
sql: `SELECT * FROM bot_tasks WHERE bot_id = ? AND status = 'active' ORDER BY created_at DESC, id DESC LIMIT 1`,
values: [bot_id],
});
const task = task_res.singleRes || task_res.payload?.[0];
if (!task?.id) {
return null;
}
const steps_res = await BunSQLite.sql<BUN_SQLITE_COBBLE_BOT_TASK_STEPS>({
sql: `SELECT * FROM bot_task_steps WHERE task_id = ? ORDER BY position ASC, id ASC`,
values: [task.id],
});
return { ...task, steps: steps_res.payload || [] };
}
@@ -0,0 +1,31 @@
import type { BUN_SQLITE_COBBLE_BOT_MESSAGES } from "@/db/types/db";
import BunSQLite from "@moduletrace/bun-sqlite";
type Params = {
bot_id: number | string;
limit?: number;
};
/**
* The latest messages of a bot's conversation, oldest first.
*/
export default async function grabBotMessages({
bot_id,
limit = 100,
}: Params): Promise<BUN_SQLITE_COBBLE_BOT_MESSAGES[]> {
let sql = ``;
sql += `SELECT * FROM (\n`;
sql += ` SELECT * FROM bot_messages\n`;
sql += ` WHERE bot_id = ?\n`;
sql += ` ORDER BY created_at DESC, id DESC\n`;
sql += ` LIMIT ?\n`;
sql += `) ORDER BY created_at ASC, id ASC\n`;
const res = await BunSQLite.sql<BUN_SQLITE_COBBLE_BOT_MESSAGES>({
sql,
values: [bot_id, limit],
});
return res.payload || [];
}
@@ -0,0 +1,39 @@
import type { BotWithLastMessage } from "@/src/types";
import BunSQLite from "@moduletrace/bun-sqlite";
type Params = {
user_id: number | string;
};
/**
* A workspace's crew for the rail: the coordinator pinned first, then
* workers by latest activity. Each row carries its latest message.
*/
export default async function grabBots({
user_id,
}: Params): Promise<BotWithLastMessage[]> {
let sql = ``;
sql += `SELECT\n`;
sql += ` b.*,\n`;
sql += ` lm.text AS last_message_text,\n`;
sql += ` lm.created_at AS last_message_at\n`;
sql += `FROM bots b\n`;
sql += `LEFT JOIN bot_messages lm ON lm.id = (\n`;
sql += ` SELECT id FROM bot_messages\n`;
sql += ` WHERE bot_id = b.id\n`;
sql += ` ORDER BY created_at DESC, id DESC\n`;
sql += ` LIMIT 1\n`;
sql += `)\n`;
sql += `WHERE b.user_id = ?\n`;
sql += `ORDER BY\n`;
sql += ` CASE WHEN b.kind = 'coordinator' THEN 0 ELSE 1 END,\n`;
sql += ` COALESCE(lm.created_at, b.last_activity_at, b.created_at) DESC\n`;
const res = await BunSQLite.sql<BotWithLastMessage>({
sql,
values: [user_id],
});
return res.payload || [];
}
@@ -0,0 +1,22 @@
import type { BUN_SQLITE_COBBLE_BOTS } from "@/db/types/db";
import BunSQLite from "@moduletrace/bun-sqlite";
type Params = {
bot_id: number | string;
user_id: number | string;
};
/**
* A bot only if it belongs to the given workspace owner.
*/
export default async function grabUserBot({
bot_id,
user_id,
}: Params): Promise<BUN_SQLITE_COBBLE_BOTS | null> {
const res = await BunSQLite.sql<BUN_SQLITE_COBBLE_BOTS>({
sql: `SELECT * FROM bots WHERE id = ? AND user_id = ? LIMIT 1`,
values: [bot_id, user_id],
});
return res.payload?.[0] || null;
}
@@ -0,0 +1,50 @@
import type {
BUN_SQLITE_COBBLE_BOT_MESSAGES,
BunSQLiteTables,
} from "@/db/types/db";
import BunSQLite from "@moduletrace/bun-sqlite";
type Params = {
bot_id: number;
text: string;
};
/**
* Appends a user message to a bot's conversation and bumps its activity.
* Callers must check the bot belongs to the session user first.
*/
export default async function insertUserMessage({
bot_id,
text,
}: Params): Promise<BUN_SQLITE_COBBLE_BOT_MESSAGES> {
const now = Date.now();
const res = await BunSQLite.insert<
BUN_SQLITE_COBBLE_BOT_MESSAGES,
(typeof BunSQLiteTables)[number]
>({
table: "bot_messages",
data: [{ bot_id, author: "user", text, needs_you: 0 }],
});
const id = res.postInsertReturn?.insertId;
if (!res.success || !id) {
throw new Error(`Failed to save message: ${res.msg}`);
}
await BunSQLite.sql({
sql: `UPDATE bots SET last_activity_at = ? WHERE id = ?`,
values: [now, bot_id],
});
return {
id,
bot_id,
author: "user",
text,
needs_you: 0,
created_at: now,
updated_at: now,
};
}
@@ -0,0 +1,16 @@
import BunSQLite from "@moduletrace/bun-sqlite";
type Params = {
bot_id: number | string;
user_id: number | string;
};
/**
* Clears a bot's unread flag when its owner opens the conversation.
*/
export default async function markBotRead({ bot_id, user_id }: Params) {
await BunSQLite.sql({
sql: `UPDATE bots SET unread = 0 WHERE id = ? AND user_id = ? AND unread = 1`,
values: [bot_id, user_id],
});
}
@@ -1,4 +1,4 @@
import type { BUN_SQLITE_BSQLITE_ALL_TYPEDEFS } from "@/db/types/db";
import type { BUN_SQLITE_COBBLE_ALL_TYPEDEFS } from "@/db/types/db";
import type { TableType } from "@/src/types";
import uniqueStringArr from "@/src/utils/unique-string-arr";
import type { ServerQueryParam } from "@moduletrace/bun-sqlite";
@@ -15,11 +15,11 @@ type Params = {
/**
* Allowed fields from joins
*/
include_fields?: (keyof BUN_SQLITE_BSQLITE_ALL_TYPEDEFS)[];
include_fields?: (keyof BUN_SQLITE_COBBLE_ALL_TYPEDEFS)[];
/**
* Fields to exclude from joins
*/
exclude_fields?: (keyof BUN_SQLITE_BSQLITE_ALL_TYPEDEFS)[];
exclude_fields?: (keyof BUN_SQLITE_COBBLE_ALL_TYPEDEFS)[];
};
export default function checkAllowedJoins({
@@ -53,7 +53,7 @@ export default function checkAllowedJoins({
for (let i = 0; i < all_joins.length; i++) {
const join = all_joins[i];
if (!join) continue;
const join_field = join.field as keyof BUN_SQLITE_BSQLITE_ALL_TYPEDEFS;
const join_field = join.field as keyof BUN_SQLITE_COBBLE_ALL_TYPEDEFS;
if (!include_fields.includes(join_field)) {
return {
success: false,
@@ -1,41 +0,0 @@
import type { BUN_SQLITE_BSQLITE_EMAIL_ATTACHMENTS } from "@/db/types/db";
import type { APIResponseObject } from "@moduletrace/bun-sqlite";
import BunSQLite from "@moduletrace/bun-sqlite";
export type EmailAttachmentRecord = BUN_SQLITE_BSQLITE_EMAIL_ATTACHMENTS & {
letter_title?: string;
invoice_number?: string;
invoice_title?: string;
quote_number?: string;
quote_title?: string;
media_write_path?: string;
media_mime_type?: string;
};
export default async function grabEmailAttachments({
email_id,
}: {
email_id: number | string;
}): Promise<APIResponseObject<EmailAttachmentRecord>> {
return await BunSQLite.sql<EmailAttachmentRecord>({
sql: `
SELECT
ea.*,
l.title AS letter_title,
i.invoice_number,
i.title AS invoice_title,
q.quote_number,
q.title AS quote_title,
m.media_write_path,
m.mime_type AS media_mime_type
FROM email_attachments ea
LEFT JOIN letters l ON l.id = ea.letter_id
LEFT JOIN invoices i ON i.id = ea.invoice_id
LEFT JOIN quotes q ON q.id = ea.quote_id
LEFT JOIN media m ON m.id = ea.media_id
WHERE ea.email_id = ?
ORDER BY ea.id ASC
`,
values: [email_id],
});
}
@@ -1,14 +0,0 @@
import type { BUN_SQLITE_BSQLITE_EMAILS } from "@/db/types/db";
import type { APIResponseObject } from "@moduletrace/bun-sqlite";
import BunSQLite from "@moduletrace/bun-sqlite";
export default async function grabEmail({
id,
}: {
id: number | string;
}): Promise<APIResponseObject<BUN_SQLITE_BSQLITE_EMAILS>> {
return await BunSQLite.select<BUN_SQLITE_BSQLITE_EMAILS>({
table: "emails",
targetId: id,
});
}
@@ -1,14 +0,0 @@
import type { BUN_SQLITE_BSQLITE_LETTERS } from "@/db/types/db";
import type { APIResponseObject } from "@moduletrace/bun-sqlite";
import BunSQLite from "@moduletrace/bun-sqlite";
export default async function grabLetter({
id,
}: {
id: number | string;
}): Promise<APIResponseObject<BUN_SQLITE_BSQLITE_LETTERS>> {
return await BunSQLite.select<BUN_SQLITE_BSQLITE_LETTERS>({
table: "letters",
targetId: id,
});
}
@@ -1,102 +0,0 @@
import { Database } from "bun:sqlite";
import { beforeEach, describe, expect, test } from "bun:test";
import grabNextDocumentSequence, {
type DocumentNumberSqlRunner,
} from "./grab-next-document-sequence";
let db: Database;
let run_sql: DocumentNumberSqlRunner;
beforeEach(() => {
db = new Database(":memory:");
db.run("CREATE TABLE invoices (invoice_number TEXT UNIQUE)");
run_sql = async ({ sql, values }) => {
const row = db.query(sql).get(...values) as
| { max_seq: number | null }
| null;
return { singleRes: row };
};
});
function insertNumbers(numbers: string[]) {
const insert = db.prepare("INSERT INTO invoices (invoice_number) VALUES (?)");
for (const number of numbers) {
insert.run(number);
}
}
describe("grabNextDocumentSequence", () => {
test("starts at 1 for an empty table", async () => {
const next = await grabNextDocumentSequence({
table: "invoices",
column: "invoice_number",
prefix: "BSQLITE-202610",
run_sql,
});
expect(next).toBe(1);
});
test("returns max + 1 for existing numbers", async () => {
insertNumbers([
"BSQLITE-202610-001",
"BSQLITE-202610-002",
"BSQLITE-202610-003",
]);
const next = await grabNextDocumentSequence({
table: "invoices",
column: "invoice_number",
prefix: "BSQLITE-202610",
run_sql,
});
expect(next).toBe(4);
});
test("does not reuse a number after a deletion", async () => {
insertNumbers(["BSQLITE-202610-001", "BSQLITE-202610-003"]);
const next = await grabNextDocumentSequence({
table: "invoices",
column: "invoice_number",
prefix: "BSQLITE-202610",
run_sql,
});
expect(next).toBe(4);
});
test("ignores numbers from other prefixes", async () => {
insertNumbers([
"BSQLITE-202609-099",
"BSQLITEQ-202610-050",
"BSQLITE-202610-002",
]);
const next = await grabNextDocumentSequence({
table: "invoices",
column: "invoice_number",
prefix: "BSQLITE-202610",
run_sql,
});
expect(next).toBe(3);
});
test("handles sequences beyond three digits", async () => {
insertNumbers(["BSQLITE-202610-009", "BSQLITE-202610-010"]);
const next = await grabNextDocumentSequence({
table: "invoices",
column: "invoice_number",
prefix: "BSQLITE-202610",
run_sql,
});
expect(next).toBe(11);
});
});
@@ -1,41 +0,0 @@
import BunSQLite from "@moduletrace/bun-sqlite";
type DocumentNumberTarget = {
table: "invoices" | "quotes";
column: "invoice_number" | "quote_number";
};
export type DocumentNumberSqlRunner = (args: {
sql: string;
values: (string | number)[];
}) => Promise<{ singleRes?: { max_seq?: number | string | null } | null }>;
type Params = DocumentNumberTarget & {
prefix: string;
/**
* Injectable query runner, used by tests to target an in-memory DB.
*/
run_sql?: DocumentNumberSqlRunner;
};
/**
* # Grab the next document sequence.
* Reads the highest existing numeric suffix for the prefix instead of
* counting rows, so deleting a document can never hand out a number that
* already exists.
*/
export default async function grabNextDocumentSequence({
table,
column,
prefix,
run_sql,
}: Params): Promise<number> {
const sql = `SELECT MAX(CAST(SUBSTR(${column}, ?) AS INTEGER)) AS max_seq FROM ${table} WHERE ${column} LIKE ?`;
const values = [prefix.length + 2, `${prefix}-%`];
const res = run_sql
? await run_sql({ sql, values })
: await BunSQLite.sql({ sql, values });
return Number(res.singleRes?.max_seq || 0) + 1;
}
@@ -1,64 +0,0 @@
import type {
BUN_SQLITE_BSQLITE_INVOICE_ITEMS,
BUN_SQLITE_BSQLITE_INVOICE_QUOTES,
BUN_SQLITE_BSQLITE_INVOICES,
} from "@/db/types/db";
import type { APIResponseObject } from "@moduletrace/bun-sqlite";
import BunSQLite from "@moduletrace/bun-sqlite";
export default async function grabInvoice({
id,
}: {
id: number | string;
}): Promise<APIResponseObject<BUN_SQLITE_BSQLITE_INVOICES>> {
return await BunSQLite.select<BUN_SQLITE_BSQLITE_INVOICES>({
table: "invoices",
targetId: id,
});
}
export async function grabInvoiceItems({
invoice_id,
}: {
invoice_id: number | string;
}): Promise<APIResponseObject<BUN_SQLITE_BSQLITE_INVOICE_ITEMS>> {
return await BunSQLite.select<BUN_SQLITE_BSQLITE_INVOICE_ITEMS>({
table: "invoice_items",
query: {
query: {
invoice_id: {
value: Number(invoice_id),
},
},
order: [
{ field: "sort_order", strategy: "ASC" },
{ field: "id", strategy: "ASC" },
],
},
});
}
export type InvoiceQuoteLinkRecord = BUN_SQLITE_BSQLITE_INVOICE_QUOTES & {
quote_number?: string;
quote_title?: string;
};
export async function grabInvoiceQuoteLinks({
invoice_id,
}: {
invoice_id: number | string;
}): Promise<APIResponseObject<InvoiceQuoteLinkRecord>> {
return await BunSQLite.sql<InvoiceQuoteLinkRecord>({
sql: `
SELECT
iq.*,
q.quote_number,
q.title AS quote_title
FROM invoice_quotes iq
LEFT JOIN quotes q ON q.id = iq.quote_id
WHERE iq.invoice_id = ?
ORDER BY iq.id ASC
`,
values: [invoice_id],
});
}
@@ -1,15 +1,15 @@
import type { BUN_SQLITE_BSQLITE_MEDIA_JOIN } from "@/src/types/sql-joins";
import type { BUN_SQLITE_COBBLE_MEDIA_JOIN } from "@/src/types/sql-joins";
import type { APIResponseObject } from "@moduletrace/bun-sqlite";
import _ from "lodash";
import grabUsers from "../users/grab-users";
type Params = {
media_res: APIResponseObject<BUN_SQLITE_BSQLITE_MEDIA_JOIN>;
media_res: APIResponseObject<BUN_SQLITE_COBBLE_MEDIA_JOIN>;
};
export default async function appendUsersToMedia({
media_res,
}: Params): Promise<APIResponseObject<BUN_SQLITE_BSQLITE_MEDIA_JOIN>> {
}: Params): Promise<APIResponseObject<BUN_SQLITE_COBBLE_MEDIA_JOIN>> {
try {
let new_media_res = _.cloneDeep(media_res);
@@ -1,5 +1,5 @@
import _ from "lodash";
import type { BUN_SQLITE_BSQLITE_MEDIA } from "@/db/types/db";
import type { BUN_SQLITE_COBBLE_MEDIA } from "@/db/types/db";
import grabMediaFromParadigm from "@/src/functions/backend/db/media/grab-media-from-paraidigm";
import type { MediaParadigmType, TableType, User } from "@/src/types";
import grabDirNames from "@/src/utils/grab-dir-names";
@@ -55,7 +55,7 @@ export default async function deleteMedia(params: Params) {
const media_id_to_delete = media_ids_to_del[i];
const target_media = (
await BunSQLite.select<BUN_SQLITE_BSQLITE_MEDIA>({
await BunSQLite.select<BUN_SQLITE_COBBLE_MEDIA>({
table: "media",
targetId: media_id_to_delete,
})
@@ -77,7 +77,7 @@ export default async function deleteMedia(params: Params) {
const media_id = media_to_delete_array[i];
const target_media = (
await BunSQLite.select<BUN_SQLITE_BSQLITE_MEDIA>({
await BunSQLite.select<BUN_SQLITE_COBBLE_MEDIA>({
table: "media",
targetId: media_id,
})
@@ -101,7 +101,7 @@ export default async function deleteMedia(params: Params) {
);
}
await BunSQLite.delete<BUN_SQLITE_BSQLITE_MEDIA, TableType>({
await BunSQLite.delete<BUN_SQLITE_COBBLE_MEDIA, TableType>({
table: "media",
targetId: target_media.id,
});
@@ -1,6 +1,6 @@
import type { BUN_SQLITE_BSQLITE_MEDIA_PARADIGMS } from "@/db/types/db";
import type { BUN_SQLITE_COBBLE_MEDIA_PARADIGMS } from "@/db/types/db";
import type { MediaParadigmType, TableType } from "@/src/types";
import type { BUN_SQLITE_BSQLITE_MEDIA_PARADIGMS_JOIN } from "@/src/types/sql-joins";
import type { BUN_SQLITE_COBBLE_MEDIA_PARADIGMS_JOIN } from "@/src/types/sql-joins";
import BunSQLite from "@moduletrace/bun-sqlite";
import type { APIResponseObject } from "@moduletrace/bunext/types";
import _ from "lodash";
@@ -13,10 +13,10 @@ type Params = {
export default async function grabMediaFromParadigm({
media_paradigm,
user_id,
}: Params): Promise<APIResponseObject<BUN_SQLITE_BSQLITE_MEDIA_PARADIGMS_JOIN>> {
}: Params): Promise<APIResponseObject<BUN_SQLITE_COBBLE_MEDIA_PARADIGMS_JOIN>> {
try {
const media_paradigms_res = await BunSQLite.select<
BUN_SQLITE_BSQLITE_MEDIA_PARADIGMS,
BUN_SQLITE_COBBLE_MEDIA_PARADIGMS,
TableType
>({
table: "media_paradigms",
+5 -5
View File
@@ -1,7 +1,7 @@
import type { BUN_SQLITE_BSQLITE_MEDIA } from "@/db/types/db";
import type { BUN_SQLITE_COBBLE_MEDIA } from "@/db/types/db";
import { AppData } from "@/src/data/app-data";
import type { ApiReqParams, User } from "@/src/types";
import type { BUN_SQLITE_BSQLITE_MEDIA_JOIN } from "@/src/types/sql-joins";
import type { BUN_SQLITE_COBBLE_MEDIA_JOIN } from "@/src/types/sql-joins";
import BunSQLite, { type APIResponseObject } from "@moduletrace/bun-sqlite";
import type { SQLInsertGenValueType } from "@moduletrace/bun-sqlite";
import _ from "lodash";
@@ -14,7 +14,7 @@ type Params = {
export default async function grabMedia(
params?: Params,
): Promise<APIResponseObject<BUN_SQLITE_BSQLITE_MEDIA_JOIN>> {
): Promise<APIResponseObject<BUN_SQLITE_COBBLE_MEDIA_JOIN>> {
const { query, user } = params || {};
try {
@@ -72,7 +72,7 @@ export default async function grabMedia(
// 4. Parameterized Search Terms
if (query?.search_terms?.length) {
const fields_to_search: (keyof BUN_SQLITE_BSQLITE_MEDIA)[] = [
const fields_to_search: (keyof BUN_SQLITE_COBBLE_MEDIA)[] = [
"title",
"description",
"summary",
@@ -109,7 +109,7 @@ export default async function grabMedia(
const final_sql = `${sql}\n${sql_limit}`;
const final_values = [...values, ...limitValues];
const res = await BunSQLite.sql<BUN_SQLITE_BSQLITE_MEDIA_JOIN>({
const res = await BunSQLite.sql<BUN_SQLITE_COBBLE_MEDIA_JOIN>({
sql: final_sql,
values: final_values,
});
@@ -1,38 +0,0 @@
import type {
BUN_SQLITE_BSQLITE_QUOTE_ITEMS,
BUN_SQLITE_BSQLITE_QUOTES,
} from "@/db/types/db";
import type { APIResponseObject } from "@moduletrace/bun-sqlite";
import BunSQLite from "@moduletrace/bun-sqlite";
export default async function grabQuote({
id,
}: {
id: number | string;
}): Promise<APIResponseObject<BUN_SQLITE_BSQLITE_QUOTES>> {
return await BunSQLite.select<BUN_SQLITE_BSQLITE_QUOTES>({
table: "quotes",
targetId: id,
});
}
export async function grabQuoteItems({
quote_id,
}: {
quote_id: number | string;
}): Promise<APIResponseObject<BUN_SQLITE_BSQLITE_QUOTE_ITEMS>> {
return await BunSQLite.select<BUN_SQLITE_BSQLITE_QUOTE_ITEMS>({
table: "quote_items",
query: {
query: {
quote_id: {
value: Number(quote_id),
},
},
order: [
{ field: "sort_order", strategy: "ASC" },
{ field: "id", strategy: "ASC" },
],
},
});
}
@@ -1,5 +1,5 @@
import type {
BUN_SQLITE_BSQLITE_USER_TYPES,
BUN_SQLITE_COBBLE_USER_TYPES,
BunSQLiteTables,
} from "@/db/types/db";
import BunSQLite from "@moduletrace/bun-sqlite";
@@ -13,9 +13,9 @@ type Params = {
*/
export default async function grabUserTypes({
user_id,
}: Params): Promise<BUN_SQLITE_BSQLITE_USER_TYPES[]> {
}: Params): Promise<BUN_SQLITE_COBBLE_USER_TYPES[]> {
const res = await BunSQLite.select<
BUN_SQLITE_BSQLITE_USER_TYPES,
BUN_SQLITE_COBBLE_USER_TYPES,
(typeof BunSQLiteTables)[number]
>({
table: "user_types",
+3 -3
View File
@@ -1,6 +1,6 @@
import { AppData } from "@/src/data/app-data";
import type { ApiReqParams, SQLInsertGenValueType } from "@/src/types";
import type { BUN_SQLITE_BSQLITE_USERS_JOIN } from "@/src/types/sql-joins";
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";
@@ -17,7 +17,7 @@ type Params = {
export default async function grabUsers(
params?: Params,
): Promise<APIResponseObject<BUN_SQLITE_BSQLITE_USERS_JOIN>> {
): Promise<APIResponseObject<BUN_SQLITE_COBBLE_USERS_JOIN>> {
const { query } = params || {};
try {
@@ -123,7 +123,7 @@ export default async function grabUsers(
sql_limit += `OFFSET ${offset}\n`;
}
const res = await BunSQLite.sql<BUN_SQLITE_BSQLITE_USERS_JOIN>({
const res = await BunSQLite.sql<BUN_SQLITE_COBBLE_USERS_JOIN>({
sql: `${sql}\n${sql_limit}`,
values,
});
@@ -1,7 +1,7 @@
import type {
BUN_SQLITE_BSQLITE_MEDIA,
BUN_SQLITE_BSQLITE_MEDIA_PARADIGMS,
BUN_SQLITE_BSQLITE_USERS,
BUN_SQLITE_COBBLE_MEDIA,
BUN_SQLITE_COBBLE_MEDIA_PARADIGMS,
BUN_SQLITE_COBBLE_USERS,
BunSQLiteTables,
} from "@/db/types/db";
import { AppData } from "@/src/data/app-data";
@@ -26,12 +26,12 @@ import deleteMedia from "../../media/delete-media";
type Params = {
media_paradigms?: MediaParadigm[];
media_type?: MediaType;
user: BUN_SQLITE_BSQLITE_USERS | User;
user: BUN_SQLITE_COBBLE_USERS | User;
data?: MediaDataType;
is_primary?: boolean;
update_on_duplicate?: boolean;
extract_media_text_content?: boolean;
media?: BUN_SQLITE_BSQLITE_MEDIA;
media?: BUN_SQLITE_COBBLE_MEDIA;
media_name?: string;
media_mime_type?: string;
is_private?: boolean;
@@ -59,7 +59,7 @@ export default async function uploadAndRecordMedia({
thumbnail_width,
existing_media_id,
media_text_content: passed_media_text_content,
}: Params): Promise<APIResponseObject<BUN_SQLITE_BSQLITE_MEDIA>> {
}: Params): Promise<APIResponseObject<BUN_SQLITE_COBBLE_MEDIA>> {
const now = Date.now();
let target_file_name = `${media_name || now}`;
let target_file_thumbnail_name = `${media_name || now}-thumb`;
@@ -207,7 +207,7 @@ export default async function uploadAndRecordMedia({
if (is_primary) {
const existing_user_primary_media = await BunSQLite.select<
BUN_SQLITE_BSQLITE_MEDIA,
BUN_SQLITE_COBBLE_MEDIA,
TableType
>({
table: "media",
@@ -227,7 +227,7 @@ export default async function uploadAndRecordMedia({
}
}
const media_data: BUN_SQLITE_BSQLITE_MEDIA = {
const media_data: BUN_SQLITE_COBBLE_MEDIA = {
...media,
user_id: Number(user.id),
is_primary: is_primary ? 1 : 0,
@@ -239,11 +239,11 @@ export default async function uploadAndRecordMedia({
text_content: media_text_content,
};
let media_record: BUN_SQLITE_BSQLITE_MEDIA | undefined = undefined;
let media_record: BUN_SQLITE_COBBLE_MEDIA | undefined = undefined;
if (existing_media_id) {
const update_media_record = await BunSQLite.update<
BUN_SQLITE_BSQLITE_MEDIA,
BUN_SQLITE_COBBLE_MEDIA,
TableType
>({
data: media_data,
@@ -254,7 +254,7 @@ export default async function uploadAndRecordMedia({
media_record = update_media_record.singleRes || undefined;
} else {
const new_media_record = await BunSQLite.insert<
BUN_SQLITE_BSQLITE_MEDIA,
BUN_SQLITE_COBBLE_MEDIA,
TableType
>({
data: [media_data],
@@ -271,7 +271,7 @@ export default async function uploadAndRecordMedia({
if (media_paradigms?.[0] && media_record?.id) {
const delete_existing_paradigms = await BunSQLite.delete<
BUN_SQLITE_BSQLITE_MEDIA_PARADIGMS,
BUN_SQLITE_COBBLE_MEDIA_PARADIGMS,
(typeof BunSQLiteTables)[number]
>({
table: "media_paradigms",
@@ -281,7 +281,7 @@ export default async function uploadAndRecordMedia({
});
await BunSQLite.insert<
BUN_SQLITE_BSQLITE_MEDIA_PARADIGMS,
BUN_SQLITE_COBBLE_MEDIA_PARADIGMS,
(typeof BunSQLiteTables)[number]
>({
data: media_paradigms.map((mp) => ({