one directory per feature: schema.ts and queries.ts together
src/databases/officer_db/src/<feature>/{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/<x>.ts.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+25
-12
@@ -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)
|
||||
│ └── <feature>/
|
||||
│ ├── 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';
|
||||
|
||||
@@ -15,7 +15,7 @@ try {
|
||||
} catch {}
|
||||
|
||||
export default defineConfig({
|
||||
schema: './src/schema/index.ts',
|
||||
schema: './src/schema.ts',
|
||||
out: './migrations',
|
||||
dialect: 'postgresql',
|
||||
dbCredentials: {
|
||||
|
||||
@@ -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",
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
+1
-1
@@ -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
|
||||
+2
-1
@@ -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
|
||||
+1
-1
@@ -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.
|
||||
//
|
||||
+1
-1
@@ -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.
|
||||
+1
-1
@@ -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 ──
|
||||
+3
-3
@@ -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.
|
||||
|
||||
+1
-1
@@ -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.
|
||||
//
|
||||
+1
-1
@@ -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<number> {
|
||||
+1
-1
@@ -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 ──
|
||||
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+1
-1
@@ -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.
|
||||
//
|
||||
+1
-1
@@ -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<EmailAccountSelect[]> {
|
||||
+1
-1
@@ -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',
|
||||
+1
-1
@@ -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 —
|
||||
+1
-1
@@ -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
|
||||
@@ -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';
|
||||
|
||||
+3
-2
@@ -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 ──
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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.
|
||||
//
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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.
|
||||
//
|
||||
+1
-1
@@ -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[] };
|
||||
+1
-1
@@ -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/<rel>/<file>" (also the /stream path + RNTP queue id)
|
||||
+1
-1
@@ -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.
|
||||
+1
-1
@@ -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.
|
||||
//
|
||||
+1
-1
@@ -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(),
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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.
|
||||
//
|
||||
+1
-1
@@ -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) {
|
||||
+1
-1
@@ -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',
|
||||
+23
-23
@@ -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
|
||||
+1
-1
@@ -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';
|
||||
|
||||
+2
-2
@@ -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 —
|
||||
+1
-1
@@ -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.
|
||||
+1
-1
@@ -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<string[]> {
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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 ──
|
||||
|
||||
+2
-2
@@ -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')
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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
|
||||
+2
-2
@@ -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'
|
||||
+1
-1
@@ -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 /
|
||||
Reference in New Issue
Block a user