Ampelos dashboard: the web face, and the owner of the schema
Split out of the single Ampelos repository. Next.js app, Drizzle schema and migrations, brand art, planning notes. What left: scripts/, which was the agent's job library misfiled under web/ and imported nothing from src/; and deploy/truenas, whose broadcast posts to the scan listener on :3427 -- an agent script -- so it belongs beside the thing it talks to. This repository keeps the schema. The agent speaks raw SQL against the same tables and holds no copy of it, so a rename here can break it silently where it used to be one commit. The README says so, and the agent carries a snapshot to check against. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
pgTable,
|
||||
uuid,
|
||||
text,
|
||||
boolean,
|
||||
timestamp,
|
||||
integer,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
export const users = pgTable("users", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
displayName: text("display_name").notNull(),
|
||||
email: text("email").notNull().unique(),
|
||||
isAdmin: boolean("is_admin").notNull().default(false),
|
||||
watchingNowTvSlots: integer("watching_now_tv_slots").notNull().default(5),
|
||||
watchingNowMovieSlots: integer("watching_now_movie_slots").notNull().default(10),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
||||
});
|
||||
|
||||
// A linked Plex account.
|
||||
//
|
||||
// Linking does two things at once, which is why it is worth a table of its own
|
||||
// rather than another row in user_identities: it grants the person access to
|
||||
// the Plex libraries, and it makes their Plex watchlist readable as demand.
|
||||
// Neither is possible without knowing which Plex account belongs to which user.
|
||||
//
|
||||
// NO TOKEN IS STORED. The link is proved by the PIN flow -- the user signs in
|
||||
// at plex.tv, we exchange the PIN for a token, ask Plex who it belongs to, and
|
||||
// then throw the token away. Reading their watchlist needs the OWNER's token
|
||||
// and the account uuid, both of which we already have, so keeping a second
|
||||
// person's credential would buy nothing and be one more thing to leak.
|
||||
export const plexAccounts = pgTable("plex_accounts", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }).unique(),
|
||||
// Plex's numeric account id, and the uuid the community API keys watchlists on.
|
||||
plexUserId: text("plex_user_id").notNull().unique(),
|
||||
plexUuid: text("plex_uuid"),
|
||||
plexUsername: text("plex_username").notNull(),
|
||||
plexEmail: text("plex_email"),
|
||||
// When the libraries were shared, and which ones. Recorded so a failed or
|
||||
// partial share is visible rather than being assumed to have worked.
|
||||
librariesSharedAt: timestamp("libraries_shared_at"),
|
||||
sharedSectionIds: text("shared_section_ids").array(),
|
||||
linkedAt: timestamp("linked_at").notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
||||
});
|
||||
|
||||
// Maps Authentik (or other OIDC) external identities to local users.
|
||||
export const userIdentities = pgTable("user_identities", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
||||
provider: text("provider").notNull(), // e.g. "authentik"
|
||||
externalId: text("external_id").notNull(), // sub claim from OIDC
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
});
|
||||
Reference in New Issue
Block a user