import { pgTable, serial, text, timestamp, integer } from 'drizzle-orm/pg-core'; /** * The connection to an OpenSEO instance. One row, and the only thing this plugin stores. * * ── Why there is nothing else here ── * * OpenSEO keeps its own data — projects, keywords, rankings, crawls — in its own SQLite database inside * its container. None of it belongs here, and the temptation to cache "just the project list" should be * resisted for a reason worth writing down: the plugin installer drops a plugin's tables when the * "delete its data" checkbox is ticked. So the strongest thing that can be said about this plugin is a * consequence of this file being one table long — **no operation on it can destroy anybody's research.** * Uninstalling removes a URL and a key; every project survives in the container. * * ── The DataForSEO key lives here, not in the container's env ── * * OpenSEO takes `DATAFORSEO_API_KEY` from its environment, so an instance somebody else started already * has one and this column stays null. It exists for the case where Officer provisions the container — * the direction `todo/infra/opm-officer-s-own-process-manager` is heading — and it is stored encrypted * from the start so that day needs no migration and no window where a key sits in plaintext. * * It is a real cost, not a free tier: DataForSEO is metered and billed directly by them. */ export const openseoConnection = pgTable('openseo_connection', { id: serial('id').primaryKey(), /** Where the instance is — the origin its UI and `/mcp` are served from. No trailing slash. */ url: text('url').notNull(), /** * Base64 of `login:password`, encrypted with `encryptSecret('openseo', …)` — its own purpose key, so * a leak of that one opens this column and nothing else. * * **Never returned across the API boundary**, at any level, to anybody including the owner. A form * asking "is one set" gets a boolean. */ dataForSeoKey: text('data_for_seo_key'), /** From the last successful probe — what that instance said about itself. Diagnostic only. */ probedVersion: text('probed_version'), lastProbeAt: timestamp('last_probe_at', { withTimezone: true }), /** * Three states, not two — the same reasoning proxy-manager gives for using an integer. Null means * never probed, which reads differently from probed-and-failed. */ lastProbeOk: integer('last_probe_ok'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }); export type OpenSeoConnection = typeof openseoConnection.$inferSelect;