From 68f2c55ecf1313b1f508449616524286ec41fee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Thu, 13 Aug 2026 01:34:04 +0000 Subject: [PATCH] one directory per feature: schema.ts and queries.ts together MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/databases/officer_db/src//{schema.ts,queries.ts}, replacing the parallel schema/ and queries/ trees. 24 feature directories, 46 files moved with git mv so history follows. The parallel trees had drifted, which is what the restructure is really fixing: four features were named differently on each side — app-store/sidecar-installs, email/email-accounts, server/server-config operations had a schema and NO query file: its task_logs is reached directly from src/servers/api/task-logger.ts, bypassing this package's own boundary integrations had queries and NO schema, because it spans two features' tables — server_integrations and user_integrations Both lopsided cases survive as directories holding one file, which states the problem instead of hiding it across two trees. Nothing outside the package changed how it imports. `officerdb`, `officerdb/types` and `officerdb/db` resolve exactly as before; index.ts absorbed the path changes. Added `"./*": "./src/*"` so the new layout is reachable — `officerdb/soulseek/schema` — which one script needed, because soulseek is a plugin and therefore commented out of the aggregator. schema/index.ts became src/schema.ts, keeping the core/plugin split from earlier tonight. drizzle.config.ts and the package's "./schema" export follow it. Verified rather than assumed: all 52 files in the package parse, every relative import resolves against the new layout (checked by walking each specifier to a real file, since parsing does not check paths), and everything in the tree importing officerdb still parses. Not typechecked — empty node_modules, frozen installs. One rewrite bug worth recording: the rule mapping a query module's sibling import also matched the './schema' this pass had just written, turning it into '../schema/queries' in 22 files. Caught by the resolver check, not by parsing — both spellings parse fine. Also corrects every path reference the move invalidated: src/databases/CLAUDE.md's layout diagram, the root CLAUDE.md data section, three docs, and seven sidecar comments naming queries/.ts. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 5 +- docs/mobile-api-keys.md | 2 +- docs/wallet-key-custody.md | 2 +- docs/workspace-panel-todo.md | 6 +- scripts/rebuild-soulseek-tree.ts | 4 +- src/databases/CLAUDE.md | 37 ++++++--- src/databases/officer_db/drizzle.config.ts | 2 +- src/databases/officer_db/package.json | 5 +- .../queries.ts} | 4 +- .../schema.ts} | 2 +- .../api-keys.ts => api-keys/queries.ts} | 3 +- .../api-keys.ts => api-keys/schema.ts} | 2 +- .../queries.ts} | 2 +- .../app-store.ts => app-store/schema.ts} | 0 .../src/{queries/auth.ts => auth/queries.ts} | 2 +- .../src/{schema/auth.ts => auth/schema.ts} | 0 .../queries.ts} | 6 +- .../schema.ts} | 2 +- .../chat-events.ts => chat-events/queries.ts} | 2 +- .../chat-events.ts => chat-events/schema.ts} | 0 .../dashboards.ts => dashboards/queries.ts} | 2 +- .../dashboards.ts => dashboards/schema.ts} | 2 +- .../src/{queries/dav.ts => dav/queries.ts} | 2 +- .../src/{schema/dav.ts => dav/schema.ts} | 2 +- .../email-accounts.ts => email/queries.ts} | 2 +- .../src/{schema/email.ts => email/schema.ts} | 2 +- .../headscale.ts => headscale/queries.ts} | 2 +- .../headscale.ts => headscale/schema.ts} | 2 +- src/databases/officer_db/src/index.ts | 80 +++++++++---------- .../queries.ts} | 5 +- .../queries.ts} | 2 +- .../schema.ts} | 2 +- .../jellyfin.ts => jellyfin/queries.ts} | 2 +- .../jellyfin.ts => jellyfin/schema.ts} | 2 +- .../{queries/music.ts => music/queries.ts} | 2 +- .../src/{schema/music.ts => music/schema.ts} | 2 +- .../{queries/notify.ts => notify/queries.ts} | 2 +- .../{schema/notify.ts => notify/schema.ts} | 2 +- .../operations.ts => operations/schema.ts} | 2 +- .../{queries/photos.ts => photos/queries.ts} | 2 +- .../{schema/photos.ts => photos/schema.ts} | 2 +- .../queries.ts} | 2 +- .../schema.ts} | 2 +- .../src/{schema/index.ts => schema.ts} | 46 +++++------ .../server-config.ts => server/queries.ts} | 2 +- .../{schema/server.ts => server/schema.ts} | 0 .../queries.ts} | 4 +- .../schema.ts} | 2 +- .../soulseek.ts => soulseek/queries.ts} | 2 +- .../soulseek.ts => soulseek/schema.ts} | 2 +- .../user-data.ts => user-data/queries.ts} | 2 +- .../user-data.ts => user-data/schema.ts} | 4 +- .../{queries/vault.ts => vault/queries.ts} | 2 +- .../src/{schema/vault.ts => vault/schema.ts} | 2 +- .../{queries/wallet.ts => wallet/queries.ts} | 4 +- .../{schema/wallet.ts => wallet/schema.ts} | 2 +- src/servers/sidecar/invoiceshelf/upstream.ts | 2 +- src/servers/sidecar/jellyfin/upstream.ts | 2 +- src/servers/sidecar/photos/upstream.ts | 2 +- src/servers/sidecar/slskd/upstream.ts | 2 +- src/servers/sidecar/transmission/upstream.ts | 2 +- src/servers/sidecar/wallet/upstream.ts | 2 +- .../officerdev/src/apps/Wallet/shared.ts | 2 +- 63 files changed, 161 insertions(+), 142 deletions(-) rename src/databases/officer_db/src/{queries/agent-panels.ts => agent-panels/queries.ts} (97%) rename src/databases/officer_db/src/{schema/agent-panels.ts => agent-panels/schema.ts} (99%) rename src/databases/officer_db/src/{queries/api-keys.ts => api-keys/queries.ts} (98%) rename src/databases/officer_db/src/{schema/api-keys.ts => api-keys/schema.ts} (98%) rename src/databases/officer_db/src/{queries/sidecar-installs.ts => app-store/queries.ts} (99%) rename src/databases/officer_db/src/{schema/app-store.ts => app-store/schema.ts} (100%) rename src/databases/officer_db/src/{queries/auth.ts => auth/queries.ts} (99%) rename src/databases/officer_db/src/{schema/auth.ts => auth/schema.ts} (100%) rename src/databases/officer_db/src/{queries/capabilities.ts => capabilities/queries.ts} (94%) rename src/databases/officer_db/src/{schema/capabilities.ts => capabilities/schema.ts} (98%) rename src/databases/officer_db/src/{queries/chat-events.ts => chat-events/queries.ts} (97%) rename src/databases/officer_db/src/{schema/chat-events.ts => chat-events/schema.ts} (100%) rename src/databases/officer_db/src/{queries/dashboards.ts => dashboards/queries.ts} (99%) rename src/databases/officer_db/src/{schema/dashboards.ts => dashboards/schema.ts} (99%) rename src/databases/officer_db/src/{queries/dav.ts => dav/queries.ts} (98%) rename src/databases/officer_db/src/{schema/dav.ts => dav/schema.ts} (98%) rename src/databases/officer_db/src/{queries/email-accounts.ts => email/queries.ts} (96%) rename src/databases/officer_db/src/{schema/email.ts => email/schema.ts} (97%) rename src/databases/officer_db/src/{queries/headscale.ts => headscale/queries.ts} (99%) rename src/databases/officer_db/src/{schema/headscale.ts => headscale/schema.ts} (98%) rename src/databases/officer_db/src/{queries/integrations.ts => integrations/queries.ts} (96%) rename src/databases/officer_db/src/{queries/invoiceshelf.ts => invoiceshelf/queries.ts} (99%) rename src/databases/officer_db/src/{schema/invoiceshelf.ts => invoiceshelf/schema.ts} (98%) rename src/databases/officer_db/src/{queries/jellyfin.ts => jellyfin/queries.ts} (99%) rename src/databases/officer_db/src/{schema/jellyfin.ts => jellyfin/schema.ts} (98%) rename src/databases/officer_db/src/{queries/music.ts => music/queries.ts} (99%) rename src/databases/officer_db/src/{schema/music.ts => music/schema.ts} (98%) rename src/databases/officer_db/src/{queries/notify.ts => notify/queries.ts} (98%) rename src/databases/officer_db/src/{schema/notify.ts => notify/schema.ts} (98%) rename src/databases/officer_db/src/{schema/operations.ts => operations/schema.ts} (98%) rename src/databases/officer_db/src/{queries/photos.ts => photos/queries.ts} (99%) rename src/databases/officer_db/src/{schema/photos.ts => photos/schema.ts} (98%) rename src/databases/officer_db/src/{queries/pipeline-jobs.ts => pipeline-jobs/queries.ts} (98%) rename src/databases/officer_db/src/{schema/pipeline-jobs.ts => pipeline-jobs/schema.ts} (97%) rename src/databases/officer_db/src/{schema/index.ts => schema.ts} (51%) rename src/databases/officer_db/src/{queries/server-config.ts => server/queries.ts} (96%) rename src/databases/officer_db/src/{schema/server.ts => server/schema.ts} (100%) rename src/databases/officer_db/src/{queries/service-connections.ts => service-connections/queries.ts} (98%) rename src/databases/officer_db/src/{schema/service-connections.ts => service-connections/schema.ts} (99%) rename src/databases/officer_db/src/{queries/soulseek.ts => soulseek/queries.ts} (99%) rename src/databases/officer_db/src/{schema/soulseek.ts => soulseek/schema.ts} (99%) rename src/databases/officer_db/src/{queries/user-data.ts => user-data/queries.ts} (96%) rename src/databases/officer_db/src/{schema/user-data.ts => user-data/schema.ts} (96%) rename src/databases/officer_db/src/{queries/vault.ts => vault/queries.ts} (98%) rename src/databases/officer_db/src/{schema/vault.ts => vault/schema.ts} (97%) rename src/databases/officer_db/src/{queries/wallet.ts => wallet/queries.ts} (99%) rename src/databases/officer_db/src/{schema/wallet.ts => wallet/schema.ts} (99%) diff --git a/CLAUDE.md b/CLAUDE.md index c009646d..0efaa5d8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -123,8 +123,9 @@ imported by their package name (`officerdev`, `hooks`, `state`, `types`, `helper Two stores, and the split matters: **Postgres** (`src/databases/officer_db`) holds the account, passkeys, settings, dashboards, -email accounts, queue and pipeline jobs. Schema in `src/schema/`, hand-written queries in -`src/queries/`, types inferred from the schema in `src/types.ts`. +email accounts, queue and pipeline jobs. One directory per feature holding `schema.ts` and +`queries.ts` beside each other; `src/schema.ts` is what `db:push` reads, and it lists the core tables +with the plugin ones commented out. Types inferred from the schema in `src/types.ts`. **The filesystem** holds everything the agent authors. `OFFICER_ITEMS_DIR` (`$OFFICER_ROOT/capabilities`) contains one directory per item under `skills/`, `tools/`, `tasks/`, `processes/`, `extensions/` — no diff --git a/docs/mobile-api-keys.md b/docs/mobile-api-keys.md index ed17484f..0f7deb96 100644 --- a/docs/mobile-api-keys.md +++ b/docs/mobile-api-keys.md @@ -327,4 +327,4 @@ service verbs exist (`listApiKeys`, `revokeApiKey`) if that changes. bearer string into a caller. All four doors call it: `userMiddleware`, `originScopeMiddleware`, the WebSocket upgrade in `server.tsx`, and the vault socket. - `src/servers/api/api-keys/router.ts` — the three endpoints. -- `src/databases/officer_db/src/schema/api-keys.ts` — the table, and why it stores what it stores. +- `src/databases/officer_db/src/api-keys/schema.ts` — the table, and why it stores what it stores. diff --git a/docs/wallet-key-custody.md b/docs/wallet-key-custody.md index 6cef953f..b34ad674 100644 --- a/docs/wallet-key-custody.md +++ b/docs/wallet-key-custody.md @@ -5,7 +5,7 @@ does and does not protect against. Authoritative for the crypto design. The code is `src/servers/sidecar/wallet/keys.ts` (sealing, derivation, unlock sessions), `src/databases/officer_db/src/crypto.ts` (storage encryption) and -`src/databases/officer_db/src/queries/wallet.ts` (where the two meet). +`src/databases/officer_db/src/wallet/queries.ts` (where the two meet). ## The requirement diff --git a/docs/workspace-panel-todo.md b/docs/workspace-panel-todo.md index a2b5929e..b96fa4fe 100644 --- a/docs/workspace-panel-todo.md +++ b/docs/workspace-panel-todo.md @@ -135,7 +135,7 @@ and the rename sequence leaves `workspaces` with no zombie. - [x] **`ws-terminals-{id}: null` on a live dashboard is a 500.** Same file, `:61-66` — the `ws-layout-*` branch has a `value === null` → `deleteDashboard` case (`:42`); the terminals branches do not. A null falls to the UPDATE branch and sets a `NOT NULL` column - (`databases/officer_db/src/queries/dashboards.ts:70`) → 23502. + (`databases/officer_db/src/dashboards/queries.ts:70`) → 23502. **Resolved.** A null on either terminals branch is now a no-op: it means "forget this key", and it only ever arrives paired with `ws-layout-{id}: null` on a rename, by which point the row is gone. @@ -202,7 +202,7 @@ these. > which uuid ids would not. - [ ] **`dashboards.id` is a global primary key but ids are `slugify(name)`.** - `databases/officer_db/src/schema/dashboards.ts` declares `id: text('id').primaryKey()`. Live: + `databases/officer_db/src/dashboards/schema.ts` declares `id: text('id').primaryKey()`. Live: `"dashboards_pkey" PRIMARY KEY, btree (id)` plus a redundant `"uq_dashboards_user_id" UNIQUE, btree (user_id, id)` — evidence per-user ids were intended and half-built. Ids come from `DashboardPreview.tsx:300` (`slugify(trimmed) || generateSlug()`) and the @@ -213,7 +213,7 @@ these. (see `databases/CLAUDE.md` → "Composite keys") — harmless churn, but read the plan. - [x] **`upsertDashboard`'s UPDATE has no `userId` predicate.** - `databases/officer_db/src/queries/dashboards.ts:73` — + `databases/officer_db/src/dashboards/queries.ts:73` — `db.update(dashboards).set(set).where(eq(dashboards.id, id))`. The `existing` lookup above it _is_ scoped, so it cannot reach another user's row today, but it is a non-transactional read-then-write. **It becomes a live cross-user overwrite the moment the PK above is made composite.** diff --git a/scripts/rebuild-soulseek-tree.ts b/scripts/rebuild-soulseek-tree.ts index c3f2b9a7..db3e23b5 100644 --- a/scripts/rebuild-soulseek-tree.ts +++ b/scripts/rebuild-soulseek-tree.ts @@ -10,7 +10,9 @@ import type { BrowsedFile } from 'officerdb'; import { eq, asc } from 'drizzle-orm'; import { db, finishSoulseekBrowse } from 'officerdb'; -import { soulseekBrowseSnapshots, soulseekBrowseDirs } from 'officerdb/schema'; +// soulseek is a plugin, so its tables are commented out of officerdb's schema aggregator — +// import them from the feature directly. +import { soulseekBrowseSnapshots, soulseekBrowseDirs } from 'officerdb/soulseek/schema'; import { buildTree } from '../src/servers/sidecar/slskd/browse'; const snapshots = await db diff --git a/src/databases/CLAUDE.md b/src/databases/CLAUDE.md index 5b2902a5..742f99ed 100644 --- a/src/databases/CLAUDE.md +++ b/src/databases/CLAUDE.md @@ -9,18 +9,31 @@ describing a different codebase. ``` src/databases/officer_db/ ├── src/ -│ ├── db.ts # the connection -│ ├── index.ts # public surface: re-exports queries, schema and drizzle helpers -│ ├── types.ts # every type export (Select / Insert / extended) -│ └── schema/ -│ ├── index.ts # re-exports all schema files -│ └── *.ts # table definitions, grouped by domain -└── package.json # exports "." and "./types" +│ ├── db.ts # the connection +│ ├── index.ts # public surface: re-exports every feature's queries +│ ├── schema.ts # what db:push creates — see below +│ ├── types.ts # every type export (Select / Insert / extended) +│ ├── crypto.ts # at-rest encryption, one key per purpose +│ ├── secret-store.ts # the key store itself (SQLite, outside Postgres) +│ └── / +│ ├── schema.ts # its tables +│ └── queries.ts # everything that reads or writes them +└── package.json # exports ".", "./types", "./db", "./schema", "./secret-store", "./*" ``` -Schema files are grouped by domain, not by table: `auth`, `chat-events`, `dashboards`, `email`, -`headscale`, `music`, `operations`, `pipeline-jobs`, `server`, `soulseek`, `user-data`, `vault`, -`wallet`. +**One directory per feature, holding both halves.** Restructured 2026-08-13 from parallel `schema/` and +`queries/` trees, where the two sides had drifted: four features were named differently on each side +(`app-store`/`sidecar-installs`, `email`/`email-accounts`, `server`/`server-config`), `operations` had no +query file at all, and `integrations` had no schema file. + +Two directories are still lopsided and say so by their contents: `operations/` has only a schema (its +`task_logs` is reached directly from `src/servers/`, bypassing this package), and `integrations/` has only +queries, because it spans `server` and `user-data`. + +**`src/schema.ts` is drizzle-kit's view, not the runtime's.** `drizzle.config.ts` points at it, so a +commented line there removes a table from the DATABASE without removing a line of code — every query +imports its tables from `./schema` inside its own feature directory. That is what lets a fresh install +create only the core tables, with the plugin ones commented out until their plugin is installed. ## Schema changes use `push`, not migrations @@ -114,8 +127,8 @@ Organise `types.ts` by domain with section comments, mirroring the schema files. ## Queries -Hand-written, one file per domain under `src/queries/`, importing tables from `../schema` and types from -`../types`: +Hand-written, one `queries.ts` per feature directory, importing tables from `./schema` beside it and +types from `../types`: ```ts import { eq, and } from 'drizzle-orm'; diff --git a/src/databases/officer_db/drizzle.config.ts b/src/databases/officer_db/drizzle.config.ts index a33bbaec..572c9286 100644 --- a/src/databases/officer_db/drizzle.config.ts +++ b/src/databases/officer_db/drizzle.config.ts @@ -15,7 +15,7 @@ try { } catch {} export default defineConfig({ - schema: './src/schema/index.ts', + schema: './src/schema.ts', out: './migrations', dialect: 'postgresql', dbCredentials: { diff --git a/src/databases/officer_db/package.json b/src/databases/officer_db/package.json index 03a455a6..e02a0d67 100644 --- a/src/databases/officer_db/package.json +++ b/src/databases/officer_db/package.json @@ -7,8 +7,9 @@ ".": "./src/index.ts", "./types": "./src/types.ts", "./db": "./src/db.ts", - "./schema": "./src/schema/index.ts", - "./secret-store": "./src/secret-store.ts" + "./schema": "./src/schema.ts", + "./secret-store": "./src/secret-store.ts", + "./*": "./src/*" }, "scripts": { "generate": "drizzle-kit generate --config=drizzle.config.ts", diff --git a/src/databases/officer_db/src/queries/agent-panels.ts b/src/databases/officer_db/src/agent-panels/queries.ts similarity index 97% rename from src/databases/officer_db/src/queries/agent-panels.ts rename to src/databases/officer_db/src/agent-panels/queries.ts index db018c88..faa69870 100644 --- a/src/databases/officer_db/src/queries/agent-panels.ts +++ b/src/databases/officer_db/src/agent-panels/queries.ts @@ -1,8 +1,8 @@ import { randomUUID } from 'crypto'; import { and, asc, eq } from 'drizzle-orm'; import { db } from '../db'; -import { agentPanels } from '../schema'; -import type { AgentPanelRow } from '../schema/agent-panels'; +import { agentPanels } from './schema'; +import type { AgentPanelRow } from './schema'; export type AgentPanel = AgentPanelRow; diff --git a/src/databases/officer_db/src/schema/agent-panels.ts b/src/databases/officer_db/src/agent-panels/schema.ts similarity index 99% rename from src/databases/officer_db/src/schema/agent-panels.ts rename to src/databases/officer_db/src/agent-panels/schema.ts index 321e3846..2f45f6b1 100644 --- a/src/databases/officer_db/src/schema/agent-panels.ts +++ b/src/databases/officer_db/src/agent-panels/schema.ts @@ -1,5 +1,5 @@ import { pgTable, serial, text, integer, timestamp, uniqueIndex, index } from 'drizzle-orm/pg-core'; -import { users } from './auth'; +import { users } from '../auth/schema'; /** * One named agent living in one dashboard panel — the address book that lets two chat panels on the diff --git a/src/databases/officer_db/src/queries/api-keys.ts b/src/databases/officer_db/src/api-keys/queries.ts similarity index 98% rename from src/databases/officer_db/src/queries/api-keys.ts rename to src/databases/officer_db/src/api-keys/queries.ts index f227301d..f6f37edc 100644 --- a/src/databases/officer_db/src/queries/api-keys.ts +++ b/src/databases/officer_db/src/api-keys/queries.ts @@ -1,6 +1,7 @@ import { eq, and, isNull, sql } from 'drizzle-orm'; import { db } from '../db'; -import { apiKeys, users } from '../schema'; +import { apiKeys } from './schema'; +import { users } from '../auth/schema'; import type { ApiKeySelect } from '../types'; // Every read here is scoped by userId except `findLiveApiKeyByHash`, which cannot be: authentication is diff --git a/src/databases/officer_db/src/schema/api-keys.ts b/src/databases/officer_db/src/api-keys/schema.ts similarity index 98% rename from src/databases/officer_db/src/schema/api-keys.ts rename to src/databases/officer_db/src/api-keys/schema.ts index a14f6da7..5328643c 100644 --- a/src/databases/officer_db/src/schema/api-keys.ts +++ b/src/databases/officer_db/src/api-keys/schema.ts @@ -1,5 +1,5 @@ import { pgTable, serial, text, integer, timestamp, index, uniqueIndex } from 'drizzle-orm/pg-core'; -import { users } from './auth'; +import { users } from '../auth/schema'; // Long-lived credentials a user mints for themselves, so a native app can hold one instead of a password. // diff --git a/src/databases/officer_db/src/queries/sidecar-installs.ts b/src/databases/officer_db/src/app-store/queries.ts similarity index 99% rename from src/databases/officer_db/src/queries/sidecar-installs.ts rename to src/databases/officer_db/src/app-store/queries.ts index b08b74b1..bb24a58a 100644 --- a/src/databases/officer_db/src/queries/sidecar-installs.ts +++ b/src/databases/officer_db/src/app-store/queries.ts @@ -1,6 +1,6 @@ import { eq } from 'drizzle-orm'; import { db } from '../db'; -import { sidecarInstalls } from '../schema'; +import { sidecarInstalls } from './schema'; // What the owner has installed from the app store. See ../schema/app-store.ts for why there is no // userId and why `installed` and `enabled` are separate. diff --git a/src/databases/officer_db/src/schema/app-store.ts b/src/databases/officer_db/src/app-store/schema.ts similarity index 100% rename from src/databases/officer_db/src/schema/app-store.ts rename to src/databases/officer_db/src/app-store/schema.ts diff --git a/src/databases/officer_db/src/queries/auth.ts b/src/databases/officer_db/src/auth/queries.ts similarity index 99% rename from src/databases/officer_db/src/queries/auth.ts rename to src/databases/officer_db/src/auth/queries.ts index 982614f4..cd9ebb59 100644 --- a/src/databases/officer_db/src/queries/auth.ts +++ b/src/databases/officer_db/src/auth/queries.ts @@ -1,6 +1,6 @@ import { eq, and, lt, sql } from 'drizzle-orm'; import { db } from '../db'; -import { users, passkeys, passkeyChallenges, tokenBlacklist, OWNER_USER_ID } from '../schema'; +import { users, passkeys, passkeyChallenges, tokenBlacklist, OWNER_USER_ID } from './schema'; import type { UserSelect, UserInsert, PasskeySelect, PasskeyInsert } from '../types'; // ── Users ── diff --git a/src/databases/officer_db/src/schema/auth.ts b/src/databases/officer_db/src/auth/schema.ts similarity index 100% rename from src/databases/officer_db/src/schema/auth.ts rename to src/databases/officer_db/src/auth/schema.ts diff --git a/src/databases/officer_db/src/queries/capabilities.ts b/src/databases/officer_db/src/capabilities/queries.ts similarity index 94% rename from src/databases/officer_db/src/queries/capabilities.ts rename to src/databases/officer_db/src/capabilities/queries.ts index db88dbe0..0b759902 100644 --- a/src/databases/officer_db/src/queries/capabilities.ts +++ b/src/databases/officer_db/src/capabilities/queries.ts @@ -1,8 +1,8 @@ import { eq, and } from 'drizzle-orm'; import { db } from '../db'; -import { roleCapabilities } from '../schema'; -import type { UserRole } from '../schema/auth'; -import type { CapabilityLevelValue } from '../schema/capabilities'; +import { roleCapabilities } from './schema'; +import type { UserRole } from '../auth/schema'; +import type { CapabilityLevelValue } from './schema'; // Grants, keyed on role. Absence denies — see the table comment. diff --git a/src/databases/officer_db/src/schema/capabilities.ts b/src/databases/officer_db/src/capabilities/schema.ts similarity index 98% rename from src/databases/officer_db/src/schema/capabilities.ts rename to src/databases/officer_db/src/capabilities/schema.ts index 2fc662eb..ecaf5d2c 100644 --- a/src/databases/officer_db/src/schema/capabilities.ts +++ b/src/databases/officer_db/src/capabilities/schema.ts @@ -1,6 +1,6 @@ import { pgTable, serial, text, timestamp, uniqueIndex, check } from 'drizzle-orm/pg-core'; import { sql } from 'drizzle-orm'; -import { USER_ROLES } from './auth'; +import { USER_ROLES } from '../auth/schema'; // What a ROLE may reach. The subject of a grant is a role, never a user. // diff --git a/src/databases/officer_db/src/queries/chat-events.ts b/src/databases/officer_db/src/chat-events/queries.ts similarity index 97% rename from src/databases/officer_db/src/queries/chat-events.ts rename to src/databases/officer_db/src/chat-events/queries.ts index e95cbc83..14573283 100644 --- a/src/databases/officer_db/src/queries/chat-events.ts +++ b/src/databases/officer_db/src/chat-events/queries.ts @@ -1,6 +1,6 @@ import { eq, and, gt, asc, desc, lt } from 'drizzle-orm'; import { db } from '../db'; -import { chatSessionEvents } from '../schema'; +import { chatSessionEvents } from './schema'; /** Append one outbound event to a session's durable log; returns its global cursor id. */ export async function appendChatEvent(sessionId: string, event: unknown): Promise { diff --git a/src/databases/officer_db/src/schema/chat-events.ts b/src/databases/officer_db/src/chat-events/schema.ts similarity index 100% rename from src/databases/officer_db/src/schema/chat-events.ts rename to src/databases/officer_db/src/chat-events/schema.ts diff --git a/src/databases/officer_db/src/queries/dashboards.ts b/src/databases/officer_db/src/dashboards/queries.ts similarity index 99% rename from src/databases/officer_db/src/queries/dashboards.ts rename to src/databases/officer_db/src/dashboards/queries.ts index a79cc036..d2533314 100644 --- a/src/databases/officer_db/src/queries/dashboards.ts +++ b/src/databases/officer_db/src/dashboards/queries.ts @@ -1,6 +1,6 @@ import { eq, and } from 'drizzle-orm'; import { db } from '../db'; -import { dashboards, screens, dashboardDefaults } from '../schema'; +import { dashboards, screens, dashboardDefaults } from './schema'; // ── Full state read ── diff --git a/src/databases/officer_db/src/schema/dashboards.ts b/src/databases/officer_db/src/dashboards/schema.ts similarity index 99% rename from src/databases/officer_db/src/schema/dashboards.ts rename to src/databases/officer_db/src/dashboards/schema.ts index 214b874a..3259b477 100644 --- a/src/databases/officer_db/src/schema/dashboards.ts +++ b/src/databases/officer_db/src/dashboards/schema.ts @@ -1,7 +1,7 @@ import type { AnyPgColumn } from 'drizzle-orm/pg-core'; import { sql } from 'drizzle-orm'; import { pgTable, serial, text, integer, timestamp, jsonb, uniqueIndex, check } from 'drizzle-orm/pg-core'; -import { users } from './auth'; +import { users } from '../auth/schema'; /** * A `LayoutNode` is an object, and the whole write path to these columns is `unknown` — the PATCH body is diff --git a/src/databases/officer_db/src/queries/dav.ts b/src/databases/officer_db/src/dav/queries.ts similarity index 98% rename from src/databases/officer_db/src/queries/dav.ts rename to src/databases/officer_db/src/dav/queries.ts index 0243c863..5c9b2689 100644 --- a/src/databases/officer_db/src/queries/dav.ts +++ b/src/databases/officer_db/src/dav/queries.ts @@ -2,7 +2,7 @@ import { and, desc, eq, isNull } from 'drizzle-orm'; import argon2 from 'argon2'; import { randomBytes } from 'node:crypto'; import { db } from '../db'; -import { davAppPasswords } from '../schema/dav'; +import { davAppPasswords } from './schema'; export type DavAppPassword = typeof davAppPasswords.$inferSelect; diff --git a/src/databases/officer_db/src/schema/dav.ts b/src/databases/officer_db/src/dav/schema.ts similarity index 98% rename from src/databases/officer_db/src/schema/dav.ts rename to src/databases/officer_db/src/dav/schema.ts index ed108d45..3827b52d 100644 --- a/src/databases/officer_db/src/schema/dav.ts +++ b/src/databases/officer_db/src/dav/schema.ts @@ -1,5 +1,5 @@ import { pgTable, serial, integer, text, timestamp, index } from 'drizzle-orm/pg-core'; -import { users } from './auth'; +import { users } from '../auth/schema'; // Per-device credentials for CalDAV / CardDAV clients — DAVx5, iOS, macOS, Thunderbird. // diff --git a/src/databases/officer_db/src/queries/email-accounts.ts b/src/databases/officer_db/src/email/queries.ts similarity index 96% rename from src/databases/officer_db/src/queries/email-accounts.ts rename to src/databases/officer_db/src/email/queries.ts index 53f8d41b..1e718564 100644 --- a/src/databases/officer_db/src/queries/email-accounts.ts +++ b/src/databases/officer_db/src/email/queries.ts @@ -1,6 +1,6 @@ import { eq, and } from 'drizzle-orm'; import { db } from '../db'; -import { emailAccounts } from '../schema/email'; +import { emailAccounts } from './schema'; import type { EmailAccountInsert, EmailAccountSelect } from '../types'; export async function getEmailAccounts(userId: number): Promise { diff --git a/src/databases/officer_db/src/schema/email.ts b/src/databases/officer_db/src/email/schema.ts similarity index 97% rename from src/databases/officer_db/src/schema/email.ts rename to src/databases/officer_db/src/email/schema.ts index 1a6d1eff..187b4525 100644 --- a/src/databases/officer_db/src/schema/email.ts +++ b/src/databases/officer_db/src/email/schema.ts @@ -1,5 +1,5 @@ import { pgTable, serial, integer, text, boolean, timestamp, jsonb, uniqueIndex } from 'drizzle-orm/pg-core'; -import { users } from './auth'; +import { users } from '../auth/schema'; export const emailAccounts = pgTable( 'email_accounts', diff --git a/src/databases/officer_db/src/queries/headscale.ts b/src/databases/officer_db/src/headscale/queries.ts similarity index 99% rename from src/databases/officer_db/src/queries/headscale.ts rename to src/databases/officer_db/src/headscale/queries.ts index de6a7008..70cab8e2 100644 --- a/src/databases/officer_db/src/queries/headscale.ts +++ b/src/databases/officer_db/src/headscale/queries.ts @@ -1,6 +1,6 @@ import { eq, and, desc } from 'drizzle-orm'; import { db } from '../db'; -import { headscaleServers } from '../schema'; +import { headscaleServers } from './schema'; import { encryptSecret, decryptSecret } from '../crypto'; // Headscale server registry access for the officer-headscale sidecar. Callers deal in PLAINTEXT — diff --git a/src/databases/officer_db/src/schema/headscale.ts b/src/databases/officer_db/src/headscale/schema.ts similarity index 98% rename from src/databases/officer_db/src/schema/headscale.ts rename to src/databases/officer_db/src/headscale/schema.ts index ae1f3ea3..b423f28d 100644 --- a/src/databases/officer_db/src/schema/headscale.ts +++ b/src/databases/officer_db/src/headscale/schema.ts @@ -1,6 +1,6 @@ import { pgTable, serial, integer, text, boolean, timestamp, uniqueIndex } from 'drizzle-orm/pg-core'; import { sql } from 'drizzle-orm'; -import { users } from './auth'; +import { users } from '../auth/schema'; // The Headscale servers the owner manages, for the officer-headscale sidecar. Officer targets no single // Headscale: the owner registers one or more servers (URL + an admin API key generated on that server) and diff --git a/src/databases/officer_db/src/index.ts b/src/databases/officer_db/src/index.ts index dc5fa039..999b06d8 100644 --- a/src/databases/officer_db/src/index.ts +++ b/src/databases/officer_db/src/index.ts @@ -18,7 +18,7 @@ export { blacklistToken, isTokenBlacklisted, cleanupExpiredTokens, -} from './queries/auth'; +} from './auth/queries'; export { findLiveApiKeyByHash, @@ -27,9 +27,9 @@ export { revokeApiKey, touchApiKey, type ApiKeyIdentity, -} from './queries/api-keys'; +} from './api-keys/queries'; -export { readServerSettings, writeServerSettings, readConfigValue, writeConfigValue } from './queries/server-config'; +export { readServerSettings, writeServerSettings, readConfigValue, writeConfigValue } from './server/queries'; export { getUserSettings, @@ -38,7 +38,7 @@ export { patchUserState, getDockPaths, setDockPaths, -} from './queries/user-data'; +} from './user-data/queries'; export { getServerIntegrations, @@ -51,7 +51,7 @@ export { upsertUserIntegration, deleteUserIntegration, findUserByIntegrationConfig, -} from './queries/integrations'; +} from './integrations/queries'; export { getEmailAccounts, @@ -61,7 +61,7 @@ export { updateEmailAccountStatus, updateEmailAccountSyncMeta, getAllSyncedAccounts, -} from './queries/email-accounts'; +} from './email/queries'; export { getAllDashboardState, @@ -73,7 +73,7 @@ export { deleteScreen, upsertDefaults, setDefaultsPanelState, -} from './queries/dashboards'; +} from './dashboards/queries'; export { createPipelineJob, @@ -86,14 +86,14 @@ export { deletePipelineJob, deleteTerminalJobsForUser, markInterruptedJobs, -} from './queries/pipeline-jobs'; +} from './pipeline-jobs/queries'; export { appendChatEvent, getChatEventsSince, getLastChatEventSeq, pruneChatEventsOlderThan, -} from './queries/chat-events'; +} from './chat-events/queries'; export { listAgentPanels, @@ -105,8 +105,8 @@ export { markAgentPanelIntroduced, deleteAgentPanel, toAgentPanelView, -} from './queries/agent-panels'; -export type { AgentPanel, AgentPanelView, CreateAgentPanelInput, UpdateAgentPanelInput } from './queries/agent-panels'; +} from './agent-panels/queries'; +export type { AgentPanel, AgentPanelView, CreateAgentPanelInput, UpdateAgentPanelInput } from './agent-panels/queries'; export { getMusicFavorites, @@ -122,7 +122,7 @@ export { deletePlaylist, addPlaylistItems, setPlaylistItems, -} from './queries/music'; +} from './music/queries'; export type { FavoriteKind, GroupedFavorites, @@ -130,8 +130,8 @@ export type { NowPlayingInput, PlaylistSummary, Playlist, -} from './queries/music'; -export { getSoulseekFavorites, addSoulseekFavorite, removeSoulseekFavorite } from './queries/soulseek'; +} from './music/queries'; +export { getSoulseekFavorites, addSoulseekFavorite, removeSoulseekFavorite } from './soulseek/queries'; export { getSoulseekBrowseSnapshots, getSoulseekBrowseSnapshot, @@ -144,7 +144,7 @@ export { getSoulseekBrowseDirFiles, getSoulseekBrowseDownload, deleteSoulseekBrowse, -} from './queries/soulseek'; +} from './soulseek/queries'; export type { BrowseDownloadFile, BrowsedFile, @@ -154,7 +154,7 @@ export type { BrowseLevel, BrowseTreeSearch, SoulseekBrowseSnapshot, -} from './queries/soulseek'; +} from './soulseek/queries'; export { listHeadscaleServers, getActiveHeadscaleCredentials, @@ -164,8 +164,8 @@ export { setActiveHeadscaleServer, deleteHeadscaleServer, recordHeadscaleProbe, -} from './queries/headscale'; -export type { HeadscaleServer, HeadscaleServerCredentials } from './queries/headscale'; +} from './headscale/queries'; +export type { HeadscaleServer, HeadscaleServerCredentials } from './headscale/queries'; export { listInvoiceshelfAccounts, getActiveInvoiceshelfCredentials, @@ -175,8 +175,8 @@ export { setActiveInvoiceshelfAccount, deleteInvoiceshelfAccount, recordInvoiceshelfProbe, -} from './queries/invoiceshelf'; -export type { InvoiceshelfAccount, InvoiceshelfCredentials } from './queries/invoiceshelf'; +} from './invoiceshelf/queries'; +export type { InvoiceshelfAccount, InvoiceshelfCredentials } from './invoiceshelf/queries'; export { listJellyfinServers, getActiveJellyfinCredentials, @@ -186,8 +186,8 @@ export { setActiveJellyfinServer, deleteJellyfinServer, recordJellyfinProbe, -} from './queries/jellyfin'; -export type { JellyfinServer, JellyfinCredentials } from './queries/jellyfin'; +} from './jellyfin/queries'; +export type { JellyfinServer, JellyfinCredentials } from './jellyfin/queries'; export { listPhotosAccounts, getActivePhotosCredentials, @@ -197,16 +197,16 @@ export { setActivePhotosAccount, deletePhotosAccount, recordPhotosProbe, -} from './queries/photos'; -export type { PhotosAccount, PhotosCredentials } from './queries/photos'; +} from './photos/queries'; +export type { PhotosAccount, PhotosCredentials } from './photos/queries'; export { listDavAppPasswords, createDavAppPassword, revokeDavAppPassword, deleteDavAppPassword, verifyDavAppPassword, -} from './queries/dav'; -export type { DavAppPassword, DavAppPasswordView } from './queries/dav'; +} from './dav/queries'; +export type { DavAppPassword, DavAppPasswordView } from './dav/queries'; export { getServiceConnection, getServiceCredentials, @@ -215,17 +215,17 @@ export { recordServiceProbe, getServiceInstanceUrl, getResolvedServiceCredentials, -} from './queries/service-connections'; -export type { ServiceName, ServiceConnection, ServiceCredentials } from './queries/service-connections'; +} from './service-connections/queries'; +export type { ServiceName, ServiceConnection, ServiceCredentials } from './service-connections/queries'; export { getAllRoleGrants, getRoleGrants, setRoleGrant, revokeRoleGrant, replaceRoleGrants, -} from './queries/capabilities'; -export type { RoleGrant } from './queries/capabilities'; -export type { CapabilityLevelValue } from './schema/capabilities'; +} from './capabilities/queries'; +export type { RoleGrant } from './capabilities/queries'; +export type { CapabilityLevelValue } from './capabilities/schema'; export { getVaultTokens, setVaultTokens, @@ -234,8 +234,8 @@ export { getVaultUnlockKey, setVaultUnlockKey, clearVaultUnlockKey, -} from './queries/vault'; -export type { VaultTokenSet } from './queries/vault'; +} from './vault/queries'; +export type { VaultTokenSet } from './vault/queries'; export { listWallets, getWallet, @@ -254,7 +254,7 @@ export { getWalletChainCache, saveWalletChainCache, recordWalletChainError, -} from './queries/wallet'; +} from './wallet/queries'; export type { WalletKind, WalletSummary, @@ -262,13 +262,13 @@ export type { WalletLabel, CreateWalletParams, WalletChainCache, -} from './queries/wallet'; -export type { WalletChainSnapshot } from './schema/wallet'; +} from './wallet/queries'; +export type { WalletChainSnapshot } from './wallet/schema'; // Exported as a value, not just a type: the API and the UI need to enumerate the roles, and the // column definition is the only place that list should exist. -export { USER_ROLES, OWNER_USER_ID } from './schema/auth'; -export type { UserRole } from './schema/auth'; +export { USER_ROLES, OWNER_USER_ID } from './auth/schema'; +export type { UserRole } from './auth/schema'; export { db } from './db'; export * as schema from './schema'; @@ -279,7 +279,7 @@ export { deletePushDevice, recordPushFailure, markPushDeviceSeen, -} from './queries/notify'; +} from './notify/queries'; export type { PushDeviceSelect, PushDeviceInsert } from './types'; // App store — what the owner has installed, and whether it should be running. @@ -294,4 +294,4 @@ export { setEnabled, removeInstall, type SidecarInstall, -} from './queries/sidecar-installs'; +} from './app-store/queries'; diff --git a/src/databases/officer_db/src/queries/integrations.ts b/src/databases/officer_db/src/integrations/queries.ts similarity index 96% rename from src/databases/officer_db/src/queries/integrations.ts rename to src/databases/officer_db/src/integrations/queries.ts index ad197e2d..a0ea9ae7 100644 --- a/src/databases/officer_db/src/queries/integrations.ts +++ b/src/databases/officer_db/src/integrations/queries.ts @@ -1,7 +1,8 @@ import { eq, and, sql } from 'drizzle-orm'; import { db } from '../db'; -import { serverIntegrations, userIntegrations } from '../schema'; -import { users } from '../schema/auth'; +import { serverIntegrations } from '../server/schema'; +import { userIntegrations } from '../user-data/schema'; +import { users } from '../auth/schema'; import type { ServerIntegrationSelect, UserIntegrationSelect } from '../types'; // ── Server Integrations ── diff --git a/src/databases/officer_db/src/queries/invoiceshelf.ts b/src/databases/officer_db/src/invoiceshelf/queries.ts similarity index 99% rename from src/databases/officer_db/src/queries/invoiceshelf.ts rename to src/databases/officer_db/src/invoiceshelf/queries.ts index e536acf0..ab6f2563 100644 --- a/src/databases/officer_db/src/queries/invoiceshelf.ts +++ b/src/databases/officer_db/src/invoiceshelf/queries.ts @@ -1,6 +1,6 @@ import { eq, and, desc } from 'drizzle-orm'; import { db } from '../db'; -import { invoiceshelfAccounts } from '../schema/invoiceshelf'; +import { invoiceshelfAccounts } from './schema'; import { encryptSecret, decryptSecret } from '../crypto'; // InvoiceShelf account registry for the officer-invoiceshelf sidecar. Callers deal in PLAINTEXT — encryption diff --git a/src/databases/officer_db/src/schema/invoiceshelf.ts b/src/databases/officer_db/src/invoiceshelf/schema.ts similarity index 98% rename from src/databases/officer_db/src/schema/invoiceshelf.ts rename to src/databases/officer_db/src/invoiceshelf/schema.ts index d8bff028..129dd7d6 100644 --- a/src/databases/officer_db/src/schema/invoiceshelf.ts +++ b/src/databases/officer_db/src/invoiceshelf/schema.ts @@ -1,6 +1,6 @@ import { pgTable, serial, integer, text, boolean, timestamp, uniqueIndex } from 'drizzle-orm/pg-core'; import { sql } from 'drizzle-orm'; -import { users } from './auth'; +import { users } from '../auth/schema'; // The InvoiceShelf accounts behind /invoices, for the officer-invoiceshelf sidecar. // diff --git a/src/databases/officer_db/src/queries/jellyfin.ts b/src/databases/officer_db/src/jellyfin/queries.ts similarity index 99% rename from src/databases/officer_db/src/queries/jellyfin.ts rename to src/databases/officer_db/src/jellyfin/queries.ts index d3a97f06..4cdef2bf 100644 --- a/src/databases/officer_db/src/queries/jellyfin.ts +++ b/src/databases/officer_db/src/jellyfin/queries.ts @@ -1,6 +1,6 @@ import { eq, and, desc } from 'drizzle-orm'; import { db } from '../db'; -import { jellyfinServers } from '../schema/jellyfin'; +import { jellyfinServers } from './schema'; import { encryptSecret, decryptSecret } from '../crypto'; // Jellyfin server registry for the officer-jellyfin sidecar. Callers deal in PLAINTEXT — encryption to and diff --git a/src/databases/officer_db/src/schema/jellyfin.ts b/src/databases/officer_db/src/jellyfin/schema.ts similarity index 98% rename from src/databases/officer_db/src/schema/jellyfin.ts rename to src/databases/officer_db/src/jellyfin/schema.ts index 466cd59f..adbf4465 100644 --- a/src/databases/officer_db/src/schema/jellyfin.ts +++ b/src/databases/officer_db/src/jellyfin/schema.ts @@ -1,6 +1,6 @@ import { pgTable, serial, integer, text, boolean, timestamp, uniqueIndex } from 'drizzle-orm/pg-core'; import { sql } from 'drizzle-orm'; -import { users } from './auth'; +import { users } from '../auth/schema'; // The Jellyfin servers behind /jellyfin, for the officer-jellyfin sidecar. // diff --git a/src/databases/officer_db/src/queries/music.ts b/src/databases/officer_db/src/music/queries.ts similarity index 99% rename from src/databases/officer_db/src/queries/music.ts rename to src/databases/officer_db/src/music/queries.ts index 924a95e4..0f622fe1 100644 --- a/src/databases/officer_db/src/queries/music.ts +++ b/src/databases/officer_db/src/music/queries.ts @@ -1,6 +1,6 @@ import { eq, and, desc, asc, sql } from 'drizzle-orm'; import { db } from '../db'; -import { musicFavorites, musicNowPlaying, musicPlaylists, musicPlaylistItems } from '../schema/music'; +import { musicFavorites, musicNowPlaying, musicPlaylists, musicPlaylistItems } from './schema'; export type FavoriteKind = 'track' | 'album' | 'artist'; export type GroupedFavorites = { tracks: string[]; albums: string[]; artists: string[] }; diff --git a/src/databases/officer_db/src/schema/music.ts b/src/databases/officer_db/src/music/schema.ts similarity index 98% rename from src/databases/officer_db/src/schema/music.ts rename to src/databases/officer_db/src/music/schema.ts index ece25fd3..096103ea 100644 --- a/src/databases/officer_db/src/schema/music.ts +++ b/src/databases/officer_db/src/music/schema.ts @@ -1,5 +1,5 @@ import { pgTable, serial, integer, text, real, timestamp, index, primaryKey, uniqueIndex } from 'drizzle-orm/pg-core'; -import { users } from './auth'; +import { users } from '../auth/schema'; // Per-user music favorites. `key` is an opaque path the app supplies and the server never interprets: // track → homePath "Music//" (also the /stream path + RNTP queue id) diff --git a/src/databases/officer_db/src/queries/notify.ts b/src/databases/officer_db/src/notify/queries.ts similarity index 98% rename from src/databases/officer_db/src/queries/notify.ts rename to src/databases/officer_db/src/notify/queries.ts index a1284e05..6ac46d65 100644 --- a/src/databases/officer_db/src/queries/notify.ts +++ b/src/databases/officer_db/src/notify/queries.ts @@ -1,6 +1,6 @@ import { eq, and, sql } from 'drizzle-orm'; import { db } from '../db'; -import { pushDevices } from '../schema/notify'; +import { pushDevices } from './schema'; import type { PushDeviceSelect, PushDeviceInsert } from '../types'; // The push device registry. Only the officer-notify sidecar uses these. diff --git a/src/databases/officer_db/src/schema/notify.ts b/src/databases/officer_db/src/notify/schema.ts similarity index 98% rename from src/databases/officer_db/src/schema/notify.ts rename to src/databases/officer_db/src/notify/schema.ts index df2add43..2902372f 100644 --- a/src/databases/officer_db/src/schema/notify.ts +++ b/src/databases/officer_db/src/notify/schema.ts @@ -1,6 +1,6 @@ import { pgTable, serial, integer, text, timestamp, index, check, uniqueIndex } from 'drizzle-orm/pg-core'; import { sql } from 'drizzle-orm'; -import { users } from './auth'; +import { users } from '../auth/schema'; // Devices that can receive a push, for the officer-notify sidecar. // diff --git a/src/databases/officer_db/src/schema/operations.ts b/src/databases/officer_db/src/operations/schema.ts similarity index 98% rename from src/databases/officer_db/src/schema/operations.ts rename to src/databases/officer_db/src/operations/schema.ts index c4f865f9..d164d882 100644 --- a/src/databases/officer_db/src/schema/operations.ts +++ b/src/databases/officer_db/src/operations/schema.ts @@ -1,5 +1,5 @@ import { pgTable, serial, text, integer, boolean, timestamp, jsonb, index } from 'drizzle-orm/pg-core'; -import { users } from './auth'; +import { users } from '../auth/schema'; export const taskLogs = pgTable('task_logs', { id: serial('id').primaryKey(), diff --git a/src/databases/officer_db/src/queries/photos.ts b/src/databases/officer_db/src/photos/queries.ts similarity index 99% rename from src/databases/officer_db/src/queries/photos.ts rename to src/databases/officer_db/src/photos/queries.ts index a5bb44d1..b07791ea 100644 --- a/src/databases/officer_db/src/queries/photos.ts +++ b/src/databases/officer_db/src/photos/queries.ts @@ -1,6 +1,6 @@ import { eq, and, desc } from 'drizzle-orm'; import { db } from '../db'; -import { photosConfig } from '../schema/photos'; +import { photosConfig } from './schema'; import { encryptSecret, decryptSecret } from '../crypto'; // Immich account registry for the officer-photos sidecar. Callers deal in PLAINTEXT — encryption to and from diff --git a/src/databases/officer_db/src/schema/photos.ts b/src/databases/officer_db/src/photos/schema.ts similarity index 98% rename from src/databases/officer_db/src/schema/photos.ts rename to src/databases/officer_db/src/photos/schema.ts index 3105524e..363a8cfb 100644 --- a/src/databases/officer_db/src/schema/photos.ts +++ b/src/databases/officer_db/src/photos/schema.ts @@ -1,6 +1,6 @@ import { pgTable, serial, integer, text, boolean, timestamp, uniqueIndex } from 'drizzle-orm/pg-core'; import { sql } from 'drizzle-orm'; -import { users } from './auth'; +import { users } from '../auth/schema'; // The Immich accounts behind /photos, for the officer-photos sidecar. // diff --git a/src/databases/officer_db/src/queries/pipeline-jobs.ts b/src/databases/officer_db/src/pipeline-jobs/queries.ts similarity index 98% rename from src/databases/officer_db/src/queries/pipeline-jobs.ts rename to src/databases/officer_db/src/pipeline-jobs/queries.ts index c7ee2b30..482cea80 100644 --- a/src/databases/officer_db/src/queries/pipeline-jobs.ts +++ b/src/databases/officer_db/src/pipeline-jobs/queries.ts @@ -2,7 +2,7 @@ import { eq, and, inArray, asc, desc } from 'drizzle-orm'; const TERMINAL_STATUSES = ['completed', 'failed', 'stopped', 'interrupted'] as const; import { db } from '../db'; -import { pipelineJobs } from '../schema/pipeline-jobs'; +import { pipelineJobs } from './schema'; import type { PipelineJobInsert } from '../types'; export async function createPipelineJob(data: PipelineJobInsert) { diff --git a/src/databases/officer_db/src/schema/pipeline-jobs.ts b/src/databases/officer_db/src/pipeline-jobs/schema.ts similarity index 97% rename from src/databases/officer_db/src/schema/pipeline-jobs.ts rename to src/databases/officer_db/src/pipeline-jobs/schema.ts index 3c570fc4..2691362f 100644 --- a/src/databases/officer_db/src/schema/pipeline-jobs.ts +++ b/src/databases/officer_db/src/pipeline-jobs/schema.ts @@ -1,5 +1,5 @@ import { pgTable, text, integer, timestamp, jsonb, index } from 'drizzle-orm/pg-core'; -import { users } from './auth'; +import { users } from '../auth/schema'; export const pipelineJobs = pgTable( 'pipeline_jobs', diff --git a/src/databases/officer_db/src/schema/index.ts b/src/databases/officer_db/src/schema.ts similarity index 51% rename from src/databases/officer_db/src/schema/index.ts rename to src/databases/officer_db/src/schema.ts index 3be4696e..9c758e69 100644 --- a/src/databases/officer_db/src/schema/index.ts +++ b/src/databases/officer_db/src/schema.ts @@ -21,35 +21,35 @@ // ── Core ───────────────────────────────────────────────────────────────────────────────────────── -export * from './auth'; // users, passkeys, passkey_challenges, token_blacklist -export * from './capabilities'; // role_capabilities — what each ROLE may reach -export * from './api-keys'; // api_keys -export * from './user-data'; // user_settings, user_state, user_integrations, dock_configs -export * from './dashboards'; // dashboards, screens, dashboard_defaults -export * from './server'; // server_config (SMTP lives here), server_integrations -export * from './operations'; // task_logs, queue_jobs, terminal_containers -export * from './pipeline-jobs'; // pipeline_jobs -export * from './chat-events'; // chat_session_events -export * from './agent-panels'; // agent_panels +export * from './auth/schema'; // users, passkeys, passkey_challenges, token_blacklist +export * from './capabilities/schema'; // role_capabilities — what each ROLE may reach +export * from './api-keys/schema'; // api_keys +export * from './user-data/schema'; // user_settings, user_state, user_integrations, dock_configs +export * from './dashboards/schema'; // dashboards, screens, dashboard_defaults +export * from './server/schema'; // server_config (SMTP lives here), server_integrations +export * from './operations/schema'; // task_logs, queue_jobs, terminal_containers +export * from './pipeline-jobs/schema'; // pipeline_jobs +export * from './chat-events/schema'; // chat_session_events +export * from './agent-panels/schema'; // agent_panels // Core because the tailnet is the perimeter — a security model resting on it cannot treat administering // it as an optional extra. The secret store bootstraps a `headscale` key on this basis. -export * from './headscale'; // headscale_servers +export * from './headscale/schema'; // headscale_servers // The app store itself, and the credentials it stores for what it installs. `app-store/effects.ts` // reads service_connections, so this is core however few plugins are installed. -export * from './app-store'; // sidecar_installs -export * from './service-connections'; // service_connections +export * from './app-store/schema'; // sidecar_installs +export * from './service-connections/schema'; // service_connections // ── Plugins — uncomment when the plugin is installed ───────────────────────────────────────────── -// export * from './email'; // email_accounts officer-email -// export * from './music'; // music_favorites, _playlists, _playlist_items, _now_playing -// export * from './notify'; // push_devices officer-notify -// export * from './dav'; // dav_app_passwords officer-caldav -// export * from './photos'; // photos_config officer-photos -// export * from './jellyfin'; // jellyfin_servers officer-jellyfin -// export * from './invoiceshelf'; // invoiceshelf_accounts officer-invoiceshelf -// export * from './soulseek'; // soulseek_favorites, _browse_snapshots, _browse_dirs -// export * from './vault'; // vault_tokens, vault_unlock_keys officer-vault -// export * from './wallet'; // wallet_wallets, _labels, _frozen_utxos, _chain_cache +// export * from './email/schema'; // email_accounts officer-email +// export * from './music/schema'; // music_favorites, _playlists, _playlist_items, _now_playing +// export * from './notify/schema'; // push_devices officer-notify +// export * from './dav/schema'; // dav_app_passwords officer-caldav +// export * from './photos/schema'; // photos_config officer-photos +// export * from './jellyfin/schema'; // jellyfin_servers officer-jellyfin +// export * from './invoiceshelf/schema'; // invoiceshelf_accounts officer-invoiceshelf +// export * from './soulseek/schema'; // soulseek_favorites, _browse_snapshots, _browse_dirs +// export * from './vault/schema'; // vault_tokens, vault_unlock_keys officer-vault +// export * from './wallet/schema'; // wallet_wallets, _labels, _frozen_utxos, _chain_cache diff --git a/src/databases/officer_db/src/queries/server-config.ts b/src/databases/officer_db/src/server/queries.ts similarity index 96% rename from src/databases/officer_db/src/queries/server-config.ts rename to src/databases/officer_db/src/server/queries.ts index 93cb64e1..64f63a16 100644 --- a/src/databases/officer_db/src/queries/server-config.ts +++ b/src/databases/officer_db/src/server/queries.ts @@ -1,6 +1,6 @@ import { eq } from 'drizzle-orm'; import { db } from '../db'; -import { serverConfig } from '../schema'; +import { serverConfig } from './schema'; const SETTINGS_KEY = 'server-settings'; diff --git a/src/databases/officer_db/src/schema/server.ts b/src/databases/officer_db/src/server/schema.ts similarity index 100% rename from src/databases/officer_db/src/schema/server.ts rename to src/databases/officer_db/src/server/schema.ts diff --git a/src/databases/officer_db/src/queries/service-connections.ts b/src/databases/officer_db/src/service-connections/queries.ts similarity index 98% rename from src/databases/officer_db/src/queries/service-connections.ts rename to src/databases/officer_db/src/service-connections/queries.ts index 93b037e2..0067be8a 100644 --- a/src/databases/officer_db/src/queries/service-connections.ts +++ b/src/databases/officer_db/src/service-connections/queries.ts @@ -1,7 +1,7 @@ import { eq, and } from 'drizzle-orm'; import { db } from '../db'; -import { serviceConnections } from '../schema'; -import { getOwnerUser } from './auth'; +import { serviceConnections } from './schema'; +import { getOwnerUser } from '../auth/queries'; import { encryptSecret, decryptSecret } from '../crypto'; // Single-connection services (transmission, slskd) for their sidecars. Callers deal in PLAINTEXT — diff --git a/src/databases/officer_db/src/schema/service-connections.ts b/src/databases/officer_db/src/service-connections/schema.ts similarity index 99% rename from src/databases/officer_db/src/schema/service-connections.ts rename to src/databases/officer_db/src/service-connections/schema.ts index f03c5971..88f8eb19 100644 --- a/src/databases/officer_db/src/schema/service-connections.ts +++ b/src/databases/officer_db/src/service-connections/schema.ts @@ -1,5 +1,5 @@ import { pgTable, serial, integer, text, timestamp, uniqueIndex } from 'drizzle-orm/pg-core'; -import { users } from './auth'; +import { users } from '../auth/schema'; // Where a self-hosted service lives, and what it takes to talk to it — for services the owner has exactly // ONE of. Transmission and slskd today. diff --git a/src/databases/officer_db/src/queries/soulseek.ts b/src/databases/officer_db/src/soulseek/queries.ts similarity index 99% rename from src/databases/officer_db/src/queries/soulseek.ts rename to src/databases/officer_db/src/soulseek/queries.ts index cc04e854..a88090aa 100644 --- a/src/databases/officer_db/src/queries/soulseek.ts +++ b/src/databases/officer_db/src/soulseek/queries.ts @@ -1,6 +1,6 @@ import { eq, and, asc, isNull, inArray, sql } from 'drizzle-orm'; import { db } from '../db'; -import { soulseekFavorites, soulseekBrowseSnapshots, soulseekBrowseDirs } from '../schema/soulseek'; +import { soulseekFavorites, soulseekBrowseSnapshots, soulseekBrowseDirs } from './schema'; /** A user's favourited Soulseek peers, alphabetical (the order the UI lists them in). */ export async function getSoulseekFavorites(userId: number): Promise { diff --git a/src/databases/officer_db/src/schema/soulseek.ts b/src/databases/officer_db/src/soulseek/schema.ts similarity index 99% rename from src/databases/officer_db/src/schema/soulseek.ts rename to src/databases/officer_db/src/soulseek/schema.ts index d50b4924..fd54bdf5 100644 --- a/src/databases/officer_db/src/schema/soulseek.ts +++ b/src/databases/officer_db/src/soulseek/schema.ts @@ -10,7 +10,7 @@ import { foreignKey, uniqueIndex, } from 'drizzle-orm/pg-core'; -import { users } from './auth'; +import { users } from '../auth/schema'; // Soulseek state that Officer owns because slskd has none. slskd exposes no favourites/buddy-list API // (verified against 0.26.0's UsersController: only endpoint/browse/directory/info/status), so the peers diff --git a/src/databases/officer_db/src/queries/user-data.ts b/src/databases/officer_db/src/user-data/queries.ts similarity index 96% rename from src/databases/officer_db/src/queries/user-data.ts rename to src/databases/officer_db/src/user-data/queries.ts index 3d72276a..84820f8f 100644 --- a/src/databases/officer_db/src/queries/user-data.ts +++ b/src/databases/officer_db/src/user-data/queries.ts @@ -1,6 +1,6 @@ import { eq } from 'drizzle-orm'; import { db } from '../db'; -import { dockConfigs, userSettings, userState } from '../schema'; +import { dockConfigs, userSettings, userState } from './schema'; // ── User Settings ── diff --git a/src/databases/officer_db/src/schema/user-data.ts b/src/databases/officer_db/src/user-data/schema.ts similarity index 96% rename from src/databases/officer_db/src/schema/user-data.ts rename to src/databases/officer_db/src/user-data/schema.ts index 0a47d92b..eed37a85 100644 --- a/src/databases/officer_db/src/schema/user-data.ts +++ b/src/databases/officer_db/src/user-data/schema.ts @@ -1,7 +1,7 @@ import { pgTable, serial, integer, text, timestamp, jsonb, check, uniqueIndex, foreignKey } from 'drizzle-orm/pg-core'; -import { users } from './auth'; +import { users } from '../auth/schema'; import { sql } from 'drizzle-orm'; -import { serverIntegrations } from './server'; +import { serverIntegrations } from '../server/schema'; export const userSettings = pgTable('user_settings', { userId: integer('user_id') diff --git a/src/databases/officer_db/src/queries/vault.ts b/src/databases/officer_db/src/vault/queries.ts similarity index 98% rename from src/databases/officer_db/src/queries/vault.ts rename to src/databases/officer_db/src/vault/queries.ts index 93686b1a..8926fb87 100644 --- a/src/databases/officer_db/src/queries/vault.ts +++ b/src/databases/officer_db/src/vault/queries.ts @@ -1,6 +1,6 @@ import { eq } from 'drizzle-orm'; import { db } from '../db'; -import { vaultTokens, vaultUnlockKeys } from '../schema/vault'; +import { vaultTokens, vaultUnlockKeys } from './schema'; import { encryptSecret, decryptSecret } from '../crypto'; // Vault store access. Callers deal in PLAINTEXT — encryption to/from at-rest ciphertext happens here, so diff --git a/src/databases/officer_db/src/schema/vault.ts b/src/databases/officer_db/src/vault/schema.ts similarity index 97% rename from src/databases/officer_db/src/schema/vault.ts rename to src/databases/officer_db/src/vault/schema.ts index 1012656e..88a046ac 100644 --- a/src/databases/officer_db/src/schema/vault.ts +++ b/src/databases/officer_db/src/vault/schema.ts @@ -1,5 +1,5 @@ import { pgTable, integer, text, timestamp } from 'drizzle-orm/pg-core'; -import { users } from './auth'; +import { users } from '../auth/schema'; // Officer Vault server-side state (see VAULT_AUTH_SPEC.md). The device never holds a Vaultwarden token; // the platform brokers it, stores it here tied to the owner account, and injects it on proxied /api/vault diff --git a/src/databases/officer_db/src/queries/wallet.ts b/src/databases/officer_db/src/wallet/queries.ts similarity index 99% rename from src/databases/officer_db/src/queries/wallet.ts rename to src/databases/officer_db/src/wallet/queries.ts index 6c043afb..76a5fbfa 100644 --- a/src/databases/officer_db/src/queries/wallet.ts +++ b/src/databases/officer_db/src/wallet/queries.ts @@ -1,7 +1,7 @@ -import type { WalletChainSnapshot } from '../schema/wallet'; +import type { WalletChainSnapshot } from './schema'; import { eq, and, desc } from 'drizzle-orm'; import { db } from '../db'; -import { walletWallets, walletLabels, walletFrozenUtxos, walletChainCache } from '../schema/wallet'; +import { walletWallets, walletLabels, walletFrozenUtxos, walletChainCache } from './schema'; import { encryptSecret, decryptSecret } from '../crypto'; // Wallet access for the officer-wallet sidecar. Callers deal in PLAINTEXT — the at-rest layer ('wallet' diff --git a/src/databases/officer_db/src/schema/wallet.ts b/src/databases/officer_db/src/wallet/schema.ts similarity index 99% rename from src/databases/officer_db/src/schema/wallet.ts rename to src/databases/officer_db/src/wallet/schema.ts index 463cf9dc..c92fa0ed 100644 --- a/src/databases/officer_db/src/schema/wallet.ts +++ b/src/databases/officer_db/src/wallet/schema.ts @@ -1,6 +1,6 @@ import { pgTable, serial, integer, text, boolean, timestamp, jsonb, uniqueIndex } from 'drizzle-orm/pg-core'; import { sql } from 'drizzle-orm'; -import { users } from './auth'; +import { users } from '../auth/schema'; // Bitcoin wallets for the officer-wallet sidecar. The owner registers one or more wallets — either a // self-custodial on-chain wallet whose seed lives here, or a connection to a node (LND / Core Lightning / diff --git a/src/servers/sidecar/invoiceshelf/upstream.ts b/src/servers/sidecar/invoiceshelf/upstream.ts index 323d6ece..f746611f 100644 --- a/src/servers/sidecar/invoiceshelf/upstream.ts +++ b/src/servers/sidecar/invoiceshelf/upstream.ts @@ -5,7 +5,7 @@ // auth+forward proxy and holds NO InvoiceShelf credentials. // // The instance is CONFIGURED BY THE OWNER FROM THE UI and stored encrypted in `invoiceshelf_accounts` (see -// databases/officer_db/src/queries/invoiceshelf.ts). It is deliberately no longer read from the environment: +// databases/officer_db/src/invoiceshelf/queries.ts). It is deliberately no longer read from the environment: // Bun auto-loads `.env` into every process started in the platform directory, so an `INVOICESHELF_TOKEN` // there was also sitting in `officer`'s own `process.env` — a credential held by the one process that has no // code to use it and the largest attack surface in the system. Nothing in this file reads process.env. diff --git a/src/servers/sidecar/jellyfin/upstream.ts b/src/servers/sidecar/jellyfin/upstream.ts index 841b16b1..10202de6 100644 --- a/src/servers/sidecar/jellyfin/upstream.ts +++ b/src/servers/sidecar/jellyfin/upstream.ts @@ -5,7 +5,7 @@ // thin auth+forward proxy and holds NO Jellyfin credentials. // // The server is CONFIGURED BY THE OWNER FROM THE UI and stored encrypted in `jellyfin_servers` (see -// databases/officer_db/src/queries/jellyfin.ts). Nothing in this file reads process.env — Bun auto-loads +// databases/officer_db/src/jellyfin/queries.ts). Nothing in this file reads process.env — Bun auto-loads // `.env` into every process started in the platform directory, so a token there would also be sitting in // `officer`'s own environment: a credential held by the one process that has no code to use it. // diff --git a/src/servers/sidecar/photos/upstream.ts b/src/servers/sidecar/photos/upstream.ts index af642eb3..8d14a794 100644 --- a/src/servers/sidecar/photos/upstream.ts +++ b/src/servers/sidecar/photos/upstream.ts @@ -4,7 +4,7 @@ // auth+forward proxy and holds NO Immich credentials. // // The instance is CONFIGURED BY THE OWNER FROM THE UI and stored encrypted in `photos_config` (see -// databases/officer_db/src/queries/photos.ts). It is deliberately no longer read from the environment: +// databases/officer_db/src/photos/queries.ts). It is deliberately no longer read from the environment: // Bun auto-loads `.env` into every process started in the platform directory, so an `IMMICH_API_KEY` there // was also sitting in `officer`'s own `process.env` — a credential held by the one process that has no code // to use it and the largest attack surface in the system. Nothing in this file reads process.env. diff --git a/src/servers/sidecar/slskd/upstream.ts b/src/servers/sidecar/slskd/upstream.ts index 55f302c4..d42f47e5 100644 --- a/src/servers/sidecar/slskd/upstream.ts +++ b/src/servers/sidecar/slskd/upstream.ts @@ -6,7 +6,7 @@ import { getServiceCredentials } from 'officerdb'; // auth+forward proxy and holds NO slskd credentials. // // The daemon is CONFIGURED BY THE OWNER FROM THE UI and stored encrypted in `service_connections` (see -// databases/officer_db/src/queries/service-connections.ts). It is deliberately no longer read from the +// databases/officer_db/src/service-connections/queries.ts). It is deliberately no longer read from the // environment: Bun auto-loads `.env` into every process started in the platform directory, so an // `SLSKD_API_KEY` there was also sitting in `officer`'s own process.env — a credential that drives the whole // Soulseek daemon, held by the one process with no code to use it. Nothing in this file reads process.env. diff --git a/src/servers/sidecar/transmission/upstream.ts b/src/servers/sidecar/transmission/upstream.ts index ca8d4623..7a0ed8cf 100644 --- a/src/servers/sidecar/transmission/upstream.ts +++ b/src/servers/sidecar/transmission/upstream.ts @@ -6,7 +6,7 @@ import { getServiceCredentials } from 'officerdb'; // a thin auth+forward proxy and holds NO Transmission credentials. // // The daemon is CONFIGURED BY THE OWNER FROM THE UI and stored in `service_connections` (see -// databases/officer_db/src/queries/service-connections.ts), no longer read from the environment: Bun +// databases/officer_db/src/service-connections/queries.ts), no longer read from the environment: Bun // auto-loads `.env` into every process started in the platform directory, so TRANSMISSION_* was also // sitting in `officer`'s own process.env, and pointing Officer at a daemon meant editing a file on the // server. Nothing in this file reads process.env. diff --git a/src/servers/sidecar/wallet/upstream.ts b/src/servers/sidecar/wallet/upstream.ts index 412bba91..9a627b19 100644 --- a/src/servers/sidecar/wallet/upstream.ts +++ b/src/servers/sidecar/wallet/upstream.ts @@ -4,7 +4,7 @@ import { getKey } from 'officerdb/secret-store'; // The ONLY reader of WALLET_* env in the tree. Everything else — node URLs, macaroons, runes, LNDHub // credentials, NWC URIs — is per-wallet configuration the owner enters at runtime and lives encrypted in -// Postgres (databases/officer_db/src/schema/wallet.ts), not here. Env holds only what is genuinely +// Postgres (databases/officer_db/src/wallet/schema.ts), not here. Env holds only what is genuinely // deployment-wide: which chain we're on. // // WHERE CHAIN DATA COMES FROM IS NOT ENV. It used to be WALLET_ESPLORA_URL, which meant the one setting diff --git a/src/workspaces/officerdev/src/apps/Wallet/shared.ts b/src/workspaces/officerdev/src/apps/Wallet/shared.ts index 8fe48948..29642557 100644 --- a/src/workspaces/officerdev/src/apps/Wallet/shared.ts +++ b/src/workspaces/officerdev/src/apps/Wallet/shared.ts @@ -1,7 +1,7 @@ // Shared types/constants for the /wallet workspace panels. // // The wire shapes mirror src/servers/sidecar/wallet/types.ts and the WalletSummary projection in -// src/databases/officer_db/src/queries/wallet.ts. They are restated here rather than imported because the +// src/databases/officer_db/src/wallet/queries.ts. They are restated here rather than imported because the // officerdev workspace has no path into src/servers — pulling the sidecar's module graph into the browser // bundle would drag bitcoinjs-lib and the key handling along with it, which is exactly what must never // reach the client. Keep this file in step with those two by hand; the field names are identical on