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:
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) => ({
|
||||
|
||||
Reference in New Issue
Block a user