feat(web): db client, operator helper, IPC notify, logger

This commit is contained in:
yiekheng 2026-05-09 22:48:00 +08:00
parent 7238369503
commit 2f7313b9ac
6 changed files with 61 additions and 0 deletions

View File

@ -16,6 +16,7 @@
"@hookform/resolvers": "^5.2.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"drizzle-orm": "^0.36.0",
"geist": "^1.7.0",
"lucide-react": "^1.14.0",
"next": "^16.0.0",
@ -28,6 +29,7 @@
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.75.0",
"server-only": "^0.0.1",
"shadcn": "^4.7.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.5.0",

8
apps/web/src/lib/db.ts Normal file
View File

@ -0,0 +1,8 @@
import "server-only";
import { createClient, type DB } from "@cmbot/db";
import { env } from "@/env";
const { db, pool } = createClient(env.DATABASE_URL);
export { db, pool };
export type { DB };

View File

@ -0,0 +1,9 @@
import "server-only";
import pino from "pino";
export const logger = pino({
level: process.env.NODE_ENV === "production" ? "info" : "debug",
...(process.env.NODE_ENV !== "production"
? { transport: { target: "pino-pretty", options: { colorize: true } } }
: {}),
});

View File

@ -0,0 +1,15 @@
import "server-only";
import { sql } from "drizzle-orm";
import { db } from "./db";
export type BotCommand =
| { type: "account.start_pairing"; accountId: string }
| { type: "account.unpair"; accountId: string }
| { type: "account.sync_groups"; accountId: string }
| { type: "group.send_test"; groupId: string; text: string }
| { type: "reminder.schedule"; reminderId: string; scheduledAtIso: string };
export async function pgNotifyBot(cmd: BotCommand): Promise<void> {
const json = JSON.stringify(cmd);
await db.execute(sql`SELECT pg_notify('bot.command', ${json})`);
}

View File

@ -0,0 +1,16 @@
import "server-only";
import { db } from "./db";
/**
* Returns the single seeded operator row. Since the app has no auth,
* every action is attributed to this operator.
*/
export async function getSeededOperator() {
const op = await db.query.operators.findFirst({
orderBy: (o, { asc }) => [asc(o.createdAt)],
});
if (!op) {
throw new Error("No operator row seeded. Run scripts/db.sh seed.");
}
return op;
}

11
pnpm-lock.yaml generated
View File

@ -90,6 +90,9 @@ importers:
clsx:
specifier: ^2.1.1
version: 2.1.1
drizzle-orm:
specifier: ^0.36.0
version: 0.36.4(@types/pg@8.20.0)(@types/react@19.2.14)(pg@8.20.0)(react@19.2.6)
geist:
specifier: ^1.7.0
version: 1.7.0(next@16.2.6(@babel/core@7.29.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))
@ -126,6 +129,9 @@ importers:
react-hook-form:
specifier: ^7.75.0
version: 7.75.0(react@19.2.6)
server-only:
specifier: ^0.0.1
version: 0.0.1
shadcn:
specifier: ^4.7.0
version: 4.7.0(@types/node@22.19.18)(typescript@5.9.3)
@ -3963,6 +3969,9 @@ packages:
resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
engines: {node: '>= 18'}
server-only@0.0.1:
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
@ -8005,6 +8014,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
server-only@0.0.1: {}
set-blocking@2.0.0: {}
set-cookie-parser@3.1.0: {}