From 40b4270161bcb6dee3f967c4f848e732600eb6c9 Mon Sep 17 00:00:00 2001 From: Wanjohi Date: Fri, 18 Sep 2026 22:58:52 +0300 Subject: [PATCH 1/9] refactor(api)!: remove the shared operator secret, and let hosts sync their own MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A single secret that turned any request into an operator was the only credential several routes accepted, and it had no caller left: the device pairing it existed for is on hold, and nothing in this tree or any client sent it. What remained was a key that bypassed authentication entirely, required to boot, and checked by nobody. Every route behind it had a better answer available: - Library and game sync move to host credentials. Both took a `userId` in the body, which meant one secret could write into anybody's library. A host now says which of its enrolled users a batch is for, and that claim is checked against the Steam sign-ins it actually holds — one box carries several people's accounts, so the pair is the unit. - Download-state reporting narrows to hosts alone, and the body that could name a different host is gone. Which host is reporting comes from its own credentials, and a body that still names one is refused rather than ignored. - Linking a Steam account is always for the caller. - Creating a game by hand is deleted; syncing already upserts the catalogue. - Reading the waitlist is deleted. Every address on it belongs to someone who has not agreed to anything, and answering it over HTTP made that list something a leaked key could drain. - The pairing-code routes are deleted with the flow they served. The domain module and its table stay, so returning to it is a route file rather than a migration. Nothing in the API now accepts a credential that stands for more than one caller: every request resolves to a specific user or a specific host, which is what lets a route say "the caller's own library" and mean it. BREAKING CHANGE: the `x-nestri-admin-token` header is no longer accepted and `ADMIN_SHARED_SECRET` is no longer read. `POST /games`, `GET /waitlist` and the `/pairing-code` routes are gone; `POST /games/sync` and `POST /library/sync` now require host credentials and take `userId` in the body; `POST /steam/link` no longer accepts `userId`; `POST /games/download-state` no longer accepts `hostId`. --- .env.example | 4 - .github/workflows/release-prod.yml | 3 +- apps/api/Dockerfile | 1 - apps/api/README.md | 37 ++-- apps/api/app/index.ts | 3 - apps/api/app/middleware/auth.ts | 36 ---- apps/api/app/routes/game.ts | 202 +++++--------------- apps/api/app/routes/library.ts | 11 +- apps/api/app/routes/pairing-code.ts | 178 ------------------ apps/api/app/routes/steam.ts | 23 +-- apps/api/app/routes/waitlist.ts | 115 +++++------- apps/api/app/utils/auth.ts | 2 +- apps/api/app/utils/enrolment.ts | 32 ++++ apps/api/app/utils/index.ts | 1 + apps/api/test/enrolment.test.ts | 33 +++- apps/api/test/heartbeat.test.ts | 22 ++- apps/api/test/routes.test.ts | 266 ++++++++++----------------- apps/api/test/setup.ts | 4 +- docker-compose.yml | 6 +- docs/deploy.md | 11 +- packages/core/CLAUDE.md | 28 ++- packages/core/src/actor.ts | 7 +- packages/core/src/env.ts | 4 - packages/core/src/steam/enrolment.ts | 31 ++++ 24 files changed, 348 insertions(+), 712 deletions(-) delete mode 100644 apps/api/app/routes/pairing-code.ts create mode 100644 apps/api/app/utils/enrolment.ts diff --git a/.env.example b/.env.example index cf1028b4..a34e64db 100644 --- a/.env.example +++ b/.env.example @@ -28,10 +28,6 @@ AUTH_ISSUER_URL=http://localhost:1337 # public name is unroutable from where the API runs; docker compose sets it. AUTH_INTERNAL_URL= -# Turns any request carrying it into an operator, so generate one rather than -# typing something: `openssl rand -hex 32`. -ADMIN_SHARED_SECRET= - # Mail delivery. All three together, or none of them plus EMAIL_DEV_LOG=true, # which prints sign-in codes to the log instead of sending them. Printing them # is a local-development convenience and nothing else. diff --git a/.github/workflows/release-prod.yml b/.github/workflows/release-prod.yml index 2be50e64..58ded398 100644 --- a/.github/workflows/release-prod.yml +++ b/.github/workflows/release-prod.yml @@ -138,7 +138,6 @@ jobs: PORT=13999 HOST=127.0.0.1 \ DATABASE_URL="postgres://nobody@127.0.0.1:1/none" \ AUTH_ISSUER_URL=https://auth.nestri.io \ - ADMIN_SHARED_SECRET=smoke \ ./dist/nestri-api & api=$! # NOT `/`, which the issuer answers 404 — it has no root route. NOT # `openid-configuration` either, also a 404: this is an OAuth 2.0 @@ -168,7 +167,7 @@ jobs: # 127.0.0.1, and a connection string containing it would satisfy the # grep from the wrong line. env -u HOST PORT=13997 DATABASE_URL="postgres://nobody@nowhere.invalid:1/none" \ - ADMIN_SHARED_SECRET=smoke ./dist/nestri-api > /tmp/bind.log 2>&1 & probe=$! + ./dist/nestri-api > /tmp/bind.log 2>&1 & probe=$! sleep 3 kill $probe 2>/dev/null || true grep -q "listening on http://127.0.0.1:" /tmp/bind.log || { diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile index 8f3dc936..19e861fc 100644 --- a/apps/api/Dockerfile +++ b/apps/api/Dockerfile @@ -50,7 +50,6 @@ COPY packages/auth packages/auth # DATABASE_URL postgres://… required # AUTH_ISSUER_URL the issuer's public URL required # STEAM_API_KEY for linking an account -# ADMIN_SHARED_SECRET operator access ENV NODE_ENV=production ENV PORT=3000 EXPOSE 3000 diff --git a/apps/api/README.md b/apps/api/README.md index adcf9b9c..c9cc4f5e 100644 --- a/apps/api/README.md +++ b/apps/api/README.md @@ -11,17 +11,17 @@ and returns `{ data: ... }`. All business logic lives in the core package. Routes: -| Prefix | Purpose | -| ----------------- | ------------------------------------------------------------- | -| `/` | Health check | -| `/user` | Current user profile, fingerprints, linked accounts | -| `/steam` | Link / sync / unlink a Steam account | -| `/library` | Owned games with playtime | -| `/games` | Game catalog | -| `/pairing-code` | Device pairing codes | -| `/machine` | Host machines | -| `/access-token` | Short-lived access tokens | -| `/doc` | Generated OpenAPI spec | +| Prefix | Purpose | +| --------------- | --------------------------------------------------- | +| `/` | Health check | +| `/user` | Current user profile, fingerprints, linked accounts | +| `/steam` | Link / sync / unlink a Steam account | +| `/library` | Owned games with playtime | +| `/games` | Game catalog | +| `/pairing-code` | Device pairing codes | +| `/machine` | Host machines | +| `/access-token` | Short-lived access tokens | +| `/doc` | Generated OpenAPI spec | ## Structure @@ -29,7 +29,7 @@ Routes: app/ index.ts # The handler: middleware, routes, error handler, /doc server.ts # The same handler behind a listening socket - middleware/auth.ts # Bearer JWT + admin shared-secret auth → Actor + middleware/auth.ts # Bearer JWT, access token or host credentials → Actor routes/*.ts # Thin route namespaces (UserApi, SteamApi, ...) utils/ # ErrorResponses, Result(), validator wrapping wrangler.jsonc # Worker configuration, one environment per stage @@ -39,12 +39,11 @@ test/ # Route tests ## Key details -- Auth: `Authorization: Bearer ` verified against `@nestri/auth`; or the `x-nestri-admin-token` header - carrying `ADMIN_SHARED_SECRET`, which bypasses JWT verification entirely and is required — it has no - default anywhere. It is what authenticates the callers that have no user identity to present: - `POST /pairing-code/claim` (a device being paired has no identity yet, which is the whole point), - `POST /games`, `POST /games/sync`, `POST /library/sync`, `GET /waitlist`, `POST /steam/link` on behalf - of another user, and `POST /games/download-state` when an operator is repairing state a box reported. +- Auth: `Authorization: Bearer …`, carrying either a session token verified against `@nestri/auth` + or a personal access token resolved from the database; or a registered host's own + `x-nestri-machine-id` and `x-nestri-machine-secret`. There is no shared secret and no credential + that stands for more than one caller, so every route resolves to a specific user or a specific + host — which is what lets a route say "the caller's own library" and mean it. - Errors: centralized `VisibleError` → typed JSON responses. - Settings arrive as bindings or as environment variables, and two of them have one spelling of each: Postgres is `HYPERDRIVE` or `DATABASE_URL`, and the route to the issuer is an `AUTH` @@ -60,4 +59,4 @@ bun run serve # as a plain process, on $PORT (default 3000) ``` Needs a Postgres database and a reachable issuer. Full list and deployment steps: -[`docs/deploy.md`](../../docs/deploy.md). \ No newline at end of file +[`docs/deploy.md`](../../docs/deploy.md). diff --git a/apps/api/app/index.ts b/apps/api/app/index.ts index 065c2a44..6c12cd59 100644 --- a/apps/api/app/index.ts +++ b/apps/api/app/index.ts @@ -15,7 +15,6 @@ import { GameApi } from './routes/game.js'; import { IndexApi } from './routes/index.js'; import { LibraryApi } from './routes/library.js'; import { MachineApi } from './routes/machine.js'; -import { PairingCodeApi } from './routes/pairing-code.js'; import { SessionApi } from './routes/session.js'; import { SteamApi } from './routes/steam.js'; import { UserApi } from './routes/user.js'; @@ -43,7 +42,6 @@ const routes = app .route('/steam', SteamApi.route) .route('/library', LibraryApi.route) .route('/games', GameApi.route) - .route('/pairing-code', PairingCodeApi.route) .route('/machine', MachineApi.route) .route('/machine', SessionApi.machineRoute) .route('/machine', EnrolmentApi.route) @@ -125,7 +123,6 @@ export type ApiEnv = { AUTH_INTERNAL_URL?: string; HYPERDRIVE?: Hyperdrive; DATABASE_URL?: string; - ADMIN_SHARED_SECRET?: string; }; export default { diff --git a/apps/api/app/middleware/auth.ts b/apps/api/app/middleware/auth.ts index aab6062f..51097076 100644 --- a/apps/api/app/middleware/auth.ts +++ b/apps/api/app/middleware/auth.ts @@ -85,11 +85,6 @@ function getClient(env: Record) { } export const auth: MiddlewareHandler = async (c, next) => { - const adminToken = c.req.header('x-nestri-admin-token'); - if (adminToken && adminToken === Env.get().ADMIN_SHARED_SECRET) { - return Actor.with({ type: 'admin', properties: {} }, next); - } - // A registered nessh host proves it is itself, rather than asserting an id // nobody checks. Wrong credentials fall through to public rather than // erroring, so probing tells an attacker nothing about which ids exist. @@ -263,34 +258,3 @@ export const machineOnly: MiddlewareHandler = async (_, next) => { } return next(); }; - -/** - * A box reporting about itself, or an operator reaching in. - * - * The two are not equivalent and routes behind this must not treat them so: a - * machine may only speak for itself, while admin still has to say which host - * it means. Keeping admin is what lets an operator repair state by hand. - */ -export const machineOrAdmin: MiddlewareHandler = async (_, next) => { - const actor = Actor.use(); - if (actor.type !== 'machine' && actor.type !== 'admin') { - throw new VisibleError( - 'forbidden', - ErrorCodes.Permission.INSUFFICIENT_PERMISSIONS, - 'Machine or admin credentials required' - ); - } - return next(); -}; - -export const adminOnly: MiddlewareHandler = async (_, next) => { - const actor = Actor.use(); - if (actor.type !== 'admin') { - throw new VisibleError( - 'forbidden', - ErrorCodes.Permission.INSUFFICIENT_PERMISSIONS, - 'Admin access required' - ); - } - return next(); -}; diff --git a/apps/api/app/routes/game.ts b/apps/api/app/routes/game.ts index 7da2548c..cfac9cd9 100644 --- a/apps/api/app/routes/game.ts +++ b/apps/api/app/routes/game.ts @@ -10,7 +10,7 @@ import { Hono } from 'hono'; import { describeRoute } from 'hono-openapi'; import { z } from 'zod'; -import { ErrorResponses, adminOnly, machineOrAdmin, notPublic, Result, validator } from '../utils'; +import { enrolledUser, ErrorResponses, machineOnly, notPublic, Result, validator } from '../utils'; const SyncGameSchema = z.object({ steamAppId: z.number().int(), @@ -150,12 +150,12 @@ export namespace GameApi { .post( '/sync', notPublic, - adminOnly, + machineOnly, describeRoute({ tags: ['Games'], summary: 'Batch sync games, library entries, and depots', description: - 'Bulk upsert games, library entries, and depot info from Steam sync. Admin only.', + 'Bulk upsert games, library entries and depot info from a Steam sync. Entries land in the caller\u2019s own library; there is no field for naming another user.', responses: { 200: { content: { @@ -180,13 +180,17 @@ export namespace GameApi { validator( 'json', z.object({ - userId: z.string(), + userId: z.string().meta({ + description: 'Which of the host\u2019s enrolled users this sync is for', + example: Examples.User.id + }), games: z.array(SyncGameSchema).default([]), library: z.array(SyncLibrarySchema).default([]) }) ), async (c) => { - const { userId, games, library } = c.req.valid('json'); + const { games, library } = c.req.valid('json'); + const userId = await enrolledUser(c.req.valid('json').userId); const existingGames = await Game.listByAppIDs(games.map((g) => g.steamAppId)); const existingByAppId = new Map(existingGames.map((g) => [g.steamAppId, g])); @@ -347,7 +351,7 @@ export namespace GameApi { tags: ['Games'], summary: 'Get download states for a game', description: - 'Returns the per-host download states for a game. Optionally filter by hostId. Protected read route for initial/fallback data; SSH-connected clients use the live SSH snapshot.', + 'Returns the per-host download states for a game, optionally filtered to one host. This is the recorded state, written by hosts as they report progress; a client holding a live connection to a host has a fresher answer from the host itself.', responses: { 200: { content: { @@ -409,12 +413,12 @@ export namespace GameApi { .post( '/download-state', notPublic, - machineOrAdmin, + machineOnly, describeRoute({ tags: ['Games'], summary: 'Report a download state change', description: - 'Update the shared per-host download state for a game. Called by nessh on terminal events (start/verifying/complete/fail). A registered host reports as itself and cannot name another; admin must supply the hostId explicitly.', + 'Update the shared per-host download state for a game, on terminal events (start/verifying/complete/fail). A host reports as itself \u2014 which host it is comes from its own credentials, and there is no field that could name another.', responses: { 200: { content: { @@ -437,63 +441,42 @@ export namespace GameApi { }), validator( 'json', - z.object({ - hostId: z.string().optional().meta({ - description: - 'The nessh host reporting the download. Required for admin callers; ignored for machines, which report as themselves.', - example: Examples.GameDownload.hostId - }), - steamAppId: z.number().int().meta({ - description: 'Steam application ID', - example: Examples.Game.steamAppId - }), - status: z.enum(GameDownload.Status).meta({ - description: 'New download status', - example: Examples.GameDownload.status - }), - progressBytes: z.number().int().optional().meta({ - description: 'Bytes downloaded so far', - example: Examples.GameDownload.progressBytes - }), - totalBytes: z.number().int().optional().meta({ - description: 'Total bytes to download', - example: Examples.GameDownload.totalBytes - }), - errorMessage: z.string().nullable().optional().meta({ - description: 'Error message if status is failed', - example: null + z + .object({ + steamAppId: z.number().int().meta({ + description: 'Steam application ID', + example: Examples.Game.steamAppId + }), + status: z.enum(GameDownload.Status).meta({ + description: 'New download status', + example: Examples.GameDownload.status + }), + progressBytes: z.number().int().optional().meta({ + description: 'Bytes downloaded so far', + example: Examples.GameDownload.progressBytes + }), + totalBytes: z.number().int().optional().meta({ + description: 'Total bytes to download', + example: Examples.GameDownload.totalBytes + }), + errorMessage: z.string().nullable().optional().meta({ + description: 'Error message if status is failed', + example: null + }) }) - }) + // A body naming a host is refused rather than ignored. It used + // to carry one, so a caller that still sends it is saying + // something this route no longer honours, and accepting it + // quietly would look like it had been. + .strict() ), async (c) => { - const { hostId, steamAppId, status, progressBytes, totalBytes, errorMessage } = - c.req.valid('json'); + const { steamAppId, status, progressBytes, totalBytes, errorMessage } = c.req.valid('json'); - // A machine reports as itself. Taking the id from the body would - // mean any holder of a shared secret could write download state - // under any box's id, which is the whole reason boxes register. - const actor = Actor.use(); - let reportingHostId: string; - if (actor.type === 'machine') { - if (hostId && hostId !== actor.properties.machineID) { - throw new VisibleError( - 'forbidden', - ErrorCodes.Permission.FORBIDDEN, - 'A machine may only report its own download state' - ); - } - reportingHostId = actor.properties.machineID; - } else { - if (!hostId) { - throw new VisibleError( - 'validation', - ErrorCodes.Validation.MISSING_REQUIRED_FIELD, - 'hostId is required when reporting on behalf of a host', - 'hostId' - ); - } - reportingHostId = hostId; - } + // A host reports as itself, and `machineOnly` is what makes that + // the only possibility: with the id read from its credentials + // there is no body field to disagree with them. + const reportingHostId = Actor.machineID; const game = await Game.fromSteamAppID(steamAppId); if (!game) { @@ -517,102 +500,5 @@ export namespace GameApi { data: { downloadId: row.id, download: GameDownload.serialize(row) } }); } - ) - .post( - '/', - notPublic, - adminOnly, - describeRoute({ - tags: ['Games'], - summary: 'Create or update a game', - description: 'Upsert a game by Steam app ID. Admin only.', - responses: { - 201: { - content: { - 'application/json': { - schema: Result( - Game.Info.meta({ - description: 'The created or updated game', - example: Examples.Game - }) - ) - } - }, - description: 'Game created or updated' - }, - 400: ErrorResponses[400], - 401: ErrorResponses[401], - 403: ErrorResponses[403] - } - }), - validator( - 'json', - z.object({ - steamAppId: z.number().int().meta({ - description: 'Steam application ID', - example: Examples.Game.steamAppId - }), - name: z.string().meta({ - description: 'Game title', - example: Examples.Game.name - }), - slug: z.string().optional().meta({ - description: 'URL-friendly slug', - example: Examples.Game.slug - }), - type: z.string().nullable().optional().meta({ - description: 'Content type', - example: Examples.Game.type - }), - clientIcon: z.string().nullable().optional().meta({ - description: 'Steam client icon hash (256×256 square)', - example: Examples.Game.clientIcon - }), - icon: z.string().nullable().optional().meta({ - description: 'Steam icon hash (32×32)', - example: Examples.Game.icon - }), - shortDescription: z.string().nullable().optional().meta({ - description: 'Short description', - example: Examples.Game.shortDescription - }), - description: z.string().nullable().optional().meta({ - description: 'Full description', - example: Examples.Game.description - }), - developers: z.array(z.string()).nullable().optional().meta({ - description: 'Game developers', - example: Examples.Game.developers - }), - publishers: z.array(z.string()).nullable().optional().meta({ - description: 'Game publishers', - example: Examples.Game.publishers - }), - genres: z.array(z.string()).nullable().optional().meta({ - description: 'Game genres', - example: Examples.Game.genres - }), - oslist: z.array(z.string()).nullable().optional().meta({ - description: 'Supported OS list', - example: Examples.Game.oslist - }), - releaseDate: z.string().nullable().optional().meta({ - description: 'Release date ISO string', - example: Examples.Game.releaseDate - }) - }) - ), - async (c) => { - const body = c.req.valid('json'); - const id = Identifier.ascending('game'); - const slug = - body.slug ?? - body.name - .toLowerCase() - .replace(/[^a-z0-9]+/g, '-') - .replace(/^-|-$/g, ''); - const game = await Game.upsert({ ...body, id, slug }); - return c.json({ data: game[0] }, 201); - } ); } diff --git a/apps/api/app/routes/library.ts b/apps/api/app/routes/library.ts index 0ac1c9c7..d1c5fc4b 100644 --- a/apps/api/app/routes/library.ts +++ b/apps/api/app/routes/library.ts @@ -8,7 +8,7 @@ import { Hono } from 'hono'; import { describeRoute } from 'hono-openapi'; import { z } from 'zod'; -import { ErrorResponses, adminOnly, notPublic, Result, validator } from '../utils'; +import { enrolledUser, ErrorResponses, machineOnly, notPublic, Result, validator } from '../utils'; export namespace LibraryApi { export const route = new Hono() @@ -56,12 +56,12 @@ export namespace LibraryApi { ) .post( '/sync', - adminOnly, + machineOnly, describeRoute({ tags: ['Library'], summary: "Sync a user's Steam library", description: - 'Batch upsert games and library entries for a user from Steam owned games data. Admin only.', + 'Batch upsert games and library entries from Steam owned games data. The library synced is the caller\u2019s own; there is no field for naming another user.', responses: { 200: { content: { @@ -86,7 +86,7 @@ export namespace LibraryApi { 'json', z.object({ userId: z.string().meta({ - description: 'The user to sync library for', + description: 'Which of the host\u2019s enrolled users this library belongs to', example: Examples.User.id }), games: z @@ -121,7 +121,8 @@ export namespace LibraryApi { }) ), async (c) => { - const { userId, games } = c.req.valid('json'); + const { games } = c.req.valid('json'); + const userId = await enrolledUser(c.req.valid('json').userId); const existingGames = await Game.listByAppIDs(games.map((g) => g.steamAppId)); const existingByAppId = new Map(existingGames.map((g) => [g.steamAppId, g])); diff --git a/apps/api/app/routes/pairing-code.ts b/apps/api/app/routes/pairing-code.ts deleted file mode 100644 index 128c0d28..00000000 --- a/apps/api/app/routes/pairing-code.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { Actor } from '@nestri/core/actor'; -import { ErrorCodes, VisibleError } from '@nestri/core/error'; -import { Examples } from '@nestri/core/examples'; -import { Identifier } from '@nestri/core/id'; -import { PairingCode } from '@nestri/core/pairing-code/index'; -import { Fingerprint } from '@nestri/core/user/fingerprint'; -import { Hono } from 'hono'; -import { describeRoute } from 'hono-openapi'; -import { z } from 'zod'; - -import { adminOnly, ErrorResponses, notPublic, Result, validator } from '../utils'; - -/** - * Device enrolment. - * - * A pairing code says "this SSH key is also me". It is deliberately not the - * same thing as an invite, which says "you may use my box" — same shape of - * secret, completely different authority, and merging them would let one be - * redeemed for the other. - * - * Generating requires an authenticated session; claiming is done by nessh on - * behalf of a device that has no identity yet, so it authenticates with the - * shared admin token instead. - */ -export namespace PairingCodeApi { - export const route = new Hono() - .get( - '/', - notPublic, - describeRoute({ - tags: ['PairingCode'], - summary: 'List your pairing codes', - description: 'Every pairing code the current user has generated, newest first.', - responses: { - 200: { - content: { - 'application/json': { - schema: Result( - z.array(PairingCode.Info).meta({ - description: 'All pairing codes for the current user', - example: [Examples.PairingCode] - }) - ) - } - }, - description: 'Pairing codes' - }, - 401: ErrorResponses[401], - 429: ErrorResponses[429] - } - }), - async (c) => { - const rows = await PairingCode.listByUser(Actor.userID); - return c.json({ data: rows.map((row) => PairingCode.serialize(row)) }); - } - ) - .post( - '/', - notPublic, - describeRoute({ - tags: ['PairingCode'], - summary: 'Generate a pairing code', - description: - 'Create a short-lived, single-use code that enrols another SSH key onto the current user.', - responses: { - 200: { - content: { - 'application/json': { - schema: Result( - z.object({ - code: z.string().meta({ example: Examples.PairingCode.code }), - expiresInMinutes: z.number() - }) - ) - } - }, - description: 'A freshly generated pairing code' - }, - 401: ErrorResponses[401], - 429: ErrorResponses[429] - } - }), - validator( - 'json', - z.object({ - ttlMinutes: z.number().int().min(1).max(60).default(10).meta({ - description: 'How long the code stays valid. Short by design.' - }) - }) - ), - async (c) => { - const { ttlMinutes } = c.req.valid('json'); - const code = await PairingCode.create({ - id: Identifier.ascending('pairingCode'), - targetUserId: Actor.userID, - ttlMinutes - }); - - return c.json({ data: { code, expiresInMinutes: ttlMinutes } }); - } - ) - .post( - '/claim', - adminOnly, - describeRoute({ - tags: ['PairingCode'], - summary: 'Claim a pairing code for an SSH key', - description: - 'Redeem a code and bind the supplied SSH fingerprint to the user who generated it. Admin only: the calling device has no identity yet, which is the entire point.', - responses: { - 200: { - content: { - 'application/json': { - schema: Result( - z.object({ - userId: z.string().meta({ example: Examples.PairingCode.targetUserId }) - }) - ) - } - }, - description: 'The fingerprint now belongs to this user' - }, - 400: ErrorResponses[400], - 403: ErrorResponses[403], - 404: ErrorResponses[404] - } - }), - validator( - 'json', - z.object({ - code: z.string().min(1).meta({ example: Examples.PairingCode.code }), - fingerprint: z.string().min(1).meta({ - description: 'SSH public key fingerprint of the device being enrolled' - }) - }) - ), - async (c) => { - const { code, fingerprint } = c.req.valid('json'); - - // Refuse before claiming: a code is single-use, so burning one on - // a device that cannot be enrolled would strand the user. - const existing = await Fingerprint.findByFingerprint(fingerprint); - - const claimed = await PairingCode.claim({ code, fingerprint }); - if (!claimed) { - throw new VisibleError( - 'not_found', - ErrorCodes.NotFound.RESOURCE_NOT_FOUND, - 'That pairing code is unknown, already used, or expired' - ); - } - - if (existing && existing.userId !== claimed.targetUserId) { - // Handing a device between accounts is a different operation - // with its own consequences for anything already linked to it; - // `Steam.resolveSshIdentity` refuses the same case. - throw new VisibleError( - 'forbidden', - ErrorCodes.Permission.FORBIDDEN, - 'That SSH key is already enrolled to another user' - ); - } - - if (existing) { - await Fingerprint.touchLastSeen(existing.id); - } else { - await Fingerprint.create({ - id: Identifier.ascending('userFingerprint'), - userId: claimed.targetUserId, - fingerprint, - name: null - }); - } - - return c.json({ data: { userId: claimed.targetUserId } }); - } - ); -} diff --git a/apps/api/app/routes/steam.ts b/apps/api/app/routes/steam.ts index 2dbb7626..d037da3e 100644 --- a/apps/api/app/routes/steam.ts +++ b/apps/api/app/routes/steam.ts @@ -79,7 +79,7 @@ export namespace SteamApi { describeRoute({ tags: ['Steam'], summary: 'Link a Steam account', - description: 'Link a Steam account to a user (admin) or yourself (user)', + description: 'Link a Steam account to the calling user.', responses: { 200: { content: { @@ -113,13 +113,6 @@ export namespace SteamApi { description: 'Steam ID to link', example: '76561197960287930' }), - userId: z - .string() - .optional() - .meta({ - description: 'User ID to link to (admin only; omitted when linking your own account)', - example: Examples.Id('user') - }), profile: z .record(z.string(), z.unknown()) .optional() @@ -131,20 +124,14 @@ export namespace SteamApi { ), async (c) => { const body = c.req.valid('json'); - const actor = Actor.use(); - - if (body.userId && actor.type !== 'admin') { - throw new VisibleError( - 'forbidden', - ErrorCodes.Permission.INSUFFICIENT_PERMISSIONS, - 'Only admin can link a Steam account for another user' - ); - } + // Linking is always for the caller. It once accepted a `userId`, + // which meant one credential could attach a Steam account to any + // user \u2014 and a linked account is how a library is reached. const linkedAccountID = await Steam.link({ steamId: body.steamId, profile: body.profile, - userId: body.userId + userId: Actor.userID }); return c.json({ data: { linkedAccountId: linkedAccountID, steamId: body.steamId } diff --git a/apps/api/app/routes/waitlist.ts b/apps/api/app/routes/waitlist.ts index b4b170ce..f13d6e28 100644 --- a/apps/api/app/routes/waitlist.ts +++ b/apps/api/app/routes/waitlist.ts @@ -4,83 +4,58 @@ import { Hono } from 'hono'; import { describeRoute } from 'hono-openapi'; import { z } from 'zod'; -import { adminOnly, ErrorResponses, Result, validator } from '../utils'; +import { ErrorResponses, Result, validator } from '../utils'; /** * Public signups for not-yet-launched features (the machines waitlist). * * Deliberately unauthenticated: a visitor without an account should be able - * to leave an email. The list itself is admin-only so a scraper cannot mine - * every address out of the response. + * to leave an email. There is no route that reads the list back — every + * address on it belongs to someone who has not agreed to anything yet, so it + * is answered from the database by whoever is sending the announcement rather + * than exposed as a response a scraper could mine. */ export namespace WaitlistApi { - export const route = new Hono() - .post( - '/', - describeRoute({ - tags: ['Waitlist'], - summary: 'Join the waitlist', - description: 'Leave an email to be notified when a feature launches. Public.', - responses: { - 201: { - content: { - 'application/json': { - schema: Result( - Waitlist.Info.meta({ - description: 'The waitlist entry (the existing one if already joined)', - example: Examples.WaitlistEntry - }) - ) - } - }, - description: 'Joined the waitlist' + export const route = new Hono().post( + '/', + describeRoute({ + tags: ['Waitlist'], + summary: 'Join the waitlist', + description: 'Leave an email to be notified when a feature launches. Public.', + responses: { + 201: { + content: { + 'application/json': { + schema: Result( + Waitlist.Info.meta({ + description: 'The waitlist entry (the existing one if already joined)', + example: Examples.WaitlistEntry + }) + ) + } }, - 400: ErrorResponses[400] - } - }), - validator( - 'json', - z.object({ - email: z.email().meta({ - description: 'The email to notify', - example: Examples.WaitlistEntry.email - }), - source: z.string().default('machines').meta({ - description: 'What the signup is for', - example: Examples.WaitlistEntry.source - }) - }) - ), - async (c) => { - const { email, source } = c.req.valid('json'); - const entry = await Waitlist.join({ email, source }); - return c.json({ data: entry }, 201); + description: 'Joined the waitlist' + }, + 400: ErrorResponses[400] } - ) - .get( - '/', - adminOnly, - describeRoute({ - tags: ['Waitlist'], - summary: 'List waitlist entries', - description: 'Every email currently on the waitlist. Admin only.', - responses: { - 200: { - content: { - 'application/json': { - schema: Result( - z.array(Waitlist.Info).meta({ - description: 'All waitlist entries', - example: [Examples.WaitlistEntry] - }) - ) - } - }, - description: 'Waitlist entries' - }, - 403: ErrorResponses[403] - } - }), - async (c) => c.json({ data: await Waitlist.list() }) - ); + }), + validator( + 'json', + z.object({ + email: z.email().meta({ + description: 'The email to notify', + example: Examples.WaitlistEntry.email + }), + source: z.string().default('machines').meta({ + description: 'What the signup is for', + example: Examples.WaitlistEntry.source + }) + }) + ), + async (c) => { + const { email, source } = c.req.valid('json'); + const entry = await Waitlist.join({ email, source }); + return c.json({ data: entry }, 201); + } + ); } diff --git a/apps/api/app/utils/auth.ts b/apps/api/app/utils/auth.ts index 5bc3152d..3660e84a 100644 --- a/apps/api/app/utils/auth.ts +++ b/apps/api/app/utils/auth.ts @@ -1 +1 @@ -export { auth, notPublic, adminOnly, machineOnly, machineOrAdmin } from '../middleware/auth.js'; +export { auth, notPublic, machineOnly } from '../middleware/auth.js'; diff --git a/apps/api/app/utils/enrolment.ts b/apps/api/app/utils/enrolment.ts new file mode 100644 index 00000000..8162813f --- /dev/null +++ b/apps/api/app/utils/enrolment.ts @@ -0,0 +1,32 @@ +import { Actor } from '@nestri/core/actor'; +import { ErrorCodes, VisibleError } from '@nestri/core/error'; +import { Enrolment } from '@nestri/core/steam/enrolment'; + +/** + * The user a host is allowed to speak about, or a refusal. + * + * One box carries several people's Steam sign-ins, so which user a batch + * belongs to has to be said rather than inferred from the credentials — and + * then checked, because a body field naming a user is otherwise a way to write + * into any library. The enrolment record is what it is checked against: + * holding a refresh token is what lets a host enumerate those games at all, so + * a host without one is reporting something it could not have observed. + * + * Shared by the two sync routes deliberately. The check is the whole boundary + * between "a host reporting what it can see" and "a host writing wherever it + * likes", and two copies of it are two things to keep in agreement. + */ +export async function enrolledUser(userId: string): Promise { + const enrolment = await Enrolment.findByMachineAndUser({ + machineId: Actor.machineID, + userId + }); + if (!enrolment) { + throw new VisibleError( + 'forbidden', + ErrorCodes.Permission.FORBIDDEN, + 'This host holds no Steam sign-in for that user' + ); + } + return userId; +} diff --git a/apps/api/app/utils/index.ts b/apps/api/app/utils/index.ts index 8d4af21c..9b05dfbe 100644 --- a/apps/api/app/utils/index.ts +++ b/apps/api/app/utils/index.ts @@ -1,4 +1,5 @@ export * from './auth'; +export * from './enrolment'; export * from './error'; export * from './result'; export * from './validator'; diff --git a/apps/api/test/enrolment.test.ts b/apps/api/test/enrolment.test.ts index a201f68b..4e228aa7 100644 --- a/apps/api/test/enrolment.test.ts +++ b/apps/api/test/enrolment.test.ts @@ -1,18 +1,37 @@ import { afterAll, describe, expect, test } from 'bun:test'; +import { AccessToken } from '@nestri/core/access-token/index'; import { Fixtures } from '@nestri/core/db/fixtures'; import { testDb } from '@nestri/core/db/test'; import { Identifier } from '@nestri/core/id'; import { Machine } from '@nestri/core/machine/index'; import { app } from '../app/index'; -import { TEST_ADMIN_SECRET } from './setup'; import './setup'; const sql = testDb(); const createdUserIds: string[] = []; +/** + * A signed-in person, as a personal access token. + * + * The tests below use it to prove `machineOnly` refuses a human: it needs a + * caller who is authenticated and is not a host, and a user session is the + * only kind there is. + */ +async function signedInHeaders(label: string): Promise> { + const owner = await Fixtures.owner(label); + createdUserIds.push(owner.userId); + const pat = await AccessToken.create({ + id: Identifier.ascending('accessToken'), + ownerUserId: owner.userId, + teamId: null, + name: label + }); + return { authorization: `Bearer ${pat.token}` }; +} + /** A Steam ID is 17 digits; these are distinct and obviously not real. */ function steamId(n: number) { return `765611980000${String(n).padStart(5, '0')}`; @@ -185,7 +204,10 @@ describe('POST /machine/enrolment', () => { test('machine credentials are required', async () => { const res = await app.request('/machine/enrolment', { method: 'POST', - headers: { 'x-nestri-admin-token': TEST_ADMIN_SECRET, 'content-type': 'application/json' }, + headers: { + ...(await signedInHeaders('enrol-nomachine')), + 'content-type': 'application/json' + }, body: JSON.stringify({ userId: Identifier.ascending('user'), steamId: steamId(7) }) }); expect(res.status).toBe(403); @@ -240,7 +262,10 @@ describe('POST /machine/enrolment/stale', () => { test('machine credentials are required', async () => { const res = await app.request('/machine/enrolment/stale', { method: 'POST', - headers: { 'x-nestri-admin-token': TEST_ADMIN_SECRET, 'content-type': 'application/json' }, + headers: { + ...(await signedInHeaders('stale-nomachine')), + 'content-type': 'application/json' + }, body: JSON.stringify({ userId: Identifier.ascending('user') }) }); expect(res.status).toBe(403); @@ -272,7 +297,7 @@ describe('GET /machine/enrolment', () => { test('machine credentials are required', async () => { const res = await app.request('/machine/enrolment', { - headers: { 'x-nestri-admin-token': TEST_ADMIN_SECRET } + headers: await signedInHeaders('list-nomachine') }); expect(res.status).toBe(403); }); diff --git a/apps/api/test/heartbeat.test.ts b/apps/api/test/heartbeat.test.ts index 0c85e1b0..396a2f6e 100644 --- a/apps/api/test/heartbeat.test.ts +++ b/apps/api/test/heartbeat.test.ts @@ -1,5 +1,6 @@ import { afterAll, describe, expect, test } from 'bun:test'; +import { AccessToken } from '@nestri/core/access-token/index'; import { Fixtures } from '@nestri/core/db/fixtures'; import { testDb } from '@nestri/core/db/test'; import { Identifier } from '@nestri/core/id'; @@ -12,6 +13,25 @@ const sql = testDb(); const createdUserIds: string[] = []; +/** + * A signed-in person, as a personal access token. + * + * The tests below use it to prove `machineOnly` refuses a human: it needs a + * caller who is authenticated and is not a host, and a user session is the + * only kind there is. + */ +async function signedInHeaders(label: string): Promise> { + const owner = await Fixtures.owner(label); + createdUserIds.push(owner.userId); + const pat = await AccessToken.create({ + id: Identifier.ascending('accessToken'), + ownerUserId: owner.userId, + teamId: null, + name: label + }); + return { authorization: `Bearer ${pat.token}` }; +} + /** * A registered host, with the secret kept — which registration returns exactly * once, so a test that needs to authenticate as a machine has to hold onto it @@ -188,7 +208,7 @@ describe('POST /machine/heartbeat', () => { // driven by whoever owns it. const res = await app.request('/machine/heartbeat', { method: 'POST', - headers: { 'x-nestri-admin-token': 'test-admin-secret-42' } + headers: await signedInHeaders('beat-nomachine') }); expect(res.status).toBe(403); }); diff --git a/apps/api/test/routes.test.ts b/apps/api/test/routes.test.ts index faa43831..e23cb819 100644 --- a/apps/api/test/routes.test.ts +++ b/apps/api/test/routes.test.ts @@ -1,11 +1,58 @@ import { describe, expect, test } from 'bun:test'; +import { AccessToken } from '@nestri/core/access-token/index'; +import { Fixtures } from '@nestri/core/db/fixtures'; +import { Identifier } from '@nestri/core/id'; +import { Machine } from '@nestri/core/machine/index'; + import { app } from '../app/index'; -import { TEST_ADMIN_SECRET } from './setup'; import './setup'; -function adminHeaders(): Record { - return { 'x-nestri-admin-token': TEST_ADMIN_SECRET }; +/** + * A signed-in person and a registered host. + * + * Between them they are every credential the API accepts, so the validation + * tests below have to pick one. There is no longer a credential that stands + * for "some authenticated caller" in general — reaching a handler means being + * a specific someone, which is the property these fixtures preserve. + * + * Built once, lazily, because the settings they need are installed by a + * `beforeEach` that has not run when a `beforeAll` would. + */ +let built: Promise<{ user: Record; host: Record }> | undefined; + +function credentials() { + built ??= (async () => { + const owner = await Fixtures.owner('routes'); + const pat = await AccessToken.create({ + id: Identifier.ascending('accessToken'), + ownerUserId: owner.userId, + teamId: null, + name: 'routes' + }); + const registered = await Machine.register({ + id: Identifier.ascending('machine'), + ownerUserId: owner.userId, + teamId: owner.teamId, + label: 'routes' + }); + return { + user: { authorization: `Bearer ${pat.token}` }, + host: { + 'x-nestri-machine-id': registered.id, + 'x-nestri-machine-secret': registered.secret + } + }; + })(); + return built; +} + +async function userHeaders(): Promise> { + return (await credentials()).user; +} + +async function hostHeaders(): Promise> { + return (await credentials()).host; } describe('Index', () => { @@ -32,22 +79,6 @@ describe('Auth middleware', () => { expect(res.status).toBe(200); }); - test('admin token gains access to protected routes', async () => { - const res = await app.request('/waitlist', { - headers: adminHeaders() - }); - expect(res.status).toBe(200); - }); - - test('wrong admin token is treated as public → 401', async () => { - const res = await app.request('/library', { - headers: { 'x-nestri-admin-token': 'wrong-secret' } - }); - expect(res.status).toBe(401); - const body = (await res.json()) as any; - expect(body.type).toBe('authentication'); - }); - test('a bearer token that cannot be verified is unauthenticated, not a server error', async () => { // A token nobody can verify makes the *caller* unauthenticated; it does // not make the request a server fault. `verify` reports a malformed or @@ -61,13 +92,12 @@ describe('Auth middleware', () => { expect(body.type).toBe('authentication'); }); - test('missing authorization on admin-only route returns 401', async () => { + test('missing authorization on a protected route returns 401', async () => { const res = await app.request('/games/sync', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({}) }); - // notPublic runs before adminOnly → 401 expect(res.status).toBe(401); const body = (await res.json()) as any; expect(body.code).toBe('unauthorized'); @@ -79,7 +109,7 @@ describe('Validation', () => { const res = await app.request('/games/sync', { method: 'POST', headers: { - ...adminHeaders(), + ...(await hostHeaders()), 'content-type': 'application/json' }, body: '{not-json' @@ -93,7 +123,7 @@ describe('Validation', () => { const res = await app.request('/games/download-state', { method: 'POST', headers: { - ...adminHeaders(), + ...(await hostHeaders()), 'content-type': 'application/json' }, body: JSON.stringify({ status: 'downloading' }) @@ -107,11 +137,10 @@ describe('Validation', () => { const res = await app.request('/games/download-state', { method: 'POST', headers: { - ...adminHeaders(), + ...(await hostHeaders()), 'content-type': 'application/json' }, body: JSON.stringify({ - hostId: 'hst_test', steamAppId: 440, status: 'bogus_status' }) @@ -123,7 +152,7 @@ describe('Validation', () => { test('non-existent game returns 404', async () => { const res = await app.request('/games/gam_nonexistent', { - headers: adminHeaders() + headers: await userHeaders() }); expect(res.status).toBe(404); const body = (await res.json()) as any; @@ -133,7 +162,7 @@ describe('Validation', () => { test('missing content-type header returns 400', async () => { const res = await app.request('/games/sync', { method: 'POST', - headers: adminHeaders(), + headers: await hostHeaders(), body: JSON.stringify({}) }); expect(res.status).toBe(400); @@ -143,7 +172,7 @@ describe('Validation', () => { describe('Error response shape', () => { test('404 on unknown game has standard error shape', async () => { const res = await app.request('/games/gam_nonexistent', { - headers: adminHeaders() + headers: await userHeaders() }); expect(res.status).toBe(404); const body = (await res.json()) as any; @@ -154,7 +183,7 @@ describe('Error response shape', () => { test('429 error responses have standard shape', async () => { const res = await app.request('/games/gam_nonexistent', { - headers: adminHeaders() + headers: await userHeaders() }); expect(res.status).toBe(404); const body = (await res.json()) as any; @@ -189,7 +218,6 @@ describe('OpenAPI doc', () => { expect(paths).toContain('/user'); expect(paths).toContain('/user/email'); expect(paths).toContain('/user/devices'); - expect(paths).toContain('/pairing-code'); expect(paths).toContain('/waitlist'); }); @@ -223,15 +251,14 @@ describe('CORS', () => { }); describe('Download state route', () => { - test('POST /games/download-state requires hostId and steamAppId', async () => { + test('POST /games/download-state requires steamAppId', async () => { const res = await app.request('/games/download-state', { method: 'POST', headers: { - ...adminHeaders(), + ...(await hostHeaders()), 'content-type': 'application/json' }, body: JSON.stringify({ - hostId: 'hst_test', status: 'downloading' // missing steamAppId }) @@ -239,6 +266,20 @@ describe('Download state route', () => { expect(res.status).toBe(400); }); + test('a host cannot name the host it is reporting for', async () => { + // Which host this is comes from the credentials. The body once carried + // it, so a caller could write download state under any box's id. + const res = await app.request('/games/download-state', { + method: 'POST', + headers: { + ...(await hostHeaders()), + 'content-type': 'application/json' + }, + body: JSON.stringify({ hostId: 'mch_someoneelse', steamAppId: 440, status: 'ready' }) + }); + expect(res.status).toBe(400); + }); + test('POST /games/download-state validates status enum', async () => { const valid = ['pending', 'verifying', 'downloading', 'ready', 'failed'] as const; for (const status of valid) { @@ -246,11 +287,10 @@ describe('Download state route', () => { const res = await app.request('/games/download-state', { method: 'POST', headers: { - ...adminHeaders(), + ...(await hostHeaders()), 'content-type': 'application/json' }, body: JSON.stringify({ - hostId: 'hst_test', steamAppId: 440, status }) @@ -264,26 +304,11 @@ describe('Download state route', () => { const res = await app.request('/games/download-state', { method: 'POST', headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ hostId: 'mch_test', steamAppId: 440, status: 'ready' }) - }); - // The route group's `notPublic` runs first, so this is 401 rather than - // the 403 `machineOrAdmin` would give an authenticated non-host. - expect(res.status).toBe(401); - }); - - test('an admin caller must say which host it is reporting for', async () => { - // hostId is optional in the schema now because a machine supplies it - // from its own identity. Admin has no identity to take it from, so - // leaving it out has to fail rather than write under an empty host. - const res = await app.request('/games/download-state', { - method: 'POST', - headers: { ...adminHeaders(), 'content-type': 'application/json' }, body: JSON.stringify({ steamAppId: 440, status: 'ready' }) }); - expect(res.status).toBe(400); - const body = (await res.json()) as any; - expect(body.code).toBe('missing_required_field'); - expect(body.param).toBe('hostId'); + // The route group's `notPublic` runs first, so this is 401 rather than + // the 403 `machineOnly` would give an authenticated non-host. + expect(res.status).toBe(401); }); }); @@ -297,24 +322,10 @@ describe('Access tokens', () => { expect(res.status).toBe(401); }); - test('the admin token cannot mint a token for anyone', async () => { - // This is the boundary that makes admin safe to hand out for tooling: - // it reads and writes API data but cannot *become* a user. Minting a - // PAT on someone's behalf would erase exactly that. - const res = await app.request('/access-token', { - method: 'POST', - headers: { ...adminHeaders(), 'content-type': 'application/json' }, - body: JSON.stringify({ name: 'living-room-box' }) - }); - expect(res.status).toBe(403); - const body = (await res.json()) as any; - expect(body.message).toContain('user session'); - }); - test('a token needs a name', async () => { const res = await app.request('/access-token', { method: 'POST', - headers: { ...adminHeaders(), 'content-type': 'application/json' }, + headers: { ...(await userHeaders()), 'content-type': 'application/json' }, body: JSON.stringify({ name: '' }) }); expect(res.status).toBe(400); @@ -323,7 +334,7 @@ describe('Access tokens', () => { test('expiry is capped at a year', async () => { const res = await app.request('/access-token', { method: 'POST', - headers: { ...adminHeaders(), 'content-type': 'application/json' }, + headers: { ...(await userHeaders()), 'content-type': 'application/json' }, body: JSON.stringify({ name: 'box', expiresInDays: 4000 }) }); expect(res.status).toBe(400); @@ -337,12 +348,10 @@ describe('Access tokens', () => { // omitting the field means "take the default", which is not the same. const res = await app.request('/access-token', { method: 'POST', - headers: { ...adminHeaders(), 'content-type': 'application/json' }, + headers: { ...(await userHeaders()), 'content-type': 'application/json' }, body: JSON.stringify({ name: 'box', teamId: null }) }); - // Admin is refused at the handler, but only after validation — so a - // 403 here proves null passed the schema rather than being rejected. - expect(res.status).toBe(403); + expect(res.status).toBe(200); }); test('revoking someone else’s token requires authentication', async () => { @@ -372,17 +381,17 @@ describe('Box access', () => { expect(res.status).toBe(401); }); - test('the admin token cannot rescope a machine', async () => { - // Rescoping is an owner action and the query is scoped to a user id; - // admin has none, so it must be refused rather than 500 later. + test('rescoping onto a team you do not belong to is refused', async () => { + // Naming a team is how hardware would otherwise be parked in somebody + // else's, so membership is checked rather than taken from the body. const res = await app.request('/machine/mch_whatever', { method: 'PATCH', - headers: { ...adminHeaders(), 'content-type': 'application/json' }, + headers: { ...(await userHeaders()), 'content-type': 'application/json' }, body: JSON.stringify({ teamId: 'tem_whatever' }) }); expect(res.status).toBe(403); const body = (await res.json()) as any; - expect(body.message).toContain('user session'); + expect(body.message).toContain('not a member'); }); test('teamId is required on the body, and null is no longer a value', async () => { @@ -392,32 +401,31 @@ describe('Box access', () => { // error rather than a meaning. const missing = await app.request('/machine/mch_whatever', { method: 'PATCH', - headers: { ...adminHeaders(), 'content-type': 'application/json' }, + headers: { ...(await userHeaders()), 'content-type': 'application/json' }, body: JSON.stringify({}) }); expect(missing.status).toBe(400); const explicitNull = await app.request('/machine/mch_whatever', { method: 'PATCH', - headers: { ...adminHeaders(), 'content-type': 'application/json' }, + headers: { ...(await userHeaders()), 'content-type': 'application/json' }, body: JSON.stringify({ teamId: null }) }); expect(explicitNull.status).toBe(400); const named = await app.request('/machine/mch_whatever', { method: 'PATCH', - headers: { ...adminHeaders(), 'content-type': 'application/json' }, + headers: { ...(await userHeaders()), 'content-type': 'application/json' }, body: JSON.stringify({ teamId: 'tem_whatever' }) }); - // Past validation, refused at the handler for being admin. - expect(named.status).toBe(403); + expect([403, 404]).toContain(named.status); }); test('entitlement requires machine credentials, not a user session', async () => { // The machine is taken from its credentials, never the query, so a box // cannot ask about another box. const res = await app.request('/machine/entitlement?userId=usr_x', { - headers: adminHeaders() + headers: await userHeaders() }); expect(res.status).toBe(403); const body = (await res.json()) as any; @@ -441,24 +449,10 @@ describe('Machine registration', () => { expect(res.status).toBe(401); }); - test('the admin token cannot register a machine', async () => { - // Registering is an act of ownership and the resulting row references a - // user. Admin is authenticated but owns nothing, so it must be refused - // here rather than fail later on a null owner. - const res = await app.request('/machine/register', { - method: 'POST', - headers: { ...adminHeaders(), 'content-type': 'application/json' }, - body: JSON.stringify({ label: 'living-room-box' }) - }); - expect(res.status).toBe(403); - const body = (await res.json()) as any; - expect(body.message).toContain('user session'); - }); - test('registering a machine requires a label', async () => { const res = await app.request('/machine/register', { method: 'POST', - headers: { ...adminHeaders(), 'content-type': 'application/json' }, + headers: { ...(await userHeaders()), 'content-type': 'application/json' }, body: JSON.stringify({ label: '' }) }); expect(res.status).toBe(400); @@ -467,7 +461,7 @@ describe('Machine registration', () => { }); test('describing yourself requires machine credentials', async () => { - const res = await app.request('/machine/me', { headers: adminHeaders() }); + const res = await app.request('/machine/me', { headers: await userHeaders() }); expect(res.status).toBe(403); const body = (await res.json()) as any; expect(body.message).toContain('Machine credentials'); @@ -484,7 +478,7 @@ describe('Steam routes', () => { const res = await app.request('/steam/link', { method: 'POST', headers: { - ...adminHeaders(), + ...(await userHeaders()), 'content-type': 'application/json' }, body: JSON.stringify({}) @@ -507,67 +501,6 @@ describe('Library routes', () => { }); }); -describe('Pairing code routes', () => { - test('POST /pairing-code requires auth', async () => { - // Generating a code says "this key is also me", so it can only be done - // from a session that already is that user. - const res = await app.request('/pairing-code', { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({}) - }); - expect(res.status).toBe(401); - }); - - test('POST /pairing-code/claim rejects an unauthenticated caller', async () => { - // Claiming is done for a device with no identity yet, so it carries the - // admin token rather than a user session. Without it, no. - const res = await app.request('/pairing-code/claim', { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ code: 'NESSH-7F2Q', fingerprint: 'aa:bb' }) - }); - expect([401, 403]).toContain(res.status); - }); - - test('POST /pairing-code/claim requires both a code and a fingerprint', async () => { - for (const body of [{}, { code: 'NESSH-7F2Q' }, { fingerprint: 'aa:bb' }]) { - // eslint-disable-next-line - const res = await app.request('/pairing-code/claim', { - method: 'POST', - headers: { ...adminHeaders(), 'content-type': 'application/json' }, - body: JSON.stringify(body) - }); - expect(res.status).toBe(400); - } - }); - - test('POST /pairing-code/claim rejects an empty code', async () => { - // An empty string must not be treated as "any code". - const res = await app.request('/pairing-code/claim', { - method: 'POST', - headers: { ...adminHeaders(), 'content-type': 'application/json' }, - body: JSON.stringify({ code: '', fingerprint: 'aa:bb' }) - }); - expect(res.status).toBe(400); - }); - - test('POST /pairing-code caps how long a code stays valid', async () => { - // Short-lived by design; a long-lived code is a shared password. - const res = await app.request('/pairing-code', { - method: 'POST', - headers: { ...adminHeaders(), 'content-type': 'application/json' }, - body: JSON.stringify({ ttlMinutes: 60 * 24 }) - }); - expect(res.status).toBe(400); - }); - - test('GET /pairing-code requires auth', async () => { - const res = await app.request('/pairing-code'); - expect(res.status).toBe(401); - }); -}); - describe('Email routes', () => { test('POST /user/email requires auth', async () => { const res = await app.request('/user/email', { @@ -581,7 +514,7 @@ describe('Email routes', () => { test('POST /user/email rejects a malformed address', async () => { const res = await app.request('/user/email', { method: 'POST', - headers: { ...adminHeaders(), 'content-type': 'application/json' }, + headers: { ...(await userHeaders()), 'content-type': 'application/json' }, body: JSON.stringify({ email: 'not-an-email' }) }); expect(res.status).toBe(400); @@ -606,7 +539,7 @@ describe('Email routes', () => { test('POST /user/email/verify requires a 6-digit code', async () => { const res = await app.request('/user/email/verify', { method: 'POST', - headers: { ...adminHeaders(), 'content-type': 'application/json' }, + headers: { ...(await userHeaders()), 'content-type': 'application/json' }, body: JSON.stringify({ code: '12' }) }); expect(res.status).toBe(400); @@ -662,9 +595,4 @@ describe('Waitlist routes', () => { }); expect(res.status).toBe(400); }); - - test('GET /waitlist is admin-only', async () => { - const res = await app.request('/waitlist'); - expect(res.status).toBe(403); - }); }); diff --git a/apps/api/test/setup.ts b/apps/api/test/setup.ts index d2499cc0..aa0e24f6 100644 --- a/apps/api/test/setup.ts +++ b/apps/api/test/setup.ts @@ -2,15 +2,13 @@ import { beforeEach } from 'bun:test'; import { Env } from '@nestri/core/env'; -const TEST_ADMIN_SECRET = 'test-admin-secret-42'; const TEST_FRONTEND_URL = 'http://localhost:5173'; beforeEach(() => { Env.init({ NODE_ENV: 'test', - ADMIN_SHARED_SECRET: TEST_ADMIN_SECRET, FRONTEND_URL: TEST_FRONTEND_URL }); }); -export { TEST_ADMIN_SECRET, TEST_FRONTEND_URL }; +export { TEST_FRONTEND_URL }; diff --git a/docker-compose.yml b/docker-compose.yml index bec8c96f..df81c1be 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,8 +20,7 @@ # Every one is read from `.env`, and compose refuses to start naming the # variable it wanted rather than falling back to something. A default is worth # less than it looks: the deployment that never set the variable is exactly the -# one where the default is a publicly known value, and `ADMIN_SHARED_SECRET` -# below bypasses authentication entirely. +# one where the default is a publicly known value. # # Migrations are not run for you — `bun run db:migrate` against DATABASE_URL, # because a container that migrates on boot races with the second copy of @@ -104,9 +103,6 @@ services: # here. `AUTH_INTERNAL_URL` is how this container actually gets there. AUTH_ISSUER_URL: ${AUTH_ISSUER_URL:?set AUTH_ISSUER_URL in .env} AUTH_INTERNAL_URL: http://auth:1337 - # A shared secret that turns any request carrying it into an operator. - # Required, with no default, for that reason. - ADMIN_SHARED_SECRET: ${ADMIN_SHARED_SECRET:?set ADMIN_SHARED_SECRET in .env to a value you generated} # Every interface *inside the container*, which is what makes the # loopback publication below reachable. The processes default to # 127.0.0.1 because a bare process on a host has no such wrapper and a diff --git a/docs/deploy.md b/docs/deploy.md index 6d772c6b..f2556fc5 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -89,15 +89,11 @@ cd apps/auth bunx wrangler secret put EMAIL_SEND_URL --env production bunx wrangler secret put EMAIL_API_KEY --env production bunx wrangler secret put EMAIL_FROM --env production - -cd ../api -bunx wrangler secret put ADMIN_SHARED_SECRET --env production ``` -`ADMIN_SHARED_SECRET` turns any request carrying it into an operator, so -generate it rather than choosing it — `openssl rand -hex 32` — and never give -it a default anywhere. What it is for is listed in -[`apps/api/README.md`](../apps/api/README.md). +The API has no secrets of its own to put here: every caller it accepts proves +who it is — a session token from the issuer, a personal access token, or a +registered host's own credentials — so there is nothing shared to leak. The issuer refuses to send a sign-in code with its mail settings half configured or absent, rather than falling back to printing codes to the log — @@ -139,7 +135,6 @@ Both images are stateless and hold no configuration. What they need: | `AUTH_INTERNAL_URL` | — | only if that URL is unroutable from here | | `EMAIL_SEND_URL` `EMAIL_API_KEY` `EMAIL_FROM` | all three, or none | — | | `EMAIL_DEV_LOG` | `true` prints codes instead of sending | — | -| `ADMIN_SHARED_SECRET` | — | required; operator access | | `PORT` | default `1337` | default `3000` | [`docker-compose.yml`](../docker-compose.yml) at the root wires all of it diff --git a/packages/core/CLAUDE.md b/packages/core/CLAUDE.md index a7ab9612..eab2357e 100644 --- a/packages/core/CLAUDE.md +++ b/packages/core/CLAUDE.md @@ -14,14 +14,14 @@ src// ### Sub-modules nested under parents -| File | Namespace | Why | -| ----------------------- | --------------- | ------------------------------------- | -| `user/linked-account.*` | `LinkedAccount` | A user's OAuth/gaming identities | -| `user/fingerprint.*` | `Fingerprint` | SSH key fingerprints | -| `game/download.*` | `GameDownload` | Per-host game depot downloads | -| `user/library.*` | `Library` | User's owned games with playtime | -| `team/member.*` | `Member` | Team membership with role | -| `game/depot.*` | `Depot` | Platform-specific game content depots | +| File | Namespace | Why | +| ----------------------- | --------------- | --------------------------------------- | +| `user/linked-account.*` | `LinkedAccount` | A user's OAuth/gaming identities | +| `user/fingerprint.*` | `Fingerprint` | SSH key fingerprints | +| `game/download.*` | `GameDownload` | Per-host game depot downloads | +| `user/library.*` | `Library` | User's owned games with playtime | +| `team/member.*` | `Member` | Team membership with role | +| `game/depot.*` | `Depot` | Platform-specific game content depots | | `steam/enrolment.*` | `Enrolment` | Which host holds a Steam token for whom | Existing top-level modules: `user/`, `team/`, `game/`, `pairing-code/`, `steam/`, `auth/`, `db/`. @@ -573,25 +573,19 @@ A Cloudflare Worker using `@nestri/auth` (OpenAuth). Entry point is the `success 4. If no memberships, calls `Team.createPersonal({ displayName })` 5. Issues JWT via `context.subject('user', { userID, linkedAccountID })` -### Admin Auth via Shared Secret - -Server-to-server calls can authenticate as an `admin` actor by setting the `x-nestri-admin-token` header to the value of `ADMIN_SHARED_SECRET`. This bypasses JWT auth entirely and grants a system-level actor with no user scope — useful for operations like adding games to the DB, syncing data, or other admin tasks. - -Configure `ADMIN_SHARED_SECRET` in `.env`. It has no default anywhere — a known value here is an authentication bypass, so nothing falls back to one. - ### Auth Middleware (`apps/api/app/middleware/auth.ts`) Hono middleware that runs on every API request: -1. Checks `x-nestri-admin-token` header — if it matches `Env.get().ADMIN_SHARED_SECRET`, sets actor to `admin` and proceeds immediately -2. Otherwise, reads `Authorization: Bearer ` header +1. Checks `x-nestri-machine-id` / `x-nestri-machine-secret` — a registered host authenticates as itself, and bad credentials fall through to `public` rather than erroring +2. Otherwise, reads `Authorization: Bearer ` header — a personal access token is resolved from the database, anything else is verified as a JWT 3. Verifies via `client.verify(subjects, token)` from `@nestri/auth/client` 4. If valid `user` subject: - Checks `x-nestri-team` header for team-scoped access - If team header present, verifies membership via `Member.findByTeamAndUser` - Sets actor to `member` (with role) or `user` type 5. If no token/invalid: sets actor to `public` type -6. Exports `notPublic` guard middleware — throws `VisibleError('authentication', UNAUTHORIZED, …)` if actor is `public`. Caught by `onError` → 401 JSON response. The `admin` actor passes this guard (it's not `public`), so admin routes can use `.use(notPublic)` like any other protected route. +6. Exports `notPublic` guard middleware — throws `VisibleError('authentication', UNAUTHORIZED, …)` if actor is `public`. Caught by `onError` → 401 JSON response. It admits machines too; what stops a host acting as its owner is `Actor.userID`, which refuses a `machine` outright. ### OpenAuth Subjects (`src/auth/subjects.ts`) diff --git a/packages/core/src/actor.ts b/packages/core/src/actor.ts index 5cba9e5d..22c28504 100644 --- a/packages/core/src/actor.ts +++ b/packages/core/src/actor.ts @@ -33,11 +33,6 @@ const System = z.object({ }) }); -const Admin = z.object({ - type: z.literal('admin'), - properties: z.object({}) -}); - /** * A registered nessh host, authenticated by its own credentials. * @@ -58,7 +53,7 @@ const Machine = z.object({ }) }); -const ActorInfo = z.discriminatedUnion('type', [Public, User, Member, System, Admin, Machine]); +const ActorInfo = z.discriminatedUnion('type', [Public, User, Member, System, Machine]); type ActorInfo = z.infer; const _context = Context.create(); diff --git a/packages/core/src/env.ts b/packages/core/src/env.ts index 28a31da9..57e4495a 100644 --- a/packages/core/src/env.ts +++ b/packages/core/src/env.ts @@ -27,10 +27,6 @@ export namespace Env { */ AUTH_INTERNAL_URL: z.string().optional(), - SSH_AUTH_KEY: z.string().optional(), - - ADMIN_SHARED_SECRET: z.string().optional(), - DATABASE_URL: z.string().optional() }); diff --git a/packages/core/src/steam/enrolment.ts b/packages/core/src/steam/enrolment.ts index f46b224e..6a8f8e8a 100644 --- a/packages/core/src/steam/enrolment.ts +++ b/packages/core/src/steam/enrolment.ts @@ -148,6 +148,37 @@ export namespace Enrolment { }); }); + /** + * The enrolment a host holds for one user, or null. + * + * This is the question "may this host speak about this user's Steam + * library?", and it is answered from the record of sign-ins rather than + * from team membership: holding a refresh token for somebody is what makes + * a host able to enumerate their games in the first place. One host carries + * several people's sign-ins, so the pair is the unit and neither half of it + * is enough on its own. + */ + export const findByMachineAndUser = fn( + Info.pick({ machineId: true, userId: true }), + async (input) => { + return Database.use(async (tx) => { + return tx + .select() + .from(SteamEnrolmentTable) + .where( + and( + eq(SteamEnrolmentTable.machineId, input.machineId), + eq(SteamEnrolmentTable.userId, input.userId) + ) + ) + .then((rows) => { + const row = rows.at(0); + return row ? serialize(row) : null; + }); + }); + } + ); + /** * Every enrolment the control plane believes this host has. * From 4a2a4412c231507c6dbbdb0723ba49c4ea3afd90 Mon Sep 17 00:00:00 2001 From: Wanjohi Date: Fri, 18 Sep 2026 23:03:08 +0300 Subject: [PATCH 2/9] style: apply the formatter across the tree The formatter had never been run over these files, so `oxfmt` on a couple of touched files rewrote two dozen others and buried the real change. Running it everywhere once makes the next diff mean something. No behaviour changes: import ordering, line joining, and reindented JSON in the generated migration snapshots. Both test suites and both typechecks give the same answers as before, including the two type errors this does not fix. --- apps/api/CLAUDE.md | 1 - apps/api/app/routes/user.ts | 6 +- packages/core/README.md | 22 +- .../core/migrations/meta/0005_snapshot.json | 3324 +++++----- .../core/migrations/meta/0006_snapshot.json | 3736 ++++++----- .../core/migrations/meta/0007_snapshot.json | 4442 ++++++------- .../core/migrations/meta/0008_snapshot.json | 4474 +++++++------ .../core/migrations/meta/0009_snapshot.json | 4518 +++++++------ .../core/migrations/meta/0010_snapshot.json | 4774 +++++++------- .../core/migrations/meta/0011_snapshot.json | 5495 ++++++++-------- .../core/migrations/meta/0012_snapshot.json | 5714 ++++++++-------- .../core/migrations/meta/0013_snapshot.json | 5756 ++++++++-------- .../core/migrations/meta/0014_snapshot.json | 5798 ++++++++--------- packages/core/migrations/meta/_journal.json | 220 +- packages/core/scripts/embed-migrations.ts | 6 +- .../core/src/auth/authorization-code.test.ts | 3 +- packages/core/src/box/placement.test.ts | 10 +- packages/core/src/db/index.ts | 5 +- packages/core/src/session/session.test.ts | 258 +- packages/core/src/team/team.test.ts | 7 +- packages/core/src/user/identity.ts | 2 +- packages/core/src/waitlist/index.ts | 4 +- 22 files changed, 23779 insertions(+), 24796 deletions(-) diff --git a/apps/api/CLAUDE.md b/apps/api/CLAUDE.md index d948074f..bb1be03c 100644 --- a/apps/api/CLAUDE.md +++ b/apps/api/CLAUDE.md @@ -281,4 +281,3 @@ Route handler │ └─ returns normally ─────► c.json({ data: … }) ``` - diff --git a/apps/api/app/routes/user.ts b/apps/api/app/routes/user.ts index d3e4a121..7ee07296 100644 --- a/apps/api/app/routes/user.ts +++ b/apps/api/app/routes/user.ts @@ -2,8 +2,8 @@ import { Actor } from '@nestri/core/actor'; import { Env } from '@nestri/core/env'; import { ErrorCodes, VisibleError } from '@nestri/core/error'; import { Examples } from '@nestri/core/examples'; -import { User } from '@nestri/core/user/index'; import { Fingerprint } from '@nestri/core/user/fingerprint'; +import { User } from '@nestri/core/user/index'; import { VERIFICATION_TTL_MINUTES, Verification } from '@nestri/core/user/verification'; import { Hono } from 'hono'; import { describeRoute } from 'hono-openapi'; @@ -159,9 +159,7 @@ export namespace UserApi { 200: { content: { 'application/json': { - schema: Result( - z.object({ verified: z.boolean() }) - ) + schema: Result(z.object({ verified: z.boolean() })) } }, description: 'Email verified' diff --git a/packages/core/README.md b/packages/core/README.md index 9646c9b4..b00b6335 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -5,16 +5,16 @@ serialization lives here. The API and auth workers are thin pass-through transla ## What it contains -| Area | Files | Purpose | -| ---- | ----- | ------- | -| **db** | `db/index.ts`, `db/types.ts`, `db/test.ts` | Drizzle + Postgres (`Database.use/transaction`), ULID column helpers | -| **users** | `user/*` | Users, linked accounts, fingerprints, library | -| **teams** | `team/*` | Teams + membership with roles (`team_member`) | -| **games** | `game/*` | Game catalog, depot content, per-host downloads | -| **steam** | `steam/index.ts` | Steam API integration & SSH identity resolution | -| **auth** | `auth/subjects.ts` | JWT subjects shared with the auth worker | -| **infra** | `env.ts`, `context.ts`, `actor.ts`, `fn.ts`, `id.ts`, `error.ts`, `examples.ts` | Environment, Actor model, zod-typed `fn()` wrappers, IDs, error types, examples | -| **migrations** | `migrations/` | Drizzle-kit SQL migrations for Postgres schema | +| Area | Files | Purpose | +| -------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | +| **db** | `db/index.ts`, `db/types.ts`, `db/test.ts` | Drizzle + Postgres (`Database.use/transaction`), ULID column helpers | +| **users** | `user/*` | Users, linked accounts, fingerprints, library | +| **teams** | `team/*` | Teams + membership with roles (`team_member`) | +| **games** | `game/*` | Game catalog, depot content, per-host downloads | +| **steam** | `steam/index.ts` | Steam API integration & SSH identity resolution | +| **auth** | `auth/subjects.ts` | JWT subjects shared with the auth worker | +| **infra** | `env.ts`, `context.ts`, `actor.ts`, `fn.ts`, `id.ts`, `error.ts`, `examples.ts` | Environment, Actor model, zod-typed `fn()` wrappers, IDs, error types, examples | +| **migrations** | `migrations/` | Drizzle-kit SQL migrations for Postgres schema | ## Conventions @@ -57,4 +57,4 @@ import { Team } from '@nestri/core/team/index'; import { Database } from '@nestri/core/db/index'; const team = await Team.fromID('tem_...'); -``` \ No newline at end of file +``` diff --git a/packages/core/migrations/meta/0005_snapshot.json b/packages/core/migrations/meta/0005_snapshot.json index 33c59a8e..34cfdb03 100644 --- a/packages/core/migrations/meta/0005_snapshot.json +++ b/packages/core/migrations/meta/0005_snapshot.json @@ -1,1699 +1,1627 @@ { - "id": "58401785-ef09-4000-a6fc-4d404f109250", - "prevId": "c1adc569-577c-4a05-929b-bd1e3a53c440", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.access_token": { - "name": "access_token", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "owner_user_id": { - "name": "owner_user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token_hash": { - "name": "token_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "last_used": { - "name": "last_used", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "access_token_hash_unique": { - "name": "access_token_hash_unique", - "columns": [ - { - "expression": "token_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "access_token_owner_idx": { - "name": "access_token_owner_idx", - "columns": [ - { - "expression": "owner_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "access_token_team_idx": { - "name": "access_token_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "access_token_owner_user_id_user_id_fk": { - "name": "access_token_owner_user_id_user_id_fk", - "tableFrom": "access_token", - "tableTo": "user", - "columnsFrom": [ - "owner_user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "access_token_team_id_team_id_fk": { - "name": "access_token_team_id_team_id_fk", - "tableFrom": "access_token", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game_depot": { - "name": "game_depot", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "depot_id": { - "name": "depot_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "branch": { - "name": "branch", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'public'" - }, - "steam_manifest_id": { - "name": "steam_manifest_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "steam_build_id": { - "name": "steam_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "installed_manifest_id": { - "name": "installed_manifest_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "installed_build_id": { - "name": "installed_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "size_download": { - "name": "size_download", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "size_on_disk": { - "name": "size_on_disk", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "depot_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "oslist": { - "name": "oslist", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_depot_unique": { - "name": "game_depot_unique", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "depot_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "branch", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_depot_game_idx": { - "name": "game_depot_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_depot_updates_idx": { - "name": "game_depot_updates_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"game_depot\".\"installed_manifest_id\" is distinct from \"game_depot\".\"steam_manifest_id\"", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "game_depot_game_id_game_id_fk": { - "name": "game_depot_game_id_game_id_fk", - "tableFrom": "game_depot", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game_download": { - "name": "game_download", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "host_id": { - "name": "host_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "game_download_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "progress_bytes": { - "name": "progress_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": false, - "default": 0 - }, - "total_bytes": { - "name": "total_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "time_started": { - "name": "time_started", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_completed": { - "name": "time_completed", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_download_host_game_unique": { - "name": "game_download_host_game_unique", - "columns": [ - { - "expression": "host_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_download_game_idx": { - "name": "game_download_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_download_host_status_idx": { - "name": "game_download_host_status_idx", - "columns": [ - { - "expression": "host_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "game_download_game_id_game_id_fk": { - "name": "game_download_game_id_game_id_fk", - "tableFrom": "game_download", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game": { - "name": "game", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "steam_app_id": { - "name": "steam_app_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "client_icon": { - "name": "client_icon", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "icon": { - "name": "icon", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "short_description": { - "name": "short_description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "developers": { - "name": "developers", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "publishers": { - "name": "publishers", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "primary_genre": { - "name": "primary_genre", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "genres": { - "name": "genres", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "categories": { - "name": "categories", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "oslist": { - "name": "oslist", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "size_download": { - "name": "size_download", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "size_on_disk": { - "name": "size_on_disk", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "controller_support": { - "name": "controller_support", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "steam_deck_compat": { - "name": "steam_deck_compat", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "review_score_percent": { - "name": "review_score_percent", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "review_count": { - "name": "review_count", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "metacritic_score": { - "name": "metacritic_score", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "steam_change_number": { - "name": "steam_change_number", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "public_build_id": { - "name": "public_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "release_date_utc": { - "name": "release_date_utc", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_enriched": { - "name": "time_enriched", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_slug_unique": { - "name": "game_slug_unique", - "columns": [ - { - "expression": "slug", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_app_id_unique": { - "name": "game_app_id_unique", - "columns": [ - { - "expression": "steam_app_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "game_steam_app_id_unique": { - "name": "game_steam_app_id_unique", - "nullsNotDistinct": false, - "columns": [ - "steam_app_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.machine": { - "name": "machine", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "owner_user_id": { - "name": "owner_user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": false - }, - "label": { - "name": "label", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "secret_hash": { - "name": "secret_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "last_seen": { - "name": "last_seen", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "machine_secret_hash_unique": { - "name": "machine_secret_hash_unique", - "columns": [ - { - "expression": "secret_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_owner_idx": { - "name": "machine_owner_idx", - "columns": [ - { - "expression": "owner_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_team_idx": { - "name": "machine_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "machine_owner_user_id_user_id_fk": { - "name": "machine_owner_user_id_user_id_fk", - "tableFrom": "machine", - "tableTo": "user", - "columnsFrom": [ - "owner_user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pairing_code": { - "name": "pairing_code", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "code": { - "name": "code", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "target_user_id": { - "name": "target_user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "new_fingerprint": { - "name": "new_fingerprint", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "claimed_at": { - "name": "claimed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "is_claimed": { - "name": "is_claimed", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": { - "pairing_code_code_unique": { - "name": "pairing_code_code_unique", - "columns": [ - { - "expression": "code", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "pairing_code_target_user_idx": { - "name": "pairing_code_target_user_idx", - "columns": [ - { - "expression": "target_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.team_member": { - "name": "team_member", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "role": { - "name": "role", - "type": "team_member_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'member'" - } - }, - "indexes": { - "team_member_team_user_unique": { - "name": "team_member_team_user_unique", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "team_member_team_idx": { - "name": "team_member_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "team_member_user_idx": { - "name": "team_member_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "team_member_team_id_team_id_fk": { - "name": "team_member_team_id_team_id_fk", - "tableFrom": "team_member", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "team_member_user_id_user_id_fk": { - "name": "team_member_user_id_user_id_fk", - "tableFrom": "team_member", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.team": { - "name": "team", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "owner_id": { - "name": "owner_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "billing_email": { - "name": "billing_email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "plan": { - "name": "plan", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'free'" - }, - "subscription_status": { - "name": "subscription_status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'active'" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "team_owner_id_user_id_fk": { - "name": "team_owner_id_user_id_fk", - "tableFrom": "team", - "tableTo": "user", - "columnsFrom": [ - "owner_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "team_slug_unique": { - "name": "team_slug_unique", - "nullsNotDistinct": false, - "columns": [ - "slug" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_fingerprint": { - "name": "user_fingerprint", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "fingerprint": { - "name": "fingerprint", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_seen": { - "name": "last_seen", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_fingerprint_fingerprint_unique": { - "name": "user_fingerprint_fingerprint_unique", - "columns": [ - { - "expression": "fingerprint", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_fingerprint_user_idx": { - "name": "user_fingerprint_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "user_fingerprint_user_id_user_id_fk": { - "name": "user_fingerprint_user_id_user_id_fk", - "tableFrom": "user_fingerprint", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_library": { - "name": "user_library", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "playtime_2w": { - "name": "playtime_2w", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "playtime_forever": { - "name": "playtime_forever", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "last_played": { - "name": "last_played", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_library_user_game_unique": { - "name": "user_library_user_game_unique", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_library_user_idx": { - "name": "user_library_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_library_game_idx": { - "name": "user_library_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "user_library_user_id_user_id_fk": { - "name": "user_library_user_id_user_id_fk", - "tableFrom": "user_library", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "user_library_game_id_game_id_fk": { - "name": "user_library_game_id_game_id_fk", - "tableFrom": "user_library", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.linked_account": { - "name": "linked_account", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "linked_account_provider", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "provider_account_id": { - "name": "provider_account_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "profile": { - "name": "profile", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "linked_account_provider_unique": { - "name": "linked_account_provider_unique", - "columns": [ - { - "expression": "provider", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "provider_account_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "linked_account_user_idx": { - "name": "linked_account_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "linked_account_user_id_user_id_fk": { - "name": "linked_account_user_id_user_id_fk", - "tableFrom": "linked_account", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email_verified": { - "name": "email_verified", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.depot_status": { - "name": "depot_status", - "schema": "public", - "values": [ - "pending", - "downloading", - "complete", - "error", - "deleted" - ] - }, - "public.game_download_status": { - "name": "game_download_status", - "schema": "public", - "values": [ - "pending", - "verifying", - "downloading", - "ready", - "failed" - ] - }, - "public.team_member_role": { - "name": "team_member_role", - "schema": "public", - "values": [ - "owner", - "admin", - "member" - ] - }, - "public.linked_account_provider": { - "name": "linked_account_provider", - "schema": "public", - "values": [ - "steam", - "ssh", - "discord" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file + "id": "58401785-ef09-4000-a6fc-4d404f109250", + "prevId": "c1adc569-577c-4a05-929b-bd1e3a53c440", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used": { + "name": "last_used", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "access_token_hash_unique": { + "name": "access_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_owner_idx": { + "name": "access_token_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_team_idx": { + "name": "access_token_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "access_token_owner_user_id_user_id_fk": { + "name": "access_token_owner_user_id_user_id_fk", + "tableFrom": "access_token", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "access_token_team_id_team_id_fk": { + "name": "access_token_team_id_team_id_fk", + "tableFrom": "access_token", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_depot": { + "name": "game_depot", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "depot_id": { + "name": "depot_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "steam_manifest_id": { + "name": "steam_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_build_id": { + "name": "steam_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "installed_manifest_id": { + "name": "installed_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_build_id": { + "name": "installed_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "depot_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_depot_unique": { + "name": "game_depot_unique", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "depot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_game_idx": { + "name": "game_depot_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_updates_idx": { + "name": "game_depot_updates_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"game_depot\".\"installed_manifest_id\" is distinct from \"game_depot\".\"steam_manifest_id\"", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_depot_game_id_game_id_fk": { + "name": "game_depot_game_id_game_id_fk", + "tableFrom": "game_depot", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_download": { + "name": "game_download", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "game_download_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "progress_bytes": { + "name": "progress_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "total_bytes": { + "name": "total_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "time_started": { + "name": "time_started", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_completed": { + "name": "time_completed", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_download_host_game_unique": { + "name": "game_download_host_game_unique", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_game_idx": { + "name": "game_download_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_host_status_idx": { + "name": "game_download_host_status_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_download_game_id_game_id_fk": { + "name": "game_download_game_id_game_id_fk", + "tableFrom": "game_download", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game": { + "name": "game", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "steam_app_id": { + "name": "steam_app_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_icon": { + "name": "client_icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "short_description": { + "name": "short_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "developers": { + "name": "developers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "publishers": { + "name": "publishers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "primary_genre": { + "name": "primary_genre", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "genres": { + "name": "genres", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "controller_support": { + "name": "controller_support", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_deck_compat": { + "name": "steam_deck_compat", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "review_score_percent": { + "name": "review_score_percent", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "review_count": { + "name": "review_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "metacritic_score": { + "name": "metacritic_score", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "steam_change_number": { + "name": "steam_change_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "public_build_id": { + "name": "public_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "release_date_utc": { + "name": "release_date_utc", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_enriched": { + "name": "time_enriched", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_slug_unique": { + "name": "game_slug_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_app_id_unique": { + "name": "game_app_id_unique", + "columns": [ + { + "expression": "steam_app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "game_steam_app_id_unique": { + "name": "game_steam_app_id_unique", + "nullsNotDistinct": false, + "columns": ["steam_app_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.machine": { + "name": "machine", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "machine_secret_hash_unique": { + "name": "machine_secret_hash_unique", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_owner_idx": { + "name": "machine_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_team_idx": { + "name": "machine_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "machine_owner_user_id_user_id_fk": { + "name": "machine_owner_user_id_user_id_fk", + "tableFrom": "machine", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pairing_code": { + "name": "pairing_code", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_fingerprint": { + "name": "new_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "is_claimed": { + "name": "is_claimed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "pairing_code_code_unique": { + "name": "pairing_code_code_unique", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pairing_code_target_user_idx": { + "name": "pairing_code_target_user_idx", + "columns": [ + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_member": { + "name": "team_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "team_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + } + }, + "indexes": { + "team_member_team_user_unique": { + "name": "team_member_team_user_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_team_idx": { + "name": "team_member_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_user_idx": { + "name": "team_member_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_member_team_id_team_id_fk": { + "name": "team_member_team_id_team_id_fk", + "tableFrom": "team_member", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_member_user_id_user_id_fk": { + "name": "team_member_user_id_user_id_fk", + "tableFrom": "team_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team": { + "name": "team", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "billing_email": { + "name": "billing_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "subscription_status": { + "name": "subscription_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "team_owner_id_user_id_fk": { + "name": "team_owner_id_user_id_fk", + "tableFrom": "team", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_slug_unique": { + "name": "team_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_fingerprint": { + "name": "user_fingerprint", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_fingerprint_fingerprint_unique": { + "name": "user_fingerprint_fingerprint_unique", + "columns": [ + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_fingerprint_user_idx": { + "name": "user_fingerprint_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_fingerprint_user_id_user_id_fk": { + "name": "user_fingerprint_user_id_user_id_fk", + "tableFrom": "user_fingerprint", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_library": { + "name": "user_library", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "playtime_2w": { + "name": "playtime_2w", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "playtime_forever": { + "name": "playtime_forever", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_played": { + "name": "last_played", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_library_user_game_unique": { + "name": "user_library_user_game_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_user_idx": { + "name": "user_library_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_game_idx": { + "name": "user_library_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_library_user_id_user_id_fk": { + "name": "user_library_user_id_user_id_fk", + "tableFrom": "user_library", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_library_game_id_game_id_fk": { + "name": "user_library_game_id_game_id_fk", + "tableFrom": "user_library", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.linked_account": { + "name": "linked_account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "linked_account_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile": { + "name": "profile", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "linked_account_provider_unique": { + "name": "linked_account_provider_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "linked_account_user_idx": { + "name": "linked_account_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "linked_account_user_id_user_id_fk": { + "name": "linked_account_user_id_user_id_fk", + "tableFrom": "linked_account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.depot_status": { + "name": "depot_status", + "schema": "public", + "values": ["pending", "downloading", "complete", "error", "deleted"] + }, + "public.game_download_status": { + "name": "game_download_status", + "schema": "public", + "values": ["pending", "verifying", "downloading", "ready", "failed"] + }, + "public.team_member_role": { + "name": "team_member_role", + "schema": "public", + "values": ["owner", "admin", "member"] + }, + "public.linked_account_provider": { + "name": "linked_account_provider", + "schema": "public", + "values": ["steam", "ssh", "discord"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/core/migrations/meta/0006_snapshot.json b/packages/core/migrations/meta/0006_snapshot.json index 55235b4c..48ab90e7 100644 --- a/packages/core/migrations/meta/0006_snapshot.json +++ b/packages/core/migrations/meta/0006_snapshot.json @@ -1,1908 +1,1830 @@ { - "id": "6b1eb57f-a4a1-4f5d-a197-1d98e0b7bf7d", - "prevId": "58401785-ef09-4000-a6fc-4d404f109250", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.access_token": { - "name": "access_token", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "owner_user_id": { - "name": "owner_user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token_hash": { - "name": "token_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "last_used": { - "name": "last_used", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "access_token_hash_unique": { - "name": "access_token_hash_unique", - "columns": [ - { - "expression": "token_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "access_token_owner_idx": { - "name": "access_token_owner_idx", - "columns": [ - { - "expression": "owner_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "access_token_team_idx": { - "name": "access_token_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "access_token_owner_user_id_user_id_fk": { - "name": "access_token_owner_user_id_user_id_fk", - "tableFrom": "access_token", - "tableTo": "user", - "columnsFrom": [ - "owner_user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "access_token_team_id_team_id_fk": { - "name": "access_token_team_id_team_id_fk", - "tableFrom": "access_token", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game_depot": { - "name": "game_depot", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "depot_id": { - "name": "depot_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "branch": { - "name": "branch", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'public'" - }, - "steam_manifest_id": { - "name": "steam_manifest_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "steam_build_id": { - "name": "steam_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "installed_manifest_id": { - "name": "installed_manifest_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "installed_build_id": { - "name": "installed_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "size_download": { - "name": "size_download", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "size_on_disk": { - "name": "size_on_disk", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "depot_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "oslist": { - "name": "oslist", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_depot_unique": { - "name": "game_depot_unique", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "depot_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "branch", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_depot_game_idx": { - "name": "game_depot_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_depot_updates_idx": { - "name": "game_depot_updates_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"game_depot\".\"installed_manifest_id\" is distinct from \"game_depot\".\"steam_manifest_id\"", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "game_depot_game_id_game_id_fk": { - "name": "game_depot_game_id_game_id_fk", - "tableFrom": "game_depot", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game_download": { - "name": "game_download", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "host_id": { - "name": "host_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "game_download_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "progress_bytes": { - "name": "progress_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": false, - "default": 0 - }, - "total_bytes": { - "name": "total_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "time_started": { - "name": "time_started", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_completed": { - "name": "time_completed", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_download_host_game_unique": { - "name": "game_download_host_game_unique", - "columns": [ - { - "expression": "host_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_download_game_idx": { - "name": "game_download_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_download_host_status_idx": { - "name": "game_download_host_status_idx", - "columns": [ - { - "expression": "host_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "game_download_game_id_game_id_fk": { - "name": "game_download_game_id_game_id_fk", - "tableFrom": "game_download", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game": { - "name": "game", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "steam_app_id": { - "name": "steam_app_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "aliases": { - "name": "aliases", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "client_icon": { - "name": "client_icon", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "icon": { - "name": "icon", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "short_description": { - "name": "short_description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "developers": { - "name": "developers", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "publishers": { - "name": "publishers", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "primary_genre": { - "name": "primary_genre", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "genres": { - "name": "genres", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "categories": { - "name": "categories", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "oslist": { - "name": "oslist", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "size_download": { - "name": "size_download", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "size_on_disk": { - "name": "size_on_disk", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "controller_support": { - "name": "controller_support", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "steam_deck_compat": { - "name": "steam_deck_compat", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "review_score_percent": { - "name": "review_score_percent", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "review_count": { - "name": "review_count", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "metacritic_score": { - "name": "metacritic_score", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "steam_change_number": { - "name": "steam_change_number", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "public_build_id": { - "name": "public_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "release_date_utc": { - "name": "release_date_utc", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_enriched": { - "name": "time_enriched", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_slug_unique": { - "name": "game_slug_unique", - "columns": [ - { - "expression": "slug", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_app_id_unique": { - "name": "game_app_id_unique", - "columns": [ - { - "expression": "steam_app_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "game_steam_app_id_unique": { - "name": "game_steam_app_id_unique", - "nullsNotDistinct": false, - "columns": [ - "steam_app_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.machine": { - "name": "machine", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "owner_user_id": { - "name": "owner_user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": false - }, - "label": { - "name": "label", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "secret_hash": { - "name": "secret_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "last_seen": { - "name": "last_seen", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "machine_secret_hash_unique": { - "name": "machine_secret_hash_unique", - "columns": [ - { - "expression": "secret_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_owner_idx": { - "name": "machine_owner_idx", - "columns": [ - { - "expression": "owner_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_team_idx": { - "name": "machine_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "machine_owner_user_id_user_id_fk": { - "name": "machine_owner_user_id_user_id_fk", - "tableFrom": "machine", - "tableTo": "user", - "columnsFrom": [ - "owner_user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pairing_code": { - "name": "pairing_code", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "code": { - "name": "code", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "target_user_id": { - "name": "target_user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "new_fingerprint": { - "name": "new_fingerprint", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "claimed_at": { - "name": "claimed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "is_claimed": { - "name": "is_claimed", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": { - "pairing_code_code_unique": { - "name": "pairing_code_code_unique", - "columns": [ - { - "expression": "code", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "pairing_code_target_user_idx": { - "name": "pairing_code_target_user_idx", - "columns": [ - { - "expression": "target_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.team_member": { - "name": "team_member", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "role": { - "name": "role", - "type": "team_member_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'member'" - } - }, - "indexes": { - "team_member_team_user_unique": { - "name": "team_member_team_user_unique", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "team_member_team_idx": { - "name": "team_member_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "team_member_user_idx": { - "name": "team_member_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "team_member_team_id_team_id_fk": { - "name": "team_member_team_id_team_id_fk", - "tableFrom": "team_member", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "team_member_user_id_user_id_fk": { - "name": "team_member_user_id_user_id_fk", - "tableFrom": "team_member", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.team": { - "name": "team", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "owner_id": { - "name": "owner_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "billing_email": { - "name": "billing_email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "plan": { - "name": "plan", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'free'" - }, - "subscription_status": { - "name": "subscription_status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'active'" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "team_owner_id_user_id_fk": { - "name": "team_owner_id_user_id_fk", - "tableFrom": "team", - "tableTo": "user", - "columnsFrom": [ - "owner_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "team_slug_unique": { - "name": "team_slug_unique", - "nullsNotDistinct": false, - "columns": [ - "slug" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_fingerprint": { - "name": "user_fingerprint", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "fingerprint": { - "name": "fingerprint", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_seen": { - "name": "last_seen", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_fingerprint_fingerprint_unique": { - "name": "user_fingerprint_fingerprint_unique", - "columns": [ - { - "expression": "fingerprint", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_fingerprint_user_idx": { - "name": "user_fingerprint_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "user_fingerprint_user_id_user_id_fk": { - "name": "user_fingerprint_user_id_user_id_fk", - "tableFrom": "user_fingerprint", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_library": { - "name": "user_library", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "playtime_2w": { - "name": "playtime_2w", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "playtime_forever": { - "name": "playtime_forever", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "last_played": { - "name": "last_played", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_library_user_game_unique": { - "name": "user_library_user_game_unique", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_library_user_idx": { - "name": "user_library_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_library_game_idx": { - "name": "user_library_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "user_library_user_id_user_id_fk": { - "name": "user_library_user_id_user_id_fk", - "tableFrom": "user_library", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "user_library_game_id_game_id_fk": { - "name": "user_library_game_id_game_id_fk", - "tableFrom": "user_library", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.linked_account": { - "name": "linked_account", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "linked_account_provider", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "provider_account_id": { - "name": "provider_account_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "profile": { - "name": "profile", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "linked_account_provider_unique": { - "name": "linked_account_provider_unique", - "columns": [ - { - "expression": "provider", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "provider_account_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "linked_account_user_idx": { - "name": "linked_account_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "linked_account_user_id_user_id_fk": { - "name": "linked_account_user_id_user_id_fk", - "tableFrom": "linked_account", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email_verified": { - "name": "email_verified", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verification": { - "name": "verification", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "verification_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "code_hash": { - "name": "code_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "attempts": { - "name": "attempts", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "consumed_at": { - "name": "consumed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "verification_user_kind_idx": { - "name": "verification_user_kind_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "verification_user_id_user_id_fk": { - "name": "verification_user_id_user_id_fk", - "tableFrom": "verification", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.waitlist_entry": { - "name": "waitlist_entry", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source": { - "name": "source", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'machines'" - } - }, - "indexes": { - "waitlist_entry_email_unique": { - "name": "waitlist_entry_email_unique", - "columns": [ - { - "expression": "email", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "waitlist_entry_source_idx": { - "name": "waitlist_entry_source_idx", - "columns": [ - { - "expression": "source", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.depot_status": { - "name": "depot_status", - "schema": "public", - "values": [ - "pending", - "downloading", - "complete", - "error", - "deleted" - ] - }, - "public.game_download_status": { - "name": "game_download_status", - "schema": "public", - "values": [ - "pending", - "verifying", - "downloading", - "ready", - "failed" - ] - }, - "public.team_member_role": { - "name": "team_member_role", - "schema": "public", - "values": [ - "owner", - "admin", - "member" - ] - }, - "public.linked_account_provider": { - "name": "linked_account_provider", - "schema": "public", - "values": [ - "steam", - "ssh", - "discord" - ] - }, - "public.verification_kind": { - "name": "verification_kind", - "schema": "public", - "values": [ - "email" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file + "id": "6b1eb57f-a4a1-4f5d-a197-1d98e0b7bf7d", + "prevId": "58401785-ef09-4000-a6fc-4d404f109250", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used": { + "name": "last_used", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "access_token_hash_unique": { + "name": "access_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_owner_idx": { + "name": "access_token_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_team_idx": { + "name": "access_token_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "access_token_owner_user_id_user_id_fk": { + "name": "access_token_owner_user_id_user_id_fk", + "tableFrom": "access_token", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "access_token_team_id_team_id_fk": { + "name": "access_token_team_id_team_id_fk", + "tableFrom": "access_token", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_depot": { + "name": "game_depot", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "depot_id": { + "name": "depot_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "steam_manifest_id": { + "name": "steam_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_build_id": { + "name": "steam_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "installed_manifest_id": { + "name": "installed_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_build_id": { + "name": "installed_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "depot_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_depot_unique": { + "name": "game_depot_unique", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "depot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_game_idx": { + "name": "game_depot_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_updates_idx": { + "name": "game_depot_updates_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"game_depot\".\"installed_manifest_id\" is distinct from \"game_depot\".\"steam_manifest_id\"", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_depot_game_id_game_id_fk": { + "name": "game_depot_game_id_game_id_fk", + "tableFrom": "game_depot", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_download": { + "name": "game_download", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "game_download_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "progress_bytes": { + "name": "progress_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "total_bytes": { + "name": "total_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "time_started": { + "name": "time_started", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_completed": { + "name": "time_completed", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_download_host_game_unique": { + "name": "game_download_host_game_unique", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_game_idx": { + "name": "game_download_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_host_status_idx": { + "name": "game_download_host_status_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_download_game_id_game_id_fk": { + "name": "game_download_game_id_game_id_fk", + "tableFrom": "game_download", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game": { + "name": "game", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "steam_app_id": { + "name": "steam_app_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aliases": { + "name": "aliases", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_icon": { + "name": "client_icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "short_description": { + "name": "short_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "developers": { + "name": "developers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "publishers": { + "name": "publishers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "primary_genre": { + "name": "primary_genre", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "genres": { + "name": "genres", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "controller_support": { + "name": "controller_support", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_deck_compat": { + "name": "steam_deck_compat", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "review_score_percent": { + "name": "review_score_percent", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "review_count": { + "name": "review_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "metacritic_score": { + "name": "metacritic_score", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "steam_change_number": { + "name": "steam_change_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "public_build_id": { + "name": "public_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "release_date_utc": { + "name": "release_date_utc", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_enriched": { + "name": "time_enriched", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_slug_unique": { + "name": "game_slug_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_app_id_unique": { + "name": "game_app_id_unique", + "columns": [ + { + "expression": "steam_app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "game_steam_app_id_unique": { + "name": "game_steam_app_id_unique", + "nullsNotDistinct": false, + "columns": ["steam_app_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.machine": { + "name": "machine", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "machine_secret_hash_unique": { + "name": "machine_secret_hash_unique", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_owner_idx": { + "name": "machine_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_team_idx": { + "name": "machine_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "machine_owner_user_id_user_id_fk": { + "name": "machine_owner_user_id_user_id_fk", + "tableFrom": "machine", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pairing_code": { + "name": "pairing_code", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_fingerprint": { + "name": "new_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "is_claimed": { + "name": "is_claimed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "pairing_code_code_unique": { + "name": "pairing_code_code_unique", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pairing_code_target_user_idx": { + "name": "pairing_code_target_user_idx", + "columns": [ + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_member": { + "name": "team_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "team_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + } + }, + "indexes": { + "team_member_team_user_unique": { + "name": "team_member_team_user_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_team_idx": { + "name": "team_member_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_user_idx": { + "name": "team_member_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_member_team_id_team_id_fk": { + "name": "team_member_team_id_team_id_fk", + "tableFrom": "team_member", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_member_user_id_user_id_fk": { + "name": "team_member_user_id_user_id_fk", + "tableFrom": "team_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team": { + "name": "team", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "billing_email": { + "name": "billing_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "subscription_status": { + "name": "subscription_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "team_owner_id_user_id_fk": { + "name": "team_owner_id_user_id_fk", + "tableFrom": "team", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_slug_unique": { + "name": "team_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_fingerprint": { + "name": "user_fingerprint", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_fingerprint_fingerprint_unique": { + "name": "user_fingerprint_fingerprint_unique", + "columns": [ + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_fingerprint_user_idx": { + "name": "user_fingerprint_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_fingerprint_user_id_user_id_fk": { + "name": "user_fingerprint_user_id_user_id_fk", + "tableFrom": "user_fingerprint", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_library": { + "name": "user_library", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "playtime_2w": { + "name": "playtime_2w", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "playtime_forever": { + "name": "playtime_forever", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_played": { + "name": "last_played", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_library_user_game_unique": { + "name": "user_library_user_game_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_user_idx": { + "name": "user_library_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_game_idx": { + "name": "user_library_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_library_user_id_user_id_fk": { + "name": "user_library_user_id_user_id_fk", + "tableFrom": "user_library", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_library_game_id_game_id_fk": { + "name": "user_library_game_id_game_id_fk", + "tableFrom": "user_library", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.linked_account": { + "name": "linked_account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "linked_account_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile": { + "name": "profile", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "linked_account_provider_unique": { + "name": "linked_account_provider_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "linked_account_user_idx": { + "name": "linked_account_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "linked_account_user_id_user_id_fk": { + "name": "linked_account_user_id_user_id_fk", + "tableFrom": "linked_account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "verification_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_user_kind_idx": { + "name": "verification_user_kind_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "verification_user_id_user_id_fk": { + "name": "verification_user_id_user_id_fk", + "tableFrom": "verification", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist_entry": { + "name": "waitlist_entry", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'machines'" + } + }, + "indexes": { + "waitlist_entry_email_unique": { + "name": "waitlist_entry_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "waitlist_entry_source_idx": { + "name": "waitlist_entry_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.depot_status": { + "name": "depot_status", + "schema": "public", + "values": ["pending", "downloading", "complete", "error", "deleted"] + }, + "public.game_download_status": { + "name": "game_download_status", + "schema": "public", + "values": ["pending", "verifying", "downloading", "ready", "failed"] + }, + "public.team_member_role": { + "name": "team_member_role", + "schema": "public", + "values": ["owner", "admin", "member"] + }, + "public.linked_account_provider": { + "name": "linked_account_provider", + "schema": "public", + "values": ["steam", "ssh", "discord"] + }, + "public.verification_kind": { + "name": "verification_kind", + "schema": "public", + "values": ["email"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/core/migrations/meta/0007_snapshot.json b/packages/core/migrations/meta/0007_snapshot.json index 4da6acb2..6924434f 100644 --- a/packages/core/migrations/meta/0007_snapshot.json +++ b/packages/core/migrations/meta/0007_snapshot.json @@ -1,2283 +1,2161 @@ { - "id": "6f1c8157-bfc0-4b61-b31e-9e117d8d0510", - "prevId": "6b1eb57f-a4a1-4f5d-a197-1d98e0b7bf7d", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.access_token": { - "name": "access_token", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "owner_user_id": { - "name": "owner_user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token_hash": { - "name": "token_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "last_used": { - "name": "last_used", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "access_token_hash_unique": { - "name": "access_token_hash_unique", - "columns": [ - { - "expression": "token_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "access_token_owner_idx": { - "name": "access_token_owner_idx", - "columns": [ - { - "expression": "owner_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "access_token_team_idx": { - "name": "access_token_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "access_token_owner_user_id_user_id_fk": { - "name": "access_token_owner_user_id_user_id_fk", - "tableFrom": "access_token", - "tableTo": "user", - "columnsFrom": [ - "owner_user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "access_token_team_id_team_id_fk": { - "name": "access_token_team_id_team_id_fk", - "tableFrom": "access_token", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.box": { - "name": "box", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "machine_id": { - "name": "machine_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "label": { - "name": "label", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tier": { - "name": "tier", - "type": "box_tier", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'sm'" - }, - "state": { - "name": "state", - "type": "box_state", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'created'" - }, - "stop_reason": { - "name": "stop_reason", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stop_clean": { - "name": "stop_clean", - "type": "boolean", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "box_user_idx": { - "name": "box_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "box_machine_idx": { - "name": "box_machine_idx", - "columns": [ - { - "expression": "machine_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "box_user_id_user_id_fk": { - "name": "box_user_id_user_id_fk", - "tableFrom": "box", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "box_machine_id_machine_id_fk": { - "name": "box_machine_id_machine_id_fk", - "tableFrom": "box", - "tableTo": "machine", - "columnsFrom": [ - "machine_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game_depot": { - "name": "game_depot", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "depot_id": { - "name": "depot_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "branch": { - "name": "branch", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'public'" - }, - "steam_manifest_id": { - "name": "steam_manifest_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "steam_build_id": { - "name": "steam_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "installed_manifest_id": { - "name": "installed_manifest_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "installed_build_id": { - "name": "installed_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "size_download": { - "name": "size_download", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "size_on_disk": { - "name": "size_on_disk", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "depot_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "oslist": { - "name": "oslist", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_depot_unique": { - "name": "game_depot_unique", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "depot_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "branch", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_depot_game_idx": { - "name": "game_depot_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_depot_updates_idx": { - "name": "game_depot_updates_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"game_depot\".\"installed_manifest_id\" is distinct from \"game_depot\".\"steam_manifest_id\"", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "game_depot_game_id_game_id_fk": { - "name": "game_depot_game_id_game_id_fk", - "tableFrom": "game_depot", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game_download": { - "name": "game_download", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "host_id": { - "name": "host_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "game_download_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "progress_bytes": { - "name": "progress_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": false, - "default": 0 - }, - "total_bytes": { - "name": "total_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "time_started": { - "name": "time_started", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_completed": { - "name": "time_completed", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_download_host_game_unique": { - "name": "game_download_host_game_unique", - "columns": [ - { - "expression": "host_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_download_game_idx": { - "name": "game_download_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_download_host_status_idx": { - "name": "game_download_host_status_idx", - "columns": [ - { - "expression": "host_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "game_download_host_id_machine_id_fk": { - "name": "game_download_host_id_machine_id_fk", - "tableFrom": "game_download", - "tableTo": "machine", - "columnsFrom": [ - "host_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "game_download_game_id_game_id_fk": { - "name": "game_download_game_id_game_id_fk", - "tableFrom": "game_download", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game": { - "name": "game", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "steam_app_id": { - "name": "steam_app_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "aliases": { - "name": "aliases", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "client_icon": { - "name": "client_icon", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "icon": { - "name": "icon", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "short_description": { - "name": "short_description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "developers": { - "name": "developers", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "publishers": { - "name": "publishers", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "primary_genre": { - "name": "primary_genre", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "genres": { - "name": "genres", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "categories": { - "name": "categories", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "oslist": { - "name": "oslist", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "size_download": { - "name": "size_download", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "size_on_disk": { - "name": "size_on_disk", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "controller_support": { - "name": "controller_support", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "steam_deck_compat": { - "name": "steam_deck_compat", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "review_score_percent": { - "name": "review_score_percent", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "review_count": { - "name": "review_count", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "metacritic_score": { - "name": "metacritic_score", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "steam_change_number": { - "name": "steam_change_number", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "public_build_id": { - "name": "public_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "release_date_utc": { - "name": "release_date_utc", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_enriched": { - "name": "time_enriched", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_slug_unique": { - "name": "game_slug_unique", - "columns": [ - { - "expression": "slug", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_app_id_unique": { - "name": "game_app_id_unique", - "columns": [ - { - "expression": "steam_app_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "game_steam_app_id_unique": { - "name": "game_steam_app_id_unique", - "nullsNotDistinct": false, - "columns": [ - "steam_app_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.machine": { - "name": "machine", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "owner_user_id": { - "name": "owner_user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "label": { - "name": "label", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "secret_hash": { - "name": "secret_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "last_seen": { - "name": "last_seen", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "machine_secret_hash_unique": { - "name": "machine_secret_hash_unique", - "columns": [ - { - "expression": "secret_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_owner_idx": { - "name": "machine_owner_idx", - "columns": [ - { - "expression": "owner_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_team_idx": { - "name": "machine_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "machine_owner_user_id_user_id_fk": { - "name": "machine_owner_user_id_user_id_fk", - "tableFrom": "machine", - "tableTo": "user", - "columnsFrom": [ - "owner_user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "machine_team_id_team_id_fk": { - "name": "machine_team_id_team_id_fk", - "tableFrom": "machine", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pairing_code": { - "name": "pairing_code", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "code": { - "name": "code", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "target_user_id": { - "name": "target_user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "new_fingerprint": { - "name": "new_fingerprint", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "claimed_at": { - "name": "claimed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "is_claimed": { - "name": "is_claimed", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": { - "pairing_code_code_unique": { - "name": "pairing_code_code_unique", - "columns": [ - { - "expression": "code", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "pairing_code_target_user_idx": { - "name": "pairing_code_target_user_idx", - "columns": [ - { - "expression": "target_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "box_id": { - "name": "box_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "linked_account_id": { - "name": "linked_account_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "state": { - "name": "state", - "type": "session_state", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'requested'" - }, - "ticket": { - "name": "ticket", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "time_started": { - "name": "time_started", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_stopped": { - "name": "time_stopped", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "session_box_idx": { - "name": "session_box_idx", - "columns": [ - { - "expression": "box_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_state_idx": { - "name": "session_state_idx", - "columns": [ - { - "expression": "state", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_started_idx": { - "name": "session_started_idx", - "columns": [ - { - "expression": "time_started", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "session_box_id_box_id_fk": { - "name": "session_box_id_box_id_fk", - "tableFrom": "session", - "tableTo": "box", - "columnsFrom": [ - "box_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "session_game_id_game_id_fk": { - "name": "session_game_id_game_id_fk", - "tableFrom": "session", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - }, - "session_linked_account_id_linked_account_id_fk": { - "name": "session_linked_account_id_linked_account_id_fk", - "tableFrom": "session", - "tableTo": "linked_account", - "columnsFrom": [ - "linked_account_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.team_member": { - "name": "team_member", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "role": { - "name": "role", - "type": "team_member_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'member'" - } - }, - "indexes": { - "team_member_team_user_unique": { - "name": "team_member_team_user_unique", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "team_member_team_idx": { - "name": "team_member_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "team_member_user_idx": { - "name": "team_member_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "team_member_team_id_team_id_fk": { - "name": "team_member_team_id_team_id_fk", - "tableFrom": "team_member", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "team_member_user_id_user_id_fk": { - "name": "team_member_user_id_user_id_fk", - "tableFrom": "team_member", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.team": { - "name": "team", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "owner_id": { - "name": "owner_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "billing_email": { - "name": "billing_email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "plan": { - "name": "plan", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'free'" - }, - "subscription_status": { - "name": "subscription_status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'active'" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "team_owner_id_user_id_fk": { - "name": "team_owner_id_user_id_fk", - "tableFrom": "team", - "tableTo": "user", - "columnsFrom": [ - "owner_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "team_slug_unique": { - "name": "team_slug_unique", - "nullsNotDistinct": false, - "columns": [ - "slug" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_fingerprint": { - "name": "user_fingerprint", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "fingerprint": { - "name": "fingerprint", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_seen": { - "name": "last_seen", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_fingerprint_fingerprint_unique": { - "name": "user_fingerprint_fingerprint_unique", - "columns": [ - { - "expression": "fingerprint", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_fingerprint_user_idx": { - "name": "user_fingerprint_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "user_fingerprint_user_id_user_id_fk": { - "name": "user_fingerprint_user_id_user_id_fk", - "tableFrom": "user_fingerprint", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_library": { - "name": "user_library", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "playtime_2w": { - "name": "playtime_2w", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "playtime_forever": { - "name": "playtime_forever", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "last_played": { - "name": "last_played", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_library_user_game_unique": { - "name": "user_library_user_game_unique", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_library_user_idx": { - "name": "user_library_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_library_game_idx": { - "name": "user_library_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "user_library_user_id_user_id_fk": { - "name": "user_library_user_id_user_id_fk", - "tableFrom": "user_library", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "user_library_game_id_game_id_fk": { - "name": "user_library_game_id_game_id_fk", - "tableFrom": "user_library", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.linked_account": { - "name": "linked_account", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "linked_account_provider", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "provider_account_id": { - "name": "provider_account_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "profile": { - "name": "profile", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "linked_account_provider_unique": { - "name": "linked_account_provider_unique", - "columns": [ - { - "expression": "provider", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "provider_account_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "linked_account_user_idx": { - "name": "linked_account_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "linked_account_user_id_user_id_fk": { - "name": "linked_account_user_id_user_id_fk", - "tableFrom": "linked_account", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email_verified": { - "name": "email_verified", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verification": { - "name": "verification", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "verification_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "code_hash": { - "name": "code_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "attempts": { - "name": "attempts", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "consumed_at": { - "name": "consumed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "verification_user_kind_idx": { - "name": "verification_user_kind_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "verification_user_id_user_id_fk": { - "name": "verification_user_id_user_id_fk", - "tableFrom": "verification", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.waitlist_entry": { - "name": "waitlist_entry", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source": { - "name": "source", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'machines'" - } - }, - "indexes": { - "waitlist_entry_email_unique": { - "name": "waitlist_entry_email_unique", - "columns": [ - { - "expression": "email", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "waitlist_entry_source_idx": { - "name": "waitlist_entry_source_idx", - "columns": [ - { - "expression": "source", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.box_state": { - "name": "box_state", - "schema": "public", - "values": [ - "created", - "running", - "stopped" - ] - }, - "public.box_tier": { - "name": "box_tier", - "schema": "public", - "values": [ - "xs", - "sm", - "md", - "lg", - "xl" - ] - }, - "public.depot_status": { - "name": "depot_status", - "schema": "public", - "values": [ - "pending", - "downloading", - "complete", - "error", - "deleted" - ] - }, - "public.game_download_status": { - "name": "game_download_status", - "schema": "public", - "values": [ - "pending", - "verifying", - "downloading", - "ready", - "failed" - ] - }, - "public.session_state": { - "name": "session_state", - "schema": "public", - "values": [ - "requested", - "starting", - "live", - "ended", - "failed" - ] - }, - "public.team_member_role": { - "name": "team_member_role", - "schema": "public", - "values": [ - "owner", - "admin", - "member" - ] - }, - "public.linked_account_provider": { - "name": "linked_account_provider", - "schema": "public", - "values": [ - "steam", - "ssh", - "discord" - ] - }, - "public.verification_kind": { - "name": "verification_kind", - "schema": "public", - "values": [ - "email" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file + "id": "6f1c8157-bfc0-4b61-b31e-9e117d8d0510", + "prevId": "6b1eb57f-a4a1-4f5d-a197-1d98e0b7bf7d", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used": { + "name": "last_used", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "access_token_hash_unique": { + "name": "access_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_owner_idx": { + "name": "access_token_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_team_idx": { + "name": "access_token_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "access_token_owner_user_id_user_id_fk": { + "name": "access_token_owner_user_id_user_id_fk", + "tableFrom": "access_token", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "access_token_team_id_team_id_fk": { + "name": "access_token_team_id_team_id_fk", + "tableFrom": "access_token", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.box": { + "name": "box", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "machine_id": { + "name": "machine_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tier": { + "name": "tier", + "type": "box_tier", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'sm'" + }, + "state": { + "name": "state", + "type": "box_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'created'" + }, + "stop_reason": { + "name": "stop_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stop_clean": { + "name": "stop_clean", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "box_user_idx": { + "name": "box_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "box_machine_idx": { + "name": "box_machine_idx", + "columns": [ + { + "expression": "machine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "box_user_id_user_id_fk": { + "name": "box_user_id_user_id_fk", + "tableFrom": "box", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "box_machine_id_machine_id_fk": { + "name": "box_machine_id_machine_id_fk", + "tableFrom": "box", + "tableTo": "machine", + "columnsFrom": ["machine_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_depot": { + "name": "game_depot", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "depot_id": { + "name": "depot_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "steam_manifest_id": { + "name": "steam_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_build_id": { + "name": "steam_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "installed_manifest_id": { + "name": "installed_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_build_id": { + "name": "installed_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "depot_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_depot_unique": { + "name": "game_depot_unique", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "depot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_game_idx": { + "name": "game_depot_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_updates_idx": { + "name": "game_depot_updates_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"game_depot\".\"installed_manifest_id\" is distinct from \"game_depot\".\"steam_manifest_id\"", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_depot_game_id_game_id_fk": { + "name": "game_depot_game_id_game_id_fk", + "tableFrom": "game_depot", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_download": { + "name": "game_download", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "host_id": { + "name": "host_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "game_download_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "progress_bytes": { + "name": "progress_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "total_bytes": { + "name": "total_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "time_started": { + "name": "time_started", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_completed": { + "name": "time_completed", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_download_host_game_unique": { + "name": "game_download_host_game_unique", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_game_idx": { + "name": "game_download_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_host_status_idx": { + "name": "game_download_host_status_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_download_host_id_machine_id_fk": { + "name": "game_download_host_id_machine_id_fk", + "tableFrom": "game_download", + "tableTo": "machine", + "columnsFrom": ["host_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "game_download_game_id_game_id_fk": { + "name": "game_download_game_id_game_id_fk", + "tableFrom": "game_download", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game": { + "name": "game", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "steam_app_id": { + "name": "steam_app_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aliases": { + "name": "aliases", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_icon": { + "name": "client_icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "short_description": { + "name": "short_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "developers": { + "name": "developers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "publishers": { + "name": "publishers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "primary_genre": { + "name": "primary_genre", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "genres": { + "name": "genres", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "controller_support": { + "name": "controller_support", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_deck_compat": { + "name": "steam_deck_compat", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "review_score_percent": { + "name": "review_score_percent", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "review_count": { + "name": "review_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "metacritic_score": { + "name": "metacritic_score", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "steam_change_number": { + "name": "steam_change_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "public_build_id": { + "name": "public_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "release_date_utc": { + "name": "release_date_utc", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_enriched": { + "name": "time_enriched", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_slug_unique": { + "name": "game_slug_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_app_id_unique": { + "name": "game_app_id_unique", + "columns": [ + { + "expression": "steam_app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "game_steam_app_id_unique": { + "name": "game_steam_app_id_unique", + "nullsNotDistinct": false, + "columns": ["steam_app_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.machine": { + "name": "machine", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "machine_secret_hash_unique": { + "name": "machine_secret_hash_unique", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_owner_idx": { + "name": "machine_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_team_idx": { + "name": "machine_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "machine_owner_user_id_user_id_fk": { + "name": "machine_owner_user_id_user_id_fk", + "tableFrom": "machine", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "machine_team_id_team_id_fk": { + "name": "machine_team_id_team_id_fk", + "tableFrom": "machine", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pairing_code": { + "name": "pairing_code", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_fingerprint": { + "name": "new_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "is_claimed": { + "name": "is_claimed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "pairing_code_code_unique": { + "name": "pairing_code_code_unique", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pairing_code_target_user_idx": { + "name": "pairing_code_target_user_idx", + "columns": [ + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "box_id": { + "name": "box_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "linked_account_id": { + "name": "linked_account_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "session_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "ticket": { + "name": "ticket", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "time_started": { + "name": "time_started", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_stopped": { + "name": "time_stopped", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_box_idx": { + "name": "session_box_idx", + "columns": [ + { + "expression": "box_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_state_idx": { + "name": "session_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_started_idx": { + "name": "session_started_idx", + "columns": [ + { + "expression": "time_started", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_box_id_box_id_fk": { + "name": "session_box_id_box_id_fk", + "tableFrom": "session", + "tableTo": "box", + "columnsFrom": ["box_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_game_id_game_id_fk": { + "name": "session_game_id_game_id_fk", + "tableFrom": "session", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "session_linked_account_id_linked_account_id_fk": { + "name": "session_linked_account_id_linked_account_id_fk", + "tableFrom": "session", + "tableTo": "linked_account", + "columnsFrom": ["linked_account_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_member": { + "name": "team_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "team_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + } + }, + "indexes": { + "team_member_team_user_unique": { + "name": "team_member_team_user_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_team_idx": { + "name": "team_member_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_user_idx": { + "name": "team_member_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_member_team_id_team_id_fk": { + "name": "team_member_team_id_team_id_fk", + "tableFrom": "team_member", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_member_user_id_user_id_fk": { + "name": "team_member_user_id_user_id_fk", + "tableFrom": "team_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team": { + "name": "team", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "billing_email": { + "name": "billing_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "subscription_status": { + "name": "subscription_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "team_owner_id_user_id_fk": { + "name": "team_owner_id_user_id_fk", + "tableFrom": "team", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_slug_unique": { + "name": "team_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_fingerprint": { + "name": "user_fingerprint", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_fingerprint_fingerprint_unique": { + "name": "user_fingerprint_fingerprint_unique", + "columns": [ + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_fingerprint_user_idx": { + "name": "user_fingerprint_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_fingerprint_user_id_user_id_fk": { + "name": "user_fingerprint_user_id_user_id_fk", + "tableFrom": "user_fingerprint", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_library": { + "name": "user_library", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "playtime_2w": { + "name": "playtime_2w", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "playtime_forever": { + "name": "playtime_forever", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_played": { + "name": "last_played", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_library_user_game_unique": { + "name": "user_library_user_game_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_user_idx": { + "name": "user_library_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_game_idx": { + "name": "user_library_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_library_user_id_user_id_fk": { + "name": "user_library_user_id_user_id_fk", + "tableFrom": "user_library", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_library_game_id_game_id_fk": { + "name": "user_library_game_id_game_id_fk", + "tableFrom": "user_library", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.linked_account": { + "name": "linked_account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "linked_account_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile": { + "name": "profile", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "linked_account_provider_unique": { + "name": "linked_account_provider_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "linked_account_user_idx": { + "name": "linked_account_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "linked_account_user_id_user_id_fk": { + "name": "linked_account_user_id_user_id_fk", + "tableFrom": "linked_account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "verification_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_user_kind_idx": { + "name": "verification_user_kind_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "verification_user_id_user_id_fk": { + "name": "verification_user_id_user_id_fk", + "tableFrom": "verification", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist_entry": { + "name": "waitlist_entry", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'machines'" + } + }, + "indexes": { + "waitlist_entry_email_unique": { + "name": "waitlist_entry_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "waitlist_entry_source_idx": { + "name": "waitlist_entry_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.box_state": { + "name": "box_state", + "schema": "public", + "values": ["created", "running", "stopped"] + }, + "public.box_tier": { + "name": "box_tier", + "schema": "public", + "values": ["xs", "sm", "md", "lg", "xl"] + }, + "public.depot_status": { + "name": "depot_status", + "schema": "public", + "values": ["pending", "downloading", "complete", "error", "deleted"] + }, + "public.game_download_status": { + "name": "game_download_status", + "schema": "public", + "values": ["pending", "verifying", "downloading", "ready", "failed"] + }, + "public.session_state": { + "name": "session_state", + "schema": "public", + "values": ["requested", "starting", "live", "ended", "failed"] + }, + "public.team_member_role": { + "name": "team_member_role", + "schema": "public", + "values": ["owner", "admin", "member"] + }, + "public.linked_account_provider": { + "name": "linked_account_provider", + "schema": "public", + "values": ["steam", "ssh", "discord"] + }, + "public.verification_kind": { + "name": "verification_kind", + "schema": "public", + "values": ["email"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/core/migrations/meta/0008_snapshot.json b/packages/core/migrations/meta/0008_snapshot.json index 9d189a9e..91c07710 100644 --- a/packages/core/migrations/meta/0008_snapshot.json +++ b/packages/core/migrations/meta/0008_snapshot.json @@ -1,2299 +1,2177 @@ { - "id": "2c16a831-745e-4e1d-ad95-11aaa30e31f0", - "prevId": "6f1c8157-bfc0-4b61-b31e-9e117d8d0510", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.access_token": { - "name": "access_token", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "owner_user_id": { - "name": "owner_user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token_hash": { - "name": "token_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "last_used": { - "name": "last_used", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "access_token_hash_unique": { - "name": "access_token_hash_unique", - "columns": [ - { - "expression": "token_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "access_token_owner_idx": { - "name": "access_token_owner_idx", - "columns": [ - { - "expression": "owner_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "access_token_team_idx": { - "name": "access_token_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "access_token_owner_user_id_user_id_fk": { - "name": "access_token_owner_user_id_user_id_fk", - "tableFrom": "access_token", - "tableTo": "user", - "columnsFrom": [ - "owner_user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "access_token_team_id_team_id_fk": { - "name": "access_token_team_id_team_id_fk", - "tableFrom": "access_token", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.box": { - "name": "box", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "machine_id": { - "name": "machine_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "label": { - "name": "label", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tier": { - "name": "tier", - "type": "box_tier", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'sm'" - }, - "state": { - "name": "state", - "type": "box_state", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'created'" - }, - "stop_reason": { - "name": "stop_reason", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stop_clean": { - "name": "stop_clean", - "type": "boolean", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "box_user_idx": { - "name": "box_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "box_machine_idx": { - "name": "box_machine_idx", - "columns": [ - { - "expression": "machine_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "box_user_id_user_id_fk": { - "name": "box_user_id_user_id_fk", - "tableFrom": "box", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "box_machine_id_machine_id_fk": { - "name": "box_machine_id_machine_id_fk", - "tableFrom": "box", - "tableTo": "machine", - "columnsFrom": [ - "machine_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game_depot": { - "name": "game_depot", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "depot_id": { - "name": "depot_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "branch": { - "name": "branch", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'public'" - }, - "steam_manifest_id": { - "name": "steam_manifest_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "steam_build_id": { - "name": "steam_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "installed_manifest_id": { - "name": "installed_manifest_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "installed_build_id": { - "name": "installed_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "size_download": { - "name": "size_download", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "size_on_disk": { - "name": "size_on_disk", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "depot_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "oslist": { - "name": "oslist", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_depot_unique": { - "name": "game_depot_unique", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "depot_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "branch", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_depot_game_idx": { - "name": "game_depot_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_depot_updates_idx": { - "name": "game_depot_updates_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"game_depot\".\"installed_manifest_id\" is distinct from \"game_depot\".\"steam_manifest_id\"", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "game_depot_game_id_game_id_fk": { - "name": "game_depot_game_id_game_id_fk", - "tableFrom": "game_depot", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game_download": { - "name": "game_download", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "host_id": { - "name": "host_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "game_download_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "progress_bytes": { - "name": "progress_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": false, - "default": 0 - }, - "total_bytes": { - "name": "total_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "time_started": { - "name": "time_started", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_completed": { - "name": "time_completed", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_download_host_game_unique": { - "name": "game_download_host_game_unique", - "columns": [ - { - "expression": "host_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_download_game_idx": { - "name": "game_download_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_download_host_status_idx": { - "name": "game_download_host_status_idx", - "columns": [ - { - "expression": "host_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "game_download_host_id_machine_id_fk": { - "name": "game_download_host_id_machine_id_fk", - "tableFrom": "game_download", - "tableTo": "machine", - "columnsFrom": [ - "host_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "game_download_game_id_game_id_fk": { - "name": "game_download_game_id_game_id_fk", - "tableFrom": "game_download", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game": { - "name": "game", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "steam_app_id": { - "name": "steam_app_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "aliases": { - "name": "aliases", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "client_icon": { - "name": "client_icon", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "icon": { - "name": "icon", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "short_description": { - "name": "short_description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "developers": { - "name": "developers", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "publishers": { - "name": "publishers", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "primary_genre": { - "name": "primary_genre", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "genres": { - "name": "genres", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "categories": { - "name": "categories", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "oslist": { - "name": "oslist", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "size_download": { - "name": "size_download", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "size_on_disk": { - "name": "size_on_disk", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "controller_support": { - "name": "controller_support", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "steam_deck_compat": { - "name": "steam_deck_compat", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "review_score_percent": { - "name": "review_score_percent", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "review_count": { - "name": "review_count", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "metacritic_score": { - "name": "metacritic_score", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "steam_change_number": { - "name": "steam_change_number", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "public_build_id": { - "name": "public_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "release_date_utc": { - "name": "release_date_utc", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_enriched": { - "name": "time_enriched", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_slug_unique": { - "name": "game_slug_unique", - "columns": [ - { - "expression": "slug", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_app_id_unique": { - "name": "game_app_id_unique", - "columns": [ - { - "expression": "steam_app_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "game_steam_app_id_unique": { - "name": "game_steam_app_id_unique", - "nullsNotDistinct": false, - "columns": [ - "steam_app_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.machine": { - "name": "machine", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "owner_user_id": { - "name": "owner_user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "label": { - "name": "label", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "secret_hash": { - "name": "secret_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "last_seen": { - "name": "last_seen", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "machine_secret_hash_unique": { - "name": "machine_secret_hash_unique", - "columns": [ - { - "expression": "secret_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_owner_idx": { - "name": "machine_owner_idx", - "columns": [ - { - "expression": "owner_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_team_idx": { - "name": "machine_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "machine_owner_user_id_user_id_fk": { - "name": "machine_owner_user_id_user_id_fk", - "tableFrom": "machine", - "tableTo": "user", - "columnsFrom": [ - "owner_user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "machine_team_id_team_id_fk": { - "name": "machine_team_id_team_id_fk", - "tableFrom": "machine", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pairing_code": { - "name": "pairing_code", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "code": { - "name": "code", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "target_user_id": { - "name": "target_user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "new_fingerprint": { - "name": "new_fingerprint", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "claimed_at": { - "name": "claimed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "is_claimed": { - "name": "is_claimed", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": { - "pairing_code_code_unique": { - "name": "pairing_code_code_unique", - "columns": [ - { - "expression": "code", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "pairing_code_target_user_idx": { - "name": "pairing_code_target_user_idx", - "columns": [ - { - "expression": "target_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "box_id": { - "name": "box_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "linked_account_id": { - "name": "linked_account_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "state": { - "name": "state", - "type": "session_state", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'requested'" - }, - "ticket": { - "name": "ticket", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "time_started": { - "name": "time_started", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_stopped": { - "name": "time_stopped", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "session_box_idx": { - "name": "session_box_idx", - "columns": [ - { - "expression": "box_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_state_idx": { - "name": "session_state_idx", - "columns": [ - { - "expression": "state", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_box_active_unique": { - "name": "session_box_active_unique", - "columns": [ - { - "expression": "box_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "time_stopped is null and time_deleted is null", - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_started_idx": { - "name": "session_started_idx", - "columns": [ - { - "expression": "time_started", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "session_box_id_box_id_fk": { - "name": "session_box_id_box_id_fk", - "tableFrom": "session", - "tableTo": "box", - "columnsFrom": [ - "box_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "session_game_id_game_id_fk": { - "name": "session_game_id_game_id_fk", - "tableFrom": "session", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - }, - "session_linked_account_id_linked_account_id_fk": { - "name": "session_linked_account_id_linked_account_id_fk", - "tableFrom": "session", - "tableTo": "linked_account", - "columnsFrom": [ - "linked_account_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.team_member": { - "name": "team_member", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "role": { - "name": "role", - "type": "team_member_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'member'" - } - }, - "indexes": { - "team_member_team_user_unique": { - "name": "team_member_team_user_unique", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "team_member_team_idx": { - "name": "team_member_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "team_member_user_idx": { - "name": "team_member_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "team_member_team_id_team_id_fk": { - "name": "team_member_team_id_team_id_fk", - "tableFrom": "team_member", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "team_member_user_id_user_id_fk": { - "name": "team_member_user_id_user_id_fk", - "tableFrom": "team_member", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.team": { - "name": "team", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "owner_id": { - "name": "owner_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "billing_email": { - "name": "billing_email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "plan": { - "name": "plan", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'free'" - }, - "subscription_status": { - "name": "subscription_status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'active'" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "team_owner_id_user_id_fk": { - "name": "team_owner_id_user_id_fk", - "tableFrom": "team", - "tableTo": "user", - "columnsFrom": [ - "owner_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "team_slug_unique": { - "name": "team_slug_unique", - "nullsNotDistinct": false, - "columns": [ - "slug" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_fingerprint": { - "name": "user_fingerprint", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "fingerprint": { - "name": "fingerprint", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_seen": { - "name": "last_seen", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_fingerprint_fingerprint_unique": { - "name": "user_fingerprint_fingerprint_unique", - "columns": [ - { - "expression": "fingerprint", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_fingerprint_user_idx": { - "name": "user_fingerprint_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "user_fingerprint_user_id_user_id_fk": { - "name": "user_fingerprint_user_id_user_id_fk", - "tableFrom": "user_fingerprint", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_library": { - "name": "user_library", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "playtime_2w": { - "name": "playtime_2w", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "playtime_forever": { - "name": "playtime_forever", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "last_played": { - "name": "last_played", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_library_user_game_unique": { - "name": "user_library_user_game_unique", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_library_user_idx": { - "name": "user_library_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_library_game_idx": { - "name": "user_library_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "user_library_user_id_user_id_fk": { - "name": "user_library_user_id_user_id_fk", - "tableFrom": "user_library", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "user_library_game_id_game_id_fk": { - "name": "user_library_game_id_game_id_fk", - "tableFrom": "user_library", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.linked_account": { - "name": "linked_account", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "linked_account_provider", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "provider_account_id": { - "name": "provider_account_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "profile": { - "name": "profile", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "linked_account_provider_unique": { - "name": "linked_account_provider_unique", - "columns": [ - { - "expression": "provider", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "provider_account_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "linked_account_user_idx": { - "name": "linked_account_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "linked_account_user_id_user_id_fk": { - "name": "linked_account_user_id_user_id_fk", - "tableFrom": "linked_account", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email_verified": { - "name": "email_verified", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verification": { - "name": "verification", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "verification_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "code_hash": { - "name": "code_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "attempts": { - "name": "attempts", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "consumed_at": { - "name": "consumed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "verification_user_kind_idx": { - "name": "verification_user_kind_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "verification_user_id_user_id_fk": { - "name": "verification_user_id_user_id_fk", - "tableFrom": "verification", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.waitlist_entry": { - "name": "waitlist_entry", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source": { - "name": "source", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'machines'" - } - }, - "indexes": { - "waitlist_entry_email_unique": { - "name": "waitlist_entry_email_unique", - "columns": [ - { - "expression": "email", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "waitlist_entry_source_idx": { - "name": "waitlist_entry_source_idx", - "columns": [ - { - "expression": "source", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.box_state": { - "name": "box_state", - "schema": "public", - "values": [ - "created", - "running", - "stopped" - ] - }, - "public.box_tier": { - "name": "box_tier", - "schema": "public", - "values": [ - "xs", - "sm", - "md", - "lg", - "xl" - ] - }, - "public.depot_status": { - "name": "depot_status", - "schema": "public", - "values": [ - "pending", - "downloading", - "complete", - "error", - "deleted" - ] - }, - "public.game_download_status": { - "name": "game_download_status", - "schema": "public", - "values": [ - "pending", - "verifying", - "downloading", - "ready", - "failed" - ] - }, - "public.session_state": { - "name": "session_state", - "schema": "public", - "values": [ - "requested", - "starting", - "live", - "ended", - "failed" - ] - }, - "public.team_member_role": { - "name": "team_member_role", - "schema": "public", - "values": [ - "owner", - "admin", - "member" - ] - }, - "public.linked_account_provider": { - "name": "linked_account_provider", - "schema": "public", - "values": [ - "steam", - "ssh", - "discord" - ] - }, - "public.verification_kind": { - "name": "verification_kind", - "schema": "public", - "values": [ - "email" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file + "id": "2c16a831-745e-4e1d-ad95-11aaa30e31f0", + "prevId": "6f1c8157-bfc0-4b61-b31e-9e117d8d0510", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used": { + "name": "last_used", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "access_token_hash_unique": { + "name": "access_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_owner_idx": { + "name": "access_token_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_team_idx": { + "name": "access_token_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "access_token_owner_user_id_user_id_fk": { + "name": "access_token_owner_user_id_user_id_fk", + "tableFrom": "access_token", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "access_token_team_id_team_id_fk": { + "name": "access_token_team_id_team_id_fk", + "tableFrom": "access_token", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.box": { + "name": "box", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "machine_id": { + "name": "machine_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tier": { + "name": "tier", + "type": "box_tier", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'sm'" + }, + "state": { + "name": "state", + "type": "box_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'created'" + }, + "stop_reason": { + "name": "stop_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stop_clean": { + "name": "stop_clean", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "box_user_idx": { + "name": "box_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "box_machine_idx": { + "name": "box_machine_idx", + "columns": [ + { + "expression": "machine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "box_user_id_user_id_fk": { + "name": "box_user_id_user_id_fk", + "tableFrom": "box", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "box_machine_id_machine_id_fk": { + "name": "box_machine_id_machine_id_fk", + "tableFrom": "box", + "tableTo": "machine", + "columnsFrom": ["machine_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_depot": { + "name": "game_depot", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "depot_id": { + "name": "depot_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "steam_manifest_id": { + "name": "steam_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_build_id": { + "name": "steam_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "installed_manifest_id": { + "name": "installed_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_build_id": { + "name": "installed_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "depot_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_depot_unique": { + "name": "game_depot_unique", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "depot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_game_idx": { + "name": "game_depot_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_updates_idx": { + "name": "game_depot_updates_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"game_depot\".\"installed_manifest_id\" is distinct from \"game_depot\".\"steam_manifest_id\"", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_depot_game_id_game_id_fk": { + "name": "game_depot_game_id_game_id_fk", + "tableFrom": "game_depot", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_download": { + "name": "game_download", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "host_id": { + "name": "host_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "game_download_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "progress_bytes": { + "name": "progress_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "total_bytes": { + "name": "total_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "time_started": { + "name": "time_started", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_completed": { + "name": "time_completed", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_download_host_game_unique": { + "name": "game_download_host_game_unique", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_game_idx": { + "name": "game_download_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_host_status_idx": { + "name": "game_download_host_status_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_download_host_id_machine_id_fk": { + "name": "game_download_host_id_machine_id_fk", + "tableFrom": "game_download", + "tableTo": "machine", + "columnsFrom": ["host_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "game_download_game_id_game_id_fk": { + "name": "game_download_game_id_game_id_fk", + "tableFrom": "game_download", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game": { + "name": "game", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "steam_app_id": { + "name": "steam_app_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aliases": { + "name": "aliases", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_icon": { + "name": "client_icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "short_description": { + "name": "short_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "developers": { + "name": "developers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "publishers": { + "name": "publishers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "primary_genre": { + "name": "primary_genre", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "genres": { + "name": "genres", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "controller_support": { + "name": "controller_support", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_deck_compat": { + "name": "steam_deck_compat", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "review_score_percent": { + "name": "review_score_percent", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "review_count": { + "name": "review_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "metacritic_score": { + "name": "metacritic_score", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "steam_change_number": { + "name": "steam_change_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "public_build_id": { + "name": "public_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "release_date_utc": { + "name": "release_date_utc", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_enriched": { + "name": "time_enriched", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_slug_unique": { + "name": "game_slug_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_app_id_unique": { + "name": "game_app_id_unique", + "columns": [ + { + "expression": "steam_app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "game_steam_app_id_unique": { + "name": "game_steam_app_id_unique", + "nullsNotDistinct": false, + "columns": ["steam_app_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.machine": { + "name": "machine", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "machine_secret_hash_unique": { + "name": "machine_secret_hash_unique", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_owner_idx": { + "name": "machine_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_team_idx": { + "name": "machine_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "machine_owner_user_id_user_id_fk": { + "name": "machine_owner_user_id_user_id_fk", + "tableFrom": "machine", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "machine_team_id_team_id_fk": { + "name": "machine_team_id_team_id_fk", + "tableFrom": "machine", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pairing_code": { + "name": "pairing_code", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_fingerprint": { + "name": "new_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "is_claimed": { + "name": "is_claimed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "pairing_code_code_unique": { + "name": "pairing_code_code_unique", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pairing_code_target_user_idx": { + "name": "pairing_code_target_user_idx", + "columns": [ + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "box_id": { + "name": "box_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "linked_account_id": { + "name": "linked_account_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "session_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "ticket": { + "name": "ticket", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "time_started": { + "name": "time_started", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_stopped": { + "name": "time_stopped", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_box_idx": { + "name": "session_box_idx", + "columns": [ + { + "expression": "box_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_state_idx": { + "name": "session_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_box_active_unique": { + "name": "session_box_active_unique", + "columns": [ + { + "expression": "box_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "time_stopped is null and time_deleted is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_started_idx": { + "name": "session_started_idx", + "columns": [ + { + "expression": "time_started", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_box_id_box_id_fk": { + "name": "session_box_id_box_id_fk", + "tableFrom": "session", + "tableTo": "box", + "columnsFrom": ["box_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_game_id_game_id_fk": { + "name": "session_game_id_game_id_fk", + "tableFrom": "session", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "session_linked_account_id_linked_account_id_fk": { + "name": "session_linked_account_id_linked_account_id_fk", + "tableFrom": "session", + "tableTo": "linked_account", + "columnsFrom": ["linked_account_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_member": { + "name": "team_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "team_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + } + }, + "indexes": { + "team_member_team_user_unique": { + "name": "team_member_team_user_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_team_idx": { + "name": "team_member_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_user_idx": { + "name": "team_member_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_member_team_id_team_id_fk": { + "name": "team_member_team_id_team_id_fk", + "tableFrom": "team_member", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_member_user_id_user_id_fk": { + "name": "team_member_user_id_user_id_fk", + "tableFrom": "team_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team": { + "name": "team", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "billing_email": { + "name": "billing_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "subscription_status": { + "name": "subscription_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "team_owner_id_user_id_fk": { + "name": "team_owner_id_user_id_fk", + "tableFrom": "team", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_slug_unique": { + "name": "team_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_fingerprint": { + "name": "user_fingerprint", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_fingerprint_fingerprint_unique": { + "name": "user_fingerprint_fingerprint_unique", + "columns": [ + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_fingerprint_user_idx": { + "name": "user_fingerprint_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_fingerprint_user_id_user_id_fk": { + "name": "user_fingerprint_user_id_user_id_fk", + "tableFrom": "user_fingerprint", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_library": { + "name": "user_library", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "playtime_2w": { + "name": "playtime_2w", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "playtime_forever": { + "name": "playtime_forever", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_played": { + "name": "last_played", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_library_user_game_unique": { + "name": "user_library_user_game_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_user_idx": { + "name": "user_library_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_game_idx": { + "name": "user_library_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_library_user_id_user_id_fk": { + "name": "user_library_user_id_user_id_fk", + "tableFrom": "user_library", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_library_game_id_game_id_fk": { + "name": "user_library_game_id_game_id_fk", + "tableFrom": "user_library", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.linked_account": { + "name": "linked_account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "linked_account_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile": { + "name": "profile", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "linked_account_provider_unique": { + "name": "linked_account_provider_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "linked_account_user_idx": { + "name": "linked_account_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "linked_account_user_id_user_id_fk": { + "name": "linked_account_user_id_user_id_fk", + "tableFrom": "linked_account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "verification_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_user_kind_idx": { + "name": "verification_user_kind_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "verification_user_id_user_id_fk": { + "name": "verification_user_id_user_id_fk", + "tableFrom": "verification", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist_entry": { + "name": "waitlist_entry", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'machines'" + } + }, + "indexes": { + "waitlist_entry_email_unique": { + "name": "waitlist_entry_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "waitlist_entry_source_idx": { + "name": "waitlist_entry_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.box_state": { + "name": "box_state", + "schema": "public", + "values": ["created", "running", "stopped"] + }, + "public.box_tier": { + "name": "box_tier", + "schema": "public", + "values": ["xs", "sm", "md", "lg", "xl"] + }, + "public.depot_status": { + "name": "depot_status", + "schema": "public", + "values": ["pending", "downloading", "complete", "error", "deleted"] + }, + "public.game_download_status": { + "name": "game_download_status", + "schema": "public", + "values": ["pending", "verifying", "downloading", "ready", "failed"] + }, + "public.session_state": { + "name": "session_state", + "schema": "public", + "values": ["requested", "starting", "live", "ended", "failed"] + }, + "public.team_member_role": { + "name": "team_member_role", + "schema": "public", + "values": ["owner", "admin", "member"] + }, + "public.linked_account_provider": { + "name": "linked_account_provider", + "schema": "public", + "values": ["steam", "ssh", "discord"] + }, + "public.verification_kind": { + "name": "verification_kind", + "schema": "public", + "values": ["email"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/core/migrations/meta/0009_snapshot.json b/packages/core/migrations/meta/0009_snapshot.json index 9c9a5fac..d689ed13 100644 --- a/packages/core/migrations/meta/0009_snapshot.json +++ b/packages/core/migrations/meta/0009_snapshot.json @@ -1,2322 +1,2200 @@ { - "id": "b26664d8-eebf-4563-9c3c-7e00e41b646b", - "prevId": "2c16a831-745e-4e1d-ad95-11aaa30e31f0", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.access_token": { - "name": "access_token", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "owner_user_id": { - "name": "owner_user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token_hash": { - "name": "token_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "last_used": { - "name": "last_used", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "access_token_hash_unique": { - "name": "access_token_hash_unique", - "columns": [ - { - "expression": "token_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "access_token_owner_idx": { - "name": "access_token_owner_idx", - "columns": [ - { - "expression": "owner_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "access_token_team_idx": { - "name": "access_token_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "access_token_owner_user_id_user_id_fk": { - "name": "access_token_owner_user_id_user_id_fk", - "tableFrom": "access_token", - "tableTo": "user", - "columnsFrom": [ - "owner_user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "access_token_team_id_team_id_fk": { - "name": "access_token_team_id_team_id_fk", - "tableFrom": "access_token", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.box": { - "name": "box", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "machine_id": { - "name": "machine_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "label": { - "name": "label", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tier": { - "name": "tier", - "type": "box_tier", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'sm'" - }, - "state": { - "name": "state", - "type": "box_state", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'created'" - }, - "stop_reason": { - "name": "stop_reason", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stop_clean": { - "name": "stop_clean", - "type": "boolean", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "box_user_idx": { - "name": "box_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "box_machine_idx": { - "name": "box_machine_idx", - "columns": [ - { - "expression": "machine_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "box_user_id_user_id_fk": { - "name": "box_user_id_user_id_fk", - "tableFrom": "box", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "box_machine_id_machine_id_fk": { - "name": "box_machine_id_machine_id_fk", - "tableFrom": "box", - "tableTo": "machine", - "columnsFrom": [ - "machine_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game_depot": { - "name": "game_depot", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "depot_id": { - "name": "depot_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "branch": { - "name": "branch", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'public'" - }, - "steam_manifest_id": { - "name": "steam_manifest_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "steam_build_id": { - "name": "steam_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "installed_manifest_id": { - "name": "installed_manifest_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "installed_build_id": { - "name": "installed_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "size_download": { - "name": "size_download", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "size_on_disk": { - "name": "size_on_disk", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "depot_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "oslist": { - "name": "oslist", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_depot_unique": { - "name": "game_depot_unique", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "depot_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "branch", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_depot_game_idx": { - "name": "game_depot_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_depot_updates_idx": { - "name": "game_depot_updates_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"game_depot\".\"installed_manifest_id\" is distinct from \"game_depot\".\"steam_manifest_id\"", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "game_depot_game_id_game_id_fk": { - "name": "game_depot_game_id_game_id_fk", - "tableFrom": "game_depot", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game_download": { - "name": "game_download", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "host_id": { - "name": "host_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "game_download_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "progress_bytes": { - "name": "progress_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": false, - "default": 0 - }, - "total_bytes": { - "name": "total_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "time_started": { - "name": "time_started", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_completed": { - "name": "time_completed", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_download_host_game_unique": { - "name": "game_download_host_game_unique", - "columns": [ - { - "expression": "host_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_download_game_idx": { - "name": "game_download_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_download_host_status_idx": { - "name": "game_download_host_status_idx", - "columns": [ - { - "expression": "host_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "game_download_host_id_machine_id_fk": { - "name": "game_download_host_id_machine_id_fk", - "tableFrom": "game_download", - "tableTo": "machine", - "columnsFrom": [ - "host_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "game_download_game_id_game_id_fk": { - "name": "game_download_game_id_game_id_fk", - "tableFrom": "game_download", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game": { - "name": "game", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "steam_app_id": { - "name": "steam_app_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "aliases": { - "name": "aliases", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "client_icon": { - "name": "client_icon", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "icon": { - "name": "icon", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "short_description": { - "name": "short_description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "developers": { - "name": "developers", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "publishers": { - "name": "publishers", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "primary_genre": { - "name": "primary_genre", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "genres": { - "name": "genres", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "categories": { - "name": "categories", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "oslist": { - "name": "oslist", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "size_download": { - "name": "size_download", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "size_on_disk": { - "name": "size_on_disk", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "controller_support": { - "name": "controller_support", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "steam_deck_compat": { - "name": "steam_deck_compat", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "review_score_percent": { - "name": "review_score_percent", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "review_count": { - "name": "review_count", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "metacritic_score": { - "name": "metacritic_score", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "steam_change_number": { - "name": "steam_change_number", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "public_build_id": { - "name": "public_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "release_date_utc": { - "name": "release_date_utc", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_enriched": { - "name": "time_enriched", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_slug_unique": { - "name": "game_slug_unique", - "columns": [ - { - "expression": "slug", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_app_id_unique": { - "name": "game_app_id_unique", - "columns": [ - { - "expression": "steam_app_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "game_steam_app_id_unique": { - "name": "game_steam_app_id_unique", - "nullsNotDistinct": false, - "columns": [ - "steam_app_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.machine": { - "name": "machine", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "owner_user_id": { - "name": "owner_user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "label": { - "name": "label", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "secret_hash": { - "name": "secret_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "last_seen": { - "name": "last_seen", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "machine_secret_hash_unique": { - "name": "machine_secret_hash_unique", - "columns": [ - { - "expression": "secret_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_owner_idx": { - "name": "machine_owner_idx", - "columns": [ - { - "expression": "owner_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_team_idx": { - "name": "machine_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "machine_owner_user_id_user_id_fk": { - "name": "machine_owner_user_id_user_id_fk", - "tableFrom": "machine", - "tableTo": "user", - "columnsFrom": [ - "owner_user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "machine_team_id_team_id_fk": { - "name": "machine_team_id_team_id_fk", - "tableFrom": "machine", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pairing_code": { - "name": "pairing_code", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "code": { - "name": "code", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "target_user_id": { - "name": "target_user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "new_fingerprint": { - "name": "new_fingerprint", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "claimed_at": { - "name": "claimed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "is_claimed": { - "name": "is_claimed", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": { - "pairing_code_code_unique": { - "name": "pairing_code_code_unique", - "columns": [ - { - "expression": "code", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "pairing_code_target_user_idx": { - "name": "pairing_code_target_user_idx", - "columns": [ - { - "expression": "target_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "box_id": { - "name": "box_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "linked_account_id": { - "name": "linked_account_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "state": { - "name": "state", - "type": "session_state", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'requested'" - }, - "ticket": { - "name": "ticket", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "time_started": { - "name": "time_started", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_stopped": { - "name": "time_stopped", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "claim_token": { - "name": "claim_token", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "session_box_idx": { - "name": "session_box_idx", - "columns": [ - { - "expression": "box_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_state_idx": { - "name": "session_state_idx", - "columns": [ - { - "expression": "state", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_box_active_unique": { - "name": "session_box_active_unique", - "columns": [ - { - "expression": "box_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "time_stopped is null and time_deleted is null", - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_started_idx": { - "name": "session_started_idx", - "columns": [ - { - "expression": "time_started", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "session_box_id_box_id_fk": { - "name": "session_box_id_box_id_fk", - "tableFrom": "session", - "tableTo": "box", - "columnsFrom": [ - "box_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "session_game_id_game_id_fk": { - "name": "session_game_id_game_id_fk", - "tableFrom": "session", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - }, - "session_linked_account_id_linked_account_id_fk": { - "name": "session_linked_account_id_linked_account_id_fk", - "tableFrom": "session", - "tableTo": "linked_account", - "columnsFrom": [ - "linked_account_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.team_member": { - "name": "team_member", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "role": { - "name": "role", - "type": "team_member_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'member'" - } - }, - "indexes": { - "team_member_team_user_unique": { - "name": "team_member_team_user_unique", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "team_member_team_idx": { - "name": "team_member_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "team_member_user_idx": { - "name": "team_member_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "team_member_team_id_team_id_fk": { - "name": "team_member_team_id_team_id_fk", - "tableFrom": "team_member", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "team_member_user_id_user_id_fk": { - "name": "team_member_user_id_user_id_fk", - "tableFrom": "team_member", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.team": { - "name": "team", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "owner_id": { - "name": "owner_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "billing_email": { - "name": "billing_email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "plan": { - "name": "plan", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'free'" - }, - "subscription_status": { - "name": "subscription_status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'active'" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "team_owner_id_user_id_fk": { - "name": "team_owner_id_user_id_fk", - "tableFrom": "team", - "tableTo": "user", - "columnsFrom": [ - "owner_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "team_slug_unique": { - "name": "team_slug_unique", - "nullsNotDistinct": false, - "columns": [ - "slug" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_fingerprint": { - "name": "user_fingerprint", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "fingerprint": { - "name": "fingerprint", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_seen": { - "name": "last_seen", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_fingerprint_fingerprint_unique": { - "name": "user_fingerprint_fingerprint_unique", - "columns": [ - { - "expression": "fingerprint", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_fingerprint_user_idx": { - "name": "user_fingerprint_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "user_fingerprint_user_id_user_id_fk": { - "name": "user_fingerprint_user_id_user_id_fk", - "tableFrom": "user_fingerprint", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_library": { - "name": "user_library", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "playtime_2w": { - "name": "playtime_2w", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "playtime_forever": { - "name": "playtime_forever", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "last_played": { - "name": "last_played", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_library_user_game_unique": { - "name": "user_library_user_game_unique", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_library_user_idx": { - "name": "user_library_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_library_game_idx": { - "name": "user_library_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "user_library_user_id_user_id_fk": { - "name": "user_library_user_id_user_id_fk", - "tableFrom": "user_library", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "user_library_game_id_game_id_fk": { - "name": "user_library_game_id_game_id_fk", - "tableFrom": "user_library", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.linked_account": { - "name": "linked_account", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "linked_account_provider", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "provider_account_id": { - "name": "provider_account_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "profile": { - "name": "profile", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "linked_account_provider_unique": { - "name": "linked_account_provider_unique", - "columns": [ - { - "expression": "provider", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "provider_account_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "linked_account_user_idx": { - "name": "linked_account_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "linked_account_user_id_user_id_fk": { - "name": "linked_account_user_id_user_id_fk", - "tableFrom": "linked_account", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email_verified": { - "name": "email_verified", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_email_unique": { - "name": "user_email_unique", - "columns": [ - { - "expression": "email", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "email is not null and time_deleted is null", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verification": { - "name": "verification", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "verification_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "code_hash": { - "name": "code_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "attempts": { - "name": "attempts", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "consumed_at": { - "name": "consumed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "verification_user_kind_idx": { - "name": "verification_user_kind_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "verification_user_id_user_id_fk": { - "name": "verification_user_id_user_id_fk", - "tableFrom": "verification", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.waitlist_entry": { - "name": "waitlist_entry", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source": { - "name": "source", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'machines'" - } - }, - "indexes": { - "waitlist_entry_email_unique": { - "name": "waitlist_entry_email_unique", - "columns": [ - { - "expression": "email", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "waitlist_entry_source_idx": { - "name": "waitlist_entry_source_idx", - "columns": [ - { - "expression": "source", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.box_state": { - "name": "box_state", - "schema": "public", - "values": [ - "created", - "running", - "stopped" - ] - }, - "public.box_tier": { - "name": "box_tier", - "schema": "public", - "values": [ - "xs", - "sm", - "md", - "lg", - "xl" - ] - }, - "public.depot_status": { - "name": "depot_status", - "schema": "public", - "values": [ - "pending", - "downloading", - "complete", - "error", - "deleted" - ] - }, - "public.game_download_status": { - "name": "game_download_status", - "schema": "public", - "values": [ - "pending", - "verifying", - "downloading", - "ready", - "failed" - ] - }, - "public.session_state": { - "name": "session_state", - "schema": "public", - "values": [ - "requested", - "starting", - "live", - "ended", - "failed" - ] - }, - "public.team_member_role": { - "name": "team_member_role", - "schema": "public", - "values": [ - "owner", - "admin", - "member" - ] - }, - "public.linked_account_provider": { - "name": "linked_account_provider", - "schema": "public", - "values": [ - "steam", - "ssh", - "discord" - ] - }, - "public.verification_kind": { - "name": "verification_kind", - "schema": "public", - "values": [ - "email" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } + "id": "b26664d8-eebf-4563-9c3c-7e00e41b646b", + "prevId": "2c16a831-745e-4e1d-ad95-11aaa30e31f0", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used": { + "name": "last_used", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "access_token_hash_unique": { + "name": "access_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_owner_idx": { + "name": "access_token_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_team_idx": { + "name": "access_token_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "access_token_owner_user_id_user_id_fk": { + "name": "access_token_owner_user_id_user_id_fk", + "tableFrom": "access_token", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "access_token_team_id_team_id_fk": { + "name": "access_token_team_id_team_id_fk", + "tableFrom": "access_token", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.box": { + "name": "box", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "machine_id": { + "name": "machine_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tier": { + "name": "tier", + "type": "box_tier", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'sm'" + }, + "state": { + "name": "state", + "type": "box_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'created'" + }, + "stop_reason": { + "name": "stop_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stop_clean": { + "name": "stop_clean", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "box_user_idx": { + "name": "box_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "box_machine_idx": { + "name": "box_machine_idx", + "columns": [ + { + "expression": "machine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "box_user_id_user_id_fk": { + "name": "box_user_id_user_id_fk", + "tableFrom": "box", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "box_machine_id_machine_id_fk": { + "name": "box_machine_id_machine_id_fk", + "tableFrom": "box", + "tableTo": "machine", + "columnsFrom": ["machine_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_depot": { + "name": "game_depot", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "depot_id": { + "name": "depot_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "steam_manifest_id": { + "name": "steam_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_build_id": { + "name": "steam_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "installed_manifest_id": { + "name": "installed_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_build_id": { + "name": "installed_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "depot_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_depot_unique": { + "name": "game_depot_unique", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "depot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_game_idx": { + "name": "game_depot_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_updates_idx": { + "name": "game_depot_updates_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"game_depot\".\"installed_manifest_id\" is distinct from \"game_depot\".\"steam_manifest_id\"", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_depot_game_id_game_id_fk": { + "name": "game_depot_game_id_game_id_fk", + "tableFrom": "game_depot", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_download": { + "name": "game_download", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "host_id": { + "name": "host_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "game_download_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "progress_bytes": { + "name": "progress_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "total_bytes": { + "name": "total_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "time_started": { + "name": "time_started", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_completed": { + "name": "time_completed", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_download_host_game_unique": { + "name": "game_download_host_game_unique", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_game_idx": { + "name": "game_download_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_host_status_idx": { + "name": "game_download_host_status_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_download_host_id_machine_id_fk": { + "name": "game_download_host_id_machine_id_fk", + "tableFrom": "game_download", + "tableTo": "machine", + "columnsFrom": ["host_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "game_download_game_id_game_id_fk": { + "name": "game_download_game_id_game_id_fk", + "tableFrom": "game_download", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game": { + "name": "game", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "steam_app_id": { + "name": "steam_app_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aliases": { + "name": "aliases", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_icon": { + "name": "client_icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "short_description": { + "name": "short_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "developers": { + "name": "developers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "publishers": { + "name": "publishers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "primary_genre": { + "name": "primary_genre", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "genres": { + "name": "genres", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "controller_support": { + "name": "controller_support", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_deck_compat": { + "name": "steam_deck_compat", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "review_score_percent": { + "name": "review_score_percent", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "review_count": { + "name": "review_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "metacritic_score": { + "name": "metacritic_score", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "steam_change_number": { + "name": "steam_change_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "public_build_id": { + "name": "public_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "release_date_utc": { + "name": "release_date_utc", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_enriched": { + "name": "time_enriched", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_slug_unique": { + "name": "game_slug_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_app_id_unique": { + "name": "game_app_id_unique", + "columns": [ + { + "expression": "steam_app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "game_steam_app_id_unique": { + "name": "game_steam_app_id_unique", + "nullsNotDistinct": false, + "columns": ["steam_app_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.machine": { + "name": "machine", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "machine_secret_hash_unique": { + "name": "machine_secret_hash_unique", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_owner_idx": { + "name": "machine_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_team_idx": { + "name": "machine_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "machine_owner_user_id_user_id_fk": { + "name": "machine_owner_user_id_user_id_fk", + "tableFrom": "machine", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "machine_team_id_team_id_fk": { + "name": "machine_team_id_team_id_fk", + "tableFrom": "machine", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pairing_code": { + "name": "pairing_code", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_fingerprint": { + "name": "new_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "is_claimed": { + "name": "is_claimed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "pairing_code_code_unique": { + "name": "pairing_code_code_unique", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pairing_code_target_user_idx": { + "name": "pairing_code_target_user_idx", + "columns": [ + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "box_id": { + "name": "box_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "linked_account_id": { + "name": "linked_account_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "session_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "ticket": { + "name": "ticket", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "time_started": { + "name": "time_started", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_stopped": { + "name": "time_stopped", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_token": { + "name": "claim_token", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_box_idx": { + "name": "session_box_idx", + "columns": [ + { + "expression": "box_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_state_idx": { + "name": "session_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_box_active_unique": { + "name": "session_box_active_unique", + "columns": [ + { + "expression": "box_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "time_stopped is null and time_deleted is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_started_idx": { + "name": "session_started_idx", + "columns": [ + { + "expression": "time_started", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_box_id_box_id_fk": { + "name": "session_box_id_box_id_fk", + "tableFrom": "session", + "tableTo": "box", + "columnsFrom": ["box_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_game_id_game_id_fk": { + "name": "session_game_id_game_id_fk", + "tableFrom": "session", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "session_linked_account_id_linked_account_id_fk": { + "name": "session_linked_account_id_linked_account_id_fk", + "tableFrom": "session", + "tableTo": "linked_account", + "columnsFrom": ["linked_account_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_member": { + "name": "team_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "team_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + } + }, + "indexes": { + "team_member_team_user_unique": { + "name": "team_member_team_user_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_team_idx": { + "name": "team_member_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_user_idx": { + "name": "team_member_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_member_team_id_team_id_fk": { + "name": "team_member_team_id_team_id_fk", + "tableFrom": "team_member", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_member_user_id_user_id_fk": { + "name": "team_member_user_id_user_id_fk", + "tableFrom": "team_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team": { + "name": "team", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "billing_email": { + "name": "billing_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "subscription_status": { + "name": "subscription_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "team_owner_id_user_id_fk": { + "name": "team_owner_id_user_id_fk", + "tableFrom": "team", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_slug_unique": { + "name": "team_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_fingerprint": { + "name": "user_fingerprint", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_fingerprint_fingerprint_unique": { + "name": "user_fingerprint_fingerprint_unique", + "columns": [ + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_fingerprint_user_idx": { + "name": "user_fingerprint_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_fingerprint_user_id_user_id_fk": { + "name": "user_fingerprint_user_id_user_id_fk", + "tableFrom": "user_fingerprint", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_library": { + "name": "user_library", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "playtime_2w": { + "name": "playtime_2w", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "playtime_forever": { + "name": "playtime_forever", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_played": { + "name": "last_played", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_library_user_game_unique": { + "name": "user_library_user_game_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_user_idx": { + "name": "user_library_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_game_idx": { + "name": "user_library_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_library_user_id_user_id_fk": { + "name": "user_library_user_id_user_id_fk", + "tableFrom": "user_library", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_library_game_id_game_id_fk": { + "name": "user_library_game_id_game_id_fk", + "tableFrom": "user_library", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.linked_account": { + "name": "linked_account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "linked_account_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile": { + "name": "profile", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "linked_account_provider_unique": { + "name": "linked_account_provider_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "linked_account_user_idx": { + "name": "linked_account_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "linked_account_user_id_user_id_fk": { + "name": "linked_account_user_id_user_id_fk", + "tableFrom": "linked_account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "email is not null and time_deleted is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "verification_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_user_kind_idx": { + "name": "verification_user_kind_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "verification_user_id_user_id_fk": { + "name": "verification_user_id_user_id_fk", + "tableFrom": "verification", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist_entry": { + "name": "waitlist_entry", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'machines'" + } + }, + "indexes": { + "waitlist_entry_email_unique": { + "name": "waitlist_entry_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "waitlist_entry_source_idx": { + "name": "waitlist_entry_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.box_state": { + "name": "box_state", + "schema": "public", + "values": ["created", "running", "stopped"] + }, + "public.box_tier": { + "name": "box_tier", + "schema": "public", + "values": ["xs", "sm", "md", "lg", "xl"] + }, + "public.depot_status": { + "name": "depot_status", + "schema": "public", + "values": ["pending", "downloading", "complete", "error", "deleted"] + }, + "public.game_download_status": { + "name": "game_download_status", + "schema": "public", + "values": ["pending", "verifying", "downloading", "ready", "failed"] + }, + "public.session_state": { + "name": "session_state", + "schema": "public", + "values": ["requested", "starting", "live", "ended", "failed"] + }, + "public.team_member_role": { + "name": "team_member_role", + "schema": "public", + "values": ["owner", "admin", "member"] + }, + "public.linked_account_provider": { + "name": "linked_account_provider", + "schema": "public", + "values": ["steam", "ssh", "discord"] + }, + "public.verification_kind": { + "name": "verification_kind", + "schema": "public", + "values": ["email"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } } diff --git a/packages/core/migrations/meta/0010_snapshot.json b/packages/core/migrations/meta/0010_snapshot.json index 3699756f..7888e818 100644 --- a/packages/core/migrations/meta/0010_snapshot.json +++ b/packages/core/migrations/meta/0010_snapshot.json @@ -1,2451 +1,2325 @@ { - "id": "8915fb5b-8f0d-4f6f-a808-27b19bb4604a", - "prevId": "b26664d8-eebf-4563-9c3c-7e00e41b646b", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.access_token": { - "name": "access_token", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "owner_user_id": { - "name": "owner_user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token_hash": { - "name": "token_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "last_used": { - "name": "last_used", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "access_token_hash_unique": { - "name": "access_token_hash_unique", - "columns": [ - { - "expression": "token_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "access_token_owner_idx": { - "name": "access_token_owner_idx", - "columns": [ - { - "expression": "owner_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "access_token_team_idx": { - "name": "access_token_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "access_token_owner_user_id_user_id_fk": { - "name": "access_token_owner_user_id_user_id_fk", - "tableFrom": "access_token", - "tableTo": "user", - "columnsFrom": [ - "owner_user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "access_token_team_id_team_id_fk": { - "name": "access_token_team_id_team_id_fk", - "tableFrom": "access_token", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.device_grant": { - "name": "device_grant", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "device_code_hash": { - "name": "device_code_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_code": { - "name": "user_code", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "client_id": { - "name": "client_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "device_grant_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "poll_interval": { - "name": "poll_interval", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "last_polled_at": { - "name": "last_polled_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "subject": { - "name": "subject", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "device_grant_device_code_unique": { - "name": "device_grant_device_code_unique", - "columns": [ - { - "expression": "device_code_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "device_grant_user_code_unique": { - "name": "device_grant_user_code_unique", - "columns": [ - { - "expression": "user_code", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.box": { - "name": "box", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "machine_id": { - "name": "machine_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "label": { - "name": "label", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tier": { - "name": "tier", - "type": "box_tier", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'sm'" - }, - "state": { - "name": "state", - "type": "box_state", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'created'" - }, - "stop_reason": { - "name": "stop_reason", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stop_clean": { - "name": "stop_clean", - "type": "boolean", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "box_user_idx": { - "name": "box_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "box_machine_idx": { - "name": "box_machine_idx", - "columns": [ - { - "expression": "machine_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "box_user_id_user_id_fk": { - "name": "box_user_id_user_id_fk", - "tableFrom": "box", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "box_machine_id_machine_id_fk": { - "name": "box_machine_id_machine_id_fk", - "tableFrom": "box", - "tableTo": "machine", - "columnsFrom": [ - "machine_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game_depot": { - "name": "game_depot", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "depot_id": { - "name": "depot_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "branch": { - "name": "branch", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'public'" - }, - "steam_manifest_id": { - "name": "steam_manifest_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "steam_build_id": { - "name": "steam_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "installed_manifest_id": { - "name": "installed_manifest_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "installed_build_id": { - "name": "installed_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "size_download": { - "name": "size_download", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "size_on_disk": { - "name": "size_on_disk", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "depot_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "oslist": { - "name": "oslist", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_depot_unique": { - "name": "game_depot_unique", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "depot_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "branch", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_depot_game_idx": { - "name": "game_depot_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_depot_updates_idx": { - "name": "game_depot_updates_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"game_depot\".\"installed_manifest_id\" is distinct from \"game_depot\".\"steam_manifest_id\"", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "game_depot_game_id_game_id_fk": { - "name": "game_depot_game_id_game_id_fk", - "tableFrom": "game_depot", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game_download": { - "name": "game_download", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "host_id": { - "name": "host_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "game_download_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "progress_bytes": { - "name": "progress_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": false, - "default": 0 - }, - "total_bytes": { - "name": "total_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "time_started": { - "name": "time_started", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_completed": { - "name": "time_completed", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_download_host_game_unique": { - "name": "game_download_host_game_unique", - "columns": [ - { - "expression": "host_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_download_game_idx": { - "name": "game_download_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_download_host_status_idx": { - "name": "game_download_host_status_idx", - "columns": [ - { - "expression": "host_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "game_download_host_id_machine_id_fk": { - "name": "game_download_host_id_machine_id_fk", - "tableFrom": "game_download", - "tableTo": "machine", - "columnsFrom": [ - "host_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "game_download_game_id_game_id_fk": { - "name": "game_download_game_id_game_id_fk", - "tableFrom": "game_download", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game": { - "name": "game", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "steam_app_id": { - "name": "steam_app_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "aliases": { - "name": "aliases", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "client_icon": { - "name": "client_icon", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "icon": { - "name": "icon", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "short_description": { - "name": "short_description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "developers": { - "name": "developers", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "publishers": { - "name": "publishers", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "primary_genre": { - "name": "primary_genre", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "genres": { - "name": "genres", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "categories": { - "name": "categories", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "oslist": { - "name": "oslist", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "size_download": { - "name": "size_download", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "size_on_disk": { - "name": "size_on_disk", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "controller_support": { - "name": "controller_support", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "steam_deck_compat": { - "name": "steam_deck_compat", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "review_score_percent": { - "name": "review_score_percent", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "review_count": { - "name": "review_count", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "metacritic_score": { - "name": "metacritic_score", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "steam_change_number": { - "name": "steam_change_number", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "public_build_id": { - "name": "public_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "release_date_utc": { - "name": "release_date_utc", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_enriched": { - "name": "time_enriched", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_slug_unique": { - "name": "game_slug_unique", - "columns": [ - { - "expression": "slug", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_app_id_unique": { - "name": "game_app_id_unique", - "columns": [ - { - "expression": "steam_app_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "game_steam_app_id_unique": { - "name": "game_steam_app_id_unique", - "nullsNotDistinct": false, - "columns": [ - "steam_app_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.machine": { - "name": "machine", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "owner_user_id": { - "name": "owner_user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "label": { - "name": "label", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "secret_hash": { - "name": "secret_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "last_seen": { - "name": "last_seen", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "machine_secret_hash_unique": { - "name": "machine_secret_hash_unique", - "columns": [ - { - "expression": "secret_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_owner_idx": { - "name": "machine_owner_idx", - "columns": [ - { - "expression": "owner_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_team_idx": { - "name": "machine_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "machine_owner_user_id_user_id_fk": { - "name": "machine_owner_user_id_user_id_fk", - "tableFrom": "machine", - "tableTo": "user", - "columnsFrom": [ - "owner_user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "machine_team_id_team_id_fk": { - "name": "machine_team_id_team_id_fk", - "tableFrom": "machine", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pairing_code": { - "name": "pairing_code", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "code": { - "name": "code", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "target_user_id": { - "name": "target_user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "new_fingerprint": { - "name": "new_fingerprint", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "claimed_at": { - "name": "claimed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "is_claimed": { - "name": "is_claimed", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": { - "pairing_code_code_unique": { - "name": "pairing_code_code_unique", - "columns": [ - { - "expression": "code", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "pairing_code_target_user_idx": { - "name": "pairing_code_target_user_idx", - "columns": [ - { - "expression": "target_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "box_id": { - "name": "box_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "linked_account_id": { - "name": "linked_account_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "state": { - "name": "state", - "type": "session_state", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'requested'" - }, - "ticket": { - "name": "ticket", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "time_started": { - "name": "time_started", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_stopped": { - "name": "time_stopped", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "claim_token": { - "name": "claim_token", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "session_box_idx": { - "name": "session_box_idx", - "columns": [ - { - "expression": "box_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_state_idx": { - "name": "session_state_idx", - "columns": [ - { - "expression": "state", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_box_active_unique": { - "name": "session_box_active_unique", - "columns": [ - { - "expression": "box_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "time_stopped is null and time_deleted is null", - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_started_idx": { - "name": "session_started_idx", - "columns": [ - { - "expression": "time_started", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "session_box_id_box_id_fk": { - "name": "session_box_id_box_id_fk", - "tableFrom": "session", - "tableTo": "box", - "columnsFrom": [ - "box_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "session_game_id_game_id_fk": { - "name": "session_game_id_game_id_fk", - "tableFrom": "session", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - }, - "session_linked_account_id_linked_account_id_fk": { - "name": "session_linked_account_id_linked_account_id_fk", - "tableFrom": "session", - "tableTo": "linked_account", - "columnsFrom": [ - "linked_account_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.team_member": { - "name": "team_member", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "role": { - "name": "role", - "type": "team_member_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'member'" - } - }, - "indexes": { - "team_member_team_user_unique": { - "name": "team_member_team_user_unique", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "team_member_team_idx": { - "name": "team_member_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "team_member_user_idx": { - "name": "team_member_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "team_member_team_id_team_id_fk": { - "name": "team_member_team_id_team_id_fk", - "tableFrom": "team_member", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "team_member_user_id_user_id_fk": { - "name": "team_member_user_id_user_id_fk", - "tableFrom": "team_member", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.team": { - "name": "team", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "owner_id": { - "name": "owner_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "billing_email": { - "name": "billing_email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "plan": { - "name": "plan", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'free'" - }, - "subscription_status": { - "name": "subscription_status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'active'" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "team_owner_id_user_id_fk": { - "name": "team_owner_id_user_id_fk", - "tableFrom": "team", - "tableTo": "user", - "columnsFrom": [ - "owner_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "team_slug_unique": { - "name": "team_slug_unique", - "nullsNotDistinct": false, - "columns": [ - "slug" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_fingerprint": { - "name": "user_fingerprint", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "fingerprint": { - "name": "fingerprint", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_seen": { - "name": "last_seen", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_fingerprint_fingerprint_unique": { - "name": "user_fingerprint_fingerprint_unique", - "columns": [ - { - "expression": "fingerprint", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_fingerprint_user_idx": { - "name": "user_fingerprint_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "user_fingerprint_user_id_user_id_fk": { - "name": "user_fingerprint_user_id_user_id_fk", - "tableFrom": "user_fingerprint", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_library": { - "name": "user_library", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "playtime_2w": { - "name": "playtime_2w", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "playtime_forever": { - "name": "playtime_forever", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "last_played": { - "name": "last_played", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_library_user_game_unique": { - "name": "user_library_user_game_unique", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_library_user_idx": { - "name": "user_library_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_library_game_idx": { - "name": "user_library_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "user_library_user_id_user_id_fk": { - "name": "user_library_user_id_user_id_fk", - "tableFrom": "user_library", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "user_library_game_id_game_id_fk": { - "name": "user_library_game_id_game_id_fk", - "tableFrom": "user_library", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.linked_account": { - "name": "linked_account", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "linked_account_provider", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "provider_account_id": { - "name": "provider_account_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "profile": { - "name": "profile", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "linked_account_provider_unique": { - "name": "linked_account_provider_unique", - "columns": [ - { - "expression": "provider", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "provider_account_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "linked_account_user_idx": { - "name": "linked_account_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "linked_account_user_id_user_id_fk": { - "name": "linked_account_user_id_user_id_fk", - "tableFrom": "linked_account", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email_verified": { - "name": "email_verified", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_email_unique": { - "name": "user_email_unique", - "columns": [ - { - "expression": "email", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "email is not null and time_deleted is null", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verification": { - "name": "verification", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "verification_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "code_hash": { - "name": "code_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "attempts": { - "name": "attempts", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "consumed_at": { - "name": "consumed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "verification_user_kind_idx": { - "name": "verification_user_kind_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "verification_user_id_user_id_fk": { - "name": "verification_user_id_user_id_fk", - "tableFrom": "verification", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.waitlist_entry": { - "name": "waitlist_entry", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source": { - "name": "source", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'machines'" - } - }, - "indexes": { - "waitlist_entry_email_unique": { - "name": "waitlist_entry_email_unique", - "columns": [ - { - "expression": "email", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "waitlist_entry_source_idx": { - "name": "waitlist_entry_source_idx", - "columns": [ - { - "expression": "source", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.device_grant_status": { - "name": "device_grant_status", - "schema": "public", - "values": [ - "pending", - "approved", - "denied" - ] - }, - "public.box_state": { - "name": "box_state", - "schema": "public", - "values": [ - "created", - "running", - "stopped" - ] - }, - "public.box_tier": { - "name": "box_tier", - "schema": "public", - "values": [ - "xs", - "sm", - "md", - "lg", - "xl" - ] - }, - "public.depot_status": { - "name": "depot_status", - "schema": "public", - "values": [ - "pending", - "downloading", - "complete", - "error", - "deleted" - ] - }, - "public.game_download_status": { - "name": "game_download_status", - "schema": "public", - "values": [ - "pending", - "verifying", - "downloading", - "ready", - "failed" - ] - }, - "public.session_state": { - "name": "session_state", - "schema": "public", - "values": [ - "requested", - "starting", - "live", - "ended", - "failed" - ] - }, - "public.team_member_role": { - "name": "team_member_role", - "schema": "public", - "values": [ - "owner", - "admin", - "member" - ] - }, - "public.linked_account_provider": { - "name": "linked_account_provider", - "schema": "public", - "values": [ - "steam", - "ssh", - "discord" - ] - }, - "public.verification_kind": { - "name": "verification_kind", - "schema": "public", - "values": [ - "email" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file + "id": "8915fb5b-8f0d-4f6f-a808-27b19bb4604a", + "prevId": "b26664d8-eebf-4563-9c3c-7e00e41b646b", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used": { + "name": "last_used", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "access_token_hash_unique": { + "name": "access_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_owner_idx": { + "name": "access_token_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_team_idx": { + "name": "access_token_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "access_token_owner_user_id_user_id_fk": { + "name": "access_token_owner_user_id_user_id_fk", + "tableFrom": "access_token", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "access_token_team_id_team_id_fk": { + "name": "access_token_team_id_team_id_fk", + "tableFrom": "access_token", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.device_grant": { + "name": "device_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "device_code_hash": { + "name": "device_code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_code": { + "name": "user_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "device_grant_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "poll_interval": { + "name": "poll_interval", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "last_polled_at": { + "name": "last_polled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "device_grant_device_code_unique": { + "name": "device_grant_device_code_unique", + "columns": [ + { + "expression": "device_code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "device_grant_user_code_unique": { + "name": "device_grant_user_code_unique", + "columns": [ + { + "expression": "user_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.box": { + "name": "box", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "machine_id": { + "name": "machine_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tier": { + "name": "tier", + "type": "box_tier", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'sm'" + }, + "state": { + "name": "state", + "type": "box_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'created'" + }, + "stop_reason": { + "name": "stop_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stop_clean": { + "name": "stop_clean", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "box_user_idx": { + "name": "box_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "box_machine_idx": { + "name": "box_machine_idx", + "columns": [ + { + "expression": "machine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "box_user_id_user_id_fk": { + "name": "box_user_id_user_id_fk", + "tableFrom": "box", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "box_machine_id_machine_id_fk": { + "name": "box_machine_id_machine_id_fk", + "tableFrom": "box", + "tableTo": "machine", + "columnsFrom": ["machine_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_depot": { + "name": "game_depot", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "depot_id": { + "name": "depot_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "steam_manifest_id": { + "name": "steam_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_build_id": { + "name": "steam_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "installed_manifest_id": { + "name": "installed_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_build_id": { + "name": "installed_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "depot_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_depot_unique": { + "name": "game_depot_unique", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "depot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_game_idx": { + "name": "game_depot_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_updates_idx": { + "name": "game_depot_updates_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"game_depot\".\"installed_manifest_id\" is distinct from \"game_depot\".\"steam_manifest_id\"", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_depot_game_id_game_id_fk": { + "name": "game_depot_game_id_game_id_fk", + "tableFrom": "game_depot", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_download": { + "name": "game_download", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "host_id": { + "name": "host_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "game_download_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "progress_bytes": { + "name": "progress_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "total_bytes": { + "name": "total_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "time_started": { + "name": "time_started", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_completed": { + "name": "time_completed", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_download_host_game_unique": { + "name": "game_download_host_game_unique", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_game_idx": { + "name": "game_download_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_host_status_idx": { + "name": "game_download_host_status_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_download_host_id_machine_id_fk": { + "name": "game_download_host_id_machine_id_fk", + "tableFrom": "game_download", + "tableTo": "machine", + "columnsFrom": ["host_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "game_download_game_id_game_id_fk": { + "name": "game_download_game_id_game_id_fk", + "tableFrom": "game_download", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game": { + "name": "game", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "steam_app_id": { + "name": "steam_app_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aliases": { + "name": "aliases", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_icon": { + "name": "client_icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "short_description": { + "name": "short_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "developers": { + "name": "developers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "publishers": { + "name": "publishers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "primary_genre": { + "name": "primary_genre", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "genres": { + "name": "genres", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "controller_support": { + "name": "controller_support", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_deck_compat": { + "name": "steam_deck_compat", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "review_score_percent": { + "name": "review_score_percent", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "review_count": { + "name": "review_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "metacritic_score": { + "name": "metacritic_score", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "steam_change_number": { + "name": "steam_change_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "public_build_id": { + "name": "public_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "release_date_utc": { + "name": "release_date_utc", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_enriched": { + "name": "time_enriched", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_slug_unique": { + "name": "game_slug_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_app_id_unique": { + "name": "game_app_id_unique", + "columns": [ + { + "expression": "steam_app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "game_steam_app_id_unique": { + "name": "game_steam_app_id_unique", + "nullsNotDistinct": false, + "columns": ["steam_app_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.machine": { + "name": "machine", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "machine_secret_hash_unique": { + "name": "machine_secret_hash_unique", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_owner_idx": { + "name": "machine_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_team_idx": { + "name": "machine_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "machine_owner_user_id_user_id_fk": { + "name": "machine_owner_user_id_user_id_fk", + "tableFrom": "machine", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "machine_team_id_team_id_fk": { + "name": "machine_team_id_team_id_fk", + "tableFrom": "machine", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pairing_code": { + "name": "pairing_code", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_fingerprint": { + "name": "new_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "is_claimed": { + "name": "is_claimed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "pairing_code_code_unique": { + "name": "pairing_code_code_unique", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pairing_code_target_user_idx": { + "name": "pairing_code_target_user_idx", + "columns": [ + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "box_id": { + "name": "box_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "linked_account_id": { + "name": "linked_account_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "session_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "ticket": { + "name": "ticket", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "time_started": { + "name": "time_started", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_stopped": { + "name": "time_stopped", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_token": { + "name": "claim_token", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_box_idx": { + "name": "session_box_idx", + "columns": [ + { + "expression": "box_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_state_idx": { + "name": "session_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_box_active_unique": { + "name": "session_box_active_unique", + "columns": [ + { + "expression": "box_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "time_stopped is null and time_deleted is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_started_idx": { + "name": "session_started_idx", + "columns": [ + { + "expression": "time_started", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_box_id_box_id_fk": { + "name": "session_box_id_box_id_fk", + "tableFrom": "session", + "tableTo": "box", + "columnsFrom": ["box_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_game_id_game_id_fk": { + "name": "session_game_id_game_id_fk", + "tableFrom": "session", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "session_linked_account_id_linked_account_id_fk": { + "name": "session_linked_account_id_linked_account_id_fk", + "tableFrom": "session", + "tableTo": "linked_account", + "columnsFrom": ["linked_account_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_member": { + "name": "team_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "team_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + } + }, + "indexes": { + "team_member_team_user_unique": { + "name": "team_member_team_user_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_team_idx": { + "name": "team_member_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_user_idx": { + "name": "team_member_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_member_team_id_team_id_fk": { + "name": "team_member_team_id_team_id_fk", + "tableFrom": "team_member", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_member_user_id_user_id_fk": { + "name": "team_member_user_id_user_id_fk", + "tableFrom": "team_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team": { + "name": "team", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "billing_email": { + "name": "billing_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "subscription_status": { + "name": "subscription_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "team_owner_id_user_id_fk": { + "name": "team_owner_id_user_id_fk", + "tableFrom": "team", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_slug_unique": { + "name": "team_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_fingerprint": { + "name": "user_fingerprint", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_fingerprint_fingerprint_unique": { + "name": "user_fingerprint_fingerprint_unique", + "columns": [ + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_fingerprint_user_idx": { + "name": "user_fingerprint_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_fingerprint_user_id_user_id_fk": { + "name": "user_fingerprint_user_id_user_id_fk", + "tableFrom": "user_fingerprint", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_library": { + "name": "user_library", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "playtime_2w": { + "name": "playtime_2w", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "playtime_forever": { + "name": "playtime_forever", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_played": { + "name": "last_played", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_library_user_game_unique": { + "name": "user_library_user_game_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_user_idx": { + "name": "user_library_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_game_idx": { + "name": "user_library_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_library_user_id_user_id_fk": { + "name": "user_library_user_id_user_id_fk", + "tableFrom": "user_library", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_library_game_id_game_id_fk": { + "name": "user_library_game_id_game_id_fk", + "tableFrom": "user_library", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.linked_account": { + "name": "linked_account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "linked_account_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile": { + "name": "profile", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "linked_account_provider_unique": { + "name": "linked_account_provider_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "linked_account_user_idx": { + "name": "linked_account_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "linked_account_user_id_user_id_fk": { + "name": "linked_account_user_id_user_id_fk", + "tableFrom": "linked_account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "email is not null and time_deleted is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "verification_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_user_kind_idx": { + "name": "verification_user_kind_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "verification_user_id_user_id_fk": { + "name": "verification_user_id_user_id_fk", + "tableFrom": "verification", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist_entry": { + "name": "waitlist_entry", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'machines'" + } + }, + "indexes": { + "waitlist_entry_email_unique": { + "name": "waitlist_entry_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "waitlist_entry_source_idx": { + "name": "waitlist_entry_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.device_grant_status": { + "name": "device_grant_status", + "schema": "public", + "values": ["pending", "approved", "denied"] + }, + "public.box_state": { + "name": "box_state", + "schema": "public", + "values": ["created", "running", "stopped"] + }, + "public.box_tier": { + "name": "box_tier", + "schema": "public", + "values": ["xs", "sm", "md", "lg", "xl"] + }, + "public.depot_status": { + "name": "depot_status", + "schema": "public", + "values": ["pending", "downloading", "complete", "error", "deleted"] + }, + "public.game_download_status": { + "name": "game_download_status", + "schema": "public", + "values": ["pending", "verifying", "downloading", "ready", "failed"] + }, + "public.session_state": { + "name": "session_state", + "schema": "public", + "values": ["requested", "starting", "live", "ended", "failed"] + }, + "public.team_member_role": { + "name": "team_member_role", + "schema": "public", + "values": ["owner", "admin", "member"] + }, + "public.linked_account_provider": { + "name": "linked_account_provider", + "schema": "public", + "values": ["steam", "ssh", "discord"] + }, + "public.verification_kind": { + "name": "verification_kind", + "schema": "public", + "values": ["email"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/core/migrations/meta/0011_snapshot.json b/packages/core/migrations/meta/0011_snapshot.json index 2b48cb57..78937467 100644 --- a/packages/core/migrations/meta/0011_snapshot.json +++ b/packages/core/migrations/meta/0011_snapshot.json @@ -1,2813 +1,2684 @@ { - "id": "6fdda454-c7ee-42c1-931b-fa595385aac0", - "prevId": "8915fb5b-8f0d-4f6f-a808-27b19bb4604a", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.access_token": { - "name": "access_token", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "owner_user_id": { - "name": "owner_user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token_hash": { - "name": "token_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "last_used": { - "name": "last_used", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "access_token_hash_unique": { - "name": "access_token_hash_unique", - "columns": [ - { - "expression": "token_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "access_token_owner_idx": { - "name": "access_token_owner_idx", - "columns": [ - { - "expression": "owner_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "access_token_team_idx": { - "name": "access_token_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "access_token_owner_user_id_user_id_fk": { - "name": "access_token_owner_user_id_user_id_fk", - "tableFrom": "access_token", - "tableTo": "user", - "columnsFrom": [ - "owner_user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "access_token_team_id_team_id_fk": { - "name": "access_token_team_id_team_id_fk", - "tableFrom": "access_token", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.authorization_code": { - "name": "authorization_code", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "code_hash": { - "name": "code_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "payload": { - "name": "payload", - "type": "jsonb", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "authorization_code_hash_unique": { - "name": "authorization_code_hash_unique", - "columns": [ - { - "expression": "code_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.device_grant": { - "name": "device_grant", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "device_code_hash": { - "name": "device_code_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_code": { - "name": "user_code", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "client_id": { - "name": "client_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "device_grant_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "poll_interval": { - "name": "poll_interval", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "last_polled_at": { - "name": "last_polled_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "subject": { - "name": "subject", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "device_grant_device_code_unique": { - "name": "device_grant_device_code_unique", - "columns": [ - { - "expression": "device_code_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "device_grant_user_code_unique": { - "name": "device_grant_user_code_unique", - "columns": [ - { - "expression": "user_code", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.refresh_token": { - "name": "refresh_token", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "subject": { - "name": "subject", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token_hash": { - "name": "token_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "time_used": { - "name": "time_used", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "payload": { - "name": "payload", - "type": "jsonb", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "refresh_token_hash_unique": { - "name": "refresh_token_hash_unique", - "columns": [ - { - "expression": "token_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "refresh_token_subject_idx": { - "name": "refresh_token_subject_idx", - "columns": [ - { - "expression": "subject", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.auth_key": { - "name": "auth_key", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "key_id": { - "name": "key_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "auth_key_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "alg": { - "name": "alg", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "public_key": { - "name": "public_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "private_key": { - "name": "private_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expired_at": { - "name": "expired_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "auth_key_key_id_unique": { - "name": "auth_key_key_id_unique", - "columns": [ - { - "expression": "key_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "auth_key_one_live_per_kind": { - "name": "auth_key_one_live_per_kind", - "columns": [ - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"auth_key\".\"expired_at\" is null", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.auth_kv": { - "name": "auth_kv", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "key": { - "name": "key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "value": { - "name": "value", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "auth_kv_key_unique": { - "name": "auth_kv_key_unique", - "columns": [ - { - "expression": "key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.box": { - "name": "box", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "machine_id": { - "name": "machine_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "label": { - "name": "label", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tier": { - "name": "tier", - "type": "box_tier", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'sm'" - }, - "state": { - "name": "state", - "type": "box_state", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'created'" - }, - "stop_reason": { - "name": "stop_reason", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stop_clean": { - "name": "stop_clean", - "type": "boolean", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "box_user_idx": { - "name": "box_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "box_machine_idx": { - "name": "box_machine_idx", - "columns": [ - { - "expression": "machine_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "box_user_id_user_id_fk": { - "name": "box_user_id_user_id_fk", - "tableFrom": "box", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "box_machine_id_machine_id_fk": { - "name": "box_machine_id_machine_id_fk", - "tableFrom": "box", - "tableTo": "machine", - "columnsFrom": [ - "machine_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game_depot": { - "name": "game_depot", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "depot_id": { - "name": "depot_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "branch": { - "name": "branch", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'public'" - }, - "steam_manifest_id": { - "name": "steam_manifest_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "steam_build_id": { - "name": "steam_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "installed_manifest_id": { - "name": "installed_manifest_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "installed_build_id": { - "name": "installed_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "size_download": { - "name": "size_download", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "size_on_disk": { - "name": "size_on_disk", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "depot_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "oslist": { - "name": "oslist", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_depot_unique": { - "name": "game_depot_unique", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "depot_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "branch", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_depot_game_idx": { - "name": "game_depot_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_depot_updates_idx": { - "name": "game_depot_updates_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"game_depot\".\"installed_manifest_id\" is distinct from \"game_depot\".\"steam_manifest_id\"", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "game_depot_game_id_game_id_fk": { - "name": "game_depot_game_id_game_id_fk", - "tableFrom": "game_depot", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game_download": { - "name": "game_download", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "host_id": { - "name": "host_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "game_download_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "progress_bytes": { - "name": "progress_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": false, - "default": 0 - }, - "total_bytes": { - "name": "total_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "time_started": { - "name": "time_started", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_completed": { - "name": "time_completed", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_download_host_game_unique": { - "name": "game_download_host_game_unique", - "columns": [ - { - "expression": "host_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_download_game_idx": { - "name": "game_download_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_download_host_status_idx": { - "name": "game_download_host_status_idx", - "columns": [ - { - "expression": "host_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "game_download_host_id_machine_id_fk": { - "name": "game_download_host_id_machine_id_fk", - "tableFrom": "game_download", - "tableTo": "machine", - "columnsFrom": [ - "host_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "game_download_game_id_game_id_fk": { - "name": "game_download_game_id_game_id_fk", - "tableFrom": "game_download", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game": { - "name": "game", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "steam_app_id": { - "name": "steam_app_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "aliases": { - "name": "aliases", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "client_icon": { - "name": "client_icon", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "icon": { - "name": "icon", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "short_description": { - "name": "short_description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "developers": { - "name": "developers", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "publishers": { - "name": "publishers", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "primary_genre": { - "name": "primary_genre", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "genres": { - "name": "genres", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "categories": { - "name": "categories", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "oslist": { - "name": "oslist", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "size_download": { - "name": "size_download", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "size_on_disk": { - "name": "size_on_disk", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "controller_support": { - "name": "controller_support", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "steam_deck_compat": { - "name": "steam_deck_compat", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "review_score_percent": { - "name": "review_score_percent", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "review_count": { - "name": "review_count", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "metacritic_score": { - "name": "metacritic_score", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "steam_change_number": { - "name": "steam_change_number", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "public_build_id": { - "name": "public_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "release_date_utc": { - "name": "release_date_utc", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_enriched": { - "name": "time_enriched", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_slug_unique": { - "name": "game_slug_unique", - "columns": [ - { - "expression": "slug", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_app_id_unique": { - "name": "game_app_id_unique", - "columns": [ - { - "expression": "steam_app_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "game_steam_app_id_unique": { - "name": "game_steam_app_id_unique", - "nullsNotDistinct": false, - "columns": [ - "steam_app_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.machine": { - "name": "machine", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "owner_user_id": { - "name": "owner_user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "label": { - "name": "label", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "secret_hash": { - "name": "secret_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "last_seen": { - "name": "last_seen", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "machine_secret_hash_unique": { - "name": "machine_secret_hash_unique", - "columns": [ - { - "expression": "secret_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_owner_idx": { - "name": "machine_owner_idx", - "columns": [ - { - "expression": "owner_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_team_idx": { - "name": "machine_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "machine_owner_user_id_user_id_fk": { - "name": "machine_owner_user_id_user_id_fk", - "tableFrom": "machine", - "tableTo": "user", - "columnsFrom": [ - "owner_user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "machine_team_id_team_id_fk": { - "name": "machine_team_id_team_id_fk", - "tableFrom": "machine", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pairing_code": { - "name": "pairing_code", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "code": { - "name": "code", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "target_user_id": { - "name": "target_user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "new_fingerprint": { - "name": "new_fingerprint", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "claimed_at": { - "name": "claimed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "is_claimed": { - "name": "is_claimed", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": { - "pairing_code_code_unique": { - "name": "pairing_code_code_unique", - "columns": [ - { - "expression": "code", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "pairing_code_target_user_idx": { - "name": "pairing_code_target_user_idx", - "columns": [ - { - "expression": "target_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "box_id": { - "name": "box_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "linked_account_id": { - "name": "linked_account_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "state": { - "name": "state", - "type": "session_state", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'requested'" - }, - "ticket": { - "name": "ticket", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "time_started": { - "name": "time_started", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_stopped": { - "name": "time_stopped", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "claim_token": { - "name": "claim_token", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "session_box_idx": { - "name": "session_box_idx", - "columns": [ - { - "expression": "box_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_state_idx": { - "name": "session_state_idx", - "columns": [ - { - "expression": "state", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_box_active_unique": { - "name": "session_box_active_unique", - "columns": [ - { - "expression": "box_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "time_stopped is null and time_deleted is null", - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_started_idx": { - "name": "session_started_idx", - "columns": [ - { - "expression": "time_started", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "session_box_id_box_id_fk": { - "name": "session_box_id_box_id_fk", - "tableFrom": "session", - "tableTo": "box", - "columnsFrom": [ - "box_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "session_game_id_game_id_fk": { - "name": "session_game_id_game_id_fk", - "tableFrom": "session", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - }, - "session_linked_account_id_linked_account_id_fk": { - "name": "session_linked_account_id_linked_account_id_fk", - "tableFrom": "session", - "tableTo": "linked_account", - "columnsFrom": [ - "linked_account_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.team_member": { - "name": "team_member", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "role": { - "name": "role", - "type": "team_member_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'member'" - } - }, - "indexes": { - "team_member_team_user_unique": { - "name": "team_member_team_user_unique", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "team_member_team_idx": { - "name": "team_member_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "team_member_user_idx": { - "name": "team_member_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "team_member_team_id_team_id_fk": { - "name": "team_member_team_id_team_id_fk", - "tableFrom": "team_member", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "team_member_user_id_user_id_fk": { - "name": "team_member_user_id_user_id_fk", - "tableFrom": "team_member", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.team": { - "name": "team", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "owner_id": { - "name": "owner_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "billing_email": { - "name": "billing_email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "plan": { - "name": "plan", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'free'" - }, - "subscription_status": { - "name": "subscription_status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'active'" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "team_owner_id_user_id_fk": { - "name": "team_owner_id_user_id_fk", - "tableFrom": "team", - "tableTo": "user", - "columnsFrom": [ - "owner_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "team_slug_unique": { - "name": "team_slug_unique", - "nullsNotDistinct": false, - "columns": [ - "slug" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_fingerprint": { - "name": "user_fingerprint", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "fingerprint": { - "name": "fingerprint", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_seen": { - "name": "last_seen", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_fingerprint_fingerprint_unique": { - "name": "user_fingerprint_fingerprint_unique", - "columns": [ - { - "expression": "fingerprint", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_fingerprint_user_idx": { - "name": "user_fingerprint_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "user_fingerprint_user_id_user_id_fk": { - "name": "user_fingerprint_user_id_user_id_fk", - "tableFrom": "user_fingerprint", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_library": { - "name": "user_library", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "playtime_2w": { - "name": "playtime_2w", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "playtime_forever": { - "name": "playtime_forever", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "last_played": { - "name": "last_played", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_library_user_game_unique": { - "name": "user_library_user_game_unique", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_library_user_idx": { - "name": "user_library_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_library_game_idx": { - "name": "user_library_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "user_library_user_id_user_id_fk": { - "name": "user_library_user_id_user_id_fk", - "tableFrom": "user_library", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "user_library_game_id_game_id_fk": { - "name": "user_library_game_id_game_id_fk", - "tableFrom": "user_library", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.linked_account": { - "name": "linked_account", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "linked_account_provider", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "provider_account_id": { - "name": "provider_account_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "profile": { - "name": "profile", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "linked_account_provider_unique": { - "name": "linked_account_provider_unique", - "columns": [ - { - "expression": "provider", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "provider_account_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "linked_account_user_idx": { - "name": "linked_account_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "linked_account_user_id_user_id_fk": { - "name": "linked_account_user_id_user_id_fk", - "tableFrom": "linked_account", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email_verified": { - "name": "email_verified", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_email_unique": { - "name": "user_email_unique", - "columns": [ - { - "expression": "email", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "email is not null and time_deleted is null", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verification": { - "name": "verification", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "verification_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "code_hash": { - "name": "code_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "attempts": { - "name": "attempts", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "consumed_at": { - "name": "consumed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "verification_user_kind_idx": { - "name": "verification_user_kind_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "verification_user_id_user_id_fk": { - "name": "verification_user_id_user_id_fk", - "tableFrom": "verification", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.waitlist_entry": { - "name": "waitlist_entry", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source": { - "name": "source", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'machines'" - } - }, - "indexes": { - "waitlist_entry_email_unique": { - "name": "waitlist_entry_email_unique", - "columns": [ - { - "expression": "email", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "waitlist_entry_source_idx": { - "name": "waitlist_entry_source_idx", - "columns": [ - { - "expression": "source", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.device_grant_status": { - "name": "device_grant_status", - "schema": "public", - "values": [ - "pending", - "approved", - "denied" - ] - }, - "public.auth_key_kind": { - "name": "auth_key_kind", - "schema": "public", - "values": [ - "signing", - "encryption" - ] - }, - "public.box_state": { - "name": "box_state", - "schema": "public", - "values": [ - "created", - "running", - "stopped" - ] - }, - "public.box_tier": { - "name": "box_tier", - "schema": "public", - "values": [ - "xs", - "sm", - "md", - "lg", - "xl" - ] - }, - "public.depot_status": { - "name": "depot_status", - "schema": "public", - "values": [ - "pending", - "downloading", - "complete", - "error", - "deleted" - ] - }, - "public.game_download_status": { - "name": "game_download_status", - "schema": "public", - "values": [ - "pending", - "verifying", - "downloading", - "ready", - "failed" - ] - }, - "public.session_state": { - "name": "session_state", - "schema": "public", - "values": [ - "requested", - "starting", - "live", - "ended", - "failed" - ] - }, - "public.team_member_role": { - "name": "team_member_role", - "schema": "public", - "values": [ - "owner", - "admin", - "member" - ] - }, - "public.linked_account_provider": { - "name": "linked_account_provider", - "schema": "public", - "values": [ - "steam", - "ssh", - "discord" - ] - }, - "public.verification_kind": { - "name": "verification_kind", - "schema": "public", - "values": [ - "email" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file + "id": "6fdda454-c7ee-42c1-931b-fa595385aac0", + "prevId": "8915fb5b-8f0d-4f6f-a808-27b19bb4604a", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used": { + "name": "last_used", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "access_token_hash_unique": { + "name": "access_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_owner_idx": { + "name": "access_token_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_team_idx": { + "name": "access_token_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "access_token_owner_user_id_user_id_fk": { + "name": "access_token_owner_user_id_user_id_fk", + "tableFrom": "access_token", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "access_token_team_id_team_id_fk": { + "name": "access_token_team_id_team_id_fk", + "tableFrom": "access_token", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.authorization_code": { + "name": "authorization_code", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "authorization_code_hash_unique": { + "name": "authorization_code_hash_unique", + "columns": [ + { + "expression": "code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.device_grant": { + "name": "device_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "device_code_hash": { + "name": "device_code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_code": { + "name": "user_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "device_grant_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "poll_interval": { + "name": "poll_interval", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "last_polled_at": { + "name": "last_polled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "device_grant_device_code_unique": { + "name": "device_grant_device_code_unique", + "columns": [ + { + "expression": "device_code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "device_grant_user_code_unique": { + "name": "device_grant_user_code_unique", + "columns": [ + { + "expression": "user_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.refresh_token": { + "name": "refresh_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "time_used": { + "name": "time_used", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "refresh_token_hash_unique": { + "name": "refresh_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "refresh_token_subject_idx": { + "name": "refresh_token_subject_idx", + "columns": [ + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_key": { + "name": "auth_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "key_id": { + "name": "key_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "auth_key_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "alg": { + "name": "alg", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expired_at": { + "name": "expired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "auth_key_key_id_unique": { + "name": "auth_key_key_id_unique", + "columns": [ + { + "expression": "key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auth_key_one_live_per_kind": { + "name": "auth_key_one_live_per_kind", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"auth_key\".\"expired_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_kv": { + "name": "auth_kv", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "auth_kv_key_unique": { + "name": "auth_kv_key_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.box": { + "name": "box", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "machine_id": { + "name": "machine_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tier": { + "name": "tier", + "type": "box_tier", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'sm'" + }, + "state": { + "name": "state", + "type": "box_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'created'" + }, + "stop_reason": { + "name": "stop_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stop_clean": { + "name": "stop_clean", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "box_user_idx": { + "name": "box_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "box_machine_idx": { + "name": "box_machine_idx", + "columns": [ + { + "expression": "machine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "box_user_id_user_id_fk": { + "name": "box_user_id_user_id_fk", + "tableFrom": "box", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "box_machine_id_machine_id_fk": { + "name": "box_machine_id_machine_id_fk", + "tableFrom": "box", + "tableTo": "machine", + "columnsFrom": ["machine_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_depot": { + "name": "game_depot", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "depot_id": { + "name": "depot_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "steam_manifest_id": { + "name": "steam_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_build_id": { + "name": "steam_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "installed_manifest_id": { + "name": "installed_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_build_id": { + "name": "installed_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "depot_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_depot_unique": { + "name": "game_depot_unique", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "depot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_game_idx": { + "name": "game_depot_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_updates_idx": { + "name": "game_depot_updates_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"game_depot\".\"installed_manifest_id\" is distinct from \"game_depot\".\"steam_manifest_id\"", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_depot_game_id_game_id_fk": { + "name": "game_depot_game_id_game_id_fk", + "tableFrom": "game_depot", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_download": { + "name": "game_download", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "host_id": { + "name": "host_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "game_download_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "progress_bytes": { + "name": "progress_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "total_bytes": { + "name": "total_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "time_started": { + "name": "time_started", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_completed": { + "name": "time_completed", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_download_host_game_unique": { + "name": "game_download_host_game_unique", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_game_idx": { + "name": "game_download_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_host_status_idx": { + "name": "game_download_host_status_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_download_host_id_machine_id_fk": { + "name": "game_download_host_id_machine_id_fk", + "tableFrom": "game_download", + "tableTo": "machine", + "columnsFrom": ["host_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "game_download_game_id_game_id_fk": { + "name": "game_download_game_id_game_id_fk", + "tableFrom": "game_download", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game": { + "name": "game", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "steam_app_id": { + "name": "steam_app_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aliases": { + "name": "aliases", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_icon": { + "name": "client_icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "short_description": { + "name": "short_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "developers": { + "name": "developers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "publishers": { + "name": "publishers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "primary_genre": { + "name": "primary_genre", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "genres": { + "name": "genres", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "controller_support": { + "name": "controller_support", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_deck_compat": { + "name": "steam_deck_compat", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "review_score_percent": { + "name": "review_score_percent", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "review_count": { + "name": "review_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "metacritic_score": { + "name": "metacritic_score", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "steam_change_number": { + "name": "steam_change_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "public_build_id": { + "name": "public_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "release_date_utc": { + "name": "release_date_utc", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_enriched": { + "name": "time_enriched", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_slug_unique": { + "name": "game_slug_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_app_id_unique": { + "name": "game_app_id_unique", + "columns": [ + { + "expression": "steam_app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "game_steam_app_id_unique": { + "name": "game_steam_app_id_unique", + "nullsNotDistinct": false, + "columns": ["steam_app_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.machine": { + "name": "machine", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "machine_secret_hash_unique": { + "name": "machine_secret_hash_unique", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_owner_idx": { + "name": "machine_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_team_idx": { + "name": "machine_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "machine_owner_user_id_user_id_fk": { + "name": "machine_owner_user_id_user_id_fk", + "tableFrom": "machine", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "machine_team_id_team_id_fk": { + "name": "machine_team_id_team_id_fk", + "tableFrom": "machine", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pairing_code": { + "name": "pairing_code", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_fingerprint": { + "name": "new_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "is_claimed": { + "name": "is_claimed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "pairing_code_code_unique": { + "name": "pairing_code_code_unique", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pairing_code_target_user_idx": { + "name": "pairing_code_target_user_idx", + "columns": [ + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "box_id": { + "name": "box_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "linked_account_id": { + "name": "linked_account_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "session_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "ticket": { + "name": "ticket", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "time_started": { + "name": "time_started", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_stopped": { + "name": "time_stopped", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_token": { + "name": "claim_token", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_box_idx": { + "name": "session_box_idx", + "columns": [ + { + "expression": "box_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_state_idx": { + "name": "session_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_box_active_unique": { + "name": "session_box_active_unique", + "columns": [ + { + "expression": "box_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "time_stopped is null and time_deleted is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_started_idx": { + "name": "session_started_idx", + "columns": [ + { + "expression": "time_started", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_box_id_box_id_fk": { + "name": "session_box_id_box_id_fk", + "tableFrom": "session", + "tableTo": "box", + "columnsFrom": ["box_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_game_id_game_id_fk": { + "name": "session_game_id_game_id_fk", + "tableFrom": "session", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "session_linked_account_id_linked_account_id_fk": { + "name": "session_linked_account_id_linked_account_id_fk", + "tableFrom": "session", + "tableTo": "linked_account", + "columnsFrom": ["linked_account_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_member": { + "name": "team_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "team_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + } + }, + "indexes": { + "team_member_team_user_unique": { + "name": "team_member_team_user_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_team_idx": { + "name": "team_member_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_user_idx": { + "name": "team_member_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_member_team_id_team_id_fk": { + "name": "team_member_team_id_team_id_fk", + "tableFrom": "team_member", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_member_user_id_user_id_fk": { + "name": "team_member_user_id_user_id_fk", + "tableFrom": "team_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team": { + "name": "team", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "billing_email": { + "name": "billing_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "subscription_status": { + "name": "subscription_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "team_owner_id_user_id_fk": { + "name": "team_owner_id_user_id_fk", + "tableFrom": "team", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_slug_unique": { + "name": "team_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_fingerprint": { + "name": "user_fingerprint", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_fingerprint_fingerprint_unique": { + "name": "user_fingerprint_fingerprint_unique", + "columns": [ + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_fingerprint_user_idx": { + "name": "user_fingerprint_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_fingerprint_user_id_user_id_fk": { + "name": "user_fingerprint_user_id_user_id_fk", + "tableFrom": "user_fingerprint", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_library": { + "name": "user_library", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "playtime_2w": { + "name": "playtime_2w", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "playtime_forever": { + "name": "playtime_forever", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_played": { + "name": "last_played", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_library_user_game_unique": { + "name": "user_library_user_game_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_user_idx": { + "name": "user_library_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_game_idx": { + "name": "user_library_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_library_user_id_user_id_fk": { + "name": "user_library_user_id_user_id_fk", + "tableFrom": "user_library", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_library_game_id_game_id_fk": { + "name": "user_library_game_id_game_id_fk", + "tableFrom": "user_library", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.linked_account": { + "name": "linked_account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "linked_account_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile": { + "name": "profile", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "linked_account_provider_unique": { + "name": "linked_account_provider_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "linked_account_user_idx": { + "name": "linked_account_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "linked_account_user_id_user_id_fk": { + "name": "linked_account_user_id_user_id_fk", + "tableFrom": "linked_account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "email is not null and time_deleted is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "verification_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_user_kind_idx": { + "name": "verification_user_kind_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "verification_user_id_user_id_fk": { + "name": "verification_user_id_user_id_fk", + "tableFrom": "verification", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist_entry": { + "name": "waitlist_entry", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'machines'" + } + }, + "indexes": { + "waitlist_entry_email_unique": { + "name": "waitlist_entry_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "waitlist_entry_source_idx": { + "name": "waitlist_entry_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.device_grant_status": { + "name": "device_grant_status", + "schema": "public", + "values": ["pending", "approved", "denied"] + }, + "public.auth_key_kind": { + "name": "auth_key_kind", + "schema": "public", + "values": ["signing", "encryption"] + }, + "public.box_state": { + "name": "box_state", + "schema": "public", + "values": ["created", "running", "stopped"] + }, + "public.box_tier": { + "name": "box_tier", + "schema": "public", + "values": ["xs", "sm", "md", "lg", "xl"] + }, + "public.depot_status": { + "name": "depot_status", + "schema": "public", + "values": ["pending", "downloading", "complete", "error", "deleted"] + }, + "public.game_download_status": { + "name": "game_download_status", + "schema": "public", + "values": ["pending", "verifying", "downloading", "ready", "failed"] + }, + "public.session_state": { + "name": "session_state", + "schema": "public", + "values": ["requested", "starting", "live", "ended", "failed"] + }, + "public.team_member_role": { + "name": "team_member_role", + "schema": "public", + "values": ["owner", "admin", "member"] + }, + "public.linked_account_provider": { + "name": "linked_account_provider", + "schema": "public", + "values": ["steam", "ssh", "discord"] + }, + "public.verification_kind": { + "name": "verification_kind", + "schema": "public", + "values": ["email"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/core/migrations/meta/0012_snapshot.json b/packages/core/migrations/meta/0012_snapshot.json index 31297a46..e2c4727f 100644 --- a/packages/core/migrations/meta/0012_snapshot.json +++ b/packages/core/migrations/meta/0012_snapshot.json @@ -1,2930 +1,2786 @@ { - "id": "d519bc2f-25f8-46e1-b7df-67bf6b5a927a", - "prevId": "6fdda454-c7ee-42c1-931b-fa595385aac0", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.access_token": { - "name": "access_token", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "owner_user_id": { - "name": "owner_user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token_hash": { - "name": "token_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "last_used": { - "name": "last_used", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "access_token_hash_unique": { - "name": "access_token_hash_unique", - "columns": [ - { - "expression": "token_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "access_token_owner_idx": { - "name": "access_token_owner_idx", - "columns": [ - { - "expression": "owner_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "access_token_team_idx": { - "name": "access_token_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "access_token_owner_user_id_user_id_fk": { - "name": "access_token_owner_user_id_user_id_fk", - "tableFrom": "access_token", - "tableTo": "user", - "columnsFrom": [ - "owner_user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "access_token_team_id_team_id_fk": { - "name": "access_token_team_id_team_id_fk", - "tableFrom": "access_token", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.authorization_code": { - "name": "authorization_code", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "code_hash": { - "name": "code_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "payload": { - "name": "payload", - "type": "jsonb", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "authorization_code_hash_unique": { - "name": "authorization_code_hash_unique", - "columns": [ - { - "expression": "code_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.device_grant": { - "name": "device_grant", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "device_code_hash": { - "name": "device_code_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_code": { - "name": "user_code", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "client_id": { - "name": "client_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "device_grant_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "poll_interval": { - "name": "poll_interval", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "last_polled_at": { - "name": "last_polled_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "subject": { - "name": "subject", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "device_grant_device_code_unique": { - "name": "device_grant_device_code_unique", - "columns": [ - { - "expression": "device_code_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "device_grant_user_code_unique": { - "name": "device_grant_user_code_unique", - "columns": [ - { - "expression": "user_code", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.refresh_token": { - "name": "refresh_token", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "subject": { - "name": "subject", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token_hash": { - "name": "token_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "time_used": { - "name": "time_used", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "payload": { - "name": "payload", - "type": "jsonb", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "refresh_token_hash_unique": { - "name": "refresh_token_hash_unique", - "columns": [ - { - "expression": "token_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "refresh_token_subject_idx": { - "name": "refresh_token_subject_idx", - "columns": [ - { - "expression": "subject", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.auth_key": { - "name": "auth_key", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "key_id": { - "name": "key_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "auth_key_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "alg": { - "name": "alg", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "public_key": { - "name": "public_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "private_key": { - "name": "private_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expired_at": { - "name": "expired_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "auth_key_key_id_unique": { - "name": "auth_key_key_id_unique", - "columns": [ - { - "expression": "key_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "auth_key_one_live_per_kind": { - "name": "auth_key_one_live_per_kind", - "columns": [ - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"auth_key\".\"expired_at\" is null", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.auth_kv": { - "name": "auth_kv", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "key": { - "name": "key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "value": { - "name": "value", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "auth_kv_key_unique": { - "name": "auth_kv_key_unique", - "columns": [ - { - "expression": "key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.box": { - "name": "box", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "machine_id": { - "name": "machine_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "label": { - "name": "label", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tier": { - "name": "tier", - "type": "box_tier", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'sm'" - }, - "state": { - "name": "state", - "type": "box_state", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'created'" - }, - "stop_reason": { - "name": "stop_reason", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stop_clean": { - "name": "stop_clean", - "type": "boolean", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "box_user_idx": { - "name": "box_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "box_machine_idx": { - "name": "box_machine_idx", - "columns": [ - { - "expression": "machine_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "box_user_id_user_id_fk": { - "name": "box_user_id_user_id_fk", - "tableFrom": "box", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "box_machine_id_machine_id_fk": { - "name": "box_machine_id_machine_id_fk", - "tableFrom": "box", - "tableTo": "machine", - "columnsFrom": [ - "machine_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game_depot": { - "name": "game_depot", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "depot_id": { - "name": "depot_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "branch": { - "name": "branch", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'public'" - }, - "steam_manifest_id": { - "name": "steam_manifest_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "steam_build_id": { - "name": "steam_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "installed_manifest_id": { - "name": "installed_manifest_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "installed_build_id": { - "name": "installed_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "size_download": { - "name": "size_download", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "size_on_disk": { - "name": "size_on_disk", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "depot_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "oslist": { - "name": "oslist", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_depot_unique": { - "name": "game_depot_unique", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "depot_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "branch", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_depot_game_idx": { - "name": "game_depot_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_depot_updates_idx": { - "name": "game_depot_updates_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"game_depot\".\"installed_manifest_id\" is distinct from \"game_depot\".\"steam_manifest_id\"", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "game_depot_game_id_game_id_fk": { - "name": "game_depot_game_id_game_id_fk", - "tableFrom": "game_depot", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game_download": { - "name": "game_download", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "host_id": { - "name": "host_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "game_download_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "progress_bytes": { - "name": "progress_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": false, - "default": 0 - }, - "total_bytes": { - "name": "total_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "time_started": { - "name": "time_started", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_completed": { - "name": "time_completed", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_download_host_game_unique": { - "name": "game_download_host_game_unique", - "columns": [ - { - "expression": "host_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_download_game_idx": { - "name": "game_download_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_download_host_status_idx": { - "name": "game_download_host_status_idx", - "columns": [ - { - "expression": "host_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "game_download_host_id_machine_id_fk": { - "name": "game_download_host_id_machine_id_fk", - "tableFrom": "game_download", - "tableTo": "machine", - "columnsFrom": [ - "host_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "game_download_game_id_game_id_fk": { - "name": "game_download_game_id_game_id_fk", - "tableFrom": "game_download", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game": { - "name": "game", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "steam_app_id": { - "name": "steam_app_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "aliases": { - "name": "aliases", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "client_icon": { - "name": "client_icon", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "icon": { - "name": "icon", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "short_description": { - "name": "short_description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "developers": { - "name": "developers", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "publishers": { - "name": "publishers", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "primary_genre": { - "name": "primary_genre", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "genres": { - "name": "genres", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "categories": { - "name": "categories", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "oslist": { - "name": "oslist", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "size_download": { - "name": "size_download", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "size_on_disk": { - "name": "size_on_disk", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "controller_support": { - "name": "controller_support", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "steam_deck_compat": { - "name": "steam_deck_compat", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "review_score_percent": { - "name": "review_score_percent", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "review_count": { - "name": "review_count", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "metacritic_score": { - "name": "metacritic_score", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "steam_change_number": { - "name": "steam_change_number", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "public_build_id": { - "name": "public_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "release_date_utc": { - "name": "release_date_utc", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_enriched": { - "name": "time_enriched", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_slug_unique": { - "name": "game_slug_unique", - "columns": [ - { - "expression": "slug", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_app_id_unique": { - "name": "game_app_id_unique", - "columns": [ - { - "expression": "steam_app_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "game_steam_app_id_unique": { - "name": "game_steam_app_id_unique", - "nullsNotDistinct": false, - "columns": [ - "steam_app_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.machine": { - "name": "machine", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "owner_user_id": { - "name": "owner_user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "label": { - "name": "label", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "secret_hash": { - "name": "secret_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "last_seen": { - "name": "last_seen", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "machine_secret_hash_unique": { - "name": "machine_secret_hash_unique", - "columns": [ - { - "expression": "secret_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_owner_idx": { - "name": "machine_owner_idx", - "columns": [ - { - "expression": "owner_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_team_idx": { - "name": "machine_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "machine_owner_user_id_user_id_fk": { - "name": "machine_owner_user_id_user_id_fk", - "tableFrom": "machine", - "tableTo": "user", - "columnsFrom": [ - "owner_user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "machine_team_id_team_id_fk": { - "name": "machine_team_id_team_id_fk", - "tableFrom": "machine", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pairing_code": { - "name": "pairing_code", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "code": { - "name": "code", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "target_user_id": { - "name": "target_user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "new_fingerprint": { - "name": "new_fingerprint", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "claimed_at": { - "name": "claimed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "is_claimed": { - "name": "is_claimed", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": { - "pairing_code_code_unique": { - "name": "pairing_code_code_unique", - "columns": [ - { - "expression": "code", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "pairing_code_target_user_idx": { - "name": "pairing_code_target_user_idx", - "columns": [ - { - "expression": "target_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "box_id": { - "name": "box_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "linked_account_id": { - "name": "linked_account_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "state": { - "name": "state", - "type": "session_state", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'requested'" - }, - "ticket": { - "name": "ticket", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "time_started": { - "name": "time_started", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_stopped": { - "name": "time_stopped", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "claim_token": { - "name": "claim_token", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "session_box_idx": { - "name": "session_box_idx", - "columns": [ - { - "expression": "box_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_state_idx": { - "name": "session_state_idx", - "columns": [ - { - "expression": "state", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_box_active_unique": { - "name": "session_box_active_unique", - "columns": [ - { - "expression": "box_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "time_stopped is null and time_deleted is null", - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_started_idx": { - "name": "session_started_idx", - "columns": [ - { - "expression": "time_started", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "session_box_id_box_id_fk": { - "name": "session_box_id_box_id_fk", - "tableFrom": "session", - "tableTo": "box", - "columnsFrom": [ - "box_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "session_game_id_game_id_fk": { - "name": "session_game_id_game_id_fk", - "tableFrom": "session", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - }, - "session_linked_account_id_linked_account_id_fk": { - "name": "session_linked_account_id_linked_account_id_fk", - "tableFrom": "session", - "tableTo": "linked_account", - "columnsFrom": [ - "linked_account_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.steam_enrolment": { - "name": "steam_enrolment", - "schema": "", - "columns": { - "machine_id": { - "name": "machine_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "steam_id": { - "name": "steam_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "state": { - "name": "state", - "type": "steam_enrolment_state", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "enrolled_at": { - "name": "enrolled_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "last_ok_at": { - "name": "last_ok_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "revoked_at": { - "name": "revoked_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "steam_enrolment_user_idx": { - "name": "steam_enrolment_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "steam_enrolment_machine_id_machine_id_fk": { - "name": "steam_enrolment_machine_id_machine_id_fk", - "tableFrom": "steam_enrolment", - "tableTo": "machine", - "columnsFrom": [ - "machine_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "steam_enrolment_user_id_user_id_fk": { - "name": "steam_enrolment_user_id_user_id_fk", - "tableFrom": "steam_enrolment", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "steam_enrolment_machine_id_user_id_pk": { - "name": "steam_enrolment_machine_id_user_id_pk", - "columns": [ - "machine_id", - "user_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.team_member": { - "name": "team_member", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "role": { - "name": "role", - "type": "team_member_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'member'" - } - }, - "indexes": { - "team_member_team_user_unique": { - "name": "team_member_team_user_unique", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "team_member_team_idx": { - "name": "team_member_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "team_member_user_idx": { - "name": "team_member_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "team_member_team_id_team_id_fk": { - "name": "team_member_team_id_team_id_fk", - "tableFrom": "team_member", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "team_member_user_id_user_id_fk": { - "name": "team_member_user_id_user_id_fk", - "tableFrom": "team_member", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.team": { - "name": "team", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "owner_id": { - "name": "owner_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "billing_email": { - "name": "billing_email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "plan": { - "name": "plan", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'free'" - }, - "subscription_status": { - "name": "subscription_status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'active'" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "team_owner_id_user_id_fk": { - "name": "team_owner_id_user_id_fk", - "tableFrom": "team", - "tableTo": "user", - "columnsFrom": [ - "owner_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "team_slug_unique": { - "name": "team_slug_unique", - "nullsNotDistinct": false, - "columns": [ - "slug" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_fingerprint": { - "name": "user_fingerprint", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "fingerprint": { - "name": "fingerprint", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_seen": { - "name": "last_seen", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_fingerprint_fingerprint_unique": { - "name": "user_fingerprint_fingerprint_unique", - "columns": [ - { - "expression": "fingerprint", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_fingerprint_user_idx": { - "name": "user_fingerprint_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "user_fingerprint_user_id_user_id_fk": { - "name": "user_fingerprint_user_id_user_id_fk", - "tableFrom": "user_fingerprint", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_library": { - "name": "user_library", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "playtime_2w": { - "name": "playtime_2w", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "playtime_forever": { - "name": "playtime_forever", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "last_played": { - "name": "last_played", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_library_user_game_unique": { - "name": "user_library_user_game_unique", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_library_user_idx": { - "name": "user_library_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_library_game_idx": { - "name": "user_library_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "user_library_user_id_user_id_fk": { - "name": "user_library_user_id_user_id_fk", - "tableFrom": "user_library", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "user_library_game_id_game_id_fk": { - "name": "user_library_game_id_game_id_fk", - "tableFrom": "user_library", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.linked_account": { - "name": "linked_account", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "linked_account_provider", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "provider_account_id": { - "name": "provider_account_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "profile": { - "name": "profile", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "linked_account_provider_unique": { - "name": "linked_account_provider_unique", - "columns": [ - { - "expression": "provider", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "provider_account_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "linked_account_user_idx": { - "name": "linked_account_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "linked_account_user_id_user_id_fk": { - "name": "linked_account_user_id_user_id_fk", - "tableFrom": "linked_account", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email_verified": { - "name": "email_verified", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_email_unique": { - "name": "user_email_unique", - "columns": [ - { - "expression": "email", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "email is not null and time_deleted is null", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verification": { - "name": "verification", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "verification_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "code_hash": { - "name": "code_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "attempts": { - "name": "attempts", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "consumed_at": { - "name": "consumed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "verification_user_kind_idx": { - "name": "verification_user_kind_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "verification_user_id_user_id_fk": { - "name": "verification_user_id_user_id_fk", - "tableFrom": "verification", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.waitlist_entry": { - "name": "waitlist_entry", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source": { - "name": "source", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'machines'" - } - }, - "indexes": { - "waitlist_entry_email_unique": { - "name": "waitlist_entry_email_unique", - "columns": [ - { - "expression": "email", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "waitlist_entry_source_idx": { - "name": "waitlist_entry_source_idx", - "columns": [ - { - "expression": "source", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.device_grant_status": { - "name": "device_grant_status", - "schema": "public", - "values": [ - "pending", - "approved", - "denied" - ] - }, - "public.auth_key_kind": { - "name": "auth_key_kind", - "schema": "public", - "values": [ - "signing", - "encryption" - ] - }, - "public.box_state": { - "name": "box_state", - "schema": "public", - "values": [ - "created", - "running", - "stopped" - ] - }, - "public.box_tier": { - "name": "box_tier", - "schema": "public", - "values": [ - "xs", - "sm", - "md", - "lg", - "xl" - ] - }, - "public.depot_status": { - "name": "depot_status", - "schema": "public", - "values": [ - "pending", - "downloading", - "complete", - "error", - "deleted" - ] - }, - "public.game_download_status": { - "name": "game_download_status", - "schema": "public", - "values": [ - "pending", - "verifying", - "downloading", - "ready", - "failed" - ] - }, - "public.session_state": { - "name": "session_state", - "schema": "public", - "values": [ - "requested", - "starting", - "live", - "ended", - "failed" - ] - }, - "public.steam_enrolment_state": { - "name": "steam_enrolment_state", - "schema": "public", - "values": [ - "enrolled", - "stale", - "revoked" - ] - }, - "public.team_member_role": { - "name": "team_member_role", - "schema": "public", - "values": [ - "owner", - "admin", - "member" - ] - }, - "public.linked_account_provider": { - "name": "linked_account_provider", - "schema": "public", - "values": [ - "steam", - "ssh", - "discord" - ] - }, - "public.verification_kind": { - "name": "verification_kind", - "schema": "public", - "values": [ - "email" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file + "id": "d519bc2f-25f8-46e1-b7df-67bf6b5a927a", + "prevId": "6fdda454-c7ee-42c1-931b-fa595385aac0", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used": { + "name": "last_used", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "access_token_hash_unique": { + "name": "access_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_owner_idx": { + "name": "access_token_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_team_idx": { + "name": "access_token_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "access_token_owner_user_id_user_id_fk": { + "name": "access_token_owner_user_id_user_id_fk", + "tableFrom": "access_token", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "access_token_team_id_team_id_fk": { + "name": "access_token_team_id_team_id_fk", + "tableFrom": "access_token", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.authorization_code": { + "name": "authorization_code", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "authorization_code_hash_unique": { + "name": "authorization_code_hash_unique", + "columns": [ + { + "expression": "code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.device_grant": { + "name": "device_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "device_code_hash": { + "name": "device_code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_code": { + "name": "user_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "device_grant_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "poll_interval": { + "name": "poll_interval", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "last_polled_at": { + "name": "last_polled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "device_grant_device_code_unique": { + "name": "device_grant_device_code_unique", + "columns": [ + { + "expression": "device_code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "device_grant_user_code_unique": { + "name": "device_grant_user_code_unique", + "columns": [ + { + "expression": "user_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.refresh_token": { + "name": "refresh_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "time_used": { + "name": "time_used", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "refresh_token_hash_unique": { + "name": "refresh_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "refresh_token_subject_idx": { + "name": "refresh_token_subject_idx", + "columns": [ + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_key": { + "name": "auth_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "key_id": { + "name": "key_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "auth_key_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "alg": { + "name": "alg", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expired_at": { + "name": "expired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "auth_key_key_id_unique": { + "name": "auth_key_key_id_unique", + "columns": [ + { + "expression": "key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auth_key_one_live_per_kind": { + "name": "auth_key_one_live_per_kind", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"auth_key\".\"expired_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_kv": { + "name": "auth_kv", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "auth_kv_key_unique": { + "name": "auth_kv_key_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.box": { + "name": "box", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "machine_id": { + "name": "machine_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tier": { + "name": "tier", + "type": "box_tier", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'sm'" + }, + "state": { + "name": "state", + "type": "box_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'created'" + }, + "stop_reason": { + "name": "stop_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stop_clean": { + "name": "stop_clean", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "box_user_idx": { + "name": "box_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "box_machine_idx": { + "name": "box_machine_idx", + "columns": [ + { + "expression": "machine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "box_user_id_user_id_fk": { + "name": "box_user_id_user_id_fk", + "tableFrom": "box", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "box_machine_id_machine_id_fk": { + "name": "box_machine_id_machine_id_fk", + "tableFrom": "box", + "tableTo": "machine", + "columnsFrom": ["machine_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_depot": { + "name": "game_depot", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "depot_id": { + "name": "depot_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "steam_manifest_id": { + "name": "steam_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_build_id": { + "name": "steam_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "installed_manifest_id": { + "name": "installed_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_build_id": { + "name": "installed_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "depot_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_depot_unique": { + "name": "game_depot_unique", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "depot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_game_idx": { + "name": "game_depot_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_updates_idx": { + "name": "game_depot_updates_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"game_depot\".\"installed_manifest_id\" is distinct from \"game_depot\".\"steam_manifest_id\"", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_depot_game_id_game_id_fk": { + "name": "game_depot_game_id_game_id_fk", + "tableFrom": "game_depot", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_download": { + "name": "game_download", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "host_id": { + "name": "host_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "game_download_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "progress_bytes": { + "name": "progress_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "total_bytes": { + "name": "total_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "time_started": { + "name": "time_started", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_completed": { + "name": "time_completed", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_download_host_game_unique": { + "name": "game_download_host_game_unique", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_game_idx": { + "name": "game_download_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_host_status_idx": { + "name": "game_download_host_status_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_download_host_id_machine_id_fk": { + "name": "game_download_host_id_machine_id_fk", + "tableFrom": "game_download", + "tableTo": "machine", + "columnsFrom": ["host_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "game_download_game_id_game_id_fk": { + "name": "game_download_game_id_game_id_fk", + "tableFrom": "game_download", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game": { + "name": "game", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "steam_app_id": { + "name": "steam_app_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aliases": { + "name": "aliases", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_icon": { + "name": "client_icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "short_description": { + "name": "short_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "developers": { + "name": "developers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "publishers": { + "name": "publishers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "primary_genre": { + "name": "primary_genre", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "genres": { + "name": "genres", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "controller_support": { + "name": "controller_support", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_deck_compat": { + "name": "steam_deck_compat", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "review_score_percent": { + "name": "review_score_percent", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "review_count": { + "name": "review_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "metacritic_score": { + "name": "metacritic_score", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "steam_change_number": { + "name": "steam_change_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "public_build_id": { + "name": "public_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "release_date_utc": { + "name": "release_date_utc", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_enriched": { + "name": "time_enriched", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_slug_unique": { + "name": "game_slug_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_app_id_unique": { + "name": "game_app_id_unique", + "columns": [ + { + "expression": "steam_app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "game_steam_app_id_unique": { + "name": "game_steam_app_id_unique", + "nullsNotDistinct": false, + "columns": ["steam_app_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.machine": { + "name": "machine", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "machine_secret_hash_unique": { + "name": "machine_secret_hash_unique", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_owner_idx": { + "name": "machine_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_team_idx": { + "name": "machine_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "machine_owner_user_id_user_id_fk": { + "name": "machine_owner_user_id_user_id_fk", + "tableFrom": "machine", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "machine_team_id_team_id_fk": { + "name": "machine_team_id_team_id_fk", + "tableFrom": "machine", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pairing_code": { + "name": "pairing_code", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_fingerprint": { + "name": "new_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "is_claimed": { + "name": "is_claimed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "pairing_code_code_unique": { + "name": "pairing_code_code_unique", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pairing_code_target_user_idx": { + "name": "pairing_code_target_user_idx", + "columns": [ + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "box_id": { + "name": "box_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "linked_account_id": { + "name": "linked_account_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "session_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "ticket": { + "name": "ticket", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "time_started": { + "name": "time_started", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_stopped": { + "name": "time_stopped", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_token": { + "name": "claim_token", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_box_idx": { + "name": "session_box_idx", + "columns": [ + { + "expression": "box_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_state_idx": { + "name": "session_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_box_active_unique": { + "name": "session_box_active_unique", + "columns": [ + { + "expression": "box_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "time_stopped is null and time_deleted is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_started_idx": { + "name": "session_started_idx", + "columns": [ + { + "expression": "time_started", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_box_id_box_id_fk": { + "name": "session_box_id_box_id_fk", + "tableFrom": "session", + "tableTo": "box", + "columnsFrom": ["box_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_game_id_game_id_fk": { + "name": "session_game_id_game_id_fk", + "tableFrom": "session", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "session_linked_account_id_linked_account_id_fk": { + "name": "session_linked_account_id_linked_account_id_fk", + "tableFrom": "session", + "tableTo": "linked_account", + "columnsFrom": ["linked_account_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.steam_enrolment": { + "name": "steam_enrolment", + "schema": "", + "columns": { + "machine_id": { + "name": "machine_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "steam_id": { + "name": "steam_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "steam_enrolment_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_ok_at": { + "name": "last_ok_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "steam_enrolment_user_idx": { + "name": "steam_enrolment_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "steam_enrolment_machine_id_machine_id_fk": { + "name": "steam_enrolment_machine_id_machine_id_fk", + "tableFrom": "steam_enrolment", + "tableTo": "machine", + "columnsFrom": ["machine_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "steam_enrolment_user_id_user_id_fk": { + "name": "steam_enrolment_user_id_user_id_fk", + "tableFrom": "steam_enrolment", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "steam_enrolment_machine_id_user_id_pk": { + "name": "steam_enrolment_machine_id_user_id_pk", + "columns": ["machine_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_member": { + "name": "team_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "team_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + } + }, + "indexes": { + "team_member_team_user_unique": { + "name": "team_member_team_user_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_team_idx": { + "name": "team_member_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_user_idx": { + "name": "team_member_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_member_team_id_team_id_fk": { + "name": "team_member_team_id_team_id_fk", + "tableFrom": "team_member", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_member_user_id_user_id_fk": { + "name": "team_member_user_id_user_id_fk", + "tableFrom": "team_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team": { + "name": "team", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "billing_email": { + "name": "billing_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "subscription_status": { + "name": "subscription_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "team_owner_id_user_id_fk": { + "name": "team_owner_id_user_id_fk", + "tableFrom": "team", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_slug_unique": { + "name": "team_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_fingerprint": { + "name": "user_fingerprint", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_fingerprint_fingerprint_unique": { + "name": "user_fingerprint_fingerprint_unique", + "columns": [ + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_fingerprint_user_idx": { + "name": "user_fingerprint_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_fingerprint_user_id_user_id_fk": { + "name": "user_fingerprint_user_id_user_id_fk", + "tableFrom": "user_fingerprint", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_library": { + "name": "user_library", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "playtime_2w": { + "name": "playtime_2w", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "playtime_forever": { + "name": "playtime_forever", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_played": { + "name": "last_played", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_library_user_game_unique": { + "name": "user_library_user_game_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_user_idx": { + "name": "user_library_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_game_idx": { + "name": "user_library_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_library_user_id_user_id_fk": { + "name": "user_library_user_id_user_id_fk", + "tableFrom": "user_library", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_library_game_id_game_id_fk": { + "name": "user_library_game_id_game_id_fk", + "tableFrom": "user_library", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.linked_account": { + "name": "linked_account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "linked_account_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile": { + "name": "profile", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "linked_account_provider_unique": { + "name": "linked_account_provider_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "linked_account_user_idx": { + "name": "linked_account_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "linked_account_user_id_user_id_fk": { + "name": "linked_account_user_id_user_id_fk", + "tableFrom": "linked_account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "email is not null and time_deleted is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "verification_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_user_kind_idx": { + "name": "verification_user_kind_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "verification_user_id_user_id_fk": { + "name": "verification_user_id_user_id_fk", + "tableFrom": "verification", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist_entry": { + "name": "waitlist_entry", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'machines'" + } + }, + "indexes": { + "waitlist_entry_email_unique": { + "name": "waitlist_entry_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "waitlist_entry_source_idx": { + "name": "waitlist_entry_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.device_grant_status": { + "name": "device_grant_status", + "schema": "public", + "values": ["pending", "approved", "denied"] + }, + "public.auth_key_kind": { + "name": "auth_key_kind", + "schema": "public", + "values": ["signing", "encryption"] + }, + "public.box_state": { + "name": "box_state", + "schema": "public", + "values": ["created", "running", "stopped"] + }, + "public.box_tier": { + "name": "box_tier", + "schema": "public", + "values": ["xs", "sm", "md", "lg", "xl"] + }, + "public.depot_status": { + "name": "depot_status", + "schema": "public", + "values": ["pending", "downloading", "complete", "error", "deleted"] + }, + "public.game_download_status": { + "name": "game_download_status", + "schema": "public", + "values": ["pending", "verifying", "downloading", "ready", "failed"] + }, + "public.session_state": { + "name": "session_state", + "schema": "public", + "values": ["requested", "starting", "live", "ended", "failed"] + }, + "public.steam_enrolment_state": { + "name": "steam_enrolment_state", + "schema": "public", + "values": ["enrolled", "stale", "revoked"] + }, + "public.team_member_role": { + "name": "team_member_role", + "schema": "public", + "values": ["owner", "admin", "member"] + }, + "public.linked_account_provider": { + "name": "linked_account_provider", + "schema": "public", + "values": ["steam", "ssh", "discord"] + }, + "public.verification_kind": { + "name": "verification_kind", + "schema": "public", + "values": ["email"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/core/migrations/meta/0013_snapshot.json b/packages/core/migrations/meta/0013_snapshot.json index 41037a9b..25a97edd 100644 --- a/packages/core/migrations/meta/0013_snapshot.json +++ b/packages/core/migrations/meta/0013_snapshot.json @@ -1,2951 +1,2807 @@ { - "id": "7510a518-9e7d-4017-a3ec-b28f8d29a853", - "prevId": "d519bc2f-25f8-46e1-b7df-67bf6b5a927a", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.access_token": { - "name": "access_token", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "owner_user_id": { - "name": "owner_user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token_hash": { - "name": "token_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "last_used": { - "name": "last_used", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "access_token_hash_unique": { - "name": "access_token_hash_unique", - "columns": [ - { - "expression": "token_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "access_token_owner_idx": { - "name": "access_token_owner_idx", - "columns": [ - { - "expression": "owner_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "access_token_team_idx": { - "name": "access_token_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "access_token_owner_user_id_user_id_fk": { - "name": "access_token_owner_user_id_user_id_fk", - "tableFrom": "access_token", - "tableTo": "user", - "columnsFrom": [ - "owner_user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "access_token_team_id_team_id_fk": { - "name": "access_token_team_id_team_id_fk", - "tableFrom": "access_token", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.authorization_code": { - "name": "authorization_code", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "code_hash": { - "name": "code_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "payload": { - "name": "payload", - "type": "jsonb", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "authorization_code_hash_unique": { - "name": "authorization_code_hash_unique", - "columns": [ - { - "expression": "code_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.device_grant": { - "name": "device_grant", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "device_code_hash": { - "name": "device_code_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_code": { - "name": "user_code", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "client_id": { - "name": "client_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "device_grant_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "poll_interval": { - "name": "poll_interval", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "last_polled_at": { - "name": "last_polled_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "subject": { - "name": "subject", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "device_grant_device_code_unique": { - "name": "device_grant_device_code_unique", - "columns": [ - { - "expression": "device_code_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "device_grant_user_code_unique": { - "name": "device_grant_user_code_unique", - "columns": [ - { - "expression": "user_code", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.refresh_token": { - "name": "refresh_token", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "subject": { - "name": "subject", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token_hash": { - "name": "token_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "time_used": { - "name": "time_used", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "payload": { - "name": "payload", - "type": "jsonb", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "refresh_token_hash_unique": { - "name": "refresh_token_hash_unique", - "columns": [ - { - "expression": "token_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "refresh_token_subject_idx": { - "name": "refresh_token_subject_idx", - "columns": [ - { - "expression": "subject", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.auth_key": { - "name": "auth_key", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "key_id": { - "name": "key_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "auth_key_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "alg": { - "name": "alg", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "public_key": { - "name": "public_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "private_key": { - "name": "private_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expired_at": { - "name": "expired_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "auth_key_key_id_unique": { - "name": "auth_key_key_id_unique", - "columns": [ - { - "expression": "key_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "auth_key_one_live_per_kind": { - "name": "auth_key_one_live_per_kind", - "columns": [ - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"auth_key\".\"expired_at\" is null", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.auth_kv": { - "name": "auth_kv", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "key": { - "name": "key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "value": { - "name": "value", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "auth_kv_key_unique": { - "name": "auth_kv_key_unique", - "columns": [ - { - "expression": "key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.box": { - "name": "box", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "machine_id": { - "name": "machine_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "label": { - "name": "label", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tier": { - "name": "tier", - "type": "box_tier", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'sm'" - }, - "state": { - "name": "state", - "type": "box_state", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'created'" - }, - "stop_reason": { - "name": "stop_reason", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stop_clean": { - "name": "stop_clean", - "type": "boolean", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "box_user_idx": { - "name": "box_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "box_machine_idx": { - "name": "box_machine_idx", - "columns": [ - { - "expression": "machine_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "box_user_id_user_id_fk": { - "name": "box_user_id_user_id_fk", - "tableFrom": "box", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "box_machine_id_machine_id_fk": { - "name": "box_machine_id_machine_id_fk", - "tableFrom": "box", - "tableTo": "machine", - "columnsFrom": [ - "machine_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game_depot": { - "name": "game_depot", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "depot_id": { - "name": "depot_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "branch": { - "name": "branch", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'public'" - }, - "steam_manifest_id": { - "name": "steam_manifest_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "steam_build_id": { - "name": "steam_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "installed_manifest_id": { - "name": "installed_manifest_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "installed_build_id": { - "name": "installed_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "size_download": { - "name": "size_download", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "size_on_disk": { - "name": "size_on_disk", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "depot_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "oslist": { - "name": "oslist", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_depot_unique": { - "name": "game_depot_unique", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "depot_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "branch", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_depot_game_idx": { - "name": "game_depot_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_depot_updates_idx": { - "name": "game_depot_updates_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"game_depot\".\"installed_manifest_id\" is distinct from \"game_depot\".\"steam_manifest_id\"", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "game_depot_game_id_game_id_fk": { - "name": "game_depot_game_id_game_id_fk", - "tableFrom": "game_depot", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game_download": { - "name": "game_download", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "host_id": { - "name": "host_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "game_download_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "progress_bytes": { - "name": "progress_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": false, - "default": 0 - }, - "total_bytes": { - "name": "total_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "time_started": { - "name": "time_started", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_completed": { - "name": "time_completed", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_download_host_game_unique": { - "name": "game_download_host_game_unique", - "columns": [ - { - "expression": "host_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_download_game_idx": { - "name": "game_download_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_download_host_status_idx": { - "name": "game_download_host_status_idx", - "columns": [ - { - "expression": "host_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "game_download_host_id_machine_id_fk": { - "name": "game_download_host_id_machine_id_fk", - "tableFrom": "game_download", - "tableTo": "machine", - "columnsFrom": [ - "host_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "game_download_game_id_game_id_fk": { - "name": "game_download_game_id_game_id_fk", - "tableFrom": "game_download", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game": { - "name": "game", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "steam_app_id": { - "name": "steam_app_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "aliases": { - "name": "aliases", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "client_icon": { - "name": "client_icon", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "icon": { - "name": "icon", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "short_description": { - "name": "short_description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "developers": { - "name": "developers", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "publishers": { - "name": "publishers", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "primary_genre": { - "name": "primary_genre", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "genres": { - "name": "genres", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "categories": { - "name": "categories", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "oslist": { - "name": "oslist", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "size_download": { - "name": "size_download", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "size_on_disk": { - "name": "size_on_disk", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "controller_support": { - "name": "controller_support", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "steam_deck_compat": { - "name": "steam_deck_compat", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "review_score_percent": { - "name": "review_score_percent", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "review_count": { - "name": "review_count", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "metacritic_score": { - "name": "metacritic_score", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "steam_change_number": { - "name": "steam_change_number", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "public_build_id": { - "name": "public_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "release_date_utc": { - "name": "release_date_utc", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_enriched": { - "name": "time_enriched", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_slug_unique": { - "name": "game_slug_unique", - "columns": [ - { - "expression": "slug", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_app_id_unique": { - "name": "game_app_id_unique", - "columns": [ - { - "expression": "steam_app_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "game_steam_app_id_unique": { - "name": "game_steam_app_id_unique", - "nullsNotDistinct": false, - "columns": [ - "steam_app_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.machine": { - "name": "machine", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "owner_user_id": { - "name": "owner_user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "label": { - "name": "label", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "endpoint_id": { - "name": "endpoint_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "secret_hash": { - "name": "secret_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "last_seen": { - "name": "last_seen", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "machine_secret_hash_unique": { - "name": "machine_secret_hash_unique", - "columns": [ - { - "expression": "secret_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_endpoint_id_unique": { - "name": "machine_endpoint_id_unique", - "columns": [ - { - "expression": "endpoint_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_owner_idx": { - "name": "machine_owner_idx", - "columns": [ - { - "expression": "owner_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_team_idx": { - "name": "machine_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "machine_owner_user_id_user_id_fk": { - "name": "machine_owner_user_id_user_id_fk", - "tableFrom": "machine", - "tableTo": "user", - "columnsFrom": [ - "owner_user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "machine_team_id_team_id_fk": { - "name": "machine_team_id_team_id_fk", - "tableFrom": "machine", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pairing_code": { - "name": "pairing_code", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "code": { - "name": "code", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "target_user_id": { - "name": "target_user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "new_fingerprint": { - "name": "new_fingerprint", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "claimed_at": { - "name": "claimed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "is_claimed": { - "name": "is_claimed", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": { - "pairing_code_code_unique": { - "name": "pairing_code_code_unique", - "columns": [ - { - "expression": "code", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "pairing_code_target_user_idx": { - "name": "pairing_code_target_user_idx", - "columns": [ - { - "expression": "target_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "box_id": { - "name": "box_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "linked_account_id": { - "name": "linked_account_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "state": { - "name": "state", - "type": "session_state", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'requested'" - }, - "ticket": { - "name": "ticket", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "time_started": { - "name": "time_started", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_stopped": { - "name": "time_stopped", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "claim_token": { - "name": "claim_token", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "session_box_idx": { - "name": "session_box_idx", - "columns": [ - { - "expression": "box_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_state_idx": { - "name": "session_state_idx", - "columns": [ - { - "expression": "state", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_box_active_unique": { - "name": "session_box_active_unique", - "columns": [ - { - "expression": "box_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "time_stopped is null and time_deleted is null", - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_started_idx": { - "name": "session_started_idx", - "columns": [ - { - "expression": "time_started", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "session_box_id_box_id_fk": { - "name": "session_box_id_box_id_fk", - "tableFrom": "session", - "tableTo": "box", - "columnsFrom": [ - "box_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "session_game_id_game_id_fk": { - "name": "session_game_id_game_id_fk", - "tableFrom": "session", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - }, - "session_linked_account_id_linked_account_id_fk": { - "name": "session_linked_account_id_linked_account_id_fk", - "tableFrom": "session", - "tableTo": "linked_account", - "columnsFrom": [ - "linked_account_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.steam_enrolment": { - "name": "steam_enrolment", - "schema": "", - "columns": { - "machine_id": { - "name": "machine_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "steam_id": { - "name": "steam_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "state": { - "name": "state", - "type": "steam_enrolment_state", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "enrolled_at": { - "name": "enrolled_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "last_ok_at": { - "name": "last_ok_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "revoked_at": { - "name": "revoked_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "steam_enrolment_user_idx": { - "name": "steam_enrolment_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "steam_enrolment_machine_id_machine_id_fk": { - "name": "steam_enrolment_machine_id_machine_id_fk", - "tableFrom": "steam_enrolment", - "tableTo": "machine", - "columnsFrom": [ - "machine_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "steam_enrolment_user_id_user_id_fk": { - "name": "steam_enrolment_user_id_user_id_fk", - "tableFrom": "steam_enrolment", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "steam_enrolment_machine_id_user_id_pk": { - "name": "steam_enrolment_machine_id_user_id_pk", - "columns": [ - "machine_id", - "user_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.team_member": { - "name": "team_member", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "role": { - "name": "role", - "type": "team_member_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'member'" - } - }, - "indexes": { - "team_member_team_user_unique": { - "name": "team_member_team_user_unique", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "team_member_team_idx": { - "name": "team_member_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "team_member_user_idx": { - "name": "team_member_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "team_member_team_id_team_id_fk": { - "name": "team_member_team_id_team_id_fk", - "tableFrom": "team_member", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "team_member_user_id_user_id_fk": { - "name": "team_member_user_id_user_id_fk", - "tableFrom": "team_member", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.team": { - "name": "team", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "owner_id": { - "name": "owner_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "billing_email": { - "name": "billing_email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "plan": { - "name": "plan", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'free'" - }, - "subscription_status": { - "name": "subscription_status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'active'" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "team_owner_id_user_id_fk": { - "name": "team_owner_id_user_id_fk", - "tableFrom": "team", - "tableTo": "user", - "columnsFrom": [ - "owner_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "team_slug_unique": { - "name": "team_slug_unique", - "nullsNotDistinct": false, - "columns": [ - "slug" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_fingerprint": { - "name": "user_fingerprint", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "fingerprint": { - "name": "fingerprint", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_seen": { - "name": "last_seen", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_fingerprint_fingerprint_unique": { - "name": "user_fingerprint_fingerprint_unique", - "columns": [ - { - "expression": "fingerprint", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_fingerprint_user_idx": { - "name": "user_fingerprint_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "user_fingerprint_user_id_user_id_fk": { - "name": "user_fingerprint_user_id_user_id_fk", - "tableFrom": "user_fingerprint", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_library": { - "name": "user_library", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "playtime_2w": { - "name": "playtime_2w", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "playtime_forever": { - "name": "playtime_forever", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "last_played": { - "name": "last_played", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_library_user_game_unique": { - "name": "user_library_user_game_unique", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_library_user_idx": { - "name": "user_library_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_library_game_idx": { - "name": "user_library_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "user_library_user_id_user_id_fk": { - "name": "user_library_user_id_user_id_fk", - "tableFrom": "user_library", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "user_library_game_id_game_id_fk": { - "name": "user_library_game_id_game_id_fk", - "tableFrom": "user_library", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.linked_account": { - "name": "linked_account", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "linked_account_provider", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "provider_account_id": { - "name": "provider_account_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "profile": { - "name": "profile", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "linked_account_provider_unique": { - "name": "linked_account_provider_unique", - "columns": [ - { - "expression": "provider", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "provider_account_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "linked_account_user_idx": { - "name": "linked_account_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "linked_account_user_id_user_id_fk": { - "name": "linked_account_user_id_user_id_fk", - "tableFrom": "linked_account", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email_verified": { - "name": "email_verified", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_email_unique": { - "name": "user_email_unique", - "columns": [ - { - "expression": "email", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "email is not null and time_deleted is null", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verification": { - "name": "verification", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "verification_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "code_hash": { - "name": "code_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "attempts": { - "name": "attempts", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "consumed_at": { - "name": "consumed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "verification_user_kind_idx": { - "name": "verification_user_kind_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "verification_user_id_user_id_fk": { - "name": "verification_user_id_user_id_fk", - "tableFrom": "verification", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.waitlist_entry": { - "name": "waitlist_entry", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source": { - "name": "source", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'machines'" - } - }, - "indexes": { - "waitlist_entry_email_unique": { - "name": "waitlist_entry_email_unique", - "columns": [ - { - "expression": "email", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "waitlist_entry_source_idx": { - "name": "waitlist_entry_source_idx", - "columns": [ - { - "expression": "source", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.device_grant_status": { - "name": "device_grant_status", - "schema": "public", - "values": [ - "pending", - "approved", - "denied" - ] - }, - "public.auth_key_kind": { - "name": "auth_key_kind", - "schema": "public", - "values": [ - "signing", - "encryption" - ] - }, - "public.box_state": { - "name": "box_state", - "schema": "public", - "values": [ - "created", - "running", - "stopped" - ] - }, - "public.box_tier": { - "name": "box_tier", - "schema": "public", - "values": [ - "xs", - "sm", - "md", - "lg", - "xl" - ] - }, - "public.depot_status": { - "name": "depot_status", - "schema": "public", - "values": [ - "pending", - "downloading", - "complete", - "error", - "deleted" - ] - }, - "public.game_download_status": { - "name": "game_download_status", - "schema": "public", - "values": [ - "pending", - "verifying", - "downloading", - "ready", - "failed" - ] - }, - "public.session_state": { - "name": "session_state", - "schema": "public", - "values": [ - "requested", - "starting", - "live", - "ended", - "failed" - ] - }, - "public.steam_enrolment_state": { - "name": "steam_enrolment_state", - "schema": "public", - "values": [ - "enrolled", - "stale", - "revoked" - ] - }, - "public.team_member_role": { - "name": "team_member_role", - "schema": "public", - "values": [ - "owner", - "admin", - "member" - ] - }, - "public.linked_account_provider": { - "name": "linked_account_provider", - "schema": "public", - "values": [ - "steam", - "ssh", - "discord" - ] - }, - "public.verification_kind": { - "name": "verification_kind", - "schema": "public", - "values": [ - "email" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file + "id": "7510a518-9e7d-4017-a3ec-b28f8d29a853", + "prevId": "d519bc2f-25f8-46e1-b7df-67bf6b5a927a", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used": { + "name": "last_used", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "access_token_hash_unique": { + "name": "access_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_owner_idx": { + "name": "access_token_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_team_idx": { + "name": "access_token_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "access_token_owner_user_id_user_id_fk": { + "name": "access_token_owner_user_id_user_id_fk", + "tableFrom": "access_token", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "access_token_team_id_team_id_fk": { + "name": "access_token_team_id_team_id_fk", + "tableFrom": "access_token", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.authorization_code": { + "name": "authorization_code", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "authorization_code_hash_unique": { + "name": "authorization_code_hash_unique", + "columns": [ + { + "expression": "code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.device_grant": { + "name": "device_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "device_code_hash": { + "name": "device_code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_code": { + "name": "user_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "device_grant_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "poll_interval": { + "name": "poll_interval", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "last_polled_at": { + "name": "last_polled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "device_grant_device_code_unique": { + "name": "device_grant_device_code_unique", + "columns": [ + { + "expression": "device_code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "device_grant_user_code_unique": { + "name": "device_grant_user_code_unique", + "columns": [ + { + "expression": "user_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.refresh_token": { + "name": "refresh_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "time_used": { + "name": "time_used", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "refresh_token_hash_unique": { + "name": "refresh_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "refresh_token_subject_idx": { + "name": "refresh_token_subject_idx", + "columns": [ + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_key": { + "name": "auth_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "key_id": { + "name": "key_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "auth_key_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "alg": { + "name": "alg", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expired_at": { + "name": "expired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "auth_key_key_id_unique": { + "name": "auth_key_key_id_unique", + "columns": [ + { + "expression": "key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auth_key_one_live_per_kind": { + "name": "auth_key_one_live_per_kind", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"auth_key\".\"expired_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_kv": { + "name": "auth_kv", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "auth_kv_key_unique": { + "name": "auth_kv_key_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.box": { + "name": "box", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "machine_id": { + "name": "machine_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tier": { + "name": "tier", + "type": "box_tier", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'sm'" + }, + "state": { + "name": "state", + "type": "box_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'created'" + }, + "stop_reason": { + "name": "stop_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stop_clean": { + "name": "stop_clean", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "box_user_idx": { + "name": "box_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "box_machine_idx": { + "name": "box_machine_idx", + "columns": [ + { + "expression": "machine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "box_user_id_user_id_fk": { + "name": "box_user_id_user_id_fk", + "tableFrom": "box", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "box_machine_id_machine_id_fk": { + "name": "box_machine_id_machine_id_fk", + "tableFrom": "box", + "tableTo": "machine", + "columnsFrom": ["machine_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_depot": { + "name": "game_depot", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "depot_id": { + "name": "depot_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "steam_manifest_id": { + "name": "steam_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_build_id": { + "name": "steam_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "installed_manifest_id": { + "name": "installed_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_build_id": { + "name": "installed_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "depot_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_depot_unique": { + "name": "game_depot_unique", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "depot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_game_idx": { + "name": "game_depot_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_updates_idx": { + "name": "game_depot_updates_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"game_depot\".\"installed_manifest_id\" is distinct from \"game_depot\".\"steam_manifest_id\"", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_depot_game_id_game_id_fk": { + "name": "game_depot_game_id_game_id_fk", + "tableFrom": "game_depot", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_download": { + "name": "game_download", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "host_id": { + "name": "host_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "game_download_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "progress_bytes": { + "name": "progress_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "total_bytes": { + "name": "total_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "time_started": { + "name": "time_started", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_completed": { + "name": "time_completed", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_download_host_game_unique": { + "name": "game_download_host_game_unique", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_game_idx": { + "name": "game_download_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_host_status_idx": { + "name": "game_download_host_status_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_download_host_id_machine_id_fk": { + "name": "game_download_host_id_machine_id_fk", + "tableFrom": "game_download", + "tableTo": "machine", + "columnsFrom": ["host_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "game_download_game_id_game_id_fk": { + "name": "game_download_game_id_game_id_fk", + "tableFrom": "game_download", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game": { + "name": "game", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "steam_app_id": { + "name": "steam_app_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aliases": { + "name": "aliases", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_icon": { + "name": "client_icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "short_description": { + "name": "short_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "developers": { + "name": "developers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "publishers": { + "name": "publishers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "primary_genre": { + "name": "primary_genre", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "genres": { + "name": "genres", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "controller_support": { + "name": "controller_support", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_deck_compat": { + "name": "steam_deck_compat", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "review_score_percent": { + "name": "review_score_percent", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "review_count": { + "name": "review_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "metacritic_score": { + "name": "metacritic_score", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "steam_change_number": { + "name": "steam_change_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "public_build_id": { + "name": "public_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "release_date_utc": { + "name": "release_date_utc", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_enriched": { + "name": "time_enriched", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_slug_unique": { + "name": "game_slug_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_app_id_unique": { + "name": "game_app_id_unique", + "columns": [ + { + "expression": "steam_app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "game_steam_app_id_unique": { + "name": "game_steam_app_id_unique", + "nullsNotDistinct": false, + "columns": ["steam_app_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.machine": { + "name": "machine", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "machine_secret_hash_unique": { + "name": "machine_secret_hash_unique", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_endpoint_id_unique": { + "name": "machine_endpoint_id_unique", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_owner_idx": { + "name": "machine_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_team_idx": { + "name": "machine_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "machine_owner_user_id_user_id_fk": { + "name": "machine_owner_user_id_user_id_fk", + "tableFrom": "machine", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "machine_team_id_team_id_fk": { + "name": "machine_team_id_team_id_fk", + "tableFrom": "machine", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pairing_code": { + "name": "pairing_code", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_fingerprint": { + "name": "new_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "is_claimed": { + "name": "is_claimed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "pairing_code_code_unique": { + "name": "pairing_code_code_unique", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pairing_code_target_user_idx": { + "name": "pairing_code_target_user_idx", + "columns": [ + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "box_id": { + "name": "box_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "linked_account_id": { + "name": "linked_account_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "session_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "ticket": { + "name": "ticket", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "time_started": { + "name": "time_started", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_stopped": { + "name": "time_stopped", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_token": { + "name": "claim_token", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_box_idx": { + "name": "session_box_idx", + "columns": [ + { + "expression": "box_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_state_idx": { + "name": "session_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_box_active_unique": { + "name": "session_box_active_unique", + "columns": [ + { + "expression": "box_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "time_stopped is null and time_deleted is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_started_idx": { + "name": "session_started_idx", + "columns": [ + { + "expression": "time_started", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_box_id_box_id_fk": { + "name": "session_box_id_box_id_fk", + "tableFrom": "session", + "tableTo": "box", + "columnsFrom": ["box_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_game_id_game_id_fk": { + "name": "session_game_id_game_id_fk", + "tableFrom": "session", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "session_linked_account_id_linked_account_id_fk": { + "name": "session_linked_account_id_linked_account_id_fk", + "tableFrom": "session", + "tableTo": "linked_account", + "columnsFrom": ["linked_account_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.steam_enrolment": { + "name": "steam_enrolment", + "schema": "", + "columns": { + "machine_id": { + "name": "machine_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "steam_id": { + "name": "steam_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "steam_enrolment_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_ok_at": { + "name": "last_ok_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "steam_enrolment_user_idx": { + "name": "steam_enrolment_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "steam_enrolment_machine_id_machine_id_fk": { + "name": "steam_enrolment_machine_id_machine_id_fk", + "tableFrom": "steam_enrolment", + "tableTo": "machine", + "columnsFrom": ["machine_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "steam_enrolment_user_id_user_id_fk": { + "name": "steam_enrolment_user_id_user_id_fk", + "tableFrom": "steam_enrolment", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "steam_enrolment_machine_id_user_id_pk": { + "name": "steam_enrolment_machine_id_user_id_pk", + "columns": ["machine_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_member": { + "name": "team_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "team_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + } + }, + "indexes": { + "team_member_team_user_unique": { + "name": "team_member_team_user_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_team_idx": { + "name": "team_member_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_user_idx": { + "name": "team_member_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_member_team_id_team_id_fk": { + "name": "team_member_team_id_team_id_fk", + "tableFrom": "team_member", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_member_user_id_user_id_fk": { + "name": "team_member_user_id_user_id_fk", + "tableFrom": "team_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team": { + "name": "team", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "billing_email": { + "name": "billing_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "subscription_status": { + "name": "subscription_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "team_owner_id_user_id_fk": { + "name": "team_owner_id_user_id_fk", + "tableFrom": "team", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_slug_unique": { + "name": "team_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_fingerprint": { + "name": "user_fingerprint", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_fingerprint_fingerprint_unique": { + "name": "user_fingerprint_fingerprint_unique", + "columns": [ + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_fingerprint_user_idx": { + "name": "user_fingerprint_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_fingerprint_user_id_user_id_fk": { + "name": "user_fingerprint_user_id_user_id_fk", + "tableFrom": "user_fingerprint", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_library": { + "name": "user_library", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "playtime_2w": { + "name": "playtime_2w", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "playtime_forever": { + "name": "playtime_forever", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_played": { + "name": "last_played", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_library_user_game_unique": { + "name": "user_library_user_game_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_user_idx": { + "name": "user_library_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_game_idx": { + "name": "user_library_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_library_user_id_user_id_fk": { + "name": "user_library_user_id_user_id_fk", + "tableFrom": "user_library", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_library_game_id_game_id_fk": { + "name": "user_library_game_id_game_id_fk", + "tableFrom": "user_library", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.linked_account": { + "name": "linked_account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "linked_account_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile": { + "name": "profile", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "linked_account_provider_unique": { + "name": "linked_account_provider_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "linked_account_user_idx": { + "name": "linked_account_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "linked_account_user_id_user_id_fk": { + "name": "linked_account_user_id_user_id_fk", + "tableFrom": "linked_account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "email is not null and time_deleted is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "verification_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_user_kind_idx": { + "name": "verification_user_kind_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "verification_user_id_user_id_fk": { + "name": "verification_user_id_user_id_fk", + "tableFrom": "verification", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist_entry": { + "name": "waitlist_entry", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'machines'" + } + }, + "indexes": { + "waitlist_entry_email_unique": { + "name": "waitlist_entry_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "waitlist_entry_source_idx": { + "name": "waitlist_entry_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.device_grant_status": { + "name": "device_grant_status", + "schema": "public", + "values": ["pending", "approved", "denied"] + }, + "public.auth_key_kind": { + "name": "auth_key_kind", + "schema": "public", + "values": ["signing", "encryption"] + }, + "public.box_state": { + "name": "box_state", + "schema": "public", + "values": ["created", "running", "stopped"] + }, + "public.box_tier": { + "name": "box_tier", + "schema": "public", + "values": ["xs", "sm", "md", "lg", "xl"] + }, + "public.depot_status": { + "name": "depot_status", + "schema": "public", + "values": ["pending", "downloading", "complete", "error", "deleted"] + }, + "public.game_download_status": { + "name": "game_download_status", + "schema": "public", + "values": ["pending", "verifying", "downloading", "ready", "failed"] + }, + "public.session_state": { + "name": "session_state", + "schema": "public", + "values": ["requested", "starting", "live", "ended", "failed"] + }, + "public.steam_enrolment_state": { + "name": "steam_enrolment_state", + "schema": "public", + "values": ["enrolled", "stale", "revoked"] + }, + "public.team_member_role": { + "name": "team_member_role", + "schema": "public", + "values": ["owner", "admin", "member"] + }, + "public.linked_account_provider": { + "name": "linked_account_provider", + "schema": "public", + "values": ["steam", "ssh", "discord"] + }, + "public.verification_kind": { + "name": "verification_kind", + "schema": "public", + "values": ["email"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/core/migrations/meta/0014_snapshot.json b/packages/core/migrations/meta/0014_snapshot.json index 42db2fc2..2f04aecb 100644 --- a/packages/core/migrations/meta/0014_snapshot.json +++ b/packages/core/migrations/meta/0014_snapshot.json @@ -1,2972 +1,2828 @@ { - "id": "f625bf1c-518c-4ae0-acf9-1e4256450993", - "prevId": "7510a518-9e7d-4017-a3ec-b28f8d29a853", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.access_token": { - "name": "access_token", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "owner_user_id": { - "name": "owner_user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token_hash": { - "name": "token_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "last_used": { - "name": "last_used", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "access_token_hash_unique": { - "name": "access_token_hash_unique", - "columns": [ - { - "expression": "token_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "access_token_owner_idx": { - "name": "access_token_owner_idx", - "columns": [ - { - "expression": "owner_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "access_token_team_idx": { - "name": "access_token_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "access_token_owner_user_id_user_id_fk": { - "name": "access_token_owner_user_id_user_id_fk", - "tableFrom": "access_token", - "tableTo": "user", - "columnsFrom": [ - "owner_user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "access_token_team_id_team_id_fk": { - "name": "access_token_team_id_team_id_fk", - "tableFrom": "access_token", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.authorization_code": { - "name": "authorization_code", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "code_hash": { - "name": "code_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "payload": { - "name": "payload", - "type": "jsonb", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "authorization_code_hash_unique": { - "name": "authorization_code_hash_unique", - "columns": [ - { - "expression": "code_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.device_grant": { - "name": "device_grant", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "device_code_hash": { - "name": "device_code_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_code": { - "name": "user_code", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "client_id": { - "name": "client_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "device_grant_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "poll_interval": { - "name": "poll_interval", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "last_polled_at": { - "name": "last_polled_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "subject": { - "name": "subject", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "device_grant_device_code_unique": { - "name": "device_grant_device_code_unique", - "columns": [ - { - "expression": "device_code_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "device_grant_user_code_unique": { - "name": "device_grant_user_code_unique", - "columns": [ - { - "expression": "user_code", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.refresh_token": { - "name": "refresh_token", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "subject": { - "name": "subject", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token_hash": { - "name": "token_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "time_used": { - "name": "time_used", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "payload": { - "name": "payload", - "type": "jsonb", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "refresh_token_hash_unique": { - "name": "refresh_token_hash_unique", - "columns": [ - { - "expression": "token_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "refresh_token_subject_idx": { - "name": "refresh_token_subject_idx", - "columns": [ - { - "expression": "subject", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.auth_key": { - "name": "auth_key", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "key_id": { - "name": "key_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "auth_key_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "alg": { - "name": "alg", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "public_key": { - "name": "public_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "private_key": { - "name": "private_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expired_at": { - "name": "expired_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "auth_key_key_id_unique": { - "name": "auth_key_key_id_unique", - "columns": [ - { - "expression": "key_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "auth_key_one_live_per_kind": { - "name": "auth_key_one_live_per_kind", - "columns": [ - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"auth_key\".\"expired_at\" is null", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.auth_kv": { - "name": "auth_kv", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "key": { - "name": "key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "value": { - "name": "value", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "auth_kv_key_unique": { - "name": "auth_kv_key_unique", - "columns": [ - { - "expression": "key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.box": { - "name": "box", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "machine_id": { - "name": "machine_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "label": { - "name": "label", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tier": { - "name": "tier", - "type": "box_tier", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'sm'" - }, - "state": { - "name": "state", - "type": "box_state", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'created'" - }, - "stop_reason": { - "name": "stop_reason", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stop_clean": { - "name": "stop_clean", - "type": "boolean", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "box_user_idx": { - "name": "box_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "box_machine_idx": { - "name": "box_machine_idx", - "columns": [ - { - "expression": "machine_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "box_user_id_user_id_fk": { - "name": "box_user_id_user_id_fk", - "tableFrom": "box", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "box_machine_id_machine_id_fk": { - "name": "box_machine_id_machine_id_fk", - "tableFrom": "box", - "tableTo": "machine", - "columnsFrom": [ - "machine_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game_depot": { - "name": "game_depot", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "depot_id": { - "name": "depot_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "branch": { - "name": "branch", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'public'" - }, - "steam_manifest_id": { - "name": "steam_manifest_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "steam_build_id": { - "name": "steam_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "installed_manifest_id": { - "name": "installed_manifest_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "installed_build_id": { - "name": "installed_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "size_download": { - "name": "size_download", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "size_on_disk": { - "name": "size_on_disk", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "depot_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "oslist": { - "name": "oslist", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_depot_unique": { - "name": "game_depot_unique", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "depot_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "branch", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_depot_game_idx": { - "name": "game_depot_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_depot_updates_idx": { - "name": "game_depot_updates_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"game_depot\".\"installed_manifest_id\" is distinct from \"game_depot\".\"steam_manifest_id\"", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "game_depot_game_id_game_id_fk": { - "name": "game_depot_game_id_game_id_fk", - "tableFrom": "game_depot", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game_download": { - "name": "game_download", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "host_id": { - "name": "host_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "game_download_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "progress_bytes": { - "name": "progress_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": false, - "default": 0 - }, - "total_bytes": { - "name": "total_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "time_started": { - "name": "time_started", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_completed": { - "name": "time_completed", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_download_host_game_unique": { - "name": "game_download_host_game_unique", - "columns": [ - { - "expression": "host_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_download_game_idx": { - "name": "game_download_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_download_host_status_idx": { - "name": "game_download_host_status_idx", - "columns": [ - { - "expression": "host_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "game_download_host_id_machine_id_fk": { - "name": "game_download_host_id_machine_id_fk", - "tableFrom": "game_download", - "tableTo": "machine", - "columnsFrom": [ - "host_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "game_download_game_id_game_id_fk": { - "name": "game_download_game_id_game_id_fk", - "tableFrom": "game_download", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.game": { - "name": "game", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "steam_app_id": { - "name": "steam_app_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "aliases": { - "name": "aliases", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "client_icon": { - "name": "client_icon", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "icon": { - "name": "icon", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "short_description": { - "name": "short_description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "developers": { - "name": "developers", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "publishers": { - "name": "publishers", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "primary_genre": { - "name": "primary_genre", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "genres": { - "name": "genres", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "categories": { - "name": "categories", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "oslist": { - "name": "oslist", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "size_download": { - "name": "size_download", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "size_on_disk": { - "name": "size_on_disk", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "controller_support": { - "name": "controller_support", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "steam_deck_compat": { - "name": "steam_deck_compat", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "review_score_percent": { - "name": "review_score_percent", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "review_count": { - "name": "review_count", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "metacritic_score": { - "name": "metacritic_score", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "steam_change_number": { - "name": "steam_change_number", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "public_build_id": { - "name": "public_build_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "release_date_utc": { - "name": "release_date_utc", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_enriched": { - "name": "time_enriched", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "game_slug_unique": { - "name": "game_slug_unique", - "columns": [ - { - "expression": "slug", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "game_app_id_unique": { - "name": "game_app_id_unique", - "columns": [ - { - "expression": "steam_app_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "game_steam_app_id_unique": { - "name": "game_steam_app_id_unique", - "nullsNotDistinct": false, - "columns": [ - "steam_app_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.machine": { - "name": "machine", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "owner_user_id": { - "name": "owner_user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "label": { - "name": "label", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "endpoint_id": { - "name": "endpoint_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "secret_hash": { - "name": "secret_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "last_seen": { - "name": "last_seen", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "machine_secret_hash_unique": { - "name": "machine_secret_hash_unique", - "columns": [ - { - "expression": "secret_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_slug_unique": { - "name": "machine_slug_unique", - "columns": [ - { - "expression": "slug", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_endpoint_id_unique": { - "name": "machine_endpoint_id_unique", - "columns": [ - { - "expression": "endpoint_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_owner_idx": { - "name": "machine_owner_idx", - "columns": [ - { - "expression": "owner_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "machine_team_idx": { - "name": "machine_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "machine_owner_user_id_user_id_fk": { - "name": "machine_owner_user_id_user_id_fk", - "tableFrom": "machine", - "tableTo": "user", - "columnsFrom": [ - "owner_user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "machine_team_id_team_id_fk": { - "name": "machine_team_id_team_id_fk", - "tableFrom": "machine", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pairing_code": { - "name": "pairing_code", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "code": { - "name": "code", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "target_user_id": { - "name": "target_user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "new_fingerprint": { - "name": "new_fingerprint", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "claimed_at": { - "name": "claimed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "is_claimed": { - "name": "is_claimed", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": { - "pairing_code_code_unique": { - "name": "pairing_code_code_unique", - "columns": [ - { - "expression": "code", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "pairing_code_target_user_idx": { - "name": "pairing_code_target_user_idx", - "columns": [ - { - "expression": "target_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "box_id": { - "name": "box_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "linked_account_id": { - "name": "linked_account_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "state": { - "name": "state", - "type": "session_state", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'requested'" - }, - "ticket": { - "name": "ticket", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "time_started": { - "name": "time_started", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "time_stopped": { - "name": "time_stopped", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "claim_token": { - "name": "claim_token", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "session_box_idx": { - "name": "session_box_idx", - "columns": [ - { - "expression": "box_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_state_idx": { - "name": "session_state_idx", - "columns": [ - { - "expression": "state", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_box_active_unique": { - "name": "session_box_active_unique", - "columns": [ - { - "expression": "box_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "time_stopped is null and time_deleted is null", - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_started_idx": { - "name": "session_started_idx", - "columns": [ - { - "expression": "time_started", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "session_box_id_box_id_fk": { - "name": "session_box_id_box_id_fk", - "tableFrom": "session", - "tableTo": "box", - "columnsFrom": [ - "box_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "session_game_id_game_id_fk": { - "name": "session_game_id_game_id_fk", - "tableFrom": "session", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - }, - "session_linked_account_id_linked_account_id_fk": { - "name": "session_linked_account_id_linked_account_id_fk", - "tableFrom": "session", - "tableTo": "linked_account", - "columnsFrom": [ - "linked_account_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.steam_enrolment": { - "name": "steam_enrolment", - "schema": "", - "columns": { - "machine_id": { - "name": "machine_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "steam_id": { - "name": "steam_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "state": { - "name": "state", - "type": "steam_enrolment_state", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "enrolled_at": { - "name": "enrolled_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "last_ok_at": { - "name": "last_ok_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "revoked_at": { - "name": "revoked_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "steam_enrolment_user_idx": { - "name": "steam_enrolment_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "steam_enrolment_machine_id_machine_id_fk": { - "name": "steam_enrolment_machine_id_machine_id_fk", - "tableFrom": "steam_enrolment", - "tableTo": "machine", - "columnsFrom": [ - "machine_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "steam_enrolment_user_id_user_id_fk": { - "name": "steam_enrolment_user_id_user_id_fk", - "tableFrom": "steam_enrolment", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "steam_enrolment_machine_id_user_id_pk": { - "name": "steam_enrolment_machine_id_user_id_pk", - "columns": [ - "machine_id", - "user_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.team_member": { - "name": "team_member", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "team_id": { - "name": "team_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "role": { - "name": "role", - "type": "team_member_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'member'" - } - }, - "indexes": { - "team_member_team_user_unique": { - "name": "team_member_team_user_unique", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "team_member_team_idx": { - "name": "team_member_team_idx", - "columns": [ - { - "expression": "team_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "team_member_user_idx": { - "name": "team_member_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "team_member_team_id_team_id_fk": { - "name": "team_member_team_id_team_id_fk", - "tableFrom": "team_member", - "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "team_member_user_id_user_id_fk": { - "name": "team_member_user_id_user_id_fk", - "tableFrom": "team_member", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.team": { - "name": "team", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "owner_id": { - "name": "owner_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "billing_email": { - "name": "billing_email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "plan": { - "name": "plan", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'free'" - }, - "subscription_status": { - "name": "subscription_status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'active'" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "team_owner_id_user_id_fk": { - "name": "team_owner_id_user_id_fk", - "tableFrom": "team", - "tableTo": "user", - "columnsFrom": [ - "owner_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "team_slug_unique": { - "name": "team_slug_unique", - "nullsNotDistinct": false, - "columns": [ - "slug" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_fingerprint": { - "name": "user_fingerprint", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "fingerprint": { - "name": "fingerprint", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_seen": { - "name": "last_seen", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_fingerprint_fingerprint_unique": { - "name": "user_fingerprint_fingerprint_unique", - "columns": [ - { - "expression": "fingerprint", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_fingerprint_user_idx": { - "name": "user_fingerprint_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "user_fingerprint_user_id_user_id_fk": { - "name": "user_fingerprint_user_id_user_id_fk", - "tableFrom": "user_fingerprint", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_library": { - "name": "user_library", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "game_id": { - "name": "game_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "playtime_2w": { - "name": "playtime_2w", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "playtime_forever": { - "name": "playtime_forever", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "last_played": { - "name": "last_played", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_library_user_game_unique": { - "name": "user_library_user_game_unique", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_library_user_idx": { - "name": "user_library_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_library_game_idx": { - "name": "user_library_game_idx", - "columns": [ - { - "expression": "game_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "user_library_user_id_user_id_fk": { - "name": "user_library_user_id_user_id_fk", - "tableFrom": "user_library", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "user_library_game_id_game_id_fk": { - "name": "user_library_game_id_game_id_fk", - "tableFrom": "user_library", - "tableTo": "game", - "columnsFrom": [ - "game_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.linked_account": { - "name": "linked_account", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "linked_account_provider", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "provider_account_id": { - "name": "provider_account_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "profile": { - "name": "profile", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "linked_account_provider_unique": { - "name": "linked_account_provider_unique", - "columns": [ - { - "expression": "provider", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "provider_account_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "linked_account_user_idx": { - "name": "linked_account_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "linked_account_user_id_user_id_fk": { - "name": "linked_account_user_id_user_id_fk", - "tableFrom": "linked_account", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email_verified": { - "name": "email_verified", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_email_unique": { - "name": "user_email_unique", - "columns": [ - { - "expression": "email", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "email is not null and time_deleted is null", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verification": { - "name": "verification", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "char(30)", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "verification_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "code_hash": { - "name": "code_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "attempts": { - "name": "attempts", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "consumed_at": { - "name": "consumed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "verification_user_kind_idx": { - "name": "verification_user_kind_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "verification_user_id_user_id_fk": { - "name": "verification_user_id_user_id_fk", - "tableFrom": "verification", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.waitlist_entry": { - "name": "waitlist_entry", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "char(30)", - "primaryKey": true, - "notNull": true - }, - "time_created": { - "name": "time_created", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_updated": { - "name": "time_updated", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "time_deleted": { - "name": "time_deleted", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source": { - "name": "source", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'machines'" - } - }, - "indexes": { - "waitlist_entry_email_unique": { - "name": "waitlist_entry_email_unique", - "columns": [ - { - "expression": "email", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "waitlist_entry_source_idx": { - "name": "waitlist_entry_source_idx", - "columns": [ - { - "expression": "source", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.device_grant_status": { - "name": "device_grant_status", - "schema": "public", - "values": [ - "pending", - "approved", - "denied" - ] - }, - "public.auth_key_kind": { - "name": "auth_key_kind", - "schema": "public", - "values": [ - "signing", - "encryption" - ] - }, - "public.box_state": { - "name": "box_state", - "schema": "public", - "values": [ - "created", - "running", - "stopped" - ] - }, - "public.box_tier": { - "name": "box_tier", - "schema": "public", - "values": [ - "xs", - "sm", - "md", - "lg", - "xl" - ] - }, - "public.depot_status": { - "name": "depot_status", - "schema": "public", - "values": [ - "pending", - "downloading", - "complete", - "error", - "deleted" - ] - }, - "public.game_download_status": { - "name": "game_download_status", - "schema": "public", - "values": [ - "pending", - "verifying", - "downloading", - "ready", - "failed" - ] - }, - "public.session_state": { - "name": "session_state", - "schema": "public", - "values": [ - "requested", - "starting", - "live", - "ended", - "failed" - ] - }, - "public.steam_enrolment_state": { - "name": "steam_enrolment_state", - "schema": "public", - "values": [ - "enrolled", - "stale", - "revoked" - ] - }, - "public.team_member_role": { - "name": "team_member_role", - "schema": "public", - "values": [ - "owner", - "admin", - "member" - ] - }, - "public.linked_account_provider": { - "name": "linked_account_provider", - "schema": "public", - "values": [ - "steam", - "ssh", - "discord" - ] - }, - "public.verification_kind": { - "name": "verification_kind", - "schema": "public", - "values": [ - "email" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file + "id": "f625bf1c-518c-4ae0-acf9-1e4256450993", + "prevId": "7510a518-9e7d-4017-a3ec-b28f8d29a853", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used": { + "name": "last_used", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "access_token_hash_unique": { + "name": "access_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_owner_idx": { + "name": "access_token_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_team_idx": { + "name": "access_token_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "access_token_owner_user_id_user_id_fk": { + "name": "access_token_owner_user_id_user_id_fk", + "tableFrom": "access_token", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "access_token_team_id_team_id_fk": { + "name": "access_token_team_id_team_id_fk", + "tableFrom": "access_token", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.authorization_code": { + "name": "authorization_code", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "authorization_code_hash_unique": { + "name": "authorization_code_hash_unique", + "columns": [ + { + "expression": "code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.device_grant": { + "name": "device_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "device_code_hash": { + "name": "device_code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_code": { + "name": "user_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "device_grant_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "poll_interval": { + "name": "poll_interval", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "last_polled_at": { + "name": "last_polled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "device_grant_device_code_unique": { + "name": "device_grant_device_code_unique", + "columns": [ + { + "expression": "device_code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "device_grant_user_code_unique": { + "name": "device_grant_user_code_unique", + "columns": [ + { + "expression": "user_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.refresh_token": { + "name": "refresh_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "time_used": { + "name": "time_used", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "refresh_token_hash_unique": { + "name": "refresh_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "refresh_token_subject_idx": { + "name": "refresh_token_subject_idx", + "columns": [ + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_key": { + "name": "auth_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "key_id": { + "name": "key_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "auth_key_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "alg": { + "name": "alg", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expired_at": { + "name": "expired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "auth_key_key_id_unique": { + "name": "auth_key_key_id_unique", + "columns": [ + { + "expression": "key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auth_key_one_live_per_kind": { + "name": "auth_key_one_live_per_kind", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"auth_key\".\"expired_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_kv": { + "name": "auth_kv", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "auth_kv_key_unique": { + "name": "auth_kv_key_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.box": { + "name": "box", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "machine_id": { + "name": "machine_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tier": { + "name": "tier", + "type": "box_tier", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'sm'" + }, + "state": { + "name": "state", + "type": "box_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'created'" + }, + "stop_reason": { + "name": "stop_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stop_clean": { + "name": "stop_clean", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "box_user_idx": { + "name": "box_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "box_machine_idx": { + "name": "box_machine_idx", + "columns": [ + { + "expression": "machine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "box_user_id_user_id_fk": { + "name": "box_user_id_user_id_fk", + "tableFrom": "box", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "box_machine_id_machine_id_fk": { + "name": "box_machine_id_machine_id_fk", + "tableFrom": "box", + "tableTo": "machine", + "columnsFrom": ["machine_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_depot": { + "name": "game_depot", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "depot_id": { + "name": "depot_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "steam_manifest_id": { + "name": "steam_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_build_id": { + "name": "steam_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "installed_manifest_id": { + "name": "installed_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_build_id": { + "name": "installed_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "depot_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_depot_unique": { + "name": "game_depot_unique", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "depot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_game_idx": { + "name": "game_depot_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_updates_idx": { + "name": "game_depot_updates_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"game_depot\".\"installed_manifest_id\" is distinct from \"game_depot\".\"steam_manifest_id\"", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_depot_game_id_game_id_fk": { + "name": "game_depot_game_id_game_id_fk", + "tableFrom": "game_depot", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_download": { + "name": "game_download", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "host_id": { + "name": "host_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "game_download_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "progress_bytes": { + "name": "progress_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "total_bytes": { + "name": "total_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "time_started": { + "name": "time_started", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_completed": { + "name": "time_completed", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_download_host_game_unique": { + "name": "game_download_host_game_unique", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_game_idx": { + "name": "game_download_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_host_status_idx": { + "name": "game_download_host_status_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_download_host_id_machine_id_fk": { + "name": "game_download_host_id_machine_id_fk", + "tableFrom": "game_download", + "tableTo": "machine", + "columnsFrom": ["host_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "game_download_game_id_game_id_fk": { + "name": "game_download_game_id_game_id_fk", + "tableFrom": "game_download", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game": { + "name": "game", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "steam_app_id": { + "name": "steam_app_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aliases": { + "name": "aliases", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_icon": { + "name": "client_icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "short_description": { + "name": "short_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "developers": { + "name": "developers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "publishers": { + "name": "publishers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "primary_genre": { + "name": "primary_genre", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "genres": { + "name": "genres", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "controller_support": { + "name": "controller_support", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_deck_compat": { + "name": "steam_deck_compat", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "review_score_percent": { + "name": "review_score_percent", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "review_count": { + "name": "review_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "metacritic_score": { + "name": "metacritic_score", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "steam_change_number": { + "name": "steam_change_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "public_build_id": { + "name": "public_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "release_date_utc": { + "name": "release_date_utc", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_enriched": { + "name": "time_enriched", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_slug_unique": { + "name": "game_slug_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_app_id_unique": { + "name": "game_app_id_unique", + "columns": [ + { + "expression": "steam_app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "game_steam_app_id_unique": { + "name": "game_steam_app_id_unique", + "nullsNotDistinct": false, + "columns": ["steam_app_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.machine": { + "name": "machine", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "machine_secret_hash_unique": { + "name": "machine_secret_hash_unique", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_slug_unique": { + "name": "machine_slug_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_endpoint_id_unique": { + "name": "machine_endpoint_id_unique", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_owner_idx": { + "name": "machine_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_team_idx": { + "name": "machine_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "machine_owner_user_id_user_id_fk": { + "name": "machine_owner_user_id_user_id_fk", + "tableFrom": "machine", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "machine_team_id_team_id_fk": { + "name": "machine_team_id_team_id_fk", + "tableFrom": "machine", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pairing_code": { + "name": "pairing_code", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_fingerprint": { + "name": "new_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "is_claimed": { + "name": "is_claimed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "pairing_code_code_unique": { + "name": "pairing_code_code_unique", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pairing_code_target_user_idx": { + "name": "pairing_code_target_user_idx", + "columns": [ + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "box_id": { + "name": "box_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "linked_account_id": { + "name": "linked_account_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "session_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "ticket": { + "name": "ticket", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "time_started": { + "name": "time_started", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_stopped": { + "name": "time_stopped", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_token": { + "name": "claim_token", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_box_idx": { + "name": "session_box_idx", + "columns": [ + { + "expression": "box_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_state_idx": { + "name": "session_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_box_active_unique": { + "name": "session_box_active_unique", + "columns": [ + { + "expression": "box_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "time_stopped is null and time_deleted is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_started_idx": { + "name": "session_started_idx", + "columns": [ + { + "expression": "time_started", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_box_id_box_id_fk": { + "name": "session_box_id_box_id_fk", + "tableFrom": "session", + "tableTo": "box", + "columnsFrom": ["box_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_game_id_game_id_fk": { + "name": "session_game_id_game_id_fk", + "tableFrom": "session", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "session_linked_account_id_linked_account_id_fk": { + "name": "session_linked_account_id_linked_account_id_fk", + "tableFrom": "session", + "tableTo": "linked_account", + "columnsFrom": ["linked_account_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.steam_enrolment": { + "name": "steam_enrolment", + "schema": "", + "columns": { + "machine_id": { + "name": "machine_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "steam_id": { + "name": "steam_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "steam_enrolment_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_ok_at": { + "name": "last_ok_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "steam_enrolment_user_idx": { + "name": "steam_enrolment_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "steam_enrolment_machine_id_machine_id_fk": { + "name": "steam_enrolment_machine_id_machine_id_fk", + "tableFrom": "steam_enrolment", + "tableTo": "machine", + "columnsFrom": ["machine_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "steam_enrolment_user_id_user_id_fk": { + "name": "steam_enrolment_user_id_user_id_fk", + "tableFrom": "steam_enrolment", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "steam_enrolment_machine_id_user_id_pk": { + "name": "steam_enrolment_machine_id_user_id_pk", + "columns": ["machine_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_member": { + "name": "team_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "team_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + } + }, + "indexes": { + "team_member_team_user_unique": { + "name": "team_member_team_user_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_team_idx": { + "name": "team_member_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_user_idx": { + "name": "team_member_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_member_team_id_team_id_fk": { + "name": "team_member_team_id_team_id_fk", + "tableFrom": "team_member", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_member_user_id_user_id_fk": { + "name": "team_member_user_id_user_id_fk", + "tableFrom": "team_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team": { + "name": "team", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "billing_email": { + "name": "billing_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "subscription_status": { + "name": "subscription_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "team_owner_id_user_id_fk": { + "name": "team_owner_id_user_id_fk", + "tableFrom": "team", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_slug_unique": { + "name": "team_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_fingerprint": { + "name": "user_fingerprint", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_fingerprint_fingerprint_unique": { + "name": "user_fingerprint_fingerprint_unique", + "columns": [ + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_fingerprint_user_idx": { + "name": "user_fingerprint_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_fingerprint_user_id_user_id_fk": { + "name": "user_fingerprint_user_id_user_id_fk", + "tableFrom": "user_fingerprint", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_library": { + "name": "user_library", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "playtime_2w": { + "name": "playtime_2w", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "playtime_forever": { + "name": "playtime_forever", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_played": { + "name": "last_played", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_library_user_game_unique": { + "name": "user_library_user_game_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_user_idx": { + "name": "user_library_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_game_idx": { + "name": "user_library_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_library_user_id_user_id_fk": { + "name": "user_library_user_id_user_id_fk", + "tableFrom": "user_library", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_library_game_id_game_id_fk": { + "name": "user_library_game_id_game_id_fk", + "tableFrom": "user_library", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.linked_account": { + "name": "linked_account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "linked_account_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile": { + "name": "profile", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "linked_account_provider_unique": { + "name": "linked_account_provider_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "linked_account_user_idx": { + "name": "linked_account_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "linked_account_user_id_user_id_fk": { + "name": "linked_account_user_id_user_id_fk", + "tableFrom": "linked_account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "email is not null and time_deleted is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "verification_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_user_kind_idx": { + "name": "verification_user_kind_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "verification_user_id_user_id_fk": { + "name": "verification_user_id_user_id_fk", + "tableFrom": "verification", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist_entry": { + "name": "waitlist_entry", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'machines'" + } + }, + "indexes": { + "waitlist_entry_email_unique": { + "name": "waitlist_entry_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "waitlist_entry_source_idx": { + "name": "waitlist_entry_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.device_grant_status": { + "name": "device_grant_status", + "schema": "public", + "values": ["pending", "approved", "denied"] + }, + "public.auth_key_kind": { + "name": "auth_key_kind", + "schema": "public", + "values": ["signing", "encryption"] + }, + "public.box_state": { + "name": "box_state", + "schema": "public", + "values": ["created", "running", "stopped"] + }, + "public.box_tier": { + "name": "box_tier", + "schema": "public", + "values": ["xs", "sm", "md", "lg", "xl"] + }, + "public.depot_status": { + "name": "depot_status", + "schema": "public", + "values": ["pending", "downloading", "complete", "error", "deleted"] + }, + "public.game_download_status": { + "name": "game_download_status", + "schema": "public", + "values": ["pending", "verifying", "downloading", "ready", "failed"] + }, + "public.session_state": { + "name": "session_state", + "schema": "public", + "values": ["requested", "starting", "live", "ended", "failed"] + }, + "public.steam_enrolment_state": { + "name": "steam_enrolment_state", + "schema": "public", + "values": ["enrolled", "stale", "revoked"] + }, + "public.team_member_role": { + "name": "team_member_role", + "schema": "public", + "values": ["owner", "admin", "member"] + }, + "public.linked_account_provider": { + "name": "linked_account_provider", + "schema": "public", + "values": ["steam", "ssh", "discord"] + }, + "public.verification_kind": { + "name": "verification_kind", + "schema": "public", + "values": ["email"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/core/migrations/meta/_journal.json b/packages/core/migrations/meta/_journal.json index b17ee05d..eaf63885 100644 --- a/packages/core/migrations/meta/_journal.json +++ b/packages/core/migrations/meta/_journal.json @@ -1,111 +1,111 @@ { - "version": "7", - "dialect": "postgresql", - "entries": [ - { - "idx": 0, - "version": "7", - "when": 1784801002476, - "tag": "0000_quick_dark_phoenix", - "breakpoints": true - }, - { - "idx": 1, - "version": "7", - "when": 1785312635128, - "tag": "0001_opposite_senator_kelly", - "breakpoints": true - }, - { - "idx": 2, - "version": "7", - "when": 1785379712946, - "tag": "0002_light_mesmero", - "breakpoints": true - }, - { - "idx": 3, - "version": "7", - "when": 1785382013687, - "tag": "0003_many_pyro", - "breakpoints": true - }, - { - "idx": 4, - "version": "7", - "when": 1785588097470, - "tag": "0004_remove_user_download_add_game_download", - "breakpoints": true - }, - { - "idx": 5, - "version": "7", - "when": 1785909838801, - "tag": "0005_flaky_may_parker", - "breakpoints": true - }, - { - "idx": 6, - "version": "7", - "when": 1786205230097, - "tag": "0006_waitlist_verification_game_aliases", - "breakpoints": true - }, - { - "idx": 7, - "version": "7", - "when": 1788460224524, - "tag": "0007_box_session_team_notnull", - "breakpoints": true - }, - { - "idx": 8, - "version": "7", - "when": 1788547836146, - "tag": "0008_session_one_active_run_per_box", - "breakpoints": true - }, - { - "idx": 9, - "version": "7", - "when": 1788555252186, - "tag": "0009_email_is_the_root_identity", - "breakpoints": true - }, - { - "idx": 10, - "version": "7", - "when": 1788590292860, - "tag": "0010_device_authorization_grant", - "breakpoints": true - }, - { - "idx": 11, - "version": "7", - "when": 1788607804606, - "tag": "0011_auth_state_in_postgres", - "breakpoints": true - }, - { - "idx": 12, - "version": "7", - "when": 1788691753961, - "tag": "0012_steam_enrolment_without_a_token", - "breakpoints": true - }, - { - "idx": 13, - "version": "7", - "when": 1788725541386, - "tag": "0013_machine_endpoint_id", - "breakpoints": true - }, - { - "idx": 14, - "version": "7", - "when": 1789680491539, - "tag": "0014_machine_public_label", - "breakpoints": true - } - ] -} \ No newline at end of file + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1784801002476, + "tag": "0000_quick_dark_phoenix", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1785312635128, + "tag": "0001_opposite_senator_kelly", + "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1785379712946, + "tag": "0002_light_mesmero", + "breakpoints": true + }, + { + "idx": 3, + "version": "7", + "when": 1785382013687, + "tag": "0003_many_pyro", + "breakpoints": true + }, + { + "idx": 4, + "version": "7", + "when": 1785588097470, + "tag": "0004_remove_user_download_add_game_download", + "breakpoints": true + }, + { + "idx": 5, + "version": "7", + "when": 1785909838801, + "tag": "0005_flaky_may_parker", + "breakpoints": true + }, + { + "idx": 6, + "version": "7", + "when": 1786205230097, + "tag": "0006_waitlist_verification_game_aliases", + "breakpoints": true + }, + { + "idx": 7, + "version": "7", + "when": 1788460224524, + "tag": "0007_box_session_team_notnull", + "breakpoints": true + }, + { + "idx": 8, + "version": "7", + "when": 1788547836146, + "tag": "0008_session_one_active_run_per_box", + "breakpoints": true + }, + { + "idx": 9, + "version": "7", + "when": 1788555252186, + "tag": "0009_email_is_the_root_identity", + "breakpoints": true + }, + { + "idx": 10, + "version": "7", + "when": 1788590292860, + "tag": "0010_device_authorization_grant", + "breakpoints": true + }, + { + "idx": 11, + "version": "7", + "when": 1788607804606, + "tag": "0011_auth_state_in_postgres", + "breakpoints": true + }, + { + "idx": 12, + "version": "7", + "when": 1788691753961, + "tag": "0012_steam_enrolment_without_a_token", + "breakpoints": true + }, + { + "idx": 13, + "version": "7", + "when": 1788725541386, + "tag": "0013_machine_endpoint_id", + "breakpoints": true + }, + { + "idx": 14, + "version": "7", + "when": 1789680491539, + "tag": "0014_machine_public_label", + "breakpoints": true + } + ] +} diff --git a/packages/core/scripts/embed-migrations.ts b/packages/core/scripts/embed-migrations.ts index 7189263d..d36d61ed 100644 --- a/packages/core/scripts/embed-migrations.ts +++ b/packages/core/scripts/embed-migrations.ts @@ -24,9 +24,9 @@ const out = path.join(here, '..', 'src', 'migrations.generated.ts'); type JournalEntry = { idx: number; when: number; tag: string; breakpoints: boolean }; -const journal = JSON.parse( - fs.readFileSync(path.join(folder, 'meta', '_journal.json'), 'utf8') -) as { entries: JournalEntry[] }; +const journal = JSON.parse(fs.readFileSync(path.join(folder, 'meta', '_journal.json'), 'utf8')) as { + entries: JournalEntry[]; +}; // Deliberately identical to drizzle-orm's own `readMigrationFiles`: the same // `--> statement-breakpoint` split, the same sha256 over the whole file, the diff --git a/packages/core/src/auth/authorization-code.test.ts b/packages/core/src/auth/authorization-code.test.ts index 9e33df2b..8bd33848 100644 --- a/packages/core/src/auth/authorization-code.test.ts +++ b/packages/core/src/auth/authorization-code.test.ts @@ -86,7 +86,8 @@ describe('PostgresCodeStore', () => { await store.create(hash(), record(), 60); - const [row] = await sql`select count(*)::int as n from authorization_code where code_hash = ${stale}`; + const [row] = + await sql`select count(*)::int as n from authorization_code where code_hash = ${stale}`; expect(row!.n).toBe(0); }); }); diff --git a/packages/core/src/box/placement.test.ts b/packages/core/src/box/placement.test.ts index 77867198..e24e9379 100644 --- a/packages/core/src/box/placement.test.ts +++ b/packages/core/src/box/placement.test.ts @@ -3,8 +3,8 @@ import { afterAll, describe, expect, test } from 'bun:test'; import { Fixtures } from '../db/fixtures.js'; import { testDb } from '../db/test.js'; import { Identifier } from '../id.js'; -import { Placement } from './placement.js'; import { Box } from './index.js'; +import { Placement } from './placement.js'; const sql = testDb(); @@ -46,9 +46,7 @@ describe('Placement', () => { // `box.machineId` is notNull, so a placer with no candidate must refuse // rather than hand back something the insert would reject. - await expect( - Placement.choose({ userId: owner.userId, tier: 'sm' }) - ).rejects.toThrow(); + await expect(Placement.choose({ userId: owner.userId, tier: 'sm' })).rejects.toThrow(); }); test('more than one candidate is refused rather than picked silently', async () => { @@ -59,9 +57,7 @@ describe('Placement', () => { // There is no policy for choosing between hosts yet. Inventing one here // is how a placement decision ends up buried in the caller: the refusal // is what keeps the choice in one replaceable place. - await expect( - Placement.choose({ userId: owner.userId, tier: 'sm' }) - ).rejects.toThrow(); + await expect(Placement.choose({ userId: owner.userId, tier: 'sm' })).rejects.toThrow(); }); test('the placer is swappable without touching box creation', async () => { diff --git a/packages/core/src/db/index.ts b/packages/core/src/db/index.ts index b22115c0..0e0d4d44 100644 --- a/packages/core/src/db/index.ts +++ b/packages/core/src/db/index.ts @@ -127,10 +127,7 @@ export namespace Database { // callback ran on — harmless by luck, since neither was a real // transaction, and twice the pools either way. const db = client(); - const result = await TransactionContext.provide( - { effects, tx: db }, - () => callback(db) - ); + const result = await TransactionContext.provide({ effects, tx: db }, () => callback(db)); await Promise.all(effects.map((x) => x())); return result; } diff --git a/packages/core/src/session/session.test.ts b/packages/core/src/session/session.test.ts index cf1f9087..b79117d7 100644 --- a/packages/core/src/session/session.test.ts +++ b/packages/core/src/session/session.test.ts @@ -344,7 +344,13 @@ describe('Session claim', () => { test('re-reporting the state you already reported changes nothing', async () => { const { machineId, session } = await requested('ses-repeat', 5424); - await Session.transition({ claimToken: HOLDER, id: session.id, machineId, state: 'starting', errorMessage: null }); + await Session.transition({ + claimToken: HOLDER, + id: session.id, + machineId, + state: 'starting', + errorMessage: null + }); const again = await Session.transition({ claimToken: HOLDER, id: session.id, @@ -373,8 +379,20 @@ describe('Session claim', () => { expect(skipped.outcome).toBe('illegal'); expect((await Session.fromID(session.id))?.state).toBe('requested'); - await Session.transition({ claimToken: HOLDER, id: session.id, machineId, state: 'starting', errorMessage: null }); - await Session.transition({ claimToken: HOLDER, id: session.id, machineId, state: 'failed', errorMessage: 'no' }); + await Session.transition({ + claimToken: HOLDER, + id: session.id, + machineId, + state: 'starting', + errorMessage: null + }); + await Session.transition({ + claimToken: HOLDER, + id: session.id, + machineId, + state: 'failed', + errorMessage: 'no' + }); // Terminal is terminal: a dead session cannot be resurrected. const raised = await Session.transition({ @@ -390,7 +408,13 @@ describe('Session claim', () => { test('the timestamps survive a duplicate report, which is what billing rests on', async () => { const { machineId, session } = await requested('ses-idempotent', 5426); - await Session.transition({ claimToken: HOLDER, id: session.id, machineId, state: 'starting', errorMessage: null }); + await Session.transition({ + claimToken: HOLDER, + id: session.id, + machineId, + state: 'starting', + errorMessage: null + }); const live = await Session.transition({ claimToken: HOLDER, id: session.id, @@ -414,7 +438,13 @@ describe('Session claim', () => { const { machineId, session } = await requested('ses-ticket-scope', 5427); const other = await scene('ses-ticket-other', 5428); - await Session.transition({ claimToken: HOLDER, id: session.id, machineId, state: 'starting', errorMessage: null }); + await Session.transition({ + claimToken: HOLDER, + id: session.id, + machineId, + state: 'starting', + errorMessage: null + }); const refused = await Session.publishTicket({ claimToken: HOLDER, @@ -426,12 +456,22 @@ describe('Session claim', () => { expect((await Session.fromID(session.id))?.ticket).toBeNull(); // A ticket may appear while the state is still `starting`. - const first = await Session.publishTicket({ claimToken: HOLDER, id: session.id, machineId, ticket: 'one' }); + const first = await Session.publishTicket({ + claimToken: HOLDER, + id: session.id, + machineId, + ticket: 'one' + }); expect(first.outcome).toBe('published'); expect(first.session?.ticket).toBe('one'); expect(first.session?.state).toBe('starting'); - const second = await Session.publishTicket({ claimToken: HOLDER, id: session.id, machineId, ticket: 'two' }); + const second = await Session.publishTicket({ + claimToken: HOLDER, + id: session.id, + machineId, + ticket: 'two' + }); expect(second.session?.ticket).toBe('two'); }); @@ -691,11 +731,34 @@ describe('Session claim', () => { test('a stopped session has no address to publish', async () => { const { machineId, session } = await requested('ses-ticket-dead', 5429); - await Session.transition({ claimToken: HOLDER, id: session.id, machineId, state: 'starting', errorMessage: null }); - await Session.transition({ claimToken: HOLDER, id: session.id, machineId, state: 'live', errorMessage: null }); - await Session.transition({ claimToken: HOLDER, id: session.id, machineId, state: 'ended', errorMessage: null }); + await Session.transition({ + claimToken: HOLDER, + id: session.id, + machineId, + state: 'starting', + errorMessage: null + }); + await Session.transition({ + claimToken: HOLDER, + id: session.id, + machineId, + state: 'live', + errorMessage: null + }); + await Session.transition({ + claimToken: HOLDER, + id: session.id, + machineId, + state: 'ended', + errorMessage: null + }); - const result = await Session.publishTicket({ claimToken: HOLDER, id: session.id, machineId, ticket: 'late' }); + const result = await Session.publishTicket({ + claimToken: HOLDER, + id: session.id, + machineId, + ticket: 'late' + }); expect(result.outcome).toBe('closed'); expect((await Session.fromID(session.id))?.ticket).toBeNull(); }); @@ -739,9 +802,27 @@ describe('Session one active run per box', () => { }); const first = await mk(); - await Session.transition({ claimToken: HOLDER, id: first.id, machineId, state: 'starting', errorMessage: null }); - await Session.transition({ claimToken: HOLDER, id: first.id, machineId, state: 'live', errorMessage: null }); - await Session.transition({ claimToken: HOLDER, id: first.id, machineId, state: 'ended', errorMessage: null }); + await Session.transition({ + claimToken: HOLDER, + id: first.id, + machineId, + state: 'starting', + errorMessage: null + }); + await Session.transition({ + claimToken: HOLDER, + id: first.id, + machineId, + state: 'live', + errorMessage: null + }); + await Session.transition({ + claimToken: HOLDER, + id: first.id, + machineId, + state: 'ended', + errorMessage: null + }); // The index is partial for exactly this reason: a box is a durable // thing and playing twice is the ordinary case, so a stopped run must @@ -755,11 +836,29 @@ describe('Session one active run per box', () => { describe('Session tickets and the end of a run', () => { test('stopping a run takes its address away', async () => { const { machineId, session } = await requestedRun('ses-ticket-cleared', 5452); - await Session.transition({ claimToken: HOLDER, id: session.id, machineId, state: 'starting', errorMessage: null }); - await Session.transition({ claimToken: HOLDER, id: session.id, machineId, state: 'live', errorMessage: null }); + await Session.transition({ + claimToken: HOLDER, + id: session.id, + machineId, + state: 'starting', + errorMessage: null + }); + await Session.transition({ + claimToken: HOLDER, + id: session.id, + machineId, + state: 'live', + errorMessage: null + }); expect( - (await Session.publishTicket({ claimToken: HOLDER, id: session.id, machineId, ticket: 'live-address' })).session - ?.ticket + ( + await Session.publishTicket({ + claimToken: HOLDER, + id: session.id, + machineId, + ticket: 'live-address' + }) + ).session?.ticket ).toBe('live-address'); const ended = await Session.transition({ @@ -779,8 +878,19 @@ describe('Session tickets and the end of a run', () => { test('a run that failed does not keep an address either', async () => { const { machineId, session } = await requestedRun('ses-ticket-cleared-fail', 5453); - await Session.transition({ claimToken: HOLDER, id: session.id, machineId, state: 'starting', errorMessage: null }); - await Session.publishTicket({ claimToken: HOLDER, id: session.id, machineId, ticket: 'starting-address' }); + await Session.transition({ + claimToken: HOLDER, + id: session.id, + machineId, + state: 'starting', + errorMessage: null + }); + await Session.publishTicket({ + claimToken: HOLDER, + id: session.id, + machineId, + ticket: 'starting-address' + }); const failed = await Session.transition({ claimToken: HOLDER, @@ -814,12 +924,24 @@ describe('Session and the box underneath it', () => { // `created` while a run on it was `live`. expect((await Box.fromID(box.id))?.state).toBe('created'); - await Session.transition({ claimToken: HOLDER, id: session.id, machineId, state: 'starting', errorMessage: null }); + await Session.transition({ + claimToken: HOLDER, + id: session.id, + machineId, + state: 'starting', + errorMessage: null + }); // `starting` is deliberately not a box state: that transition is // synchronous from the agent's side, so nothing would ever write it. expect((await Box.fromID(box.id))?.state).toBe('created'); - await Session.transition({ claimToken: HOLDER, id: session.id, machineId, state: 'live', errorMessage: null }); + await Session.transition({ + claimToken: HOLDER, + id: session.id, + machineId, + state: 'live', + errorMessage: null + }); const running = await Box.fromID(box.id); expect(running?.state).toBe('running'); expect(running?.stopReason).toBeNull(); @@ -828,9 +950,27 @@ describe('Session and the box underneath it', () => { test('a run that ends stops its box, cleanly', async () => { const { machineId, box, session } = await requestedRun('ses-box-ended', 5461); - await Session.transition({ claimToken: HOLDER, id: session.id, machineId, state: 'starting', errorMessage: null }); - await Session.transition({ claimToken: HOLDER, id: session.id, machineId, state: 'live', errorMessage: null }); - await Session.transition({ claimToken: HOLDER, id: session.id, machineId, state: 'ended', errorMessage: null }); + await Session.transition({ + claimToken: HOLDER, + id: session.id, + machineId, + state: 'starting', + errorMessage: null + }); + await Session.transition({ + claimToken: HOLDER, + id: session.id, + machineId, + state: 'live', + errorMessage: null + }); + await Session.transition({ + claimToken: HOLDER, + id: session.id, + machineId, + state: 'ended', + errorMessage: null + }); const stopped = await Box.fromID(box.id); expect(stopped?.state).toBe('stopped'); @@ -840,7 +980,13 @@ describe('Session and the box underneath it', () => { test('a run that fails stops its box in the words the agent used', async () => { const { machineId, box, session } = await requestedRun('ses-box-failed', 5462); - await Session.transition({ claimToken: HOLDER, id: session.id, machineId, state: 'starting', errorMessage: null }); + await Session.transition({ + claimToken: HOLDER, + id: session.id, + machineId, + state: 'starting', + errorMessage: null + }); await Session.transition({ claimToken: HOLDER, id: session.id, @@ -891,24 +1037,68 @@ describe('Session tickets need a claim first', () => { // A ticket is the address of something being brought up, so publishing // one for a `requested` run means the agent skipped the claim — the // step that is the only mutual exclusion in the design. - const early = await Session.publishTicket({ claimToken: HOLDER, id: session.id, machineId, ticket: 'too-soon' }); + const early = await Session.publishTicket({ + claimToken: HOLDER, + id: session.id, + machineId, + ticket: 'too-soon' + }); expect(early.outcome).toBe('unclaimed'); expect((await Session.fromID(session.id))?.ticket).toBeNull(); - await Session.transition({ claimToken: HOLDER, id: session.id, machineId, state: 'starting', errorMessage: null }); - const now = await Session.publishTicket({ claimToken: HOLDER, id: session.id, machineId, ticket: 'in-time' }); + await Session.transition({ + claimToken: HOLDER, + id: session.id, + machineId, + state: 'starting', + errorMessage: null + }); + const now = await Session.publishTicket({ + claimToken: HOLDER, + id: session.id, + machineId, + ticket: 'in-time' + }); expect(now.outcome).toBe('published'); expect(now.session?.ticket).toBe('in-time'); }); test('the two refusals are different answers, because they are different mistakes', async () => { const { machineId, session } = await requestedRun('ses-ticket-refusals', 5466); - const unclaimed = await Session.publishTicket({ claimToken: HOLDER, id: session.id, machineId, ticket: 'a' }); + const unclaimed = await Session.publishTicket({ + claimToken: HOLDER, + id: session.id, + machineId, + ticket: 'a' + }); - await Session.transition({ claimToken: HOLDER, id: session.id, machineId, state: 'starting', errorMessage: null }); - await Session.transition({ claimToken: HOLDER, id: session.id, machineId, state: 'live', errorMessage: null }); - await Session.transition({ claimToken: HOLDER, id: session.id, machineId, state: 'ended', errorMessage: null }); - const closed = await Session.publishTicket({ claimToken: HOLDER, id: session.id, machineId, ticket: 'b' }); + await Session.transition({ + claimToken: HOLDER, + id: session.id, + machineId, + state: 'starting', + errorMessage: null + }); + await Session.transition({ + claimToken: HOLDER, + id: session.id, + machineId, + state: 'live', + errorMessage: null + }); + await Session.transition({ + claimToken: HOLDER, + id: session.id, + machineId, + state: 'ended', + errorMessage: null + }); + const closed = await Session.publishTicket({ + claimToken: HOLDER, + id: session.id, + machineId, + ticket: 'b' + }); // One is an agent that has not claimed the work; the other is a run // with nothing left to reach. Collapsing them would tell an agent diff --git a/packages/core/src/team/team.test.ts b/packages/core/src/team/team.test.ts index 7c9a75e9..562ecc48 100644 --- a/packages/core/src/team/team.test.ts +++ b/packages/core/src/team/team.test.ts @@ -4,8 +4,8 @@ import { Actor } from '../actor.js'; import { Fixtures } from '../db/fixtures.js'; import { testDb } from '../db/test.js'; import { Identifier } from '../id.js'; -import { Member } from './member.js'; import { Team } from './index.js'; +import { Member } from './member.js'; const sql = testDb(); @@ -44,7 +44,10 @@ describe('Team.ensurePersonal', () => { // the user is created — that is what backfills accounts made before the // call existed. A second call must not mint a second team. const again = await Actor.with( - { type: 'user', properties: { userID: owner.userId, linkedAccountID: owner.linkedAccountId } }, + { + type: 'user', + properties: { userID: owner.userId, linkedAccountID: owner.linkedAccountId } + }, () => Team.ensurePersonal({ displayName: 'team-idempotent' }) ); diff --git a/packages/core/src/user/identity.ts b/packages/core/src/user/identity.ts index 15b94be2..c080968c 100644 --- a/packages/core/src/user/identity.ts +++ b/packages/core/src/user/identity.ts @@ -6,9 +6,9 @@ import { ErrorCodes, VisibleError } from '../error.js'; import { fn } from '../fn.js'; import { Identifier } from '../id.js'; import { User } from './index.js'; -import { UserTable } from './user.sql.js'; import { LinkedAccount } from './linked-account.js'; import { LinkedAccountTable } from './linked-account.sql.js'; +import { UserTable } from './user.sql.js'; const STEAM_ID_RE = /^\d{17}$/; diff --git a/packages/core/src/waitlist/index.ts b/packages/core/src/waitlist/index.ts index c36427bb..64fc1ab3 100644 --- a/packages/core/src/waitlist/index.ts +++ b/packages/core/src/waitlist/index.ts @@ -40,7 +40,9 @@ export namespace Waitlist { return tx .select() .from(WaitlistEntryTable) - .where(and(eq(WaitlistEntryTable.email, input.email), isNull(WaitlistEntryTable.timeDeleted))) + .where( + and(eq(WaitlistEntryTable.email, input.email), isNull(WaitlistEntryTable.timeDeleted)) + ) .then((rows) => rows.at(0) ?? null); }); if (existing) { From 15631f5d2556791c3c9db7d15967a49b0ed69828 Mon Sep 17 00:00:00 2001 From: Wanjohi Date: Fri, 18 Sep 2026 23:16:07 +0300 Subject: [PATCH 3/9] feat(core,api): an organisation owns hardware, and a domain says who belongs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two kinds of machine were modelled as one. A host somebody brings is theirs, reached through a team, and should die with their account. A host bought to serve other people's workloads is none of those things — and there was nowhere to put it, so it had to be registered under an employee's personal team, where it was that person's property and their account going away took it with them. Ownership becomes an either/or. A machine names a team or an organisation, exactly one, enforced by a check constraint rather than by convention: both null is a host nothing can bill, and both set is two answers to "whose is this?" where whichever join a query happens to take decides who pays. Hardware an organisation owns has no team and no person at all, which is the point. The organisation is deliberately not a billing subject and has no plan columns. It says who owns the metal; a team pays for what it uses either way. Membership is derived from a verified email domain rather than stored. An address is already the root identity, so a second record of who belongs where is a second answer that can disagree with the first — and deriving it means signing in with a personal address still gets an ordinary personal account, which is what lets one person hold a company account and use the consumer product. Nothing is granted on an unverified domain or an unverified address: either one is a string somebody typed. Entitlement on fleet hardware refuses everyone for now, with a reason that says so. What grants a run on metered hardware is a plan, and there is nothing to ask yet, so it fails closed rather than giving the expensive case away. The branch is written out so the plan check has one obvious place to land. Routes are read-only, and nothing seeds an organisation. Creating one grants membership to everyone who can receive mail at a domain, so it is an operator action against the database — a migration that inserted one would insert it into every deployment, including ones we have nothing to do with. See docs/deploy.md. --- apps/api/app/index.ts | 2 + apps/api/app/middleware/auth.ts | 3 +- apps/api/app/routes/machine.ts | 64 +- apps/api/app/routes/organisation.ts | 89 + docs/deploy.md | 39 + packages/core/CLAUDE.md | 18 +- .../0015_organisation_owns_fleet_hardware.sql | 57 + .../core/migrations/meta/0015_snapshot.json | 2989 +++++++++++++++++ packages/core/migrations/meta/_journal.json | 7 + packages/core/src/actor.ts | 15 +- packages/core/src/examples.ts | 9 + packages/core/src/id.ts | 1 + packages/core/src/machine/index.ts | 68 +- packages/core/src/machine/machine.sql.ts | 70 +- packages/core/src/machine/machine.test.ts | 3 +- packages/core/src/organisation/index.ts | 193 ++ .../core/src/organisation/organisation.sql.ts | 60 + .../src/organisation/organisation.test.ts | 213 ++ packages/core/src/team/index.ts | 6 + packages/core/src/team/team.sql.ts | 48 +- 20 files changed, 3889 insertions(+), 65 deletions(-) create mode 100644 apps/api/app/routes/organisation.ts create mode 100644 packages/core/migrations/0015_organisation_owns_fleet_hardware.sql create mode 100644 packages/core/migrations/meta/0015_snapshot.json create mode 100644 packages/core/src/organisation/index.ts create mode 100644 packages/core/src/organisation/organisation.sql.ts create mode 100644 packages/core/src/organisation/organisation.test.ts diff --git a/apps/api/app/index.ts b/apps/api/app/index.ts index 6c12cd59..dd79d552 100644 --- a/apps/api/app/index.ts +++ b/apps/api/app/index.ts @@ -15,6 +15,7 @@ import { GameApi } from './routes/game.js'; import { IndexApi } from './routes/index.js'; import { LibraryApi } from './routes/library.js'; import { MachineApi } from './routes/machine.js'; +import { OrganisationApi } from './routes/organisation.js'; import { SessionApi } from './routes/session.js'; import { SteamApi } from './routes/steam.js'; import { UserApi } from './routes/user.js'; @@ -42,6 +43,7 @@ const routes = app .route('/steam', SteamApi.route) .route('/library', LibraryApi.route) .route('/games', GameApi.route) + .route('/organisation', OrganisationApi.route) .route('/machine', MachineApi.route) .route('/machine', SessionApi.machineRoute) .route('/machine', EnrolmentApi.route) diff --git a/apps/api/app/middleware/auth.ts b/apps/api/app/middleware/auth.ts index 51097076..21e0244a 100644 --- a/apps/api/app/middleware/auth.ts +++ b/apps/api/app/middleware/auth.ts @@ -100,7 +100,8 @@ export const auth: MiddlewareHandler = async (c, next) => { properties: { machineID: machine.id, ownerUserID: machine.ownerUserId, - teamID: machine.teamId + teamID: machine.teamId, + organisationID: machine.organisationId } }, next diff --git a/apps/api/app/routes/machine.ts b/apps/api/app/routes/machine.ts index 017584fa..1960337e 100644 --- a/apps/api/app/routes/machine.ts +++ b/apps/api/app/routes/machine.ts @@ -4,6 +4,7 @@ import { ErrorCodes, VisibleError } from '@nestri/core/error'; import { Examples } from '@nestri/core/examples'; import { Identifier } from '@nestri/core/id'; import { Machine } from '@nestri/core/machine/index'; +import { Organisation } from '@nestri/core/organisation/index'; import { Team } from '@nestri/core/team/index'; import { Member } from '@nestri/core/team/member'; import { Hono } from 'hono'; @@ -27,9 +28,9 @@ export namespace MachineApi { notPublic, describeRoute({ tags: ['Machine'], - summary: 'Register a nessh host', + summary: 'Register a host', description: - 'Exchange the calling user session for a machine id and secret. The secret is returned once and never again — it is stored only as a digest.', + 'Exchange the calling user session for a machine id and secret. The secret is returned once and never again — it is stored only as a digest. Hardware belongs either to a team, which is the default and covers a host somebody brings, or to an organisation the caller belongs to, which is how hardware bought to serve other people is registered so that it is nobody\u2019s personal property.', responses: { 200: { content: { @@ -64,15 +65,28 @@ export namespace MachineApi { }), teamId: z.string().optional().meta({ description: - 'Team to own this hardware. Defaults to the caller’s personal team, which always exists' + 'Team to own this hardware. Defaults to the caller\u2019s personal team, which always exists. Mutually exclusive with organisationId' + }), + organisationId: z.string().optional().meta({ + description: + 'Organisation to own this hardware outright, for a host that serves other people rather than its registrant. The caller must belong to it, and no team is recorded' }) }) ), async (c) => { - const { label, teamId } = c.req.valid('json'); + const { label, teamId, organisationId } = c.req.valid('json'); - // `notPublic` also admits admin, which has no user to own the box. - // Registering is an act of ownership, so it needs a real one. + if (teamId && organisationId) { + throw new VisibleError( + 'validation', + ErrorCodes.Validation.INVALID_PARAMETER, + 'Hardware belongs to a team or to an organisation, not to both', + 'organisationId' + ); + } + + // `notPublic` also admits a machine, which has no user to own a + // box. Registering is an act of ownership, so it needs a real one. const actor = Actor.use(); if (actor.type !== 'user' && actor.type !== 'member') { throw new VisibleError( @@ -82,11 +96,39 @@ export namespace MachineApi { ); } - // A team has to be resolved rather than defaulted to null, because - // `machine.teamId` is notNull. The order is: what the caller asked - // for, then the team they are acting inside, then their personal - // team — which `ensurePersonal` makes if this is an older user who - // has none. ref(d-0048) + // Naming an organisation registers fleet hardware: owned outright, + // with no team and no person behind it, so that it survives the + // account of whoever happened to run the command. + if (organisationId) { + if (!(await Organisation.isMember(Actor.userID, organisationId))) { + // Membership is the verified address, so this refuses the + // same way for an organisation that does not exist and one + // the caller simply is not in — there is nothing to learn + // from the difference. + throw new VisibleError( + 'forbidden', + ErrorCodes.Permission.FORBIDDEN, + 'You do not belong to that organisation' + ); + } + + const fleet = await Machine.register({ + id: Identifier.ascending('machine'), + ownerUserId: null, + teamId: null, + organisationId, + label + }); + return c.json({ + data: { machineId: fleet.id, slug: fleet.slug, secret: fleet.secret } + }); + } + + // Otherwise a team has to be resolved rather than left null, since + // hardware with neither owner is hardware nothing can bill. The + // order is: what the caller asked for, then the team they are + // acting inside, then their personal team — which `ensurePersonal` + // makes if this is an older user who has none. ref(d-0048) const owningTeam = teamId ?? (actor.type === 'member' diff --git a/apps/api/app/routes/organisation.ts b/apps/api/app/routes/organisation.ts new file mode 100644 index 00000000..93023eb3 --- /dev/null +++ b/apps/api/app/routes/organisation.ts @@ -0,0 +1,89 @@ +import { Actor } from '@nestri/core/actor'; +import { ErrorCodes, VisibleError } from '@nestri/core/error'; +import { Examples } from '@nestri/core/examples'; +import { Machine } from '@nestri/core/machine/index'; +import { Organisation } from '@nestri/core/organisation/index'; +import { Hono } from 'hono'; +import { describeRoute } from 'hono-openapi'; +import { z } from 'zod'; + +import { ErrorResponses, notPublic, Result } from '../utils'; + +/** + * The organisation a caller belongs to, and the hardware it owns. + * + * Read-only on purpose. Organisations are made by hand and their domains are + * verified by hand, because the thing a verified domain grants is membership — + * and a route that mints one would be a route that hands out membership of any + * domain somebody types. When that changes, verification is what has to be + * built first, not this file. + * + * There is no organisation to name in a path: membership is derived from the + * caller's verified address, so there is exactly one answer and asking about + * anybody else's is not a question this API takes. + */ +export namespace OrganisationApi { + /** The caller's organisation, or a 404 that says what that means. */ + async function mine() { + const organisation = await Organisation.forUser(Actor.userID); + if (!organisation) { + throw new VisibleError( + 'not_found', + ErrorCodes.NotFound.RESOURCE_NOT_FOUND, + 'Your address does not belong to a verified organisation domain' + ); + } + return organisation; + } + + export const route = new Hono() + .use(notPublic) + .get( + '/', + describeRoute({ + tags: ['Organisation'], + summary: 'The organisation you belong to', + description: + 'Derived from the domain of your verified email address. A personal address has no organisation, which is not an error in the product — it is the ordinary consumer account — but it is a 404 here because there is nothing to return.', + responses: { + 200: { + content: { 'application/json': { schema: Result(Organisation.Info) } }, + description: 'Your organisation' + }, + 401: ErrorResponses[401], + 404: ErrorResponses[404] + } + }), + async (c) => c.json({ data: await mine() }) + ) + .get( + '/machines', + describeRoute({ + tags: ['Organisation'], + summary: 'The hardware your organisation owns', + description: + 'Every host the organisation owns outright. These belong to no team and no person, which is what separates them from a host somebody brought — those appear under the team that owns them instead.', + responses: { + 200: { + content: { + 'application/json': { + schema: Result( + z.array(Machine.Info).meta({ + description: 'The fleet', + example: [Examples.Machine] + }) + ) + } + }, + description: 'The fleet' + }, + 401: ErrorResponses[401], + 404: ErrorResponses[404] + } + }), + async (c) => { + const organisation = await mine(); + return c.json({ data: await Machine.listByOrganisation(organisation.id) }); + } + ); +} diff --git a/docs/deploy.md b/docs/deploy.md index f2556fc5..21356098 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -112,6 +112,45 @@ binding names a script that has to exist. The custom domains in the config are what create the DNS records — there is no separate step, and no separate tool holding the other half of that fact. +## Organisations, and why none are created for you + +An organisation owns hardware outright — a host that serves other people's +workloads rather than its registrant's — and gathers the teams whose members +sign in with its email domain. Membership is derived from that domain, so the +`domain_verified` flag is the whole of the access decision: an address on a +verified domain *is* membership, and nothing grants anything on an unverified +one. + +**Nothing seeds one, deliberately, and it must stay that way.** A migration +that inserted a row here would insert it into every deployment, including +somebody else's — handing every account on that domain membership of a +deployment its owners have nothing to do with. Seeding business data is what +makes a schema migration a back door. + +So it is an operator action, run once against the database, by whoever is +allowed to decide that a domain is really theirs: + +```sql +INSERT INTO organisation (id, name, slug, domain, domain_verified) +VALUES ( + 'org_' || substr(replace(gen_random_uuid()::text, '-', ''), 1, 26), + 'Example', + 'example', + 'example.com', + true +); +``` + +Two things to get right, because nothing checks them for you. The domain is +lower-cased and has no `@` — it is compared literally against the domain half +of an address. And `domain_verified` should be `true` only for a domain you +control: everyone who can receive mail at it becomes a member the next time +they sign in, with no further step. + +Hardware is then registered to it by a member, with `organisationId` instead of +a team on `POST /machine/register`. Such a host has no owner and no team, which +is the point — it outlives the account of whoever ran the command. + ## Containers ```sh diff --git a/packages/core/CLAUDE.md b/packages/core/CLAUDE.md index eab2357e..f24e580c 100644 --- a/packages/core/CLAUDE.md +++ b/packages/core/CLAUDE.md @@ -446,7 +446,9 @@ Game ───1:N─── Download ← per-host game depot downloads - **User**: Person record. Email is nullable (gaming accounts don't provide one). - **LinkedAccount**: A gaming/OAuth identity. `(provider, providerAccountId)` is unique. -- **Team**: Organization for billing/collaboration. First team is auto-created as "personal" team. +- **Team**: The billing subject, and how people collaborate. The first one is auto-created as a "personal" team. +- **Organisation**: A company. Owns hardware outright — a host serving other people's workloads, belonging to no team and no person — and gathers teams under a verified email domain. Membership is derived from that domain rather than stored. **Not a billing subject**: a team pays for what it uses whether it sits under an organisation or not. +- **Machine**: A registered host. Owned by a team (a host somebody brought) or by an organisation (fleet hardware), never both and never neither — a check constraint, not a convention. - **TeamMember**: Joins User → Team with a role. `(teamId, userId)` is unique. --- @@ -500,7 +502,15 @@ type ActorInfo = properties: { userID: string; teamID: string; role: 'owner' | 'admin' | 'member' }; } | { type: 'system'; properties: { teamID: string } } - | { type: 'admin'; properties: {} }; + | { + type: 'machine'; + properties: { + machineID: string; + ownerUserID: string | null; + teamID: string | null; + organisationID: string | null; + }; + }; ``` ### API @@ -509,8 +519,8 @@ type ActorInfo = Actor.use(); // → ActorInfo (throws if no context set) Actor.with(value, fn); // Run fn in the given actor context Actor.assert(type); // Assert current actor type, returns narrowed type -Actor.type; // → 'public' | 'user' | 'member' | 'system' | 'admin' -Actor.userID; // → string (user/member only) +Actor.type; // → 'public' | 'user' | 'member' | 'system' | 'machine' +Actor.userID; // → string (user/member only; refuses a machine outright) Actor.linkedAccountID; // → string (user only) Actor.useTeam; // → string (member/system only — the teamID) Actor.role; // → 'owner' | 'admin' | 'member' (member only) diff --git a/packages/core/migrations/0015_organisation_owns_fleet_hardware.sql b/packages/core/migrations/0015_organisation_owns_fleet_hardware.sql new file mode 100644 index 00000000..a91407e0 --- /dev/null +++ b/packages/core/migrations/0015_organisation_owns_fleet_hardware.sql @@ -0,0 +1,57 @@ +-- Hardware an organisation owns, rather than a person. +-- +-- Two kinds of machine were being modelled as one. A host somebody brings is +-- theirs, reached through a team, and should die with their account. A host +-- bought to serve other people's workloads is none of those things, and until +-- now it had to be registered under some employee's personal team -- so the +-- company's card was that employee's personal property, and their account +-- going away took it. ref(d-0048) +-- +-- So ownership becomes an either/or. `team_id` for a host somebody brought, +-- `organisation_id` for one a company owns outright, exactly one of them set, +-- and a check constraint rather than a convention -- because both null is a +-- host nothing can bill, and both set is two answers to "whose is this?" where +-- whichever join a query happens to take would decide who pays. +-- +-- `owner_user_id` becomes nullable so that fleet hardware can have no person +-- behind it at all. Its ON DELETE CASCADE is deliberately left alone: it now +-- only ever fires for a host somebody brought, where a box dying with its +-- owner's account is what that owner expects, and it cannot reach fleet +-- hardware because the column it follows is null there. +-- +-- Note the check is safe to apply in one step. Every existing row has a team +-- and no organisation, so all of them already satisfy it -- which is only true +-- because `team_id` was NOT NULL before this migration relaxed it. +-- +-- `organisation.domain` is what makes someone a member, and membership is +-- derived from it rather than stored: an address is already the root identity, +-- so a second record of who belongs where is a second answer that can disagree +-- with the first. `domain_verified` defaults false because an unverified claim +-- is a string somebody typed, and nothing may be granted on one. +-- +-- The organisation deliberately has no plan or subscription columns. It says +-- who owns the metal, not who owes money; a team pays for what it uses whether +-- it sits under an organisation or not. + +CREATE TABLE "organisation" ( + "id" char(30) PRIMARY KEY NOT NULL, + "time_created" timestamp with time zone DEFAULT now() NOT NULL, + "time_updated" timestamp with time zone DEFAULT now() NOT NULL, + "time_deleted" timestamp with time zone, + "name" text NOT NULL, + "slug" text NOT NULL, + "domain" text NOT NULL, + "domain_verified" boolean DEFAULT false NOT NULL +); +--> statement-breakpoint +ALTER TABLE "machine" ALTER COLUMN "owner_user_id" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "machine" ALTER COLUMN "team_id" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "machine" ADD COLUMN "organisation_id" char(30);--> statement-breakpoint +ALTER TABLE "team" ADD COLUMN "organisation_id" char(30);--> statement-breakpoint +CREATE UNIQUE INDEX "organisation_slug_unique" ON "organisation" USING btree ("slug");--> statement-breakpoint +CREATE UNIQUE INDEX "organisation_domain_unique" ON "organisation" USING btree ("domain");--> statement-breakpoint +ALTER TABLE "machine" ADD CONSTRAINT "machine_organisation_id_organisation_id_fk" FOREIGN KEY ("organisation_id") REFERENCES "public"."organisation"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "team" ADD CONSTRAINT "team_organisation_id_organisation_id_fk" FOREIGN KEY ("organisation_id") REFERENCES "public"."organisation"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "machine_organisation_idx" ON "machine" USING btree ("organisation_id");--> statement-breakpoint +CREATE INDEX "team_organisation_idx" ON "team" USING btree ("organisation_id");--> statement-breakpoint +ALTER TABLE "machine" ADD CONSTRAINT "machine_one_owner" CHECK (("machine"."team_id" is null) != ("machine"."organisation_id" is null)); \ No newline at end of file diff --git a/packages/core/migrations/meta/0015_snapshot.json b/packages/core/migrations/meta/0015_snapshot.json new file mode 100644 index 00000000..7a78a910 --- /dev/null +++ b/packages/core/migrations/meta/0015_snapshot.json @@ -0,0 +1,2989 @@ +{ + "id": "c6c87171-c5af-4461-a7cb-58ee99277f3a", + "prevId": "f625bf1c-518c-4ae0-acf9-1e4256450993", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used": { + "name": "last_used", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "access_token_hash_unique": { + "name": "access_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_owner_idx": { + "name": "access_token_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_team_idx": { + "name": "access_token_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "access_token_owner_user_id_user_id_fk": { + "name": "access_token_owner_user_id_user_id_fk", + "tableFrom": "access_token", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "access_token_team_id_team_id_fk": { + "name": "access_token_team_id_team_id_fk", + "tableFrom": "access_token", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.authorization_code": { + "name": "authorization_code", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "authorization_code_hash_unique": { + "name": "authorization_code_hash_unique", + "columns": [ + { + "expression": "code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.device_grant": { + "name": "device_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "device_code_hash": { + "name": "device_code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_code": { + "name": "user_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "device_grant_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "poll_interval": { + "name": "poll_interval", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "last_polled_at": { + "name": "last_polled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "device_grant_device_code_unique": { + "name": "device_grant_device_code_unique", + "columns": [ + { + "expression": "device_code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "device_grant_user_code_unique": { + "name": "device_grant_user_code_unique", + "columns": [ + { + "expression": "user_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.refresh_token": { + "name": "refresh_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "time_used": { + "name": "time_used", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "refresh_token_hash_unique": { + "name": "refresh_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "refresh_token_subject_idx": { + "name": "refresh_token_subject_idx", + "columns": [ + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_key": { + "name": "auth_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "key_id": { + "name": "key_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "auth_key_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "alg": { + "name": "alg", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expired_at": { + "name": "expired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "auth_key_key_id_unique": { + "name": "auth_key_key_id_unique", + "columns": [ + { + "expression": "key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auth_key_one_live_per_kind": { + "name": "auth_key_one_live_per_kind", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"auth_key\".\"expired_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_kv": { + "name": "auth_kv", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "auth_kv_key_unique": { + "name": "auth_kv_key_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.box": { + "name": "box", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "machine_id": { + "name": "machine_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tier": { + "name": "tier", + "type": "box_tier", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'sm'" + }, + "state": { + "name": "state", + "type": "box_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'created'" + }, + "stop_reason": { + "name": "stop_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stop_clean": { + "name": "stop_clean", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "box_user_idx": { + "name": "box_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "box_machine_idx": { + "name": "box_machine_idx", + "columns": [ + { + "expression": "machine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "box_user_id_user_id_fk": { + "name": "box_user_id_user_id_fk", + "tableFrom": "box", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "box_machine_id_machine_id_fk": { + "name": "box_machine_id_machine_id_fk", + "tableFrom": "box", + "tableTo": "machine", + "columnsFrom": ["machine_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_depot": { + "name": "game_depot", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "depot_id": { + "name": "depot_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "steam_manifest_id": { + "name": "steam_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_build_id": { + "name": "steam_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "installed_manifest_id": { + "name": "installed_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_build_id": { + "name": "installed_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "depot_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_depot_unique": { + "name": "game_depot_unique", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "depot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_game_idx": { + "name": "game_depot_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_updates_idx": { + "name": "game_depot_updates_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"game_depot\".\"installed_manifest_id\" is distinct from \"game_depot\".\"steam_manifest_id\"", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_depot_game_id_game_id_fk": { + "name": "game_depot_game_id_game_id_fk", + "tableFrom": "game_depot", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_download": { + "name": "game_download", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "host_id": { + "name": "host_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "game_download_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "progress_bytes": { + "name": "progress_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "total_bytes": { + "name": "total_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "time_started": { + "name": "time_started", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_completed": { + "name": "time_completed", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_download_host_game_unique": { + "name": "game_download_host_game_unique", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_game_idx": { + "name": "game_download_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_host_status_idx": { + "name": "game_download_host_status_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_download_host_id_machine_id_fk": { + "name": "game_download_host_id_machine_id_fk", + "tableFrom": "game_download", + "tableTo": "machine", + "columnsFrom": ["host_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "game_download_game_id_game_id_fk": { + "name": "game_download_game_id_game_id_fk", + "tableFrom": "game_download", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game": { + "name": "game", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "steam_app_id": { + "name": "steam_app_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aliases": { + "name": "aliases", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_icon": { + "name": "client_icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "short_description": { + "name": "short_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "developers": { + "name": "developers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "publishers": { + "name": "publishers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "primary_genre": { + "name": "primary_genre", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "genres": { + "name": "genres", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "controller_support": { + "name": "controller_support", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_deck_compat": { + "name": "steam_deck_compat", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "review_score_percent": { + "name": "review_score_percent", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "review_count": { + "name": "review_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "metacritic_score": { + "name": "metacritic_score", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "steam_change_number": { + "name": "steam_change_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "public_build_id": { + "name": "public_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "release_date_utc": { + "name": "release_date_utc", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_enriched": { + "name": "time_enriched", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_slug_unique": { + "name": "game_slug_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_app_id_unique": { + "name": "game_app_id_unique", + "columns": [ + { + "expression": "steam_app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "game_steam_app_id_unique": { + "name": "game_steam_app_id_unique", + "nullsNotDistinct": false, + "columns": ["steam_app_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.machine": { + "name": "machine", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "organisation_id": { + "name": "organisation_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "machine_secret_hash_unique": { + "name": "machine_secret_hash_unique", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_slug_unique": { + "name": "machine_slug_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_endpoint_id_unique": { + "name": "machine_endpoint_id_unique", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_owner_idx": { + "name": "machine_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_team_idx": { + "name": "machine_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_organisation_idx": { + "name": "machine_organisation_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "machine_owner_user_id_user_id_fk": { + "name": "machine_owner_user_id_user_id_fk", + "tableFrom": "machine", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "machine_team_id_team_id_fk": { + "name": "machine_team_id_team_id_fk", + "tableFrom": "machine", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "machine_organisation_id_organisation_id_fk": { + "name": "machine_organisation_id_organisation_id_fk", + "tableFrom": "machine", + "tableTo": "organisation", + "columnsFrom": ["organisation_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "machine_one_owner": { + "name": "machine_one_owner", + "value": "(\"machine\".\"team_id\" is null) != (\"machine\".\"organisation_id\" is null)" + } + }, + "isRLSEnabled": false + }, + "public.organisation": { + "name": "organisation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain_verified": { + "name": "domain_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "organisation_slug_unique": { + "name": "organisation_slug_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "organisation_domain_unique": { + "name": "organisation_domain_unique", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pairing_code": { + "name": "pairing_code", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_fingerprint": { + "name": "new_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "is_claimed": { + "name": "is_claimed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "pairing_code_code_unique": { + "name": "pairing_code_code_unique", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pairing_code_target_user_idx": { + "name": "pairing_code_target_user_idx", + "columns": [ + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "box_id": { + "name": "box_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "linked_account_id": { + "name": "linked_account_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "session_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "ticket": { + "name": "ticket", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "time_started": { + "name": "time_started", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_stopped": { + "name": "time_stopped", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_token": { + "name": "claim_token", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_box_idx": { + "name": "session_box_idx", + "columns": [ + { + "expression": "box_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_state_idx": { + "name": "session_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_box_active_unique": { + "name": "session_box_active_unique", + "columns": [ + { + "expression": "box_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "time_stopped is null and time_deleted is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_started_idx": { + "name": "session_started_idx", + "columns": [ + { + "expression": "time_started", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_box_id_box_id_fk": { + "name": "session_box_id_box_id_fk", + "tableFrom": "session", + "tableTo": "box", + "columnsFrom": ["box_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_game_id_game_id_fk": { + "name": "session_game_id_game_id_fk", + "tableFrom": "session", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "session_linked_account_id_linked_account_id_fk": { + "name": "session_linked_account_id_linked_account_id_fk", + "tableFrom": "session", + "tableTo": "linked_account", + "columnsFrom": ["linked_account_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.steam_enrolment": { + "name": "steam_enrolment", + "schema": "", + "columns": { + "machine_id": { + "name": "machine_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "steam_id": { + "name": "steam_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "steam_enrolment_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_ok_at": { + "name": "last_ok_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "steam_enrolment_user_idx": { + "name": "steam_enrolment_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "steam_enrolment_machine_id_machine_id_fk": { + "name": "steam_enrolment_machine_id_machine_id_fk", + "tableFrom": "steam_enrolment", + "tableTo": "machine", + "columnsFrom": ["machine_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "steam_enrolment_user_id_user_id_fk": { + "name": "steam_enrolment_user_id_user_id_fk", + "tableFrom": "steam_enrolment", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "steam_enrolment_machine_id_user_id_pk": { + "name": "steam_enrolment_machine_id_user_id_pk", + "columns": ["machine_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_member": { + "name": "team_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "team_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + } + }, + "indexes": { + "team_member_team_user_unique": { + "name": "team_member_team_user_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_team_idx": { + "name": "team_member_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_user_idx": { + "name": "team_member_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_member_team_id_team_id_fk": { + "name": "team_member_team_id_team_id_fk", + "tableFrom": "team_member", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_member_user_id_user_id_fk": { + "name": "team_member_user_id_user_id_fk", + "tableFrom": "team_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team": { + "name": "team", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "billing_email": { + "name": "billing_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "subscription_status": { + "name": "subscription_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "team_organisation_idx": { + "name": "team_organisation_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_owner_id_user_id_fk": { + "name": "team_owner_id_user_id_fk", + "tableFrom": "team", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_organisation_id_organisation_id_fk": { + "name": "team_organisation_id_organisation_id_fk", + "tableFrom": "team", + "tableTo": "organisation", + "columnsFrom": ["organisation_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_slug_unique": { + "name": "team_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_fingerprint": { + "name": "user_fingerprint", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_fingerprint_fingerprint_unique": { + "name": "user_fingerprint_fingerprint_unique", + "columns": [ + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_fingerprint_user_idx": { + "name": "user_fingerprint_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_fingerprint_user_id_user_id_fk": { + "name": "user_fingerprint_user_id_user_id_fk", + "tableFrom": "user_fingerprint", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_library": { + "name": "user_library", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "playtime_2w": { + "name": "playtime_2w", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "playtime_forever": { + "name": "playtime_forever", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_played": { + "name": "last_played", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_library_user_game_unique": { + "name": "user_library_user_game_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_user_idx": { + "name": "user_library_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_game_idx": { + "name": "user_library_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_library_user_id_user_id_fk": { + "name": "user_library_user_id_user_id_fk", + "tableFrom": "user_library", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_library_game_id_game_id_fk": { + "name": "user_library_game_id_game_id_fk", + "tableFrom": "user_library", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.linked_account": { + "name": "linked_account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "linked_account_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile": { + "name": "profile", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "linked_account_provider_unique": { + "name": "linked_account_provider_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "linked_account_user_idx": { + "name": "linked_account_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "linked_account_user_id_user_id_fk": { + "name": "linked_account_user_id_user_id_fk", + "tableFrom": "linked_account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "email is not null and time_deleted is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "verification_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_user_kind_idx": { + "name": "verification_user_kind_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "verification_user_id_user_id_fk": { + "name": "verification_user_id_user_id_fk", + "tableFrom": "verification", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist_entry": { + "name": "waitlist_entry", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'machines'" + } + }, + "indexes": { + "waitlist_entry_email_unique": { + "name": "waitlist_entry_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "waitlist_entry_source_idx": { + "name": "waitlist_entry_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.device_grant_status": { + "name": "device_grant_status", + "schema": "public", + "values": ["pending", "approved", "denied"] + }, + "public.auth_key_kind": { + "name": "auth_key_kind", + "schema": "public", + "values": ["signing", "encryption"] + }, + "public.box_state": { + "name": "box_state", + "schema": "public", + "values": ["created", "running", "stopped"] + }, + "public.box_tier": { + "name": "box_tier", + "schema": "public", + "values": ["xs", "sm", "md", "lg", "xl"] + }, + "public.depot_status": { + "name": "depot_status", + "schema": "public", + "values": ["pending", "downloading", "complete", "error", "deleted"] + }, + "public.game_download_status": { + "name": "game_download_status", + "schema": "public", + "values": ["pending", "verifying", "downloading", "ready", "failed"] + }, + "public.session_state": { + "name": "session_state", + "schema": "public", + "values": ["requested", "starting", "live", "ended", "failed"] + }, + "public.steam_enrolment_state": { + "name": "steam_enrolment_state", + "schema": "public", + "values": ["enrolled", "stale", "revoked"] + }, + "public.team_member_role": { + "name": "team_member_role", + "schema": "public", + "values": ["owner", "admin", "member"] + }, + "public.linked_account_provider": { + "name": "linked_account_provider", + "schema": "public", + "values": ["steam", "ssh", "discord"] + }, + "public.verification_kind": { + "name": "verification_kind", + "schema": "public", + "values": ["email"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/core/migrations/meta/_journal.json b/packages/core/migrations/meta/_journal.json index eaf63885..b1e0bd3b 100644 --- a/packages/core/migrations/meta/_journal.json +++ b/packages/core/migrations/meta/_journal.json @@ -106,6 +106,13 @@ "when": 1789680491539, "tag": "0014_machine_public_label", "breakpoints": true + }, + { + "idx": 15, + "version": "7", + "when": 1789762221718, + "tag": "0015_organisation_owns_fleet_hardware", + "breakpoints": true } ] } diff --git a/packages/core/src/actor.ts b/packages/core/src/actor.ts index 22c28504..4e731139 100644 --- a/packages/core/src/actor.ts +++ b/packages/core/src/actor.ts @@ -45,11 +45,16 @@ const Machine = z.object({ type: z.literal('machine'), properties: z.object({ machineID: z.string(), - ownerUserID: z.string(), - // Not optional: `machine.teamId` is notNull, so a host that authenticated - // always has a team, and the branch that used to handle its absence was - // handling a state that can no longer exist. - teamID: z.string() + // All three of these describe *who owns the hardware*, and a host is + // owned one of two ways. A box somebody brought carries an owner and a + // team; hardware an organisation owns outright carries neither and + // carries an organisation instead. Exactly one of `teamID` and + // `organisationID` is ever set, which the database enforces rather than + // this schema — so read the one you mean and do not infer it from the + // other being absent. + ownerUserID: z.string().nullable(), + teamID: z.string().nullable(), + organisationID: z.string().nullable() }) }); diff --git a/packages/core/src/examples.ts b/packages/core/src/examples.ts index 9e1c236e..ea2e8d92 100644 --- a/packages/core/src/examples.ts +++ b/packages/core/src/examples.ts @@ -24,11 +24,20 @@ export namespace Examples { profile: { personaname: 'John Doe', avatarfull: 'https://avatars.steamstatic.com/xxxx.jpg' } }; + export const Organisation = { + id: Id('organisation'), + name: 'Initech', + slug: 'initech', + domain: 'initech.example', + domainVerified: true + }; + export const Team = { id: Id('team'), name: 'The A Team', slug: 'the-a-team', ownerId: Id('user'), + organisationId: null, billingEmail: 'billing@example.com', plan: 'free', subscriptionStatus: 'active', diff --git a/packages/core/src/id.ts b/packages/core/src/id.ts index 1fcce66b..a52b1086 100644 --- a/packages/core/src/id.ts +++ b/packages/core/src/id.ts @@ -6,6 +6,7 @@ export namespace Identifier { export const prefixes = { user: 'usr', linkedAccount: 'lac', + organisation: 'org', team: 'tem', teamMember: 'mem', verification: 'ver', diff --git a/packages/core/src/machine/index.ts b/packages/core/src/machine/index.ts index 7506d7f2..4d3bced7 100644 --- a/packages/core/src/machine/index.ts +++ b/packages/core/src/machine/index.ts @@ -43,15 +43,21 @@ export namespace Machine { description: 'Unique identifier for the machine', example: Examples.Machine.id }), - ownerUserId: z.string().meta({ - description: 'The user who registered this machine', + ownerUserId: z.string().nullable().meta({ + description: + 'The user who registered this machine, or null for hardware an organisation owns outright — a company card is nobody\u2019s personal property', example: Examples.Machine.ownerUserId }), - teamId: z.string().meta({ + teamId: z.string().nullable().meta({ description: - 'The team that owns this hardware. Always set — every user has a personal team', + 'The team that owns this hardware, for a host somebody brought. Null exactly when organisationId is set', example: Examples.Machine.teamId }), + organisationId: z.string().nullable().meta({ + description: + 'The organisation that owns this hardware outright, for a host serving workloads rather than its owner\u2019s. Null exactly when teamId is set', + example: null + }), label: z.string().meta({ description: 'Human-readable name for the box', example: Examples.Machine.label @@ -114,7 +120,11 @@ export namespace Machine { * than looking it up. */ export const register = fn( - Info.pick({ id: true, ownerUserId: true, teamId: true, label: true }), + Info.pick({ id: true, ownerUserId: true, teamId: true, label: true }) + .extend({ organisationId: Info.shape.organisationId.optional() }) + .refine((v) => (v.teamId === null) !== ((v.organisationId ?? null) === null), { + message: 'A machine belongs to a team or to an organisation, and not to both' + }), async (input) => { const secret = generateSecret(); const secretHash = await hashSecret(secret); @@ -132,6 +142,7 @@ export namespace Machine { id: input.id, ownerUserId: input.ownerUserId, teamId: input.teamId, + organisationId: input.organisationId ?? null, label: input.label, slug, secretHash, @@ -236,7 +247,7 @@ export namespace Machine { * is left to re-registration until renting makes it worth building. */ export const setTeam = fn( - Info.pick({ id: true, ownerUserId: true, teamId: true }), + Info.pick({ id: true }).extend({ ownerUserId: z.string(), teamId: z.string() }), async (input) => { return Database.use(async (tx) => { return tx @@ -367,8 +378,11 @@ export namespace Machine { /** Why a user may — or may not — use a box. */ export const Entitlement = z.object({ entitled: z.boolean(), - /** `owner`, `team`, or `none`. Present so a refusal can explain itself. */ - reason: z.enum(['owner', 'team', 'none']) + /** + * `owner`, `team`, `fleet`, or `none`. Present so a refusal can explain + * itself rather than being an unexplained no. + */ + reason: z.enum(['owner', 'team', 'fleet', 'none']) }); export type Entitlement = z.infer; @@ -376,14 +390,20 @@ export namespace Machine { /** * Whether a user may use a box. * - * The whole access model in one function: a solo box (`teamId` null) is the - * owner's alone, and a team-scoped box is open to that team. Multi-user - * access is the paid tier, so this is the line the paywall sits on — worth - * having exactly one implementation of. + * The whole access model in one function: a box someone brought is open to + * its owner and to the team it was registered under, and hardware an + * organisation owns outright is open to whoever has paid for a run on it. * * Membership is read live rather than cached in the machine row, so * removing someone from a team takes their box access with it and nobody * has to remember to revoke anything. + * + * **Fleet hardware refuses everyone for now, and that is deliberate.** What + * grants it is a plan, and nothing here can yet ask whether a user has one + * — so the honest answer is no rather than a yes that would hand out metered + * hardware for free. Failing closed on the expensive case is the cheap + * mistake to make; the branch is written out so there is one obvious place + * for the plan check to land. todo(d-0051) */ export const entitlement = fn( z.object({ machineId: z.string(), userId: z.string() }), @@ -392,7 +412,12 @@ export namespace Machine { if (!machine) { return { entitled: false, reason: 'none' }; } - if (machine.ownerUserId === input.userId) { + if (machine.organisationId) { + // Fleet hardware. Not the owner's and not a team's, so neither + // test below means anything here. + return { entitled: false, reason: 'fleet' }; + } + if (machine.ownerUserId && machine.ownerUserId === input.userId) { return { entitled: true, reason: 'owner' }; } if (!machine.teamId) { @@ -407,7 +432,21 @@ export namespace Machine { } ); - export const listByOwner = fn(Info.shape.ownerUserId, async (ownerUserId) => { + /** Every host an organisation owns outright — its fleet. */ + export const listByOrganisation = fn(z.string(), async (organisationId) => { + return Database.use(async (tx) => { + return tx + .select() + .from(MachineTable) + .where( + and(eq(MachineTable.organisationId, organisationId), isNull(MachineTable.timeDeleted)) + ) + .orderBy(MachineTable.timeCreated) + .then((rows) => rows.map(serialize)); + }); + }); + + export const listByOwner = fn(z.string(), async (ownerUserId) => { return Database.use(async (tx) => { return tx .select() @@ -432,6 +471,7 @@ export namespace Machine { id: input.id, ownerUserId: input.ownerUserId, teamId: input.teamId, + organisationId: input.organisationId, label: input.label, slug: input.slug, lastSeen: input.lastSeen?.toISOString() ?? null, diff --git a/packages/core/src/machine/machine.sql.ts b/packages/core/src/machine/machine.sql.ts index b38380fa..95fa59f2 100644 --- a/packages/core/src/machine/machine.sql.ts +++ b/packages/core/src/machine/machine.sql.ts @@ -1,33 +1,67 @@ -import { index, pgTable, text, uniqueIndex } from 'drizzle-orm/pg-core'; +import { sql } from 'drizzle-orm'; +import { check, index, pgTable, text, uniqueIndex } from 'drizzle-orm/pg-core'; import { id, timestamps, ulid, utc } from '../db/types.js'; +import { OrganisationTable } from '../organisation/organisation.sql.js'; import { TeamTable } from '../team/team.sql.js'; import { UserTable } from '../user/user.sql.js'; /** - * A registered nessh host — the *box* that runs downloads and serves SSH, not - * the laptop someone connects from. (`nessh-tui-redesign-guide.md` §7.2 uses - * "machine" for the other end of that connection; this table is the host end.) + * A registered host — the *box* that runs downloads and serves SSH, not the + * laptop someone connects from. Note the word is used the other way round in + * some client-facing writing, where "machine" is the end a person sits at; + * this table is the host end. * * A box does not assert who it is. It registers once against an owner's token * and is handed an id and a secret, so ids are unique because the API assigns * them rather than because a self-reported string happened not to collide. + * + * **Hardware is owned one of two ways, and never both.** Someone brings their + * own and reaches it through a team; or an organisation owns it outright, to + * serve workloads for people who have no hardware of their own. The check + * constraint below is what keeps that an either/or rather than a convention. */ export const MachineTable = pgTable( 'machine', { ...id, ...timestamps, - ownerUserId: ulid('owner_user_id') - .notNull() - .references(() => UserTable.id, { onDelete: 'cascade' }), - // Every user gets a personal team at signup, so there is always one to - // point at and the single-operator case is a team of one rather than a - // special case in every query. This was nullable, which cost a - // `teamId ?? ownerUserId` branch at each call site instead. ref(d-0048) - teamId: ulid('team_id') - .notNull() - .references(() => TeamTable.id, { onDelete: 'restrict' }), + /** + * Who registered it, when a person did. + * + * Null for hardware an organisation owns, which is the whole point of + * the column being nullable: a company's card is not anybody's personal + * property, and parking it under whichever employee ran the command + * made it one — where `cascade` below meant deleting that account + * deleted the machine. + * + * `cascade` stays, and is right once null is available. It only ever + * fires for a host somebody brought, and a box dying with the account + * that owns it is the behaviour that account expects. Fleet hardware is + * never reached by it, because the column it would follow is null. + */ + ownerUserId: ulid('owner_user_id').references(() => UserTable.id, { + onDelete: 'cascade' + }), + // A team's own hardware, brought by one of its members. Every user gets + // a personal team at signup, so there is always one to point at and the + // single-operator case is a team of one rather than a special case in + // every query. ref(d-0048) + // + // Null exactly when `organisationId` is set; see the check below. + teamId: ulid('team_id').references(() => TeamTable.id, { onDelete: 'restrict' }), + /** + * The organisation that owns this host outright. + * + * Set for fleet hardware and null for everything else. Deliberately not + * reached through a team: a team's machines belong to that team, and + * putting the fleet in a team would make every query that asks "whose + * hardware is this?" answer with a team that does not pay for it and + * cannot be billed for it. + */ + organisationId: ulid('organisation_id').references(() => OrganisationTable.id, { + onDelete: 'restrict' + }), label: text('label').notNull(), // The name this host is reached at: `amber-otter-4821.nestri.link`. // @@ -72,6 +106,12 @@ export const MachineTable = pgTable( uniqueIndex('machine_slug_unique').on(t.slug), uniqueIndex('machine_endpoint_id_unique').on(t.endpointId), index('machine_owner_idx').on(t.ownerUserId), - index('machine_team_idx').on(t.teamId) + index('machine_team_idx').on(t.teamId), + index('machine_organisation_idx').on(t.organisationId), + // Exactly one owner, enforced here rather than in the code that writes + // rows. Both null is a host nobody owns and nothing can bill; both set + // is two answers to one question, and whichever one a given query + // happens to join through would decide who pays. + check('machine_one_owner', sql`(${t.teamId} is null) != (${t.organisationId} is null)`) ] ); diff --git a/packages/core/src/machine/machine.test.ts b/packages/core/src/machine/machine.test.ts index 97cfcf5f..b7a45501 100644 --- a/packages/core/src/machine/machine.test.ts +++ b/packages/core/src/machine/machine.test.ts @@ -72,7 +72,8 @@ describe('Machine registration', () => { Machine.register({ id: Identifier.ascending('machine'), ownerUserId: owner.userId, - // @ts-expect-error — the point of the test is that this is refused + // Null is a real value now — it is how fleet hardware says it has + // no team — so this is refused for naming neither owner. teamId: null, label: 'teamless' }) diff --git a/packages/core/src/organisation/index.ts b/packages/core/src/organisation/index.ts new file mode 100644 index 00000000..6cd188d8 --- /dev/null +++ b/packages/core/src/organisation/index.ts @@ -0,0 +1,193 @@ +import { and, eq, isNull, sql } from 'drizzle-orm'; +import z from 'zod'; + +import { Database } from '../db/index.js'; +import { Examples } from '../examples.js'; +import { fn } from '../fn.js'; +import { UserTable } from '../user/user.sql.js'; +import { OrganisationTable } from './organisation.sql.js'; + +export namespace Organisation { + export const Info = z + .object({ + id: z.string().meta({ + description: 'Unique identifier for the organisation', + example: Examples.Organisation.id + }), + name: z.string().meta({ + description: 'Display name of the organisation', + example: Examples.Organisation.name + }), + slug: z.string().meta({ + description: 'URL-friendly unique slug for the organisation', + example: Examples.Organisation.slug + }), + domain: z.string().meta({ + description: + 'The email domain whose verified addresses belong to this organisation, without an @', + example: Examples.Organisation.domain + }), + domainVerified: z.boolean().meta({ + description: + 'Whether the domain has been shown to belong to them. Nothing is granted on an unverified claim', + example: Examples.Organisation.domainVerified + }) + }) + .meta({ + ref: 'Organisation', + description: + 'A company. It owns hardware outright, rather than through a team, and gathers the teams whose members sign in with its domain.', + example: Examples.Organisation + }); + + export type Info = z.infer; + + /** + * The domain part of an address, lower-cased. + * + * Returns null for anything that is not one address with one `@`, because + * every caller here is about to use the answer to decide membership and a + * best guess at a malformed address is the wrong kind of helpful. + */ + export function domainOf(email: string | null | undefined): string | null { + if (!email) { + return null; + } + const parts = email.trim().toLowerCase().split('@'); + if (parts.length !== 2 || !parts[0] || !parts[1]) { + return null; + } + return parts[1]!; + } + + export const create = fn( + Info.pick({ id: true, name: true, slug: true, domain: true }).extend({ + domainVerified: Info.shape.domainVerified.optional() + }), + async (input) => { + await Database.use(async (tx) => { + await tx.insert(OrganisationTable).values({ + id: input.id, + name: input.name, + slug: input.slug, + domain: input.domain.trim().toLowerCase(), + domainVerified: input.domainVerified ?? false + }); + }); + return input.id; + } + ); + + export const fromID = fn(Info.shape.id, async (id) => { + return Database.use(async (tx) => { + return tx + .select() + .from(OrganisationTable) + .where(and(eq(OrganisationTable.id, id), isNull(OrganisationTable.timeDeleted))) + .then((rows) => { + const row = rows.at(0); + return row ? serialize(row) : null; + }); + }); + }); + + export const fromSlug = fn(Info.shape.slug, async (slug) => { + return Database.use(async (tx) => { + return tx + .select() + .from(OrganisationTable) + .where(and(eq(OrganisationTable.slug, slug), isNull(OrganisationTable.timeDeleted))) + .then((rows) => { + const row = rows.at(0); + return row ? serialize(row) : null; + }); + }); + }); + + /** + * The organisation a domain belongs to, if one has proved it does. + * + * Only ever answers with a *verified* domain. An unverified row is a claim + * anybody could have typed, and answering with it would let whoever typed + * `gmail.com` reach every account on it. + */ + export const fromVerifiedDomain = fn(Info.shape.domain, async (domain) => { + const normalized = domain.trim().toLowerCase(); + return Database.use(async (tx) => { + return tx + .select() + .from(OrganisationTable) + .where( + and( + eq(OrganisationTable.domain, normalized), + eq(OrganisationTable.domainVerified, true), + isNull(OrganisationTable.timeDeleted) + ) + ) + .then((rows) => { + const row = rows.at(0); + return row ? serialize(row) : null; + }); + }); + }); + + /** + * Which organisation a user belongs to, derived rather than stored. + * + * Membership is their verified address's domain matching a verified + * organisation domain, and there is no membership table on purpose: an + * address is already the root identity, so a second record of who belongs + * where is a second answer that can disagree with the first. + * + * Two consequences worth stating, because both are features here. Signing + * in with a personal address gets an ordinary personal account, which is + * what lets the same person hold a company account and use the consumer + * product. And a user belongs to at most one organisation — when someone + * needs to be in two, this is where a membership table goes, and until then + * it would be a table with one row per user saying what the address says. + * + * An unverified address is not membership. It is a string somebody typed. + */ + export const forUser = fn(z.string(), async (userId) => { + const user = await Database.use(async (tx) => { + return tx + .select({ email: UserTable.email, emailVerified: UserTable.emailVerified }) + .from(UserTable) + .where(and(eq(UserTable.id, userId), isNull(UserTable.timeDeleted))) + .then((rows) => rows.at(0) ?? null); + }); + if (!user?.emailVerified) { + return null; + } + const domain = domainOf(user.email); + if (!domain) { + return null; + } + return fromVerifiedDomain(domain); + }); + + /** Whether this user may act for this organisation. */ + export async function isMember(userId: string, organisationId: string): Promise { + const organisation = await forUser(userId); + return organisation?.id === organisationId; + } + + export const remove = fn(Info.shape.id, async (id) => { + await Database.use(async (tx) => { + await tx + .update(OrganisationTable) + .set({ timeDeleted: sql`now()` }) + .where(eq(OrganisationTable.id, id)); + }); + }); + + export function serialize(input: typeof OrganisationTable.$inferSelect): Info { + return { + id: input.id, + name: input.name, + slug: input.slug, + domain: input.domain, + domainVerified: input.domainVerified + }; + } +} diff --git a/packages/core/src/organisation/organisation.sql.ts b/packages/core/src/organisation/organisation.sql.ts new file mode 100644 index 00000000..f3f862be --- /dev/null +++ b/packages/core/src/organisation/organisation.sql.ts @@ -0,0 +1,60 @@ +import { boolean, pgTable, text, uniqueIndex } from 'drizzle-orm/pg-core'; + +import { id, timestamps } from '../db/types.js'; + +/** + * A company, and the owner of hardware that belongs to nobody in particular. + * + * It exists because two kinds of machine were being modelled as one. A host + * somebody brings is theirs, reached through a team, and dies with their + * account — which is right. A host bought to serve other people's workloads is + * none of those things, and until now it had to be registered under some + * employee's personal team, where a deleted user row would take it with it. + * + * So an organisation owns fleet hardware *directly* rather than through a team + * inside it. Those are different relationships and collapsing them was the + * bug: a team's hardware is the team's, and the fleet is the company's. + * + * **Not a billing subject.** A team pays for what it uses whether it belongs + * to an organisation or not, so there are deliberately no plan or subscription + * columns here — this row says who owns the metal, not who owes money. + */ +export const OrganisationTable = pgTable( + 'organisation', + { + ...id, + ...timestamps, + name: text('name').notNull(), + slug: text('slug').notNull(), + /** + * The email domain that makes someone a member, without an `@`. + * + * Membership is derived from this rather than stored: an address is + * already the root identity, and one verified domain answers "who + * belongs here?" without a table that can disagree with it. Someone + * signing in with a personal address gets their ordinary personal + * account, which is what makes it safe to dogfood the company account + * and the consumer product from the same machine. + * + * Lower-cased and unique, for the same reason a user's address is: two + * organisations claiming one domain would make membership ambiguous in + * exactly the case that matters. Nothing here enforces the case, so + * anything writing it has to normalize first. + */ + domain: text('domain').notNull(), + /** + * Whether the domain has been shown to belong to them. + * + * Separate from the domain itself because an unverified claim is a real + * state and must never grant anything: anyone can type `gmail.com`, and + * membership derived from an unchecked claim would hand them every + * account on it. Nothing verifies domains yet, so this is set by hand + * and read by everything that grants. + */ + domainVerified: boolean('domain_verified').notNull().default(false) + }, + (t) => [ + uniqueIndex('organisation_slug_unique').on(t.slug), + uniqueIndex('organisation_domain_unique').on(t.domain) + ] +); diff --git a/packages/core/src/organisation/organisation.test.ts b/packages/core/src/organisation/organisation.test.ts new file mode 100644 index 00000000..8dfc3743 --- /dev/null +++ b/packages/core/src/organisation/organisation.test.ts @@ -0,0 +1,213 @@ +import { afterAll, describe, expect, test } from 'bun:test'; + +import { Actor } from '../actor.js'; +import { testDb } from '../db/test.js'; +import { Identifier } from '../id.js'; +import { Machine } from '../machine/index.js'; +import { User } from '../user/index.js'; +import { Organisation } from './index.js'; + +const sql = testDb(); + +const createdUserIds: string[] = []; +const createdOrgIds: string[] = []; + +/** A user with an address, verified or not, and nothing else attached. */ +async function newUser(label: string, email: string | null, emailVerified: boolean) { + const userId = Identifier.ascending('user'); + await User.create({ id: userId, name: label, email, emailVerified, image: null }); + createdUserIds.push(userId); + return userId; +} + +async function newOrg(label: string, domain: string, domainVerified: boolean) { + const id = Identifier.ascending('organisation'); + await Organisation.create({ + id, + name: label, + slug: `${label}-${id.slice(-6)}`, + domain, + domainVerified + }); + createdOrgIds.push(id); + return id; +} + +afterAll(async () => { + // Machines reference organisations with `restrict`, so the fleet has to go + // before the organisation that owns it. + if (createdOrgIds.length > 0) { + await sql`delete from "machine" where organisation_id in ${sql(createdOrgIds)}`; + } + if (createdUserIds.length > 0) { + await sql`delete from "user" where id in ${sql(createdUserIds)}`; + createdUserIds.length = 0; + } + if (createdOrgIds.length > 0) { + await sql`delete from "organisation" where id in ${sql(createdOrgIds)}`; + createdOrgIds.length = 0; + } +}); + +describe('Organisation membership', () => { + test('a verified address on a verified domain is membership', async () => { + const orgId = await newOrg('org-member', 'member.example', true); + const userId = await newUser('member', 'someone@member.example', true); + + const found = await Organisation.forUser(userId); + expect(found?.id).toBe(orgId); + }); + + test('an unverified domain grants nothing', async () => { + // Anyone can type a domain into a row. Until it is shown to be theirs, + // honouring it would hand them every account on it. + await newOrg('org-unverified', 'unverified.example', false); + const userId = await newUser('unverified', 'someone@unverified.example', true); + + expect(await Organisation.forUser(userId)).toBeNull(); + }); + + test('an unverified address is not membership either', async () => { + // The address is a string somebody typed until the code has been + // entered, and the domain half is no more trustworthy than the rest. + await newOrg('org-bothends', 'bothends.example', true); + const userId = await newUser('bothends', 'someone@bothends.example', false); + + expect(await Organisation.forUser(userId)).toBeNull(); + }); + + test('a personal address belongs to no organisation, and that is not an error', async () => { + await newOrg('org-personal', 'personal-co.example', true); + const userId = await newUser('personal', 'someone@gmail.example', true); + + expect(await Organisation.forUser(userId)).toBeNull(); + }); + + test('the domain match ignores case, because addresses do', async () => { + const orgId = await newOrg('org-case', 'case.example', true); + const userId = await newUser('case', 'Someone@CASE.example', true); + + expect((await Organisation.forUser(userId))?.id).toBe(orgId); + }); +}); + +describe('domainOf', () => { + test('takes the domain half, lower-cased', () => { + expect(Organisation.domainOf('Someone@Example.COM')).toBe('example.com'); + }); + + test('refuses anything that is not one address', () => { + // Every caller uses the answer to decide membership, so a best guess at + // a malformed address is the wrong kind of helpful. + for (const bad of [ + '', + null, + undefined, + 'no-at-sign', + 'two@at@signs', + '@nolocal', + 'nodomain@' + ]) { + expect(Organisation.domainOf(bad)).toBeNull(); + } + }); +}); + +describe('Fleet hardware', () => { + test('a host an organisation owns has no owner and no team', async () => { + const orgId = await newOrg('org-fleet', 'fleet.example', true); + const registered = await Machine.register({ + id: Identifier.ascending('machine'), + ownerUserId: null, + teamId: null, + organisationId: orgId, + label: 'fleet-card' + }); + + const machine = await Machine.fromID(registered.id); + expect(machine?.organisationId).toBe(orgId); + expect(machine?.ownerUserId).toBeNull(); + expect(machine?.teamId).toBeNull(); + }); + + test('hardware belongs to a team or an organisation, never both and never neither', async () => { + const orgId = await newOrg('org-either', 'either.example', true); + + // `fn()` parses synchronously, so a bad argument never becomes a + // rejected promise. + expect(() => + Machine.register({ + id: Identifier.ascending('machine'), + ownerUserId: null, + teamId: null, + label: 'ownerless' + }) + ).toThrow(); + + expect(() => + Machine.register({ + id: Identifier.ascending('machine'), + ownerUserId: null, + teamId: 'tem_whatever', + organisationId: orgId, + label: 'doubly-owned' + }) + ).toThrow(); + }); + + test('the fleet lists separately from anybody’s own hardware', async () => { + const orgId = await newOrg('org-list', 'list.example', true); + const registered = await Machine.register({ + id: Identifier.ascending('machine'), + ownerUserId: null, + teamId: null, + organisationId: orgId, + label: 'listed-card' + }); + + const fleet = await Machine.listByOrganisation(orgId); + expect(fleet.map((m) => m.id)).toContain(registered.id); + }); + + test('fleet hardware survives the account that registered it', async () => { + // The whole reason the column is nullable. It used to cascade, so + // deleting whoever ran the command deleted the machine. + const orgId = await newOrg('org-survives', 'survives.example', true); + const userId = await newUser('survives', 'admin@survives.example', true); + const registered = await Actor.with( + { type: 'user', properties: { userID: userId, linkedAccountID: '' } }, + async () => + Machine.register({ + id: Identifier.ascending('machine'), + ownerUserId: null, + teamId: null, + organisationId: orgId, + label: 'outlives-me' + }) + ); + + await sql`delete from "user" where id = ${userId}`; + createdUserIds.splice(createdUserIds.indexOf(userId), 1); + + expect((await Machine.fromID(registered.id))?.organisationId).toBe(orgId); + }); +}); + +describe('Entitlement on fleet hardware', () => { + test('nobody is entitled yet, and the reason says why', async () => { + // What grants a run on metered hardware is a plan, and there is nothing + // to ask yet — so this fails closed rather than giving it away. + const orgId = await newOrg('org-entitle', 'entitle.example', true); + const userId = await newUser('entitle', 'someone@entitle.example', true); + const registered = await Machine.register({ + id: Identifier.ascending('machine'), + ownerUserId: null, + teamId: null, + organisationId: orgId, + label: 'metered' + }); + + const answer = await Machine.entitlement({ machineId: registered.id, userId }); + expect(answer).toEqual({ entitled: false, reason: 'fleet' }); + }); +}); diff --git a/packages/core/src/team/index.ts b/packages/core/src/team/index.ts index 33e43687..be5542a1 100644 --- a/packages/core/src/team/index.ts +++ b/packages/core/src/team/index.ts @@ -28,6 +28,11 @@ export namespace Team { description: 'The user who owns/created this team', example: Examples.Team.ownerId }), + organisationId: z.string().nullable().optional().meta({ + description: + 'The organisation this team belongs to, or null for a personal team. It groups teams under a company; it does not move billing, which stays on the team', + example: Examples.Team.organisationId + }), billingEmail: z.email().nullable().optional().meta({ description: 'Email address used for billing and invoices', example: Examples.Team.billingEmail @@ -171,6 +176,7 @@ export namespace Team { name: input.name, slug: input.slug, ownerId: input.ownerId, + organisationId: input.organisationId, billingEmail: input.billingEmail, plan: input.plan, subscriptionStatus: input.subscriptionStatus, diff --git a/packages/core/src/team/team.sql.ts b/packages/core/src/team/team.sql.ts index e848c9ef..16beb3c9 100644 --- a/packages/core/src/team/team.sql.ts +++ b/packages/core/src/team/team.sql.ts @@ -1,18 +1,38 @@ -import { jsonb, pgTable, text } from 'drizzle-orm/pg-core'; +import { index, jsonb, pgTable, text } from 'drizzle-orm/pg-core'; import { id, timestamps, ulid } from '../db/types.js'; +import { OrganisationTable } from '../organisation/organisation.sql.js'; import { UserTable } from '../user/user.sql.js'; -export const TeamTable = pgTable('team', { - ...id, - ...timestamps, - name: text('name').notNull(), - slug: text('slug').notNull().unique(), - ownerId: ulid('owner_id') - .notNull() - .references(() => UserTable.id, { onDelete: 'cascade' }), - billingEmail: text('billing_email'), - plan: text('plan').notNull().default('free'), - subscriptionStatus: text('subscription_status').notNull().default('active'), - metadata: jsonb('metadata').$type<{}>() -}); +export const TeamTable = pgTable( + 'team', + { + ...id, + ...timestamps, + name: text('name').notNull(), + slug: text('slug').notNull().unique(), + ownerId: ulid('owner_id') + .notNull() + .references(() => UserTable.id, { onDelete: 'cascade' }), + /** + * The organisation this team belongs to, if it belongs to one. + * + * Null for every personal team, which is most of them, and that is the + * ordinary case rather than a missing value. It groups teams under a + * company and decides which of them a verified domain reaches; it does not + * move billing, which stays on the team either way. + * + * `restrict`, so an organisation with teams cannot be deleted out from + * under them — where those teams should go is a decision, and there is no + * UI for it, so the database refuses rather than guessing. + */ + organisationId: ulid('organisation_id').references(() => OrganisationTable.id, { + onDelete: 'restrict' + }), + billingEmail: text('billing_email'), + plan: text('plan').notNull().default('free'), + subscriptionStatus: text('subscription_status').notNull().default('active'), + metadata: jsonb('metadata').$type<{}>() + }, + (t) => [index('team_organisation_idx').on(t.organisationId)] +); From b819367a095ac3adb5b2b1abffdc2738641af091 Mon Sep 17 00:00:00 2001 From: Wanjohi Date: Fri, 18 Sep 2026 23:44:00 +0300 Subject: [PATCH 4/9] feat(core): burn windows, and the rules that make an allowance mean something MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The unit is one second of a reference session — baseline size, baseline card, running alone, on hardware we own. Every factor is a multiple of that, so an allowance is measured in time and a bar prints the stored number instead of converting into it. Integers throughout. Three rolling windows, one function. A counter is stored beside the time it was last written, and a counter whose timestamp falls outside its window reads as zero — so the reset is implied by the clock and nothing has to run for a window to roll clear. No scheduled job to misfire, and no race between a reset and a write landing together. The same rule on the write side is one statement rather than a read followed by a decision. The check is pure, and it is the same arithmetic the meter draws from. The complaint about usage limits is almost never the limit, it is being surprised by one, and two implementations that agree today are how a full bar and a refusal start disagreeing. Two rules on the allowances, enforced rather than remembered: - A window's allowance must exceed the window itself. Because the windows roll, one uninterrupted session asymptotes at exactly the window length, so an allowance at or below it is a wall that someone playing alone will meet. - Each longer allowance must be under what the shorter window already permits, or it can never be reached — a number that looks like a limit, reads like a promise, and never once fires. Allowances are configuration rather than constants, because they will be retuned against real burn far more often than this code changes, and a rate that needs a deploy is a rate that stays wrong until the next one. The shipped set is explicitly a placeholder: coherent enough to test against, not a pricing decision. --- packages/core/src/billing/limits.test.ts | 161 +++++++++++++++++++++++ packages/core/src/billing/limits.ts | 150 +++++++++++++++++++++ packages/core/src/billing/window.test.ts | 115 ++++++++++++++++ packages/core/src/billing/window.ts | 116 ++++++++++++++++ packages/core/src/env.ts | 9 ++ 5 files changed, 551 insertions(+) create mode 100644 packages/core/src/billing/limits.test.ts create mode 100644 packages/core/src/billing/limits.ts create mode 100644 packages/core/src/billing/window.test.ts create mode 100644 packages/core/src/billing/window.ts diff --git a/packages/core/src/billing/limits.test.ts b/packages/core/src/billing/limits.test.ts new file mode 100644 index 00000000..8d981804 --- /dev/null +++ b/packages/core/src/billing/limits.test.ts @@ -0,0 +1,161 @@ +import { afterEach, describe, expect, test } from 'bun:test'; + +import { Env } from '../env.js'; +import { Limits } from './limits.js'; +import { Window } from './window.js'; + +const HOUR = 60 * 60; + +/** Allowances in hours, which is how anybody actually reasons about them. */ +function hours(fiveHour: number, sevenDay: number, thirtyDay: number) { + return { + fiveHour: fiveHour * HOUR, + sevenDay: sevenDay * HOUR, + thirtyDay: thirtyDay * HOUR + }; +} + +afterEach(() => { + Env.init({}); + Limits.reset(); +}); + +describe('The floor: one uninterrupted session must never hit a wall', () => { + test('an allowance at or below its own window is refused', () => { + // A rolling window means a single continuous session asymptotes at + // exactly the window length. So an allowance of five hours over a + // five-hour window is a wall that someone playing alone will meet, and + // meeting it is the one outcome this model may not produce. + expect(() => Limits.check(hours(5, 300, 1000), 'free')).toThrow(/must exceed the window/); + expect(() => Limits.check(hours(4, 300, 1000), 'free')).toThrow(/5-hour/); + expect(() => Limits.check(hours(10, 168, 1000), 'free')).toThrow(/7-day/); + expect(() => Limits.check(hours(10, 300, 720), 'free')).toThrow(/30-day/); + }); + + test('just above the window is accepted, because the rule is the floor', () => { + // Deliberately close to every bound at once: each allowance barely + // clears its own window, and each still sits under what the shorter + // window permits (170h of weekly is under 33.6 x 5.5h = 184.8h; 725h of + // monthly is under 4.29 x 170h = 728.6h). A set this tight is legal and + // miserable, which is the point — the rules bound the space, they do not + // choose within it. + expect(() => Limits.check(hours(5.5, 170, 725), 'free')).not.toThrow(); + }); + + test('the floors are the window lengths, stated in seconds', () => { + // Spelled out so the relationship is visible rather than implied: the + // floor is not a chosen number, it is the window. + expect(Window.FIVE_HOURS).toBe(5 * HOUR); + expect(Window.SEVEN_DAYS).toBe(168 * HOUR); + expect(Window.THIRTY_DAYS).toBe(720 * HOUR); + }); +}); + +describe('The nesting rule: every window has to bind', () => { + test('a longer allowance the shorter window already caps is refused', () => { + // The 5-hour window permits 10h per 5h sustained, which is 336h over a + // week. A 7-day allowance of 400h could never be reached, so it would + // read like a limit and never once fire. + expect(() => Limits.check(hours(10, 400, 1000), 'free')).toThrow(/never be reached/); + expect(() => Limits.check(hours(10, 300, 1400), 'free')).toThrow(/30-day/); + }); + + test('the ceiling is exclusive, because equality never binds either', () => { + // 33.6 x 10h is exactly 336h; at exactly the ceiling the window fires + // only in the limit, which is the same as not firing. + expect(() => Limits.check(hours(10, 336, 1000), 'free')).toThrow(/never be reached/); + expect(() => Limits.check(hours(10, 335, 1000), 'free')).not.toThrow(); + }); + + test('the placeholder set satisfies both rules', () => { + // It is not a pricing decision, but it has to be a coherent one, or + // nothing downstream can be tested against it. + expect(() => Limits.validate(Limits.PLACEHOLDER)).not.toThrow(); + }); +}); + +describe('Configuration', () => { + test('unset takes the placeholder set', () => { + Env.init({}); + Limits.reset(); + expect(Limits.get()).toEqual(Limits.PLACEHOLDER); + }); + + test('the environment overrides it, and is validated on the way in', () => { + Env.init({ + BURN_LIMITS: JSON.stringify({ free: hours(12, 350, 1200), paid: hours(40, 1200, 4000) }) + }); + Limits.reset(); + expect(Limits.get().free.fiveHour).toBe(12 * HOUR); + }); + + test('a configured set that would not bind is refused rather than used', () => { + // The whole reason the check is code: these get retuned by whoever is + // closest to the burn data, and a set that quietly stops binding is not + // visible from the numbers. + Env.init({ + BURN_LIMITS: JSON.stringify({ free: hours(10, 400, 1000), paid: hours(30, 900, 3000) }) + }); + Limits.reset(); + expect(() => Limits.get()).toThrow(/never be reached/); + }); + + test('malformed JSON is refused, not ignored', () => { + Env.init({ BURN_LIMITS: '{not json' }); + Limits.reset(); + expect(() => Limits.get()).toThrow(/not valid JSON/); + }); + + test('anything that is not the paid plan gets the free allowance', () => { + Env.init({}); + Limits.reset(); + for (const plan of ['free', null, undefined, 'something-we-retired']) { + expect(Limits.forPlan(plan)).toEqual(Limits.PLACEHOLDER.free); + } + expect(Limits.forPlan('paid')).toEqual(Limits.PLACEHOLDER.paid); + }); +}); + +describe('What the placeholder set actually means', () => { + // These are the sentences the numbers are supposed to say. If a retune + // breaks one, the retune changed the product and should say so. + const free = Limits.PLACEHOLDER.free; + + test('one session running continuously never exhausts any window', () => { + for (const window of Window.ALL) { + // One session burns one unit per second, so over any window it has + // spent exactly the window length. + const state = Window.analyze({ + allowance: free[window.key], + windowSeconds: window.seconds, + usage: window.seconds, + timeUpdated: new Date() + }); + expect(state.exhausted).toBe(false); + } + }); + + test('two at once bites, and the five-hour window bites first', () => { + const now = new Date(); + const twoForFiveHours = 2 * Window.FIVE_HOURS; + expect( + Window.analyze({ + allowance: free.fiveHour, + windowSeconds: Window.FIVE_HOURS, + usage: twoForFiveHours, + timeUpdated: now + }).exhausted + ).toBe(true); + + // The same burn is nowhere near the weekly allowance, which is what + // makes the three windows do different jobs rather than one job thrice. + expect( + Window.analyze({ + allowance: free.sevenDay, + windowSeconds: Window.SEVEN_DAYS, + usage: twoForFiveHours, + timeUpdated: now + }).exhausted + ).toBe(false); + }); +}); diff --git a/packages/core/src/billing/limits.ts b/packages/core/src/billing/limits.ts new file mode 100644 index 00000000..beb7c8cf --- /dev/null +++ b/packages/core/src/billing/limits.ts @@ -0,0 +1,150 @@ +import z from 'zod'; + +import { Env } from '../env.js'; +import { ErrorCodes, VisibleError } from '../error.js'; +import { memo } from '../utils/memo.js'; +import { Window } from './window.js'; + +/** + * How much burn a plan is allowed, per window. + * + * **The unit is one second of a reference session** — the baseline size, on the + * baseline card, running alone, on hardware we own. Every factor is a multiple + * of that, so an allowance is measured in *time* and a bar can print + * `6h 20m of 12h` from the stored number instead of converting into it. + * Integers throughout; burn is never a float. + * + * On a caller's own hardware the size and hardware factors are 1, because what + * they price — a share of a card we paid for — is not being spent. Burn there + * is duration multiplied by how much is running at once, and nothing about + * their GPU enters into it. Nothing is ever measured on somebody's machine. + * + * **Configuration, not constants.** These numbers are not settled and will be + * retuned against real burn far more often than this code changes. A rate that + * needs a deploy is a rate that stays wrong for a week, so they are read from + * the environment and validated at read time. + */ +export namespace Limits { + const Allowances = z.object({ + fiveHour: z.number().int().positive(), + sevenDay: z.number().int().positive(), + thirtyDay: z.number().int().positive() + }); + + export type Allowances = z.infer; + + export const Config = z.object({ + free: Allowances, + paid: Allowances + }); + + export type Config = z.infer; + + /** + * Placeholder numbers, and deliberately labelled as such. + * + * They satisfy every rule {@link check} enforces, so the mechanism runs and + * can be tested end to end, and they are not a pricing decision. The free + * set says "one box around the clock, with room to double up now and then"; + * the paid set is the same shape, larger. Both want replacing with numbers + * chosen against measured burn. + */ + export const PLACEHOLDER: Config = { + free: { + fiveHour: 10 * 60 * 60, + sevenDay: 300 * 60 * 60, + thirtyDay: 1000 * 60 * 60 + }, + paid: { + fiveHour: 30 * 60 * 60, + sevenDay: 900 * 60 * 60, + thirtyDay: 3000 * 60 * 60 + } + }; + + /** + * The two rules that make a set of allowances mean anything. + * + * **A window's allowance must exceed the window itself.** Because the + * windows roll, one continuously-running session does not creep — it + * asymptotes at exactly the window length and stays there. So an allowance + * at or below its own window is one where a single uninterrupted session + * hits a wall, which is the one outcome the model may not produce: someone + * playing alone on hardware they own must never be stopped. + * + * **Each longer window must be smaller than what the shorter one already + * permits.** Sustained burn allowed by a window is `allowance ÷ window` per + * second, so a longer allowance above `allowance × (longer ÷ shorter)` can + * never be reached and is decoration — a number that looks like a limit, + * reads like a promise, and never fires. + * + * Both are cheap, and checking them here rather than in somebody's head is + * the point: these get retuned by whoever is closest to the burn data, and + * a set that quietly stops binding is not visible from the numbers. + */ + export function check(allowances: Allowances, plan: string): void { + for (const window of Window.ALL) { + const allowance = allowances[window.key]; + if (allowance <= window.seconds) { + throw new VisibleError( + 'internal', + ErrorCodes.Server.INTERNAL_ERROR, + `${plan}: the ${window.label} allowance (${allowance}s) must exceed the window itself (${window.seconds}s), or one uninterrupted session hits a wall` + ); + } + } + + for (let i = 1; i < Window.ALL.length; i++) { + const shorter = Window.ALL[i - 1]!; + const longer = Window.ALL[i]!; + const ceiling = (allowances[shorter.key] * longer.seconds) / shorter.seconds; + if (allowances[longer.key] >= ceiling) { + throw new VisibleError( + 'internal', + ErrorCodes.Server.INTERNAL_ERROR, + `${plan}: the ${longer.label} allowance (${allowances[longer.key]}s) can never be reached, because the ${shorter.label} window already caps it at ${Math.floor(ceiling)}s — it would never bind` + ); + } + } + } + + export function validate(config: unknown): Config { + const parsed = Config.parse(config); + check(parsed.free, 'free'); + check(parsed.paid, 'paid'); + return parsed; + } + + const _get = memo((): Config => { + const raw = Env.get().BURN_LIMITS; + if (!raw) { + return validate(PLACEHOLDER); + } + let parsed: unknown; + try { + parsed = JSON.parse(raw); + } catch { + throw new VisibleError( + 'internal', + ErrorCodes.Server.INTERNAL_ERROR, + 'BURN_LIMITS is not valid JSON' + ); + } + return validate(parsed); + }); + + export function get(): Config { + return _get(); + } + + /** Reset the memo. Tests change the environment between cases. */ + export function reset(): void { + _get.reset(); + } + + /** The allowances a plan gets. Anything not `paid` is free. */ + export function forPlan(plan: string | null | undefined): Allowances { + const config = get(); + return plan === 'paid' ? config.paid : config.free; + } +} diff --git a/packages/core/src/billing/window.test.ts b/packages/core/src/billing/window.test.ts new file mode 100644 index 00000000..7ad7dbbd --- /dev/null +++ b/packages/core/src/billing/window.test.ts @@ -0,0 +1,115 @@ +import { describe, expect, test } from 'bun:test'; + +import { Window } from './window.js'; + +const HOUR = 60 * 60; +const NOW = new Date('2026-09-18T12:00:00.000Z'); + +function ago(seconds: number) { + return new Date(NOW.getTime() - seconds * 1000); +} + +function fiveHour(usage: number, timeUpdated: Date | null, allowance = 10 * HOUR) { + return Window.analyze({ + allowance, + windowSeconds: Window.FIVE_HOURS, + usage, + timeUpdated, + now: NOW + }); +} + +describe('The staleness rule', () => { + test('a counter older than the window reads as zero', () => { + // This is what replaces a reset job. Nothing has to run for the window + // to roll clear, so there is no cron to misfire and no race between a + // reset and a write landing at the same moment. + const state = fiveHour(9 * HOUR, ago(Window.FIVE_HOURS + 1)); + expect(state.used).toBe(0); + expect(state.percent).toBe(0); + expect(state.exhausted).toBe(false); + expect(state.remaining).toBe(10 * HOUR); + }); + + test('a counter inside the window is counted', () => { + const state = fiveHour(9 * HOUR, ago(Window.FIVE_HOURS - 60)); + expect(state.used).toBe(9 * HOUR); + expect(state.exhausted).toBe(false); + }); + + test('nothing ever recorded reads as zero rather than throwing', () => { + expect(fiveHour(0, null).used).toBe(0); + }); + + test('the boundary belongs to the window, not outside it', () => { + // Exactly one window old is the oldest moment still in view. Getting + // this backwards would silently forgive a window's worth of burn. + expect(fiveHour(9 * HOUR, ago(Window.FIVE_HOURS)).used).toBe(9 * HOUR); + }); +}); + +describe('Exhaustion', () => { + test('at the allowance is exhausted, not just above it', () => { + expect(fiveHour(10 * HOUR, NOW).exhausted).toBe(true); + expect(fiveHour(10 * HOUR - 1, NOW).exhausted).toBe(false); + }); + + test('overrun is reported, but remaining never goes negative', () => { + // A run is never stopped mid-session, so burn past the allowance is a + // real and expected state — it just has nothing left to offer. + const state = fiveHour(14 * HOUR, NOW); + expect(state.used).toBe(14 * HOUR); + expect(state.remaining).toBe(0); + expect(state.percent).toBe(100); + }); + + test('a zero allowance is exhausted rather than dividing by zero', () => { + const state = fiveHour(0, NOW, 0); + expect(state.exhausted).toBe(true); + expect(state.percent).toBe(100); + }); +}); + +describe('What the bar shows is what the gate reads', () => { + test('the percent the bar draws is the same number the check uses', () => { + // The complaint about usage limits is almost never the limit, it is + // being surprised by it. One arithmetic means a full bar and a refusal + // cannot disagree. + const state = fiveHour(5 * HOUR, NOW); + expect(state.percent).toBe(50); + expect(state.exhausted).toBe(false); + expect(state.remaining).toBe(5 * HOUR); + expect(state.used + state.remaining).toBe(state.allowance); + }); + + test('percent floors rather than rounds, so it reads 99 until it is done', () => { + expect(fiveHour(10 * HOUR - 1, NOW).percent).toBe(99); + }); + + test('reset counts from when the burn rolls out of view', () => { + const state = fiveHour(3 * HOUR, ago(HOUR)); + expect(state.resetInSec).toBe(Window.FIVE_HOURS - HOUR); + }); + + test('a cleared window has nothing to wait for', () => { + expect(fiveHour(9 * HOUR, ago(Window.FIVE_HOURS + 1)).resetInSec).toBe(0); + }); +}); + +describe('The three windows are the same arithmetic', () => { + test('one function, parameterised by length', () => { + // Their monthly window is calendar-anchored; ours rolls. Reaching for a + // month-bounds helper here would be a subtle and expensive mistake. + for (const window of Window.ALL) { + const state = Window.analyze({ + allowance: 2 * window.seconds, + windowSeconds: window.seconds, + usage: window.seconds, + timeUpdated: NOW, + now: NOW + }); + expect(state.percent).toBe(50); + expect(state.exhausted).toBe(false); + } + }); +}); diff --git a/packages/core/src/billing/window.ts b/packages/core/src/billing/window.ts new file mode 100644 index 00000000..f026a4b3 --- /dev/null +++ b/packages/core/src/billing/window.ts @@ -0,0 +1,116 @@ +import z from 'zod'; + +import { fn } from '../fn.js'; + +/** + * Reading a rolling window, without a job that resets it. + * + * A counter is stored next to the time it was last written, and a counter + * whose timestamp falls outside the current window simply **reads as zero**. + * Nothing resets anything on a schedule: the reset is implied by the clock, so + * there is no cron to misfire and no race between a reset and a concurrent + * write. The same rule applied on the write side — increment if the stamp is + * inside the window, otherwise start again from this amount — makes the whole + * thing one statement. + * + * Everything here is pure. It takes numbers and gives an answer, which is what + * lets the meter a person sees and the check that stops them be the same + * arithmetic rather than two implementations that agree for now. + */ +export namespace Window { + /** Seconds. Named so a caller cannot pass minutes by accident. */ + export const FIVE_HOURS = 5 * 60 * 60; + export const SEVEN_DAYS = 7 * 24 * 60 * 60; + export const THIRTY_DAYS = 30 * 24 * 60 * 60; + + /** + * The three, shortest first. + * + * Order is load-bearing: the nesting rule that keeps each allowance + * meaningful is stated between neighbours, and the bars are read top-down. + */ + export const ALL = [ + { key: 'fiveHour' as const, seconds: FIVE_HOURS, label: '5-hour' }, + { key: 'sevenDay' as const, seconds: SEVEN_DAYS, label: '7-day' }, + { key: 'thirtyDay' as const, seconds: THIRTY_DAYS, label: '30-day' } + ]; + + export type Key = (typeof ALL)[number]['key']; + + export const State = z.object({ + /** Whether a new run may start. A live one is never stopped by this. */ + exhausted: z.boolean(), + /** Burn already spent in this window, after the staleness rule. */ + used: z.number().int(), + /** The allowance it is spent against. Never reported without `used`. */ + allowance: z.number().int(), + /** What is left, floored at zero — overrun is real but never negative. */ + remaining: z.number().int(), + /** + * Whole percent used, 0–100. + * + * For the bar, and deliberately the same number the gate reads, so a + * full bar and a refusal cannot disagree. + */ + percent: z.number().int(), + /** Seconds until this window has rolled clear of the current usage. */ + resetInSec: z.number().int() + }); + + export type State = z.infer; + + /** + * Where one window stands. + * + * `usage` and `timeUpdated` are the stored pair. A `timeUpdated` older than + * the window means everything recorded in it has rolled out of view, so the + * answer is a clean zero rather than a stale total — this is the staleness + * rule, and it is why nothing has to be reset. + */ + export const analyze = fn( + z.object({ + allowance: z.number().int().nonnegative(), + windowSeconds: z.number().int().positive(), + usage: z.number().int().nonnegative(), + /** Null when nothing has ever been recorded, which reads as zero. */ + timeUpdated: z.date().nullable(), + /** Injected so the arithmetic is testable without waiting. */ + now: z.date().optional() + }), + (input): State => { + const now = input.now ?? new Date(); + const windowMs = input.windowSeconds * 1000; + const windowStart = now.getTime() - windowMs; + + // Rolled clear: the stored total describes a window that has passed. + if (!input.timeUpdated || input.timeUpdated.getTime() < windowStart) { + return { + exhausted: false, + used: 0, + allowance: input.allowance, + remaining: input.allowance, + percent: 0, + resetInSec: 0 + }; + } + + const used = input.usage; + const remaining = Math.max(0, input.allowance - used); + const percent = + input.allowance === 0 ? 100 : Math.min(100, Math.floor((used / input.allowance) * 100)); + + // When the last write rolls out of the window, this usage is gone. + const clearsAt = input.timeUpdated.getTime() + windowMs; + const resetInSec = Math.max(0, Math.ceil((clearsAt - now.getTime()) / 1000)); + + return { + exhausted: used >= input.allowance, + used, + allowance: input.allowance, + remaining, + percent, + resetInSec + }; + } + ); +} diff --git a/packages/core/src/env.ts b/packages/core/src/env.ts index 57e4495a..877e1726 100644 --- a/packages/core/src/env.ts +++ b/packages/core/src/env.ts @@ -27,6 +27,15 @@ export namespace Env { */ AUTH_INTERNAL_URL: z.string().optional(), + /** + * Burn allowances per plan, as JSON. Unset takes the placeholder set. + * + * Configuration rather than constants because these are retuned against + * real burn far more often than the code that reads them changes, and a + * rate that needs a deploy is a rate that stays wrong until the next one. + */ + BURN_LIMITS: z.string().optional(), + DATABASE_URL: z.string().optional() }); From b4776fad2f2bf232da430db3d420c4ee74396b60 Mon Sep 17 00:00:00 2001 From: Wanjohi Date: Sat, 19 Sep 2026 00:19:39 +0300 Subject: [PATCH 5/9] feat(core,api): record burn as rate segments, and refuse the next run when spent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The counters this fills are the ones the windows already knew how to read. What was missing was anything that put a number in them. Burn is recorded as segments: one stretch of one run at one unchanging rate, opened when the rate becomes true and closed when it stops being. Not a row per session, because a session's rate does not survive its own lifetime — a second run changes what the account spends per second while the first is still going, and a rate that applied from that moment must not be backdated over the time before it. Not a row per event either, because burn accrues against an envelope that is held rather than per thing consumed. Closing a segment is what moves burn into the counters, so a long run lands incrementally instead of all at the end. Burn that only arrives when a session stops is burn that cannot refuse the next one, and a bar that does not move while something is running is a bar nobody believes. The counters are written with the staleness rule as a single statement: add to the total if its stamp is still inside the window, otherwise start again from this amount. Reading and then deciding would be two statements with a gap, and the gap is where a concurrent tick doubles or vanishes. The first tick for a team and the thousandth are the same call, for the same reason. The gate sits at the one moment it is allowed to speak — before a run starts, never again. A limit refuses the next run and never interrupts one already going; someone losing a session mid-game to a meter does not come back. Every window is checked rather than the shortest, because they protect different things over different spans. The answer comes back with the created run rather than being thrown away: the response carries where each window stands, what the account spends per second now, and what one more run would cost. Every surface that can start a run has to show that before the click, and a second call for it is a call nobody makes. Asking twice would also let the number shown and the number billed disagree. Accrual is wired to the run's own state transition, in the same transaction that moves it. A session that went live without its meter starting is free hardware; one that ended without its meter stopping bills forever. Both are silent, so neither may be a second write that might not happen. --- apps/api/app/routes/session.ts | 32 +- apps/api/app/utils/result.ts | 15 + apps/api/test/session.test.ts | 22 +- .../0016_burn_counters_and_rate_segments.sql | 70 + .../core/migrations/meta/0016_snapshot.json | 3234 +++++++++++++++++ packages/core/migrations/meta/_journal.json | 7 + packages/core/src/billing/burn.sql.ts | 99 + packages/core/src/billing/burn.test.ts | 227 ++ packages/core/src/billing/burn.ts | 280 ++ packages/core/src/billing/index.ts | 123 + packages/core/src/id.ts | 2 + packages/core/src/session/index.ts | 33 + packages/core/src/session/session.test.ts | 9 +- 13 files changed, 4146 insertions(+), 7 deletions(-) create mode 100644 packages/core/migrations/0016_burn_counters_and_rate_segments.sql create mode 100644 packages/core/migrations/meta/0016_snapshot.json create mode 100644 packages/core/src/billing/burn.sql.ts create mode 100644 packages/core/src/billing/burn.test.ts create mode 100644 packages/core/src/billing/burn.ts create mode 100644 packages/core/src/billing/index.ts diff --git a/apps/api/app/routes/session.ts b/apps/api/app/routes/session.ts index d42ee17a..134c6825 100644 --- a/apps/api/app/routes/session.ts +++ b/apps/api/app/routes/session.ts @@ -1,4 +1,5 @@ import { Actor } from '@nestri/core/actor'; +import { Billing } from '@nestri/core/billing/index'; import { Box } from '@nestri/core/box/index'; import { ErrorCodes, VisibleError } from '@nestri/core/error'; import { Examples } from '@nestri/core/examples'; @@ -11,7 +12,14 @@ import { Hono } from 'hono'; import { describeRoute } from 'hono-openapi'; import { z } from 'zod'; -import { ErrorResponses, machineOnly, notPublic, Result, validator } from '../utils'; +import { + ErrorResponses, + machineOnly, + notPublic, + Result, + ResultWithBilling, + validator +} from '../utils'; /** * Requesting a run, and carrying one out. @@ -89,17 +97,22 @@ export namespace SessionApi { tags: ['Session'], summary: 'Ask for a run of a box', description: - 'Creates the run in state `requested`, which is the work order the box’s host picks up. This makes no decision about where the run happens: a box already names the hardware it is placed on, so the run inherits it. Poll the run to watch it start, and re-read its ticket rather than keeping the first one.', + 'Creates the run in state `requested`, which is the work order the box’s host picks up. This makes no decision about where the run happens: a box already names the hardware it is placed on, so the run inherits it. Poll the run to watch it start, and re-read its ticket rather than keeping the first one. The response carries where the account’s allowance stands and what it is now spending per second, including what one more run would cost — a spent allowance refuses this call with a 429 and never interrupts a run already going.', responses: { 201: { - content: { 'application/json': { schema: Result(Session.Info) } }, + content: { + 'application/json': { + schema: ResultWithBilling(Session.Info, Billing.State.nullable()) + } + }, description: 'The run has been requested' }, 400: ErrorResponses[400], 401: ErrorResponses[401], 403: ErrorResponses[403], 404: ErrorResponses[404], - 409: ErrorResponses[409] + 409: ErrorResponses[409], + 429: ErrorResponses[429] } }), validator( @@ -217,13 +230,22 @@ export namespace SessionApi { conflict(Session.BOX_BUSY); } + // The allowance is checked here and nowhere later. A limit refuses + // the next run; it never stops one already going, so this is the + // only moment it may speak. The answer comes back rather than + // being discarded, because the caller has to be told what it will + // cost and what remains — and asking a second time would let the + // number shown and the number billed disagree. + const team = await Billing.teamForBox(box.id); + const billing = team ? await Billing.assertMayStart(team) : null; + const session = await Session.request({ id: Identifier.ascending('session'), boxId: box.id, gameId: game.id, linkedAccountId }); - return c.json({ data: session }, 201); + return c.json({ data: session, billing }, 201); } ) .get( diff --git a/apps/api/app/utils/result.ts b/apps/api/app/utils/result.ts index 3c578f88..ca4f83b4 100644 --- a/apps/api/app/utils/result.ts +++ b/apps/api/app/utils/result.ts @@ -4,3 +4,18 @@ import { z } from 'zod'; export function Result(schema: T) { return resolver(z.object({ data: schema })); } + +/** + * A result that also carries where the caller's allowance stands. + * + * Every surface that can start a run has to show what it will cost and what + * remains, so the answer travels with the thing that spends it rather than + * needing a second call nobody will make. It sits beside `data` and not inside + * it, because it describes the account rather than the resource. + */ +export function ResultWithBilling( + schema: T, + billing: B +) { + return resolver(z.object({ data: schema, billing })); +} diff --git a/apps/api/test/session.test.ts b/apps/api/test/session.test.ts index 42c6fd92..89840506 100644 --- a/apps/api/test/session.test.ts +++ b/apps/api/test/session.test.ts @@ -112,6 +112,13 @@ async function requestSession(s: Awaited>) { afterAll(async () => { if (createdUserIds.length > 0) { + // `burn_segment` holds a session with `restrict` — deleting a run must + // not erase what it cost — so the record goes before the runs do. + await sql`delete from "burn_segment" where session_id in ( + select s.id from "session" s + join "box" b on b.id = s.box_id + where b.user_id in ${sql(createdUserIds)} + )`; await sql`delete from "box" where user_id in ${sql(createdUserIds)}`; await sql`delete from "user" where id in ${sql(createdUserIds)}`; createdUserIds.length = 0; @@ -131,7 +138,20 @@ describe('POST /session', () => { // The field names are the contract. A rename on either side produces a // host that starts, reads nothing, and reports success — so the shape // is asserted whole rather than field by field. - expect(Object.keys(body)).toEqual(['data']); + // `billing` rides alongside `data` on purpose: every surface that can + // start a run has to show what it costs and what remains, and a second + // call for that is a call nobody makes. + expect(Object.keys(body)).toEqual(['data', 'billing']); + expect(body.billing.exhausted).toBe(false); + expect(body.billing.windows.map((w: { window: string }) => w.window)).toEqual([ + 'fiveHour', + 'sevenDay', + 'thirtyDay' + ]); + // Nothing is live yet, so nothing is being spent — and one more run + // would cost exactly one unit per second. + expect(body.billing.rateMilli).toBe(0); + expect(body.billing.rateMilliIfOneMore).toBe(1000); expect(body.data).toEqual({ id: body.data.id, boxId: s.box.id, diff --git a/packages/core/migrations/0016_burn_counters_and_rate_segments.sql b/packages/core/migrations/0016_burn_counters_and_rate_segments.sql new file mode 100644 index 00000000..c92b4af6 --- /dev/null +++ b/packages/core/migrations/0016_burn_counters_and_rate_segments.sql @@ -0,0 +1,70 @@ +-- What a team has spent, and the record it is derived from. +-- +-- Two tables because they answer different questions and are written at very +-- different rates. `burn_counter` is one row per team holding a running total +-- per window; `burn_segment` is the append-only record those totals come from. +-- The totals are disposable and can be rebuilt from the record, which is what +-- makes zeroing one a support action rather than data loss. +-- +-- **Each total is stored beside the time it began.** A total whose stamp has +-- fallen outside its window reads as zero, so a window rolls clear without +-- anything running -- no schedule to misfire, and no race between a reset and a +-- write arriving together. The same rule on the way in is a single statement: +-- add to the total if the stamp is still inside the window, otherwise start +-- again from this amount. +-- +-- Counters live apart from `team` on purpose. This row is written every time +-- anything ticks, while `team` is read on a great many paths with nothing to do +-- with billing, and keeping the hot write off the row everyone reads is worth +-- the join. +-- +-- A segment is one stretch of one run at one unchanging rate. Not a row per +-- session, because a session's rate does not survive its own lifetime -- a +-- second run changes what the account spends per second while the first is +-- still going. Not a row per event either, because burn accrues continuously +-- against an envelope that is held rather than per thing consumed. So the rate +-- is stamped at the moment it applied and never edited, and the number shown is +-- the number billed because no later pass could reach a different one. +-- +-- `rate_milli` is the per-second rate times a thousand, so fractional factors +-- never make any of this floating point. +-- +-- The partial unique index is load-bearing: two open segments for one session +-- would double-count every tick, for as long as both stayed open, silently. +-- +-- Sessions and teams are `restrict` on the segment, because deleting a run or a +-- team must not erase what it cost. ref(d-0048) + +CREATE TABLE "burn_counter" ( + "id" char(30) PRIMARY KEY NOT NULL, + "time_created" timestamp with time zone DEFAULT now() NOT NULL, + "time_updated" timestamp with time zone DEFAULT now() NOT NULL, + "time_deleted" timestamp with time zone, + "team_id" char(30) NOT NULL, + "five_hour_usage" bigint DEFAULT 0 NOT NULL, + "five_hour_at" timestamp with time zone, + "seven_day_usage" bigint DEFAULT 0 NOT NULL, + "seven_day_at" timestamp with time zone, + "thirty_day_usage" bigint DEFAULT 0 NOT NULL, + "thirty_day_at" timestamp with time zone +); +--> statement-breakpoint +CREATE TABLE "burn_segment" ( + "id" char(30) PRIMARY KEY NOT NULL, + "time_created" timestamp with time zone DEFAULT now() NOT NULL, + "time_updated" timestamp with time zone DEFAULT now() NOT NULL, + "time_deleted" timestamp with time zone, + "team_id" char(30) NOT NULL, + "session_id" char(30) NOT NULL, + "rate_milli" integer NOT NULL, + "started_at" timestamp with time zone NOT NULL, + "ended_at" timestamp with time zone +); +--> statement-breakpoint +ALTER TABLE "burn_counter" ADD CONSTRAINT "burn_counter_team_id_team_id_fk" FOREIGN KEY ("team_id") REFERENCES "public"."team"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "burn_segment" ADD CONSTRAINT "burn_segment_team_id_team_id_fk" FOREIGN KEY ("team_id") REFERENCES "public"."team"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "burn_segment" ADD CONSTRAINT "burn_segment_session_id_session_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."session"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "burn_counter_team_unique" ON "burn_counter" USING btree ("team_id");--> statement-breakpoint +CREATE INDEX "burn_segment_team_idx" ON "burn_segment" USING btree ("team_id");--> statement-breakpoint +CREATE INDEX "burn_segment_session_idx" ON "burn_segment" USING btree ("session_id");--> statement-breakpoint +CREATE UNIQUE INDEX "burn_segment_one_open_per_session" ON "burn_segment" USING btree ("session_id") WHERE "burn_segment"."ended_at" is null; \ No newline at end of file diff --git a/packages/core/migrations/meta/0016_snapshot.json b/packages/core/migrations/meta/0016_snapshot.json new file mode 100644 index 00000000..de2662c7 --- /dev/null +++ b/packages/core/migrations/meta/0016_snapshot.json @@ -0,0 +1,3234 @@ +{ + "id": "1407e59d-170e-4a9f-85e1-eb69012ad99a", + "prevId": "c6c87171-c5af-4461-a7cb-58ee99277f3a", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used": { + "name": "last_used", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "access_token_hash_unique": { + "name": "access_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_owner_idx": { + "name": "access_token_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "access_token_team_idx": { + "name": "access_token_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "access_token_owner_user_id_user_id_fk": { + "name": "access_token_owner_user_id_user_id_fk", + "tableFrom": "access_token", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "access_token_team_id_team_id_fk": { + "name": "access_token_team_id_team_id_fk", + "tableFrom": "access_token", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.authorization_code": { + "name": "authorization_code", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "authorization_code_hash_unique": { + "name": "authorization_code_hash_unique", + "columns": [ + { + "expression": "code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.device_grant": { + "name": "device_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "device_code_hash": { + "name": "device_code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_code": { + "name": "user_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "device_grant_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "poll_interval": { + "name": "poll_interval", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "last_polled_at": { + "name": "last_polled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "device_grant_device_code_unique": { + "name": "device_grant_device_code_unique", + "columns": [ + { + "expression": "device_code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "device_grant_user_code_unique": { + "name": "device_grant_user_code_unique", + "columns": [ + { + "expression": "user_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.refresh_token": { + "name": "refresh_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "time_used": { + "name": "time_used", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "refresh_token_hash_unique": { + "name": "refresh_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "refresh_token_subject_idx": { + "name": "refresh_token_subject_idx", + "columns": [ + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_key": { + "name": "auth_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "key_id": { + "name": "key_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "auth_key_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "alg": { + "name": "alg", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expired_at": { + "name": "expired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "auth_key_key_id_unique": { + "name": "auth_key_key_id_unique", + "columns": [ + { + "expression": "key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auth_key_one_live_per_kind": { + "name": "auth_key_one_live_per_kind", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"auth_key\".\"expired_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_kv": { + "name": "auth_kv", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "auth_kv_key_unique": { + "name": "auth_kv_key_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.burn_counter": { + "name": "burn_counter", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "five_hour_usage": { + "name": "five_hour_usage", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "five_hour_at": { + "name": "five_hour_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "seven_day_usage": { + "name": "seven_day_usage", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "seven_day_at": { + "name": "seven_day_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "thirty_day_usage": { + "name": "thirty_day_usage", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "thirty_day_at": { + "name": "thirty_day_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "burn_counter_team_unique": { + "name": "burn_counter_team_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "burn_counter_team_id_team_id_fk": { + "name": "burn_counter_team_id_team_id_fk", + "tableFrom": "burn_counter", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.burn_segment": { + "name": "burn_segment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "rate_milli": { + "name": "rate_milli", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "burn_segment_team_idx": { + "name": "burn_segment_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "burn_segment_session_idx": { + "name": "burn_segment_session_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "burn_segment_one_open_per_session": { + "name": "burn_segment_one_open_per_session", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"burn_segment\".\"ended_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "burn_segment_team_id_team_id_fk": { + "name": "burn_segment_team_id_team_id_fk", + "tableFrom": "burn_segment", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "burn_segment_session_id_session_id_fk": { + "name": "burn_segment_session_id_session_id_fk", + "tableFrom": "burn_segment", + "tableTo": "session", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.box": { + "name": "box", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "machine_id": { + "name": "machine_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tier": { + "name": "tier", + "type": "box_tier", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'sm'" + }, + "state": { + "name": "state", + "type": "box_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'created'" + }, + "stop_reason": { + "name": "stop_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stop_clean": { + "name": "stop_clean", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "box_user_idx": { + "name": "box_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "box_machine_idx": { + "name": "box_machine_idx", + "columns": [ + { + "expression": "machine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "box_user_id_user_id_fk": { + "name": "box_user_id_user_id_fk", + "tableFrom": "box", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "box_machine_id_machine_id_fk": { + "name": "box_machine_id_machine_id_fk", + "tableFrom": "box", + "tableTo": "machine", + "columnsFrom": ["machine_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_depot": { + "name": "game_depot", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "depot_id": { + "name": "depot_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "steam_manifest_id": { + "name": "steam_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_build_id": { + "name": "steam_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "installed_manifest_id": { + "name": "installed_manifest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_build_id": { + "name": "installed_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "depot_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_depot_unique": { + "name": "game_depot_unique", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "depot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_game_idx": { + "name": "game_depot_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_depot_updates_idx": { + "name": "game_depot_updates_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"game_depot\".\"installed_manifest_id\" is distinct from \"game_depot\".\"steam_manifest_id\"", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_depot_game_id_game_id_fk": { + "name": "game_depot_game_id_game_id_fk", + "tableFrom": "game_depot", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_download": { + "name": "game_download", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "host_id": { + "name": "host_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "game_download_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "progress_bytes": { + "name": "progress_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "total_bytes": { + "name": "total_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "time_started": { + "name": "time_started", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_completed": { + "name": "time_completed", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_download_host_game_unique": { + "name": "game_download_host_game_unique", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_game_idx": { + "name": "game_download_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_download_host_status_idx": { + "name": "game_download_host_status_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_download_host_id_machine_id_fk": { + "name": "game_download_host_id_machine_id_fk", + "tableFrom": "game_download", + "tableTo": "machine", + "columnsFrom": ["host_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "game_download_game_id_game_id_fk": { + "name": "game_download_game_id_game_id_fk", + "tableFrom": "game_download", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game": { + "name": "game", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "steam_app_id": { + "name": "steam_app_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aliases": { + "name": "aliases", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_icon": { + "name": "client_icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "short_description": { + "name": "short_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "developers": { + "name": "developers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "publishers": { + "name": "publishers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "primary_genre": { + "name": "primary_genre", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "genres": { + "name": "genres", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "oslist": { + "name": "oslist", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "size_download": { + "name": "size_download", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_on_disk": { + "name": "size_on_disk", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "controller_support": { + "name": "controller_support", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steam_deck_compat": { + "name": "steam_deck_compat", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "review_score_percent": { + "name": "review_score_percent", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "review_count": { + "name": "review_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "metacritic_score": { + "name": "metacritic_score", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "steam_change_number": { + "name": "steam_change_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "public_build_id": { + "name": "public_build_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "release_date_utc": { + "name": "release_date_utc", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_enriched": { + "name": "time_enriched", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "game_slug_unique": { + "name": "game_slug_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "game_app_id_unique": { + "name": "game_app_id_unique", + "columns": [ + { + "expression": "steam_app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "game_steam_app_id_unique": { + "name": "game_steam_app_id_unique", + "nullsNotDistinct": false, + "columns": ["steam_app_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.machine": { + "name": "machine", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "organisation_id": { + "name": "organisation_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "machine_secret_hash_unique": { + "name": "machine_secret_hash_unique", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_slug_unique": { + "name": "machine_slug_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_endpoint_id_unique": { + "name": "machine_endpoint_id_unique", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_owner_idx": { + "name": "machine_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_team_idx": { + "name": "machine_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "machine_organisation_idx": { + "name": "machine_organisation_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "machine_owner_user_id_user_id_fk": { + "name": "machine_owner_user_id_user_id_fk", + "tableFrom": "machine", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "machine_team_id_team_id_fk": { + "name": "machine_team_id_team_id_fk", + "tableFrom": "machine", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "machine_organisation_id_organisation_id_fk": { + "name": "machine_organisation_id_organisation_id_fk", + "tableFrom": "machine", + "tableTo": "organisation", + "columnsFrom": ["organisation_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "machine_one_owner": { + "name": "machine_one_owner", + "value": "(\"machine\".\"team_id\" is null) != (\"machine\".\"organisation_id\" is null)" + } + }, + "isRLSEnabled": false + }, + "public.organisation": { + "name": "organisation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain_verified": { + "name": "domain_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "organisation_slug_unique": { + "name": "organisation_slug_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "organisation_domain_unique": { + "name": "organisation_domain_unique", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pairing_code": { + "name": "pairing_code", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_fingerprint": { + "name": "new_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "is_claimed": { + "name": "is_claimed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "pairing_code_code_unique": { + "name": "pairing_code_code_unique", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pairing_code_target_user_idx": { + "name": "pairing_code_target_user_idx", + "columns": [ + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "box_id": { + "name": "box_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "linked_account_id": { + "name": "linked_account_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "session_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "ticket": { + "name": "ticket", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "time_started": { + "name": "time_started", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_stopped": { + "name": "time_stopped", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_token": { + "name": "claim_token", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_box_idx": { + "name": "session_box_idx", + "columns": [ + { + "expression": "box_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_state_idx": { + "name": "session_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_box_active_unique": { + "name": "session_box_active_unique", + "columns": [ + { + "expression": "box_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "time_stopped is null and time_deleted is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_started_idx": { + "name": "session_started_idx", + "columns": [ + { + "expression": "time_started", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_box_id_box_id_fk": { + "name": "session_box_id_box_id_fk", + "tableFrom": "session", + "tableTo": "box", + "columnsFrom": ["box_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_game_id_game_id_fk": { + "name": "session_game_id_game_id_fk", + "tableFrom": "session", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "session_linked_account_id_linked_account_id_fk": { + "name": "session_linked_account_id_linked_account_id_fk", + "tableFrom": "session", + "tableTo": "linked_account", + "columnsFrom": ["linked_account_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.steam_enrolment": { + "name": "steam_enrolment", + "schema": "", + "columns": { + "machine_id": { + "name": "machine_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "steam_id": { + "name": "steam_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "steam_enrolment_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_ok_at": { + "name": "last_ok_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "steam_enrolment_user_idx": { + "name": "steam_enrolment_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "steam_enrolment_machine_id_machine_id_fk": { + "name": "steam_enrolment_machine_id_machine_id_fk", + "tableFrom": "steam_enrolment", + "tableTo": "machine", + "columnsFrom": ["machine_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "steam_enrolment_user_id_user_id_fk": { + "name": "steam_enrolment_user_id_user_id_fk", + "tableFrom": "steam_enrolment", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "steam_enrolment_machine_id_user_id_pk": { + "name": "steam_enrolment_machine_id_user_id_pk", + "columns": ["machine_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_member": { + "name": "team_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "team_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + } + }, + "indexes": { + "team_member_team_user_unique": { + "name": "team_member_team_user_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_team_idx": { + "name": "team_member_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_member_user_idx": { + "name": "team_member_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_member_team_id_team_id_fk": { + "name": "team_member_team_id_team_id_fk", + "tableFrom": "team_member", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_member_user_id_user_id_fk": { + "name": "team_member_user_id_user_id_fk", + "tableFrom": "team_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team": { + "name": "team", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "billing_email": { + "name": "billing_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "subscription_status": { + "name": "subscription_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "team_organisation_idx": { + "name": "team_organisation_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_owner_id_user_id_fk": { + "name": "team_owner_id_user_id_fk", + "tableFrom": "team", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_organisation_id_organisation_id_fk": { + "name": "team_organisation_id_organisation_id_fk", + "tableFrom": "team", + "tableTo": "organisation", + "columnsFrom": ["organisation_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_slug_unique": { + "name": "team_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_fingerprint": { + "name": "user_fingerprint", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_fingerprint_fingerprint_unique": { + "name": "user_fingerprint_fingerprint_unique", + "columns": [ + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_fingerprint_user_idx": { + "name": "user_fingerprint_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_fingerprint_user_id_user_id_fk": { + "name": "user_fingerprint_user_id_user_id_fk", + "tableFrom": "user_fingerprint", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_library": { + "name": "user_library", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "playtime_2w": { + "name": "playtime_2w", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "playtime_forever": { + "name": "playtime_forever", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_played": { + "name": "last_played", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_library_user_game_unique": { + "name": "user_library_user_game_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_user_idx": { + "name": "user_library_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_library_game_idx": { + "name": "user_library_game_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_library_user_id_user_id_fk": { + "name": "user_library_user_id_user_id_fk", + "tableFrom": "user_library", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_library_game_id_game_id_fk": { + "name": "user_library_game_id_game_id_fk", + "tableFrom": "user_library", + "tableTo": "game", + "columnsFrom": ["game_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.linked_account": { + "name": "linked_account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "linked_account_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile": { + "name": "profile", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "linked_account_provider_unique": { + "name": "linked_account_provider_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "linked_account_user_idx": { + "name": "linked_account_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "linked_account_user_id_user_id_fk": { + "name": "linked_account_user_id_user_id_fk", + "tableFrom": "linked_account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "email is not null and time_deleted is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "verification_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_user_kind_idx": { + "name": "verification_user_kind_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "verification_user_id_user_id_fk": { + "name": "verification_user_id_user_id_fk", + "tableFrom": "verification", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist_entry": { + "name": "waitlist_entry", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'machines'" + } + }, + "indexes": { + "waitlist_entry_email_unique": { + "name": "waitlist_entry_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "waitlist_entry_source_idx": { + "name": "waitlist_entry_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.device_grant_status": { + "name": "device_grant_status", + "schema": "public", + "values": ["pending", "approved", "denied"] + }, + "public.auth_key_kind": { + "name": "auth_key_kind", + "schema": "public", + "values": ["signing", "encryption"] + }, + "public.box_state": { + "name": "box_state", + "schema": "public", + "values": ["created", "running", "stopped"] + }, + "public.box_tier": { + "name": "box_tier", + "schema": "public", + "values": ["xs", "sm", "md", "lg", "xl"] + }, + "public.depot_status": { + "name": "depot_status", + "schema": "public", + "values": ["pending", "downloading", "complete", "error", "deleted"] + }, + "public.game_download_status": { + "name": "game_download_status", + "schema": "public", + "values": ["pending", "verifying", "downloading", "ready", "failed"] + }, + "public.session_state": { + "name": "session_state", + "schema": "public", + "values": ["requested", "starting", "live", "ended", "failed"] + }, + "public.steam_enrolment_state": { + "name": "steam_enrolment_state", + "schema": "public", + "values": ["enrolled", "stale", "revoked"] + }, + "public.team_member_role": { + "name": "team_member_role", + "schema": "public", + "values": ["owner", "admin", "member"] + }, + "public.linked_account_provider": { + "name": "linked_account_provider", + "schema": "public", + "values": ["steam", "ssh", "discord"] + }, + "public.verification_kind": { + "name": "verification_kind", + "schema": "public", + "values": ["email"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/core/migrations/meta/_journal.json b/packages/core/migrations/meta/_journal.json index b1e0bd3b..7ce99d9d 100644 --- a/packages/core/migrations/meta/_journal.json +++ b/packages/core/migrations/meta/_journal.json @@ -113,6 +113,13 @@ "when": 1789762221718, "tag": "0015_organisation_owns_fleet_hardware", "breakpoints": true + }, + { + "idx": 16, + "version": "7", + "when": 1789765075037, + "tag": "0016_burn_counters_and_rate_segments", + "breakpoints": true } ] } diff --git a/packages/core/src/billing/burn.sql.ts b/packages/core/src/billing/burn.sql.ts new file mode 100644 index 00000000..6778b299 --- /dev/null +++ b/packages/core/src/billing/burn.sql.ts @@ -0,0 +1,99 @@ +import { sql } from 'drizzle-orm'; +import { bigint, index, integer, pgTable, uniqueIndex } from 'drizzle-orm/pg-core'; + +import { id, timestamps, ulid, utc } from '../db/types.js'; +import { SessionTable } from '../session/session.sql.js'; +import { TeamTable } from '../team/team.sql.js'; + +/** + * What a team has spent, per window, and when that total started. + * + * Its own table rather than columns on `team`, because this row is written + * every time anything ticks while `team` is read on a great many paths that + * have nothing to do with billing. Keeping the hot write off the row everyone + * reads is the whole reason for the join. + * + * **Each total is stored beside the time it began**, and a total whose stamp + * has fallen outside its window reads as zero. That is what replaces a reset + * job: nothing has to run for a window to roll clear, so there is no schedule + * to misfire and no race between a reset and a write arriving together. The + * same rule on the way in — add to the total if the stamp is still inside the + * window, otherwise start again from this amount — is one statement. + * + * Totals are derived and disposable. {@link BurnSegmentTable} is the record; + * these can be rebuilt from it, which is why zeroing one by hand is a support + * action rather than data loss. + */ +export const BurnCounterTable = pgTable( + 'burn_counter', + { + ...id, + ...timestamps, + teamId: ulid('team_id') + .notNull() + .references(() => TeamTable.id, { onDelete: 'cascade' }), + // Reference-seconds, so these are readable as time. `bigint` because a + // busy team on a long window is well past what an int holds. + fiveHourUsage: bigint('five_hour_usage', { mode: 'number' }).notNull().default(0), + fiveHourAt: utc('five_hour_at'), + sevenDayUsage: bigint('seven_day_usage', { mode: 'number' }).notNull().default(0), + sevenDayAt: utc('seven_day_at'), + thirtyDayUsage: bigint('thirty_day_usage', { mode: 'number' }).notNull().default(0), + thirtyDayAt: utc('thirty_day_at') + }, + (t) => [uniqueIndex('burn_counter_team_unique').on(t.teamId)] +); + +/** + * One stretch of one run at one unchanging rate. + * + * Not a row per session and not a row per event. A session's rate is fixed + * when it starts — every factor is knowable before the run, which is what lets + * a person be told the cost before they commit to it — but it does not stay + * fixed for the session's life, because starting a second run changes what the + * account spends per second while the first is still going. + * + * So the record is a segment: opened when the rate becomes true, closed when it + * stops being true, and never edited afterwards. Burn is the sum of duration + * times rate over segments, every rate stamped at the moment it applied, and + * "the number shown is the number billed" holds because there is no later + * recalculation that could reach a different answer. + */ +export const BurnSegmentTable = pgTable( + 'burn_segment', + { + ...id, + ...timestamps, + // The billing subject. Denormalized from the session's box on purpose: + // which team paid is a fact about the moment, and re-deriving it later + // through hardware that may since have moved would answer differently. + teamId: ulid('team_id') + .notNull() + .references(() => TeamTable.id, { onDelete: 'restrict' }), + // `restrict`, because deleting a run must not erase what it cost. + sessionId: ulid('session_id') + .notNull() + .references(() => SessionTable.id, { onDelete: 'restrict' }), + /** + * Units of burn per second, times a thousand. + * + * Scaled so the factors can be fractional without any of this becoming + * floating point: a rate of 1.5x is 1500. Burn is then + * `seconds * rate_milli / 1000`, in integers, and two readers of the + * same row cannot disagree in the last digit. + */ + rateMilli: integer('rate_milli').notNull(), + startedAt: utc('started_at').notNull(), + /** Null while the segment is the current one for that run. */ + endedAt: utc('ended_at') + }, + (t) => [ + index('burn_segment_team_idx').on(t.teamId), + index('burn_segment_session_idx').on(t.sessionId), + // At most one open segment per run: a second would double-count every + // tick for as long as both stayed open, and silently. + uniqueIndex('burn_segment_one_open_per_session') + .on(t.sessionId) + .where(sql`${t.endedAt} is null`) + ] +); diff --git a/packages/core/src/billing/burn.test.ts b/packages/core/src/billing/burn.test.ts new file mode 100644 index 00000000..9398dbb2 --- /dev/null +++ b/packages/core/src/billing/burn.test.ts @@ -0,0 +1,227 @@ +import { afterAll, describe, expect, test } from 'bun:test'; + +import { Box } from '../box/index.js'; +import { Fixtures } from '../db/fixtures.js'; +import { testDb } from '../db/test.js'; +import { Game } from '../game/index.js'; +import { Identifier } from '../id.js'; +import { Machine } from '../machine/index.js'; +import { Session } from '../session/index.js'; +import { Burn } from './burn.js'; +import { Window } from './window.js'; + +const sql = testDb(); + +const createdUserIds: string[] = []; +const createdGameIds: string[] = []; + +async function scene(label: string, steamAppId: number) { + const owner = await Fixtures.owner(label); + createdUserIds.push(owner.userId); + const machine = await Machine.register({ + id: Identifier.ascending('machine'), + ownerUserId: owner.userId, + teamId: owner.teamId, + label + }); + const gameId = Identifier.ascending('game'); + await Game.upsert({ id: gameId, steamAppId, slug: `${label}-${steamAppId}`, name: label }); + createdGameIds.push(gameId); + + async function newRun() { + const box = await Box.create({ + id: Identifier.ascending('box'), + userId: owner.userId, + machineId: machine.id, + label, + tier: 'sm' + }); + return Session.request({ + id: Identifier.ascending('session'), + boxId: box.id, + gameId, + linkedAccountId: owner.linkedAccountId + }); + } + + return { owner, teamId: owner.teamId, newRun }; +} + +afterAll(async () => { + if (createdUserIds.length > 0) { + await sql`delete from "burn_segment" where session_id in ( + select s.id from "session" s + join "box" b on b.id = s.box_id + where b.user_id in ${sql(createdUserIds)} + )`; + await sql`delete from "box" where user_id in ${sql(createdUserIds)}`; + await sql`delete from "user" where id in ${sql(createdUserIds)}`; + createdUserIds.length = 0; + } + if (createdGameIds.length > 0) { + await sql`delete from "game" where id in ${sql(createdGameIds)}`; + createdGameIds.length = 0; + } +}); + +const MINUTE = 60; + +function at(secondsFromStart: number, base: Date) { + return new Date(base.getTime() + secondsFromStart * 1000); +} + +describe('Segments', () => { + test('a run accrues one unit a second while it is the only one', async () => { + const s = await scene('burn-solo', 7100); + const run = await s.newRun(); + const t0 = new Date(); + + await Burn.start({ teamId: s.teamId, sessionId: run.id, at: t0 }); + const banked = await Burn.stop({ + teamId: s.teamId, + sessionId: run.id, + at: at(10 * MINUTE, t0) + }); + + expect(banked).toBe(10 * MINUTE); + const counters = await Burn.counters(s.teamId); + expect(Number(counters?.fiveHourUsage)).toBe(10 * MINUTE); + expect(Number(counters?.sevenDayUsage)).toBe(10 * MINUTE); + expect(Number(counters?.thirtyDayUsage)).toBe(10 * MINUTE); + }); + + test('two at once cost two a second between them, not four', async () => { + // The factor is on the total. Two deadline guarantees cost twice one, + // and reading it per-session would have made this four. + const s = await scene('burn-pair', 7101); + const first = await s.newRun(); + const second = await s.newRun(); + const t0 = new Date(); + + await Burn.start({ teamId: s.teamId, sessionId: first.id, at: t0 }); + await Burn.start({ teamId: s.teamId, sessionId: second.id, at: t0 }); + await Burn.stop({ teamId: s.teamId, sessionId: first.id, at: at(MINUTE, t0) }); + await Burn.stop({ teamId: s.teamId, sessionId: second.id, at: at(MINUTE, t0) }); + + const counters = await Burn.counters(s.teamId); + expect(Number(counters?.fiveHourUsage)).toBe(2 * MINUTE); + }); + + test('a run only pays the higher rate for the time the sibling was there', async () => { + // The reason segments exist. A rate that applied from the moment the + // second run started must not be backdated over the first run's solo + // time, and must not linger after the sibling has gone. + const s = await scene('burn-overlap', 7102); + const long = await s.newRun(); + const brief = await s.newRun(); + const t0 = new Date(); + + await Burn.start({ teamId: s.teamId, sessionId: long.id, at: t0 }); + // One minute alone. + await Burn.start({ teamId: s.teamId, sessionId: brief.id, at: at(MINUTE, t0) }); + // One minute together, which costs two. + await Burn.stop({ teamId: s.teamId, sessionId: brief.id, at: at(2 * MINUTE, t0) }); + // One minute alone again. + await Burn.stop({ teamId: s.teamId, sessionId: long.id, at: at(3 * MINUTE, t0) }); + + // 60 solo + 120 shared + 60 solo. + const counters = await Burn.counters(s.teamId); + expect(Number(counters?.fiveHourUsage)).toBe(4 * MINUTE); + }); + + test('stopping twice does not bill twice', async () => { + const s = await scene('burn-idempotent', 7103); + const run = await s.newRun(); + const t0 = new Date(); + + await Burn.start({ teamId: s.teamId, sessionId: run.id, at: t0 }); + await Burn.stop({ teamId: s.teamId, sessionId: run.id, at: at(MINUTE, t0) }); + const second = await Burn.stop({ + teamId: s.teamId, + sessionId: run.id, + at: at(2 * MINUTE, t0) + }); + + expect(second).toBe(0); + expect(Number((await Burn.counters(s.teamId))?.fiveHourUsage)).toBe(MINUTE); + }); + + test('resegmenting mid-run banks what has been spent without stopping it', async () => { + // A long run has to land incrementally: burn that only arrives when a + // session ends is burn that cannot refuse the next one. + const s = await scene('burn-tick', 7104); + const run = await s.newRun(); + const t0 = new Date(); + + await Burn.start({ teamId: s.teamId, sessionId: run.id, at: t0 }); + await Burn.resegment({ teamId: s.teamId, at: at(5 * MINUTE, t0) }); + + expect(Number((await Burn.counters(s.teamId))?.fiveHourUsage)).toBe(5 * MINUTE); + expect((await Burn.openSegments(s.teamId)).length).toBe(1); + + await Burn.stop({ teamId: s.teamId, sessionId: run.id, at: at(6 * MINUTE, t0) }); + expect(Number((await Burn.counters(s.teamId))?.fiveHourUsage)).toBe(6 * MINUTE); + }); +}); + +describe('The counters', () => { + test('the first tick and the thousandth are the same call', async () => { + // There is no row until something burns, and a read-then-insert would + // race two first ticks into two rows the unique index then refuses — + // turning an ordinary heartbeat into an error. + const s = await scene('burn-first', 7105); + expect(await Burn.counters(s.teamId)).toBeNull(); + + await Burn.record({ teamId: s.teamId, amount: 30 }); + await Burn.record({ teamId: s.teamId, amount: 12 }); + expect(Number((await Burn.counters(s.teamId))?.fiveHourUsage)).toBe(42); + }); + + test('a total whose window has rolled past starts again rather than adding', async () => { + // The staleness rule on the write side. Asserted through the stored + // stamp, because this is the behaviour that replaces a reset job. + const s = await scene('burn-stale', 7106); + await Burn.record({ teamId: s.teamId, amount: 100 }); + + // Age the five-hour stamp past its window, leaving the others fresh. + await sql` + update "burn_counter" + set five_hour_at = now() - make_interval(secs => ${Window.FIVE_HOURS + 60}) + where team_id = ${s.teamId} + `; + await Burn.record({ teamId: s.teamId, amount: 7 }); + + const counters = await Burn.counters(s.teamId); + // Started again from the new amount alone. + expect(Number(counters?.fiveHourUsage)).toBe(7); + // The windows that had not rolled kept accumulating. + expect(Number(counters?.sevenDayUsage)).toBe(107); + expect(Number(counters?.thirtyDayUsage)).toBe(107); + }); + + test('recording nothing writes nothing', async () => { + const s = await scene('burn-zero', 7107); + await Burn.record({ teamId: s.teamId, amount: 0 }); + expect(await Burn.counters(s.teamId)).toBeNull(); + }); +}); + +describe('Rates', () => { + test('a run costs the same whoever is running it', () => { + // The plan buys an allowance, never a discount on the meter. If the + // rate moved with the tier, an upgrade would change what past runs cost + // and the bars would stop being comparable. + expect(Burn.rateMilliFor(1)).toBe(Burn.SCALE); + expect(Burn.rateMilliFor(4)).toBe(Burn.SCALE); + }); + + test('nothing running costs nothing', () => { + expect(Burn.rateMilliFor(0)).toBe(0); + }); + + test('burn is whole seconds, never a fraction of one', () => { + expect(Burn.amountFor(90, 1500)).toBe(135); + expect(Burn.amountFor(1, 1)).toBe(0); + expect(Burn.amountFor(-5, Burn.SCALE)).toBe(0); + }); +}); diff --git a/packages/core/src/billing/burn.ts b/packages/core/src/billing/burn.ts new file mode 100644 index 00000000..ca7bc166 --- /dev/null +++ b/packages/core/src/billing/burn.ts @@ -0,0 +1,280 @@ +import { and, eq, isNull, sql, type SQL } from 'drizzle-orm'; +import type { PgColumn } from 'drizzle-orm/pg-core'; +import z from 'zod'; + +import { Database } from '../db/index.js'; +import { fn } from '../fn.js'; +import { Identifier } from '../id.js'; +import { BurnCounterTable, BurnSegmentTable } from './burn.sql.js'; +import { Window } from './window.js'; + +/** + * Recording what a team spends, as segments at a constant rate. + * + * The rate of a run is knowable before it starts — that is what lets somebody + * be told the cost before they commit — but it does not stay fixed, because a + * second run changes what the account spends per second while the first is + * still going. So burn is recorded as stretches at one rate: opened when the + * rate becomes true, closed when it stops being, never edited after. + * + * Closing a segment is what moves burn into the counters, so a long run lands + * incrementally rather than all at the end. That matters for more than + * freshness: burn that only arrives when a session stops is burn that cannot + * stop the *next* session from starting, and a bar that does not move while + * something is running is a bar nobody believes. + */ +export namespace Burn { + /** Rates are scaled by this so fractional factors stay integers. */ + export const SCALE = 1000; + + /** + * What one running session costs per second, given how many are running. + * + * On hardware the caller owns this is the whole calculation: the factors + * that price a share of a card price *our* cost basis, and on somebody + * else's card there is no card of ours being spent, so they are 1 and burn + * is duration times how much is running at once. + * + * The factor is on the **total**, not on each session — N concurrent runs + * cost N between them, and a run's own rate does not change because a + * sibling started. Two deadline guarantees cost twice one, not four times. + * Anything steeper would be a commercial decision to price concentration + * above cost, and has not been made. + */ + export function rateMilliFor(concurrency: number): number { + if (concurrency <= 0) { + return 0; + } + // Total rate is `concurrency`, shared equally, so each open segment + // carries 1x. Spelled out rather than written as the constant it + // currently equals, because this is the line that changes if the factor + // ever moves off cost. + return SCALE; + } + + /** Burn from one closed stretch, in whole reference-seconds. */ + export function amountFor(seconds: number, rateMilli: number): number { + return Math.floor((Math.max(0, seconds) * rateMilli) / SCALE); + } + + /** + * Add to a window's total, or start it again, in one statement. + * + * The `CASE` is the whole staleness rule on the write side: if the stamp is + * still inside the window the amount joins the total and the stamp is left + * where it was; if it has rolled out, the total *becomes* this amount and + * the stamp moves to now. Reading and then deciding would be two statements + * with a gap in between, and the gap is where a concurrent tick doubles or + * vanishes. + */ + function windowSet( + usageColumn: PgColumn, + atColumn: PgColumn, + windowSeconds: number, + amount: number + ): { usage: SQL; at: SQL } { + const fresh = sql`${atColumn} >= now() - make_interval(secs => ${windowSeconds})`; + return { + usage: sql`case when ${fresh} then ${usageColumn} + ${amount} else ${amount} end`, + at: sql`case when ${fresh} then ${atColumn} else now() end` + }; + } + + /** Apply one amount of burn to all three of a team's windows. */ + export const record = fn( + z.object({ teamId: z.string(), amount: z.number().int().nonnegative() }), + async (input) => { + if (input.amount === 0) { + return; + } + const five = windowSet( + BurnCounterTable.fiveHourUsage, + BurnCounterTable.fiveHourAt, + Window.FIVE_HOURS, + input.amount + ); + const seven = windowSet( + BurnCounterTable.sevenDayUsage, + BurnCounterTable.sevenDayAt, + Window.SEVEN_DAYS, + input.amount + ); + const thirty = windowSet( + BurnCounterTable.thirtyDayUsage, + BurnCounterTable.thirtyDayAt, + Window.THIRTY_DAYS, + input.amount + ); + + await Database.use(async (tx) => { + await tx + .insert(BurnCounterTable) + .values({ + id: Identifier.ascending('burnCounter'), + teamId: input.teamId, + fiveHourUsage: input.amount, + fiveHourAt: sql`now()`, + sevenDayUsage: input.amount, + sevenDayAt: sql`now()`, + thirtyDayUsage: input.amount, + thirtyDayAt: sql`now()` + }) + // The first tick for a team and the thousandth are the same + // call. A read-then-insert would race two first ticks into two + // rows, which the unique index would then refuse — turning an + // ordinary heartbeat into an error. + .onConflictDoUpdate({ + target: BurnCounterTable.teamId, + set: { + fiveHourUsage: five.usage, + fiveHourAt: five.at, + sevenDayUsage: seven.usage, + sevenDayAt: seven.at, + thirtyDayUsage: thirty.usage, + thirtyDayAt: thirty.at + } + }); + }); + } + ); + + /** A team's three totals, or nulls where nothing has been recorded. */ + export const counters = fn(z.string(), async (teamId) => { + return Database.use(async (tx) => { + return tx + .select() + .from(BurnCounterTable) + .where(eq(BurnCounterTable.teamId, teamId)) + .then((rows) => rows.at(0) ?? null); + }); + }); + + /** Every run currently accruing for a team. */ + export const openSegments = fn(z.string(), async (teamId) => { + return Database.use(async (tx) => { + return tx + .select() + .from(BurnSegmentTable) + .where(and(eq(BurnSegmentTable.teamId, teamId), isNull(BurnSegmentTable.endedAt))) + .orderBy(BurnSegmentTable.startedAt); + }); + }); + + /** + * Close every open stretch for a team and start new ones at the new rate. + * + * Called whenever the number of running sessions changes, and periodically + * while they run so the counters do not lag a long session. It is one + * operation rather than a close and an open, because between them the + * account would be spending nothing — and a tick that lands in that gap + * would record a rate nobody was ever charged. + * + * Safe to call when nothing has changed: a segment closed and reopened at + * the same rate bills identically, it is just two rows instead of one. + */ + export const resegment = fn( + z.object({ teamId: z.string(), at: z.date().optional() }), + async (input) => { + return Database.transaction(async (tx) => { + const now = input.at ?? new Date(); + const open = await tx + .select() + .from(BurnSegmentTable) + .where(and(eq(BurnSegmentTable.teamId, input.teamId), isNull(BurnSegmentTable.endedAt))); + + if (open.length === 0) { + return 0; + } + + let total = 0; + for (const segment of open) { + const seconds = Math.floor((now.getTime() - segment.startedAt.getTime()) / 1000); + total += amountFor(seconds, segment.rateMilli); + } + + await tx + .update(BurnSegmentTable) + .set({ endedAt: now }) + .where(and(eq(BurnSegmentTable.teamId, input.teamId), isNull(BurnSegmentTable.endedAt))); + + const rateMilli = rateMilliFor(open.length); + if (open.length > 0) { + await tx.insert(BurnSegmentTable).values( + open.map((segment) => ({ + id: Identifier.ascending('burnSegment'), + teamId: segment.teamId, + sessionId: segment.sessionId, + rateMilli, + startedAt: now + })) + ); + } + + await record({ teamId: input.teamId, amount: total }); + return total; + }); + } + ); + + /** + * Start accruing for a run. + * + * Existing runs are resegmented first, so their old rate is banked before + * the new count applies to anybody — otherwise the change would be + * backdated over time that was spent under the old one. + */ + export const start = fn( + z.object({ teamId: z.string(), sessionId: z.string(), at: z.date().optional() }), + async (input) => { + return Database.transaction(async (tx) => { + const now = input.at ?? new Date(); + await resegment({ teamId: input.teamId, at: now }); + + const open = await tx + .select() + .from(BurnSegmentTable) + .where(and(eq(BurnSegmentTable.teamId, input.teamId), isNull(BurnSegmentTable.endedAt))); + + await tx.insert(BurnSegmentTable).values({ + id: Identifier.ascending('burnSegment'), + teamId: input.teamId, + sessionId: input.sessionId, + rateMilli: rateMilliFor(open.length + 1), + startedAt: now + }); + }); + } + ); + + /** + * Stop accruing for a run, banking what it spent. + * + * The remaining runs are resegmented afterwards, so they stop paying for a + * sibling that has gone. Idempotent: a run with nothing open is a run that + * already stopped, and saying so twice must not bill twice. + */ + export const stop = fn( + z.object({ teamId: z.string(), sessionId: z.string(), at: z.date().optional() }), + async (input) => { + return Database.transaction(async (tx) => { + const now = input.at ?? new Date(); + const closed = await tx + .update(BurnSegmentTable) + .set({ endedAt: now }) + .where( + and(eq(BurnSegmentTable.sessionId, input.sessionId), isNull(BurnSegmentTable.endedAt)) + ) + .returning(); + + let total = 0; + for (const segment of closed) { + const seconds = Math.floor((now.getTime() - segment.startedAt.getTime()) / 1000); + total += amountFor(seconds, segment.rateMilli); + } + await record({ teamId: input.teamId, amount: total }); + await resegment({ teamId: input.teamId, at: now }); + return total; + }); + } + ); +} diff --git a/packages/core/src/billing/index.ts b/packages/core/src/billing/index.ts new file mode 100644 index 00000000..78ed5f78 --- /dev/null +++ b/packages/core/src/billing/index.ts @@ -0,0 +1,123 @@ +import z from 'zod'; + +import { Box } from '../box/index.js'; +import { ErrorCodes, VisibleError } from '../error.js'; +import { fn } from '../fn.js'; +import { Machine } from '../machine/index.js'; +import { Team } from '../team/index.js'; +import { Burn } from './burn.js'; +import { Limits } from './limits.js'; +import { Window } from './window.js'; + +/** + * Whether a run may start, and what it will cost to have started it. + * + * The one rule this must never break: **a limit refuses the next run, it never + * stops one already going.** Someone losing a session mid-game to a meter does + * not come back, and no amount of correct arithmetic makes that a good trade. + * So everything here is asked before a run begins and never again. + */ +export namespace Billing { + export const WindowState = Window.State.extend({ + window: z.enum(['fiveHour', 'sevenDay', 'thirtyDay']), + label: z.string() + }); + + export const State = z.object({ + teamId: z.string(), + plan: z.string(), + /** True when any window is spent. */ + exhausted: z.boolean(), + /** + * What the account is spending per second right now, times a thousand, + * and what it would spend with one more run. + * + * Both, because the rule is that a cost is shown *before* it is + * incurred: a person about to start a third session needs to be told + * what that does to the rate while they can still not do it. + */ + rateMilli: z.number().int(), + rateMilliIfOneMore: z.number().int(), + windows: z.array(WindowState) + }); + + export type State = z.infer; + + /** + * The team that pays for a box. + * + * A box runs on a host, and the host says who owns it. Fleet hardware + * belongs to an organisation, which is not a billing subject — nothing is + * placed there yet, and when it is, what grants it is a plan rather than + * this lookup. + */ + export const teamForBox = fn(z.string(), async (boxId) => { + const box = await Box.fromID(boxId); + if (!box) { + return null; + } + const machine = await Machine.fromID(box.machineId); + return machine?.teamId ?? null; + }); + + /** Where a team stands, in every window, with the rates to show beside it. */ + export const state = fn(z.string(), async (teamId): Promise => { + const team = await Team.fromID(teamId); + const plan = team?.plan ?? 'free'; + const allowances = Limits.forPlan(plan); + const counters = await Burn.counters(teamId); + const open = await Burn.openSegments(teamId); + + const windows = Window.ALL.map((window) => { + const usage = counters ? Number(counters[`${window.key}Usage`] ?? 0) : 0; + const at = counters ? (counters[`${window.key}At`] ?? null) : null; + return { + window: window.key, + label: window.label, + ...Window.analyze({ + allowance: allowances[window.key], + windowSeconds: window.seconds, + usage, + timeUpdated: at + }) + }; + }); + + return { + teamId, + plan, + exhausted: windows.some((w) => w.exhausted), + // The account's total, which is what moves when a run starts — not + // any one session's own rate, which does not change. + rateMilli: open.length * Burn.rateMilliFor(open.length), + rateMilliIfOneMore: (open.length + 1) * Burn.rateMilliFor(open.length + 1), + windows + }; + }); + + /** + * Refuse a new run when any window is spent. + * + * Every window is checked, not the shortest: they protect different things + * over different spans, and a set where only one could ever fire is a set + * with two decorative numbers in it. + * + * The refusal names the window and when it clears, because a limit a person + * cannot plan around is the one they resent. `QUOTA_EXCEEDED` maps to 429, + * which is the honest status — this is a rate limit the customer experiences + * as a budget, and it will succeed later without anything changing. + */ + export const assertMayStart = fn(z.string(), async (teamId) => { + const current = await state(teamId); + const spent = current.windows.find((w) => w.exhausted); + if (!spent) { + return current; + } + const minutes = Math.ceil(spent.resetInSec / 60); + throw new VisibleError( + 'rate_limit', + ErrorCodes.RateLimit.QUOTA_EXCEEDED, + `Your ${spent.label} allowance is spent. It clears in about ${minutes} minute${minutes === 1 ? '' : 's'}. Runs already going are not affected.` + ); + }); +} diff --git a/packages/core/src/id.ts b/packages/core/src/id.ts index a52b1086..7d5019f4 100644 --- a/packages/core/src/id.ts +++ b/packages/core/src/id.ts @@ -21,6 +21,8 @@ export namespace Identifier { gameDepot: 'gdp', gameDownload: 'gdl', waitlistEntry: 'wle', + burnCounter: 'bct', + burnSegment: 'bsg', deviceGrant: 'dvg', authKv: 'akv', authKey: 'aky', diff --git a/packages/core/src/session/index.ts b/packages/core/src/session/index.ts index d6a2cb19..75f36419 100644 --- a/packages/core/src/session/index.ts +++ b/packages/core/src/session/index.ts @@ -1,6 +1,7 @@ import { and, desc, eq, inArray, isNull, sql } from 'drizzle-orm'; import z from 'zod'; +import { Burn } from '../billing/burn.js'; import { BoxTable, BoxTier } from '../box/box.sql.js'; import { Box } from '../box/index.js'; import { Database } from '../db/index.js'; @@ -8,6 +9,7 @@ import { ErrorCodes, VisibleError } from '../error.js'; import { Examples } from '../examples.js'; import { fn } from '../fn.js'; import { GameTable } from '../game/game.sql.js'; +import { Machine } from '../machine/index.js'; import { SessionState, SessionTable } from './session.sql.js'; /** @@ -650,6 +652,27 @@ export namespace Session { await Box.setState({ id: moved.boxId, ...box }); } + // Burn follows the run's state, in the same transaction that + // moved it. A session that went live without its meter starting + // is free hardware; one that ended without its meter stopping + // bills forever. Both are silent, so neither may be a second + // write that might not happen. + // + // `live` is where it starts rather than `starting`, because what + // is billed is an envelope actually held — a box that never + // finished coming up held nothing. + const machine = await Machine.fromID(input.machineId); + if (machine?.teamId) { + if (current.state !== 'live' && moved.state === 'live') { + await Burn.start({ teamId: machine.teamId, sessionId: moved.id }); + } else if ( + ACCRUING.includes(current.state as (typeof ACCRUING)[number]) && + !ACCRUING.includes(moved.state as (typeof ACCRUING)[number]) + ) { + await Burn.stop({ teamId: machine.teamId, sessionId: moved.id }); + } + } + return { outcome: 'moved', session: moved }; }); } @@ -671,6 +694,16 @@ export namespace Session { */ const ADDRESSABLE = ['starting', 'live'] as const; + /** + * The states in which a run is spending. + * + * Only `live`. A run that is being brought up holds nothing yet, and the + * terminal states hold nothing any more — so this is a list of one, written + * as a list because the question it answers is "is this run costing + * anything?" and that will not always have one answer. + */ + const ACCRUING = ['live'] as const; + /** * Publish a ticket for a run, on behalf of the host it is placed on. * diff --git a/packages/core/src/session/session.test.ts b/packages/core/src/session/session.test.ts index b79117d7..38482565 100644 --- a/packages/core/src/session/session.test.ts +++ b/packages/core/src/session/session.test.ts @@ -47,7 +47,14 @@ async function scene(label: string, steamAppId: number) { afterAll(async () => { if (createdUserIds.length > 0) { // session cascades from box; box has to precede the machine, which - // cascades from the user. + // cascades from the user. `burn_segment` holds a session with + // `restrict` — deleting a run must not erase what it cost — so what the + // runs cost goes before the runs do. + await sql`delete from "burn_segment" where session_id in ( + select s.id from "session" s + join "box" b on b.id = s.box_id + where b.user_id in ${sql(createdUserIds)} + )`; await sql`delete from "box" where user_id in ${sql(createdUserIds)}`; await sql`delete from "user" where id in ${sql(createdUserIds)}`; createdUserIds.length = 0; From 4c22586d591e1b83b2f82001981fdfb2b8074f29 Mon Sep 17 00:00:00 2001 From: Wanjohi Date: Sat, 19 Sep 2026 00:32:01 +0300 Subject: [PATCH 6/9] feat(core): price a run by the size it holds, on hardware we pay for MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rate was a constant. That was correct for everything the system can currently run and wrong the moment it can run anything else, because a tier buys a share of a card — so a bigger one on our own hardware is more of something we bought being spent, and a flat rate there sells a whole card for the price of a quarter of one. So a run's rate now comes from what the run is: its size tier, and whose hardware it sits on. On the caller's own hardware the tier changes nothing. There is no share of a card of ours in play, so a run costs one unit a second whatever size it asked for. Charging somebody more for taking more of a GPU they bought is a tax on their own hardware, and not doing that is most of what this model is for. This exposed a bug in what went before. Resegmenting recomputed one shared rate and wrote it to every open stretch, which was harmless while all runs cost the same and would have quietly repriced an expensive run as whatever the last one to start was. Each stretch now keeps its own rate, which is also the more honest shape: a run's rate is a property of that run, and nothing about it changed because a sibling appeared or the clock ticked. The account's total is now the sum of what its runs cost rather than a count times one rate — an expensive run and a cheap one alongside it are not two of anything. Concurrency still lands exactly where it did, as there being more to add, and no run gets dearer because another started. There is no hardware factor yet and its absence is deliberate: nothing records which card a host has, so a table keyed on a model would be keyed on nothing. A faster card should cost more, and that starts with a column. The reference tier is pinned at exactly one unit a second, checked rather than assumed. The unit is a second of a reference session, so moving it would silently redefine every allowance — the same stored number would mean a different number of hours. --- apps/api/app/routes/session.ts | 10 +- packages/core/src/billing/burn.test.ts | 69 ++++++++++--- packages/core/src/billing/burn.ts | 92 ++++++++++------- packages/core/src/billing/index.ts | 120 +++++++++++++++-------- packages/core/src/billing/limits.test.ts | 22 ++++- packages/core/src/billing/limits.ts | 67 ++++++++++++- packages/core/src/session/index.ts | 12 ++- 7 files changed, 290 insertions(+), 102 deletions(-) diff --git a/apps/api/app/routes/session.ts b/apps/api/app/routes/session.ts index 134c6825..e5c5696f 100644 --- a/apps/api/app/routes/session.ts +++ b/apps/api/app/routes/session.ts @@ -236,8 +236,14 @@ export namespace SessionApi { // being discarded, because the caller has to be told what it will // cost and what remains — and asking a second time would let the // number shown and the number billed disagree. - const team = await Billing.teamForBox(box.id); - const billing = team ? await Billing.assertMayStart(team) : null; + const payer = await Billing.teamForBox(box.id); + const billing = payer + ? await Billing.assertMayStart({ + teamId: payer.teamId, + nextTier: payer.tier, + nextHostClass: payer.hostClass + }) + : null; const session = await Session.request({ id: Identifier.ascending('session'), diff --git a/packages/core/src/billing/burn.test.ts b/packages/core/src/billing/burn.test.ts index 9398dbb2..5dfb01a7 100644 --- a/packages/core/src/billing/burn.test.ts +++ b/packages/core/src/billing/burn.test.ts @@ -76,7 +76,7 @@ describe('Segments', () => { const run = await s.newRun(); const t0 = new Date(); - await Burn.start({ teamId: s.teamId, sessionId: run.id, at: t0 }); + await Burn.start({ teamId: s.teamId, sessionId: run.id, tier: 'sm', hostClass: 'byo', at: t0 }); const banked = await Burn.stop({ teamId: s.teamId, sessionId: run.id, @@ -98,8 +98,20 @@ describe('Segments', () => { const second = await s.newRun(); const t0 = new Date(); - await Burn.start({ teamId: s.teamId, sessionId: first.id, at: t0 }); - await Burn.start({ teamId: s.teamId, sessionId: second.id, at: t0 }); + await Burn.start({ + teamId: s.teamId, + sessionId: first.id, + tier: 'sm', + hostClass: 'byo', + at: t0 + }); + await Burn.start({ + teamId: s.teamId, + sessionId: second.id, + tier: 'sm', + hostClass: 'byo', + at: t0 + }); await Burn.stop({ teamId: s.teamId, sessionId: first.id, at: at(MINUTE, t0) }); await Burn.stop({ teamId: s.teamId, sessionId: second.id, at: at(MINUTE, t0) }); @@ -116,9 +128,21 @@ describe('Segments', () => { const brief = await s.newRun(); const t0 = new Date(); - await Burn.start({ teamId: s.teamId, sessionId: long.id, at: t0 }); + await Burn.start({ + teamId: s.teamId, + sessionId: long.id, + tier: 'sm', + hostClass: 'byo', + at: t0 + }); // One minute alone. - await Burn.start({ teamId: s.teamId, sessionId: brief.id, at: at(MINUTE, t0) }); + await Burn.start({ + teamId: s.teamId, + sessionId: brief.id, + tier: 'sm', + hostClass: 'byo', + at: at(MINUTE, t0) + }); // One minute together, which costs two. await Burn.stop({ teamId: s.teamId, sessionId: brief.id, at: at(2 * MINUTE, t0) }); // One minute alone again. @@ -134,7 +158,7 @@ describe('Segments', () => { const run = await s.newRun(); const t0 = new Date(); - await Burn.start({ teamId: s.teamId, sessionId: run.id, at: t0 }); + await Burn.start({ teamId: s.teamId, sessionId: run.id, tier: 'sm', hostClass: 'byo', at: t0 }); await Burn.stop({ teamId: s.teamId, sessionId: run.id, at: at(MINUTE, t0) }); const second = await Burn.stop({ teamId: s.teamId, @@ -153,7 +177,7 @@ describe('Segments', () => { const run = await s.newRun(); const t0 = new Date(); - await Burn.start({ teamId: s.teamId, sessionId: run.id, at: t0 }); + await Burn.start({ teamId: s.teamId, sessionId: run.id, tier: 'sm', hostClass: 'byo', at: t0 }); await Burn.resegment({ teamId: s.teamId, at: at(5 * MINUTE, t0) }); expect(Number((await Burn.counters(s.teamId))?.fiveHourUsage)).toBe(5 * MINUTE); @@ -207,16 +231,31 @@ describe('The counters', () => { }); describe('Rates', () => { - test('a run costs the same whoever is running it', () => { - // The plan buys an allowance, never a discount on the meter. If the - // rate moved with the tier, an upgrade would change what past runs cost - // and the bars would stop being comparable. - expect(Burn.rateMilliFor(1)).toBe(Burn.SCALE); - expect(Burn.rateMilliFor(4)).toBe(Burn.SCALE); + test('on our hardware a bigger tier costs more, superlinearly', () => { + const rate = (tier: Burn.Tier) => Burn.baseRateMilli({ tier, hostClass: 'fleet' }); + expect(rate('sm')).toBe(Burn.SCALE); + expect(rate('xl')).toBeGreaterThan(rate('lg')); + // A tier buys a share of a card, and the ladder has to pinch harder + // than the share grows or running a small title at the top of it is + // cheaper than it costs us. + expect(rate('xl') / rate('sm')).toBeGreaterThan(4); }); - test('nothing running costs nothing', () => { - expect(Burn.rateMilliFor(0)).toBe(0); + test('on the caller\u2019s own hardware the tier changes nothing', () => { + // There is no share of a card of ours being spent, so charging more for + // a bigger one would be a tax on hardware they bought. + for (const tier of ['xs', 'sm', 'md', 'lg', 'xl'] as const) { + expect(Burn.baseRateMilli({ tier, hostClass: 'byo' })).toBe(Burn.SCALE); + } + }); + + test('a run costs the same whatever plan is paying for it', () => { + // The plan buys an allowance, never a discount on the meter. If the + // rate moved with the tier somebody is on, an upgrade would change what + // past runs cost and the bars would stop being comparable. + expect(Burn.baseRateMilli({ tier: 'md', hostClass: 'fleet' })).toBe( + Burn.baseRateMilli({ tier: 'md', hostClass: 'fleet' }) + ); }); test('burn is whole seconds, never a fraction of one', () => { diff --git a/packages/core/src/billing/burn.ts b/packages/core/src/billing/burn.ts index ca7bc166..ce60da97 100644 --- a/packages/core/src/billing/burn.ts +++ b/packages/core/src/billing/burn.ts @@ -6,6 +6,7 @@ import { Database } from '../db/index.js'; import { fn } from '../fn.js'; import { Identifier } from '../id.js'; import { BurnCounterTable, BurnSegmentTable } from './burn.sql.js'; +import { Limits } from './limits.js'; import { Window } from './window.js'; /** @@ -27,30 +28,39 @@ export namespace Burn { /** Rates are scaled by this so fractional factors stay integers. */ export const SCALE = 1000; + /** Whose hardware a run is on, which is what decides the cost basis. */ + export const HostClass = z.enum(['byo', 'fleet']); + export type HostClass = z.infer; + + export const Tier = z.enum(['xs', 'sm', 'md', 'lg', 'xl']); + export type Tier = z.infer; + /** - * What one running session costs per second, given how many are running. + * What one run costs per second, before anything else is running. * - * On hardware the caller owns this is the whole calculation: the factors - * that price a share of a card price *our* cost basis, and on somebody - * else's card there is no card of ours being spent, so they are 1 and burn - * is duration times how much is running at once. + * **On our own hardware the tier decides it**, because a tier buys a share + * of a card we paid for and a bigger share is more of something real being + * spent. On the caller's own hardware it does not: there is no share of a + * card of ours in play, so a run costs one unit a second whatever size it + * asked for. Charging somebody more for taking more of their own GPU is a + * tax on hardware they bought, and avoiding that is most of the point. * - * The factor is on the **total**, not on each session — N concurrent runs - * cost N between them, and a run's own rate does not change because a - * sibling started. Two deadline guarantees cost twice one, not four times. - * Anything steeper would be a commercial decision to price concentration - * above cost, and has not been made. + * Note what is *not* here: the number of other runs. Concurrency is on the + * account's total, not on any one run — two deadline guarantees cost twice + * one, so two runs cost the sum of their two rates and neither of them gets + * more expensive because the other started. That is why this rate is fixed + * for a run's whole life, and why a sibling starting does not have to + * rewrite anything. */ - export function rateMilliFor(concurrency: number): number { - if (concurrency <= 0) { - return 0; + export const baseRateMilli = fn( + z.object({ tier: Tier, hostClass: HostClass }), + (input): number => { + if (input.hostClass === 'byo') { + return SCALE; + } + return Limits.get().factors.size[input.tier]; } - // Total rate is `concurrency`, shared equally, so each open segment - // carries 1x. Spelled out rather than written as the constant it - // currently equals, because this is the line that changes if the factor - // ever moves off cost. - return SCALE; - } + ); /** Burn from one closed stretch, in whole reference-seconds. */ export function amountFor(seconds: number, rateMilli: number): number { @@ -197,18 +207,20 @@ export namespace Burn { .set({ endedAt: now }) .where(and(eq(BurnSegmentTable.teamId, input.teamId), isNull(BurnSegmentTable.endedAt))); - const rateMilli = rateMilliFor(open.length); - if (open.length > 0) { - await tx.insert(BurnSegmentTable).values( - open.map((segment) => ({ - id: Identifier.ascending('burnSegment'), - teamId: segment.teamId, - sessionId: segment.sessionId, - rateMilli, - startedAt: now - })) - ); - } + // Each run keeps its own rate. It is a property of what that run + // is — its tier, and whose hardware it sits on — and none of that + // changed because the clock ticked or a sibling appeared. + // Recomputing a single shared rate here would quietly reprice an + // `xl` run as whatever the last one to start was. + await tx.insert(BurnSegmentTable).values( + open.map((segment) => ({ + id: Identifier.ascending('burnSegment'), + teamId: segment.teamId, + sessionId: segment.sessionId, + rateMilli: segment.rateMilli, + startedAt: now + })) + ); await record({ teamId: input.teamId, amount: total }); return total; @@ -224,22 +236,26 @@ export namespace Burn { * backdated over time that was spent under the old one. */ export const start = fn( - z.object({ teamId: z.string(), sessionId: z.string(), at: z.date().optional() }), + z.object({ + teamId: z.string(), + sessionId: z.string(), + tier: Tier, + hostClass: HostClass, + at: z.date().optional() + }), async (input) => { return Database.transaction(async (tx) => { const now = input.at ?? new Date(); + // Bank what the runs already going have spent, so the moment this + // one appears is a clean boundary in the record rather than a + // point inside somebody else's open stretch. await resegment({ teamId: input.teamId, at: now }); - const open = await tx - .select() - .from(BurnSegmentTable) - .where(and(eq(BurnSegmentTable.teamId, input.teamId), isNull(BurnSegmentTable.endedAt))); - await tx.insert(BurnSegmentTable).values({ id: Identifier.ascending('burnSegment'), teamId: input.teamId, sessionId: input.sessionId, - rateMilli: rateMilliFor(open.length + 1), + rateMilli: baseRateMilli({ tier: input.tier, hostClass: input.hostClass }), startedAt: now }); }); diff --git a/packages/core/src/billing/index.ts b/packages/core/src/billing/index.ts index 78ed5f78..ea4fda70 100644 --- a/packages/core/src/billing/index.ts +++ b/packages/core/src/billing/index.ts @@ -57,43 +57,70 @@ export namespace Billing { return null; } const machine = await Machine.fromID(box.machineId); - return machine?.teamId ?? null; + if (!machine?.teamId) { + return null; + } + return { + teamId: machine.teamId, + tier: box.tier as Burn.Tier, + // Whose hardware decides the cost basis, and the machine is the only + // thing that knows. A host an organisation owns is ours to pay for; + // anything else is the caller's own card. + hostClass: (machine.organisationId ? 'fleet' : 'byo') as Burn.HostClass + }; }); /** Where a team stands, in every window, with the rates to show beside it. */ - export const state = fn(z.string(), async (teamId): Promise => { - const team = await Team.fromID(teamId); - const plan = team?.plan ?? 'free'; - const allowances = Limits.forPlan(plan); - const counters = await Burn.counters(teamId); - const open = await Burn.openSegments(teamId); + export const state = fn( + z.object({ + teamId: z.string(), + /** The run being considered, so "one more" can be costed honestly. */ + nextTier: Burn.Tier.optional(), + nextHostClass: Burn.HostClass.optional() + }), + async (input): Promise => { + const teamId = input.teamId; + const team = await Team.fromID(teamId); + const plan = team?.plan ?? 'free'; + const allowances = Limits.forPlan(plan); + const counters = await Burn.counters(teamId); + const open = await Burn.openSegments(teamId); + + const windows = Window.ALL.map((window) => { + const usage = counters ? Number(counters[`${window.key}Usage`] ?? 0) : 0; + const at = counters ? (counters[`${window.key}At`] ?? null) : null; + return { + window: window.key, + label: window.label, + ...Window.analyze({ + allowance: allowances[window.key], + windowSeconds: window.seconds, + usage, + timeUpdated: at + }) + }; + }); + + // The account's total is the sum of what each run costs, not a count + // times one rate: an `xl` run and an `xs` one alongside it are not + // two of anything. Concurrency shows up here, as there being more to + // add, rather than as a multiplier on any of them. + const rateMilli = open.reduce((total, segment) => total + segment.rateMilli, 0); + const next = Burn.baseRateMilli({ + tier: input.nextTier ?? 'sm', + hostClass: input.nextHostClass ?? 'byo' + }); - const windows = Window.ALL.map((window) => { - const usage = counters ? Number(counters[`${window.key}Usage`] ?? 0) : 0; - const at = counters ? (counters[`${window.key}At`] ?? null) : null; return { - window: window.key, - label: window.label, - ...Window.analyze({ - allowance: allowances[window.key], - windowSeconds: window.seconds, - usage, - timeUpdated: at - }) + teamId, + plan, + exhausted: windows.some((w) => w.exhausted), + rateMilli, + rateMilliIfOneMore: rateMilli + next, + windows }; - }); - - return { - teamId, - plan, - exhausted: windows.some((w) => w.exhausted), - // The account's total, which is what moves when a run starts — not - // any one session's own rate, which does not change. - rateMilli: open.length * Burn.rateMilliFor(open.length), - rateMilliIfOneMore: (open.length + 1) * Burn.rateMilliFor(open.length + 1), - windows - }; - }); + } + ); /** * Refuse a new run when any window is spent. @@ -107,17 +134,24 @@ export namespace Billing { * which is the honest status — this is a rate limit the customer experiences * as a budget, and it will succeed later without anything changing. */ - export const assertMayStart = fn(z.string(), async (teamId) => { - const current = await state(teamId); - const spent = current.windows.find((w) => w.exhausted); - if (!spent) { - return current; + export const assertMayStart = fn( + z.object({ + teamId: z.string(), + nextTier: Burn.Tier.optional(), + nextHostClass: Burn.HostClass.optional() + }), + async (input) => { + const current = await state(input); + const spent = current.windows.find((w) => w.exhausted); + if (!spent) { + return current; + } + const minutes = Math.ceil(spent.resetInSec / 60); + throw new VisibleError( + 'rate_limit', + ErrorCodes.RateLimit.QUOTA_EXCEEDED, + `Your ${spent.label} allowance is spent. It clears in about ${minutes} minute${minutes === 1 ? '' : 's'}. Runs already going are not affected.` + ); } - const minutes = Math.ceil(spent.resetInSec / 60); - throw new VisibleError( - 'rate_limit', - ErrorCodes.RateLimit.QUOTA_EXCEEDED, - `Your ${spent.label} allowance is spent. It clears in about ${minutes} minute${minutes === 1 ? '' : 's'}. Runs already going are not affected.` - ); - }); + ); } diff --git a/packages/core/src/billing/limits.test.ts b/packages/core/src/billing/limits.test.ts index 8d981804..841cacb3 100644 --- a/packages/core/src/billing/limits.test.ts +++ b/packages/core/src/billing/limits.test.ts @@ -67,6 +67,16 @@ describe('The nesting rule: every window has to bind', () => { expect(() => Limits.check(hours(10, 335, 1000), 'free')).not.toThrow(); }); + test('the reference tier has to cost exactly one unit a second', () => { + // The unit *is* a second of a reference session, so moving `sm` off 1 + // would silently redefine every allowance — the same stored number + // would be a different number of hours. + expect(() => + Limits.checkFactors({ size: { xs: 500, sm: 900, md: 2200, lg: 5000, xl: 12000 } }) + ).toThrow(/reference tier/); + expect(() => Limits.checkFactors(Limits.PLACEHOLDER.factors)).not.toThrow(); + }); + test('the placeholder set satisfies both rules', () => { // It is not a pricing decision, but it has to be a coherent one, or // nothing downstream can be tested against it. @@ -83,7 +93,11 @@ describe('Configuration', () => { test('the environment overrides it, and is validated on the way in', () => { Env.init({ - BURN_LIMITS: JSON.stringify({ free: hours(12, 350, 1200), paid: hours(40, 1200, 4000) }) + BURN_LIMITS: JSON.stringify({ + free: hours(12, 350, 1200), + paid: hours(40, 1200, 4000), + factors: Limits.PLACEHOLDER.factors + }) }); Limits.reset(); expect(Limits.get().free.fiveHour).toBe(12 * HOUR); @@ -94,7 +108,11 @@ describe('Configuration', () => { // closest to the burn data, and a set that quietly stops binding is not // visible from the numbers. Env.init({ - BURN_LIMITS: JSON.stringify({ free: hours(10, 400, 1000), paid: hours(30, 900, 3000) }) + BURN_LIMITS: JSON.stringify({ + free: hours(10, 400, 1000), + paid: hours(30, 900, 3000), + factors: Limits.PLACEHOLDER.factors + }) }); Limits.reset(); expect(() => Limits.get()).toThrow(/never be reached/); diff --git a/packages/core/src/billing/limits.ts b/packages/core/src/billing/limits.ts index beb7c8cf..ce6f1de5 100644 --- a/packages/core/src/billing/limits.ts +++ b/packages/core/src/billing/limits.ts @@ -33,9 +33,49 @@ export namespace Limits { export type Allowances = z.infer; + /** + * What a size tier costs per second, times a thousand, on our own hardware. + * + * A tier buys a share of a card, so a bigger one spends more of something + * we paid for. These must be **superlinear in that share**: a small title + * asked to run at the top of the ladder has to cost what a whole card + * costs, or the ladder is gamed and the density that makes any of this + * priceable is theoretical. + * + * They do not apply on a caller's own hardware. See {@link Factors}. + */ + const SizeFactors = z.object({ + xs: z.number().int().positive(), + sm: z.number().int().positive(), + md: z.number().int().positive(), + lg: z.number().int().positive(), + xl: z.number().int().positive() + }); + + /** + * How much a running session costs per second, before concurrency. + * + * **Only on hardware we own.** The size factor prices a share of a card we + * bought; on somebody else's card there is no such share being spent, so a + * session there costs one unit a second whatever tier it asked for. Charging + * more for taking more of their own GPU would be a tax on hardware they paid + * for, which is the complaint this whole model is shaped to avoid. + * + * There is no hardware factor here yet, and its absence is deliberate rather + * than an oversight: nothing records which card a host has, so a table keyed + * on a model would be keyed on nothing. A faster card should cost more, and + * that starts with a column, not a number. + */ + export const Factors = z.object({ + size: SizeFactors + }); + + export type Factors = z.infer; + export const Config = z.object({ free: Allowances, - paid: Allowances + paid: Allowances, + factors: Factors }); export type Config = z.infer; @@ -59,6 +99,13 @@ export namespace Limits { fiveHour: 30 * 60 * 60, sevenDay: 900 * 60 * 60, thirtyDay: 3000 * 60 * 60 + }, + // Superlinear, and no more principled than that. `sm` is the reference + // and is 1 by definition; the rest roughly double per step so the shape + // is visible in tests. Real values come from what a card-hour costs us + // divided by the share a tier holds. + factors: { + size: { xs: 500, sm: 1000, md: 2200, lg: 5000, xl: 12000 } } }; @@ -108,10 +155,28 @@ export namespace Limits { } } + /** + * The reference tier costs exactly one unit a second, by definition. + * + * The unit *is* a second of a reference session, so a size factor that made + * `sm` anything other than 1 would silently redefine what every allowance + * means — the same stored number would be a different number of hours. + */ + export function checkFactors(factors: Factors): void { + if (factors.size.sm !== 1000) { + throw new VisibleError( + 'internal', + ErrorCodes.Server.INTERNAL_ERROR, + `the reference tier must cost exactly one unit a second (1000), not ${factors.size.sm} \u2014 it is what every allowance is denominated in` + ); + } + } + export function validate(config: unknown): Config { const parsed = Config.parse(config); check(parsed.free, 'free'); check(parsed.paid, 'paid'); + checkFactors(parsed.factors); return parsed; } diff --git a/packages/core/src/session/index.ts b/packages/core/src/session/index.ts index 75f36419..a65a3135 100644 --- a/packages/core/src/session/index.ts +++ b/packages/core/src/session/index.ts @@ -664,7 +664,17 @@ export namespace Session { const machine = await Machine.fromID(input.machineId); if (machine?.teamId) { if (current.state !== 'live' && moved.state === 'live') { - await Burn.start({ teamId: machine.teamId, sessionId: moved.id }); + // The rate is fixed here, from what this run actually is: + // the size it holds, and whose card it holds it on. Both + // are settled before the run starts, which is what lets a + // person be told the cost before committing to it. + const box = await Box.fromID(moved.boxId); + await Burn.start({ + teamId: machine.teamId, + sessionId: moved.id, + tier: (box?.tier ?? 'sm') as Burn.Tier, + hostClass: machine.organisationId ? 'fleet' : 'byo' + }); } else if ( ACCRUING.includes(current.state as (typeof ACCRUING)[number]) && !ACCRUING.includes(moved.state as (typeof ACCRUING)[number]) From 757ff79233648d42be302eee6e4bfc84ed4b7eec Mon Sep 17 00:00:00 2001 From: Wanjohi Date: Sat, 19 Sep 2026 00:57:57 +0300 Subject: [PATCH 7/9] feat(core,api): take payment, and let the provider decide who is paid up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Checkout, the customer portal, and the webhook that moves a team's plan. Nothing money-shaped is stored. No price, no currency, no card detail — a subscription's existence and its state are the whole of what crosses back, because they are the only two facts the product needs and anything more would be a second copy of a record somebody else is authoritative for. Currency is deliberately not ours to hold. A product carries a price per currency on their side and the customer's location picks one at checkout, so there is no figure in this API that could drift from the one somebody is charged. The team id travels as the customer's external id, which keeps the mapping on their side rather than putting a foreign primary key in our schema. Access follows their state, and the interesting cases are where that is not the same as "paying right now". Cancelling keeps the plan: they paid to the end of the period and turning them off when they click it takes something they bought. A failed card keeps it too, because a retry that ends in payment should not have cost them access in the middle. Only a revoked subscription takes it away, which is the one moment nothing is left that was paid for. An event we do not recognise changes nothing at all — new types are added by people who do not know what we do with them, and a default that moved a plan would eventually cancel an account nobody cancelled. The webhook is the only route here no session protects, because its caller has no account and never will. A signature over the raw body stands in for one, and it is checked before the body is looked at — a body that has been parsed and re-serialized is not the body that was signed. With no secret configured it refuses everything rather than accepting anything, since otherwise knowing the URL would be enough to set somebody's plan. Note also what is absent: no route sets a plan, so there is no endpoint for granting yourself a subscription. The product is written down as a definition with a script rather than clicked into a dashboard, because the two environments are separate servers and nothing made in one can be moved to the other. Promoting it is running the same script with the other token, which is the only version of that which cannot drift. It writes nothing without --apply and refuses to add a second product with a name already taken. --- .env.example | 10 ++ apps/api/app/index.ts | 2 + apps/api/app/routes/billing.ts | 197 +++++++++++++++++++++ bun.lock | 14 +- package.json | 3 +- packages/core/package.json | 1 + packages/core/scripts/polar-product.ts | 104 +++++++++++ packages/core/src/billing/polar.test.ts | 112 ++++++++++++ packages/core/src/billing/polar.ts | 218 ++++++++++++++++++++++++ packages/core/src/env.ts | 13 ++ packages/core/src/team/index.ts | 31 ++++ 11 files changed, 702 insertions(+), 3 deletions(-) create mode 100644 apps/api/app/routes/billing.ts create mode 100644 packages/core/scripts/polar-product.ts create mode 100644 packages/core/src/billing/polar.test.ts create mode 100644 packages/core/src/billing/polar.ts diff --git a/.env.example b/.env.example index a34e64db..58a50384 100644 --- a/.env.example +++ b/.env.example @@ -35,3 +35,13 @@ EMAIL_SEND_URL= EMAIL_API_KEY= EMAIL_FROM= EMAIL_DEV_LOG=true + +# Billing. The provider's sandbox and production are separate servers with +# separate data, so a token from one is refused by the other and a product id +# from one means nothing to it. `POLAR_SERVER` says which you are talking to. +POLAR_SERVER=sandbox +POLAR_ACCESS_TOKEN= +POLAR_PRODUCT_ID= +# Signs every webhook delivery. Without it the webhook route refuses everything, +# on purpose: nothing else stands in front of it. +POLAR_WEBHOOK_SECRET= diff --git a/apps/api/app/index.ts b/apps/api/app/index.ts index dd79d552..14f8710c 100644 --- a/apps/api/app/index.ts +++ b/apps/api/app/index.ts @@ -10,6 +10,7 @@ import { type ContentfulStatusCode } from 'hono/utils/http-status'; import { auth } from './middleware/auth.js'; import { AccessTokenApi } from './routes/access-token.js'; +import { BillingApi } from './routes/billing.js'; import { EnrolmentApi } from './routes/enrolment.js'; import { GameApi } from './routes/game.js'; import { IndexApi } from './routes/index.js'; @@ -43,6 +44,7 @@ const routes = app .route('/steam', SteamApi.route) .route('/library', LibraryApi.route) .route('/games', GameApi.route) + .route('/billing', BillingApi.route) .route('/organisation', OrganisationApi.route) .route('/machine', MachineApi.route) .route('/machine', SessionApi.machineRoute) diff --git a/apps/api/app/routes/billing.ts b/apps/api/app/routes/billing.ts new file mode 100644 index 00000000..8a003848 --- /dev/null +++ b/apps/api/app/routes/billing.ts @@ -0,0 +1,197 @@ +import { Actor } from '@nestri/core/actor'; +import { Billing } from '@nestri/core/billing/index'; +import { Polar } from '@nestri/core/billing/polar'; +import { ErrorCodes, VisibleError } from '@nestri/core/error'; +import { Team } from '@nestri/core/team/index'; +import { User } from '@nestri/core/user/index'; +import { Hono } from 'hono'; +import { describeRoute } from 'hono-openapi'; +import { z } from 'zod'; + +import { ErrorResponses, notPublic, Result, validator } from '../utils'; + +/** + * Paying, and seeing what has been spent. + * + * The webhook at the bottom is **the only route in this app that no session + * protects**, and that is not an oversight: it is called by somebody else's + * server, which has no account here and never will. What stands in for a + * session is a signature over the raw body, and it is checked before the body + * is looked at. + * + * Everything else is ordinary and team-scoped. Note what is missing: there is + * no route that sets a plan. A plan is what the provider says it is, so the + * only thing that writes one is a delivery that proved it came from them — + * anything else would be an endpoint for granting yourself a subscription. + */ +export namespace BillingApi { + /** The team the caller is acting for, which is who the bill belongs to. */ + async function payingTeam(): Promise { + const actor = Actor.use(); + if (actor.type === 'member') { + return actor.properties.teamID; + } + const team = await Team.personalFor(Actor.userID); + if (!team) { + throw new VisibleError( + 'not_found', + ErrorCodes.NotFound.RESOURCE_NOT_FOUND, + 'You have no team to bill' + ); + } + return team.id; + } + + function mustBeConfigured() { + if (!Polar.configured()) { + throw new VisibleError( + 'internal', + ErrorCodes.Server.DEPENDENCY_FAILURE, + 'Billing is not configured on this deployment' + ); + } + } + + export const route = new Hono() + .post( + '/webhook', + describeRoute({ + tags: ['Billing'], + summary: 'Receive a subscription event', + description: + 'Called by the payment provider, not by you. Authenticated by a signature over the raw body rather than by a session, because the caller has no account here. Deliveries for a customer we did not create are acknowledged and ignored — a retry loop against a delivery nothing can act on helps nobody.', + responses: { + 200: { description: 'Delivery accepted' }, + 401: ErrorResponses[401] + } + }), + async (c) => { + mustBeConfigured(); + + // The raw body, before any parsing. A body that has been through + // `JSON.parse` and re-serialized is not the body that was signed, + // and the signature is the whole of this route's authentication. + const body = await c.req.text(); + const headers: Record = {}; + c.req.raw.headers.forEach((value, key) => { + headers[key] = value; + }); + + const delivery = Polar.receive({ body, headers }); + + // Acknowledged rather than refused. A delivery we cannot act on is + // still a delivery that arrived intact, and answering an error + // would have them retry it for days against a thing that will + // never become actionable. + if (!delivery.teamId || !delivery.standing) { + return c.json({ data: { applied: false, type: delivery.type } }); + } + + const updated = await Team.setPlan({ + id: delivery.teamId, + plan: delivery.standing.plan, + subscriptionStatus: delivery.standing.status + }); + + return c.json({ + data: { applied: Boolean(updated), type: delivery.type } + }); + } + ) + .get( + '/', + notPublic, + describeRoute({ + tags: ['Billing'], + summary: 'What you are on, and what you have spent', + description: + 'The plan, and where each of the three windows stands. This is what a meter is drawn from — the percentages here are the same numbers that decide whether a run may start, so a full bar and a refusal cannot disagree.', + responses: { + 200: { + content: { 'application/json': { schema: Result(Billing.State) } }, + description: 'Your standing' + }, + 401: ErrorResponses[401], + 404: ErrorResponses[404] + } + }), + async (c) => c.json({ data: await Billing.state({ teamId: await payingTeam() }) }) + ) + .post( + '/checkout', + notPublic, + describeRoute({ + tags: ['Billing'], + summary: 'Start paying', + description: + 'Returns a URL to send the person to. The price they are shown is set on the provider’s side per currency and chosen from where they are, so nothing here names an amount — there is no figure in this API that could drift from the one they are charged.', + responses: { + 200: { + content: { + 'application/json': { + schema: Result( + z.object({ + id: z.string().meta({ description: 'The checkout' }), + url: z.url().meta({ description: 'Where to send the person' }) + }) + ) + } + }, + description: 'A checkout to send them to' + }, + 401: ErrorResponses[401], + 404: ErrorResponses[404], + 500: ErrorResponses[500] + } + }), + validator( + 'json', + z + .object({ + successUrl: z.url().optional().meta({ + description: 'Where to return to once they have paid' + }) + }) + .strict() + ), + async (c) => { + mustBeConfigured(); + const teamId = await payingTeam(); + const user = await User.fromID(Actor.userID); + return c.json({ + data: await Polar.checkout({ + teamId, + email: user?.email ?? undefined, + successUrl: c.req.valid('json').successUrl + }) + }); + } + ) + .get( + '/portal', + notPublic, + describeRoute({ + tags: ['Billing'], + summary: 'Manage what you are paying', + description: + 'A URL to the provider’s own portal, where a card is changed, an invoice is read and a subscription is cancelled. None of that is rebuilt here, because rebuilding it would mean holding payment details in order to show them.', + responses: { + 200: { + content: { + 'application/json': { + schema: Result(z.object({ url: z.url() })) + } + }, + description: 'Where to manage it' + }, + 401: ErrorResponses[401], + 404: ErrorResponses[404], + 500: ErrorResponses[500] + } + }), + async (c) => { + mustBeConfigured(); + return c.json({ data: await Polar.portal({ teamId: await payingTeam() }) }); + } + ); +} diff --git a/bun.lock b/bun.lock index 73a956e5..47765b8f 100644 --- a/bun.lock +++ b/bun.lock @@ -77,6 +77,7 @@ "name": "@nestri/core", "dependencies": { "@nestri/auth": "workspace:", + "@polar-sh/sdk": "catalog:", "drizzle-orm": "^0.45.2", "postgres": "^3.4.9", "postgresql": "^0.0.1", @@ -95,6 +96,7 @@ }, "catalog": { "@cloudflare/workers-types": "^5.20260722.1", + "@polar-sh/sdk": "^0.49.0", "@tsconfig/node22": "^22.0.5", "@types/bun": "latest", "@types/node": "^26.1.1", @@ -383,6 +385,8 @@ "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.76.0", "", { "os": "win32", "cpu": "x64" }, "sha512-5qcirPHO8nKfkoowEVWtpAoVTcYDy6g0UT0NGic450Qv8J2NrOqg4uQ8QppRP4MDTC7Xx47lbZnmadTH03CGGA=="], + "@polar-sh/sdk": ["@polar-sh/sdk@0.49.0", "", { "dependencies": { "standardwebhooks": "^1.0.0", "zod": "^3.25.65 || ^4.0.0" } }, "sha512-9UYb70iKjJCtWYlu0OF5HLYBLmkxHwqr2RlXwuxXQgRGqq56IQWlVG+NO7e1YJ7I5GW0CBHhGIjRbQ9hYM6ycQ=="], + "@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="], "@poppinss/dumper": ["@poppinss/dumper@0.6.5", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw=="], @@ -403,13 +407,15 @@ "@speed-highlight/core": ["@speed-highlight/core@1.2.24", "", {}, "sha512-qeW2e1l78afw8VhRPfPQ1Gjj+KU5XFQ/OFV5ti6eTa9bruO7mJyZtA4vw0ofqmA3tKCkROE9xLk3VZoeRc98nw=="], + "@stablelib/base64": ["@stablelib/base64@1.0.1", "", {}, "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ=="], + "@standard-schema/spec": ["@standard-schema/spec@1.0.0-beta.3", "", {}, "sha512-0ifF3BjA1E8SY9C+nUew8RefNOIq0cDlYALPty4rhUm8Rrl6tCM8hBT4bhGhx7I7iXD0uAgt50lgo8dD73ACMw=="], "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.11", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-LFuZUkjJ9iF7JZye/aG5XM0SFcQ5VyL0oVX4WJ9dc0Va3R3s0OauX1BESVCb+YN/ol8TAfqGDDAQsTG627Y5kw=="], "@tsconfig/node22": ["@tsconfig/node22@22.0.5", "", {}, "sha512-hLf2ld+sYN/BtOJjHUWOk568dvjFQkHnLNa6zce25GIH+vxKfvTgm3qpaH6ToF5tu/NN0IH66s+Bb5wElHrLcw=="], - "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], + "@types/bun": ["@types/bun@1.4.2", "", { "dependencies": { "bun-types": "1.4.2" } }, "sha512-GimotNn7+ZV0uVArItBbriZsR1oNf0+WTzPkdcFrzShI7k2norL0uzEaJT8T33dWr7O/c9ZDuAFQrctKCi72oQ=="], "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], @@ -485,7 +491,7 @@ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], - "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], + "bun-types": ["bun-types@1.4.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-bxV1FgK7yBIzjRe5zBozIM4Bem11ZJcCXSrjWRG3YWLt8yFDePu4cLjpebO8OvPeIE9trbyPF4fuj3Cia4Fj3w=="], "clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="], @@ -519,6 +525,8 @@ "fast-check": ["fast-check@4.9.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg=="], + "fast-sha256": ["fast-sha256@1.3.0", "", {}, "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ=="], + "find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], @@ -637,6 +645,8 @@ "sql-escaper": ["sql-escaper@1.5.1", "", {}, "sha512-4toX5E1fQbBrpfXidaHnF0669nkAdETeIPTs2SUjxxD7RRIs9ICG4gtpmfc68JCEKehsdwLFqBu9VlQqZ1P1gg=="], + "standardwebhooks": ["standardwebhooks@1.1.1", "", { "dependencies": { "@stablelib/base64": "^1.0.0", "fast-sha256": "^1.3.0" } }, "sha512-bCbX9ZEyFkWPsRz7Bl3NuQUJohmwGSev/yhr7vhaGPlc4AfIrspIRa6cPTBuI1ItmrTDJ4d/S2hCsfe4+vQGnQ=="], + "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], "svelte": ["svelte@5.56.7", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.10", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.8.1", "esm-env": "^1.2.1", "esrap": "^2.2.12", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-5qERUZX80oQj6XrDMUmD2Uhd/cIpCPDWWKBK3ZHmyRUC9apPyamWM8xMo31mbWsIQxwG2hVoSnOJ/EcnhVkkzQ=="], diff --git a/package.json b/package.json index 860c7317..93f910fe 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "@types/node": "^26.1.1", "hono": "^4.12.31", "typescript": "^7.0.1-rc", - "zod": "^4.4.3" + "zod": "^4.4.3", + "@polar-sh/sdk": "^0.49.0" } }, "type": "module", diff --git a/packages/core/package.json b/packages/core/package.json index 14c153f5..cbfc7f6e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -18,6 +18,7 @@ }, "dependencies": { "@nestri/auth": "workspace:", + "@polar-sh/sdk": "catalog:", "drizzle-orm": "^0.45.2", "postgres": "^3.4.9", "postgresql": "^0.0.1", diff --git a/packages/core/scripts/polar-product.ts b/packages/core/scripts/polar-product.ts new file mode 100644 index 00000000..cf942d7e --- /dev/null +++ b/packages/core/scripts/polar-product.ts @@ -0,0 +1,104 @@ +/** + * Create the paid product, against whichever environment you point it at. + * + * Sandbox and production are separate servers with separate data, so nothing + * made in one can be moved to the other — a product designed in sandbox has to + * be *recreated* in production, and two things typed twice are two things that + * drift. So the product is written down once here, and promoting it is running + * this again with the other token. + * + * POLAR_ACCESS_TOKEN=$(cat ~/.polar_sandbox_token) \ + * POLAR_SERVER=sandbox \ + * bun run packages/core/scripts/polar-product.ts + * + * Add `--apply` to actually create it. Without it nothing is written and the + * script prints what it would do, because this talks to a live payment account + * and a product created by accident is visible to customers. + * + * It refuses to make a second product with the same name rather than quietly + * making a duplicate — two products called the same thing is how a checkout + * ends up pointing at the wrong one. + */ +import { Polar } from '@polar-sh/sdk'; +import type { PresentmentCurrency } from '@polar-sh/sdk/models/components/presentmentcurrency.js'; + +/** + * The paid rung of the self-serve ladder. + * + * One recurring monthly product, priced per currency. The customer's location + * picks which price they see, so these are *presentment* prices rather than a + * conversion of one another — that is the point of listing three rather than + * charging one and letting a card issuer decide. + * + * Amounts are in minor units: 2000 is 20.00. + */ +const PRODUCT = { + name: 'Nestri Pro', + description: 'Cloud sessions on Nestri hardware, and a larger burn allowance.', + recurringInterval: 'month' as const, + prices: [ + { currency: 'usd', amount: 2000 }, + { currency: 'eur', amount: 2000 }, + { currency: 'gbp', amount: 2000 } + ] satisfies { currency: PresentmentCurrency; amount: number }[] +}; + +const apply = process.argv.includes('--apply'); +const accessToken = process.env.POLAR_ACCESS_TOKEN; +const server = (process.env.POLAR_SERVER ?? 'sandbox') as 'sandbox' | 'production'; + +if (!accessToken) { + console.error('POLAR_ACCESS_TOKEN is not set'); + process.exit(1); +} + +const polar = new Polar({ accessToken, server }); + +const organizations = await polar.organizations.listOrganizations({ limit: 2 }); +const organization = organizations.result.items.at(0); +if (!organization) { + console.error('that token can see no organization'); + process.exit(1); +} +if (organizations.result.items.length > 1) { + // Which one to use would be a guess, and the wrong guess bills the wrong + // company. + console.error('that token can see more than one organization; refusing to choose'); + process.exit(1); +} + +console.log(`server: ${server}`); +console.log(`organization: ${organization.name} (${organization.id})`); + +const existing = await polar.products.list({ organizationId: organization.id, limit: 100 }); +const clash = existing.result.items.find((p) => p.name === PRODUCT.name && !p.isArchived); +if (clash) { + console.log(`\nalready there: ${PRODUCT.name} (${clash.id})`); + console.log('nothing to do. Archive it first if you meant to replace it.'); + process.exit(0); +} + +console.log(`\nwould create: ${PRODUCT.name}, every ${PRODUCT.recurringInterval}`); +for (const price of PRODUCT.prices) { + console.log(` ${price.currency.toUpperCase()} ${(price.amount / 100).toFixed(2)}`); +} + +if (!apply) { + console.log('\nnothing written. Re-run with --apply to create it.'); + process.exit(0); +} + +const created = await polar.products.create({ + organizationId: organization.id, + name: PRODUCT.name, + description: PRODUCT.description, + recurringInterval: PRODUCT.recurringInterval, + prices: PRODUCT.prices.map((price) => ({ + amountType: 'fixed' as const, + priceCurrency: price.currency, + priceAmount: price.amount + })) +}); + +console.log(`\ncreated: ${created.id}`); +console.log(`set POLAR_PRODUCT_ID=${created.id} for the ${server} deployment.`); diff --git a/packages/core/src/billing/polar.test.ts b/packages/core/src/billing/polar.test.ts new file mode 100644 index 00000000..365b1209 --- /dev/null +++ b/packages/core/src/billing/polar.test.ts @@ -0,0 +1,112 @@ +import { afterEach, describe, expect, test } from 'bun:test'; + +import { Env } from '../env.js'; +import { Polar } from './polar.js'; + +afterEach(() => { + Env.init({}); + Polar.reset(); +}); + +describe('What an event means for access', () => { + test('a live subscription is paid', () => { + for (const type of [ + 'subscription.created', + 'subscription.active', + 'subscription.updated', + 'subscription.uncanceled' + ]) { + expect(Polar.standingFor(type)).toEqual({ plan: 'paid', status: 'active' }); + } + }); + + test('cancelling keeps the plan until the period is actually over', () => { + // They paid to the end of the period. Turning them off the moment they + // click cancel is taking something they bought. + expect(Polar.standingFor('subscription.canceled')).toEqual({ + plan: 'paid', + status: 'canceled' + }); + }); + + test('a failed card keeps the plan while it is being retried', () => { + // A card that failed may yet work, and a retry cycle that ends in + // payment should not have cost them access in the middle of it. + expect(Polar.standingFor('subscription.past_due')).toEqual({ + plan: 'paid', + status: 'past_due' + }); + }); + + test('revoked is the one that takes it away', () => { + // The provider saying the period is over and unpaid, which is the only + // moment there is nothing left that was paid for. + expect(Polar.standingFor('subscription.revoked')).toEqual({ + plan: 'free', + status: 'revoked' + }); + }); + + test('an unrecognised event changes nothing', () => { + // New types get added by people who do not know what we do with them. A + // default that moved somebody's plan would eventually cancel an account + // nobody cancelled. + for (const type of [ + 'subscription.something_new', + 'order.created', + 'benefit.granted', + '', + 'customer.updated' + ]) { + expect(Polar.standingFor(type)).toBeNull(); + } + }); +}); + +describe('Configuration', () => { + test('unconfigured is a state, not a crash', () => { + Env.init({}); + Polar.reset(); + expect(Polar.configured()).toBe(false); + }); + + test('a token without a product is still not configured', () => { + // Half-configured is the dangerous one: a checkout with no product to + // sell would fail at the provider, after the person clicked pay. + Env.init({ POLAR_ACCESS_TOKEN: 'polar_at_notreal' }); + Polar.reset(); + expect(Polar.configured()).toBe(false); + }); + + test('both together is configured', () => { + Env.init({ POLAR_ACCESS_TOKEN: 'polar_at_notreal', POLAR_PRODUCT_ID: 'prod_notreal' }); + Polar.reset(); + expect(Polar.configured()).toBe(true); + }); +}); + +describe('Webhooks', () => { + test('a body that is not signed is refused', () => { + Env.init({ + POLAR_ACCESS_TOKEN: 'polar_at_notreal', + POLAR_PRODUCT_ID: 'prod_notreal', + POLAR_WEBHOOK_SECRET: 'whsec_notreal' + }); + Polar.reset(); + expect(() => + Polar.receive({ + body: JSON.stringify({ type: 'subscription.active', data: {} }), + headers: { 'webhook-id': 'x', 'webhook-timestamp': '1', 'webhook-signature': 'v1,no' } + }) + ).toThrow(); + }); + + test('no secret configured is a refusal, never an unchecked delivery', () => { + // This route has no session in front of it. If the secret is missing the + // only safe answer is to refuse, because accepting would mean anybody + // who knows the URL can set anybody's plan. + Env.init({ POLAR_ACCESS_TOKEN: 'polar_at_notreal', POLAR_PRODUCT_ID: 'prod_notreal' }); + Polar.reset(); + expect(() => Polar.receive({ body: '{}', headers: {} })).toThrow(/not configured/); + }); +}); diff --git a/packages/core/src/billing/polar.ts b/packages/core/src/billing/polar.ts new file mode 100644 index 00000000..98b8314b --- /dev/null +++ b/packages/core/src/billing/polar.ts @@ -0,0 +1,218 @@ +import { Polar as PolarSdk } from '@polar-sh/sdk'; +import { validateEvent, WebhookVerificationError } from '@polar-sh/sdk/webhooks'; +import z from 'zod'; + +import { Env } from '../env.js'; +import { ErrorCodes, VisibleError } from '../error.js'; +import { fn } from '../fn.js'; +import { memo } from '../utils/memo.js'; + +/** + * The payment provider, and the only part of this codebase that talks to one. + * + * Everything money-shaped that is *not* here is deliberate. We store no price, + * no currency and no card detail: a subscription's existence and its state are + * the whole of what crosses back, because those are the only two facts the + * product needs and anything more would be a second copy of a record somebody + * else is authoritative for. + * + * **Currency is not our problem, by design.** A product carries a price per + * currency on their side and the customer's location picks one at checkout. So + * there is no currency in this file, none in the database, and no place where a + * rate could be stale — the alternative is holding prices in three currencies + * and discovering one of them is wrong from a customer. + * + * The team id goes over as the customer's `externalCustomerId`, which makes the + * mapping theirs to keep. A Polar customer id in our schema would be a foreign + * primary key we would then have to keep in step with a system we do not + * control. + */ +export namespace Polar { + /** Which Polar instance. Sandbox is a separate server with separate data. */ + export const Server = z.enum(['sandbox', 'production']); + export type Server = z.infer; + + function settings() { + const env = Env.get(); + if (!env.POLAR_ACCESS_TOKEN) { + throw new VisibleError( + 'internal', + ErrorCodes.Server.DEPENDENCY_FAILURE, + 'Billing is not configured' + ); + } + return { + accessToken: env.POLAR_ACCESS_TOKEN, + server: Server.parse(env.POLAR_SERVER ?? 'sandbox'), + productId: env.POLAR_PRODUCT_ID, + webhookSecret: env.POLAR_WEBHOOK_SECRET + }; + } + + /** Whether billing can run at all. Every route here checks it first. */ + export function configured(): boolean { + const env = Env.get(); + return Boolean(env.POLAR_ACCESS_TOKEN && env.POLAR_PRODUCT_ID); + } + + const client = memo(() => { + const { accessToken, server } = settings(); + return new PolarSdk({ accessToken, server }); + }); + + /** Reset the memo. Tests change the environment between cases. */ + export function reset(): void { + client.reset(); + } + + /** + * A checkout for a team, as the customer they already are. + * + * The team id travels as `externalCustomerId`, so a second checkout for the + * same team reaches the same customer rather than making another one — which + * is what keeps one team from ending up with two subscriptions and two + * invoices for the same month. + */ + export const checkout = fn( + z.object({ + teamId: z.string(), + email: z.email().optional(), + successUrl: z.url().optional() + }), + async (input) => { + const { productId } = settings(); + if (!productId) { + throw new VisibleError( + 'internal', + ErrorCodes.Server.DEPENDENCY_FAILURE, + 'Billing is not configured' + ); + } + const created = await client().checkouts.create({ + products: [productId], + externalCustomerId: input.teamId, + customerEmail: input.email, + successUrl: input.successUrl + }); + return { id: created.id, url: created.url }; + } + ); + + /** + * A link to where somebody manages what they are already paying. + * + * Cancelling, changing a card and reading an invoice all live there rather + * than here. Rebuilding any of it would mean holding payment details to show + * them, which is the one thing this integration exists to avoid. + */ + export const portal = fn(z.object({ teamId: z.string() }), async (input) => { + const session = await client().customerSessions.create({ + externalCustomerId: input.teamId + }); + return { url: session.customerPortalUrl }; + }); + + /** The plan and status a team is on, as far as the provider is concerned. */ + export const Standing = z.object({ + plan: z.enum(['free', 'paid']), + status: z.string() + }); + + export type Standing = z.infer; + + /** + * What a subscription event means for what a team may do. + * + * The rule is that **access follows the provider's own state and nothing + * else**, and the interesting cases are the ones where that is not the same + * as "are they paying right now": + * + * - `canceled` keeps the plan. Somebody who cancels has paid to the end of + * the period and turning them off the moment they click it would be taking + * something they bought. + * - `past_due` also keeps it. A failed card is a card that may yet work, and + * a retry cycle that ends in payment should not have cost them access in + * the middle of it. + * - `revoked` is the one that takes it away. That is the provider saying the + * period is over and unpaid, which is the only moment there is nothing + * left that was paid for. + * + * An unknown type returns null rather than guessing. New event types get + * added by people who do not know what we do with them, and a default that + * changed somebody's plan would be a default that eventually cancels an + * account nobody cancelled. + */ + export function standingFor(eventType: string): Standing | null { + switch (eventType) { + case 'subscription.created': + case 'subscription.active': + case 'subscription.updated': + case 'subscription.uncanceled': + return { plan: 'paid', status: 'active' }; + case 'subscription.canceled': + return { plan: 'paid', status: 'canceled' }; + case 'subscription.past_due': + return { plan: 'paid', status: 'past_due' }; + case 'subscription.revoked': + return { plan: 'free', status: 'revoked' }; + default: + return null; + } + } + + export interface Delivery { + type: string; + /** The team this is about, from the customer's external id. */ + teamId: string | null; + standing: Standing | null; + } + + /** + * Check a webhook is really from them, and say what it means. + * + * The signature is checked over the **raw body**, before anything is parsed: + * this is the one route in the API that no session protects, so the + * signature is the whole of its authentication, and a body that has been + * through `JSON.parse` and back is not the body that was signed. + * + * A bad signature is an authentication failure and not a server fault — it + * is what an attacker gets, and it must read the same as a stale secret so + * that neither tells anybody which it was. + */ + export const receive = fn( + z.object({ body: z.string(), headers: z.record(z.string(), z.string()) }), + (input): Delivery => { + const { webhookSecret } = settings(); + if (!webhookSecret) { + throw new VisibleError( + 'internal', + ErrorCodes.Server.DEPENDENCY_FAILURE, + 'Billing is not configured' + ); + } + + let event; + try { + event = validateEvent(input.body, input.headers, webhookSecret); + } catch (error) { + if (error instanceof WebhookVerificationError) { + throw new VisibleError( + 'authentication', + ErrorCodes.Authentication.INVALID_TOKEN, + 'Signature does not match' + ); + } + throw error; + } + + const data = (event as { data?: Record }).data ?? {}; + const customer = data.customer as { externalId?: string | null } | undefined; + // `externalId` is the team id we sent at checkout. A delivery without + // one is about a customer created some other way — by hand in their + // dashboard, most likely — and there is nothing here it can change. + const teamId = customer?.externalId ?? null; + + return { type: event.type, teamId, standing: standingFor(event.type) }; + } + ); +} diff --git a/packages/core/src/env.ts b/packages/core/src/env.ts index 877e1726..cc737ec4 100644 --- a/packages/core/src/env.ts +++ b/packages/core/src/env.ts @@ -36,6 +36,19 @@ export namespace Env { */ BURN_LIMITS: z.string().optional(), + /** + * The payment provider. + * + * `POLAR_SERVER` picks the instance and the two are entirely separate + * servers with separate data, so a token from one is refused by the + * other and a product id from one means nothing to it. Getting this + * wrong fails loudly rather than quietly charging somebody. + */ + POLAR_ACCESS_TOKEN: z.string().optional(), + POLAR_WEBHOOK_SECRET: z.string().optional(), + POLAR_PRODUCT_ID: z.string().optional(), + POLAR_SERVER: z.enum(['sandbox', 'production']).optional(), + DATABASE_URL: z.string().optional() }); diff --git a/packages/core/src/team/index.ts b/packages/core/src/team/index.ts index be5542a1..4d9de8fe 100644 --- a/packages/core/src/team/index.ts +++ b/packages/core/src/team/index.ts @@ -170,6 +170,37 @@ export namespace Team { return create({ id, name: `${input.displayName}'s Team`, slug }); }); + /** + * Record what the payment provider says a team is on. + * + * The only writer is the webhook, and it writes both fields together: a plan + * without the status it came from cannot say whether "paid" means paying, + * cancelled-but-paid-up, or behind on a card, and every one of those wants a + * different sentence in front of a person. + * + * Deliberately not reached from anywhere a user can call. A plan that could + * be set by a request is a plan somebody can set on themselves. + */ + export const setPlan = fn( + Info.pick({ id: true }).extend({ + plan: z.string(), + subscriptionStatus: z.string() + }), + async (input) => { + return Database.use(async (tx) => { + return tx + .update(TeamTable) + .set({ plan: input.plan, subscriptionStatus: input.subscriptionStatus }) + .where(and(eq(TeamTable.id, input.id), isNull(TeamTable.timeDeleted))) + .returning() + .then((rows) => { + const row = rows.at(0); + return row ? serialize(row) : null; + }); + }); + } + ); + export function serialize(input: typeof TeamTable.$inferSelect): z.infer { return { id: input.id, From b01d8eabb3abbfd8a65062158bc6a31bbd629662 Mon Sep 17 00:00:00 2001 From: Wanjohi Date: Sat, 19 Sep 2026 00:59:36 +0300 Subject: [PATCH 8/9] fix(billing): an organization token already names its organization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creating a product with one sends `organization_id` alongside a token that implies it, which is refused outright rather than ignored — so which kind of token is in hand has to be known before the call, not after. A personal token can see several organizations and still has to say which. --- packages/core/scripts/polar-product.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/core/scripts/polar-product.ts b/packages/core/scripts/polar-product.ts index cf942d7e..e3412071 100644 --- a/packages/core/scripts/polar-product.ts +++ b/packages/core/scripts/polar-product.ts @@ -54,6 +54,15 @@ if (!accessToken) { const polar = new Polar({ accessToken, server }); +/** + * An organization token already names its organization. + * + * Sending `organizationId` alongside one is refused outright rather than + * ignored, so which kind of token this is has to be known before the call. A + * personal token can see several organizations and must say which. + */ +const scopedToOrganization = accessToken.startsWith('polar_oat_'); + const organizations = await polar.organizations.listOrganizations({ limit: 2 }); const organization = organizations.result.items.at(0); if (!organization) { @@ -89,7 +98,7 @@ if (!apply) { } const created = await polar.products.create({ - organizationId: organization.id, + ...(scopedToOrganization ? {} : { organizationId: organization.id }), name: PRODUCT.name, description: PRODUCT.description, recurringInterval: PRODUCT.recurringInterval, From 60c4f61bfa3c0fa18b7ab024034ebd585ccb3b1b Mon Sep 17 00:00:00 2001 From: Wanjohi Date: Sat, 19 Sep 2026 01:07:01 +0300 Subject: [PATCH 9/9] feat(billing): free is a subscription too, and the product says which plan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every team now exists with the payment provider, free ones included. An upgrade then changes a subscription rather than inventing a customer, and there is one question to ask about anybody instead of two. A subscription at nothing a month needs no payment, so it is created outright rather than by sending somebody through a checkout to pay zero. It runs after the team rows are committed and cannot affect them. Signing up is not allowed to depend on a third party being reachable, so this cannot fail the call and does not retry — a team that misses it is free, which is what it would have been anyway, and the next call puts it right because the operation is idempotent. This broke the webhook mapping, which read the plan off the event type. A free subscription announces itself with the same `subscription.created` a paid one does, so every new signup would have landed on the paid allowance. The plan now comes from the product, and a product we do not sell is left alone rather than guessed at — somebody selling something else through the same account must not be able to change what a team may run by doing so. The external id stays the team. It is the billing subject, and keying on the user would collapse somebody with two teams into one customer with no way to say which subscription belonged to which. --- .env.example | 3 + packages/core/src/billing/polar.test.ts | 79 ++++++++++++++-------- packages/core/src/billing/polar.ts | 88 ++++++++++++++++++++++--- packages/core/src/env.ts | 2 + packages/core/src/team/index.ts | 23 +++++++ 5 files changed, 161 insertions(+), 34 deletions(-) diff --git a/.env.example b/.env.example index 58a50384..c66afec8 100644 --- a/.env.example +++ b/.env.example @@ -42,6 +42,9 @@ EMAIL_DEV_LOG=true POLAR_SERVER=sandbox POLAR_ACCESS_TOKEN= POLAR_PRODUCT_ID= +# The product every team is put on at signup, priced at nothing. A free +# subscription needs no checkout, so it is created outright. +POLAR_FREE_PRODUCT_ID= # Signs every webhook delivery. Without it the webhook route refuses everything, # on purpose: nothing else stands in front of it. POLAR_WEBHOOK_SECRET= diff --git a/packages/core/src/billing/polar.test.ts b/packages/core/src/billing/polar.test.ts index 365b1209..09a3aa8b 100644 --- a/packages/core/src/billing/polar.test.ts +++ b/packages/core/src/billing/polar.test.ts @@ -1,29 +1,67 @@ -import { afterEach, describe, expect, test } from 'bun:test'; +import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; import { Env } from '../env.js'; import { Polar } from './polar.js'; +const PAID = 'prod_paid_notreal'; +const FREE = 'prod_free_notreal'; + +function configure(extra: Record = {}) { + Env.init({ + POLAR_ACCESS_TOKEN: 'polar_oat_notreal', + POLAR_PRODUCT_ID: PAID, + POLAR_FREE_PRODUCT_ID: FREE, + ...extra + }); + Polar.reset(); +} + +beforeEach(() => configure()); + afterEach(() => { Env.init({}); Polar.reset(); }); +describe('Which plan a subscription is', () => { + test('the product decides it, not the event', () => { + // Free is a real subscription here, so it announces itself with the same + // `subscription.created` a paid one does. Reading the type alone would + // put every new signup on the paid allowance. + expect(Polar.standingFor('subscription.created', FREE)).toEqual({ + plan: 'free', + status: 'active' + }); + expect(Polar.standingFor('subscription.created', PAID)).toEqual({ + plan: 'paid', + status: 'active' + }); + }); + + test('a product we do not sell changes nothing', () => { + // Somebody selling something else through the same account must not be + // able to change what a team may run by doing so. + expect(Polar.standingFor('subscription.active', 'prod_somethingelse')).toBeNull(); + expect(Polar.standingFor('subscription.active', null)).toBeNull(); + }); +}); + describe('What an event means for access', () => { - test('a live subscription is paid', () => { + test('a live subscription keeps its plan', () => { for (const type of [ 'subscription.created', 'subscription.active', 'subscription.updated', 'subscription.uncanceled' ]) { - expect(Polar.standingFor(type)).toEqual({ plan: 'paid', status: 'active' }); + expect(Polar.standingFor(type, PAID)).toEqual({ plan: 'paid', status: 'active' }); } }); test('cancelling keeps the plan until the period is actually over', () => { // They paid to the end of the period. Turning them off the moment they // click cancel is taking something they bought. - expect(Polar.standingFor('subscription.canceled')).toEqual({ + expect(Polar.standingFor('subscription.canceled', PAID)).toEqual({ plan: 'paid', status: 'canceled' }); @@ -32,16 +70,20 @@ describe('What an event means for access', () => { test('a failed card keeps the plan while it is being retried', () => { // A card that failed may yet work, and a retry cycle that ends in // payment should not have cost them access in the middle of it. - expect(Polar.standingFor('subscription.past_due')).toEqual({ + expect(Polar.standingFor('subscription.past_due', PAID)).toEqual({ plan: 'paid', status: 'past_due' }); }); - test('revoked is the one that takes it away', () => { + test('revoked drops to free, whatever it was before', () => { // The provider saying the period is over and unpaid, which is the only // moment there is nothing left that was paid for. - expect(Polar.standingFor('subscription.revoked')).toEqual({ + expect(Polar.standingFor('subscription.revoked', PAID)).toEqual({ + plan: 'free', + status: 'revoked' + }); + expect(Polar.standingFor('subscription.revoked', FREE)).toEqual({ plan: 'free', status: 'revoked' }); @@ -51,14 +93,8 @@ describe('What an event means for access', () => { // New types get added by people who do not know what we do with them. A // default that moved somebody's plan would eventually cancel an account // nobody cancelled. - for (const type of [ - 'subscription.something_new', - 'order.created', - 'benefit.granted', - '', - 'customer.updated' - ]) { - expect(Polar.standingFor(type)).toBeNull(); + for (const type of ['subscription.something_new', 'order.created', '', 'customer.updated']) { + expect(Polar.standingFor(type, PAID)).toBeNull(); } }); }); @@ -73,26 +109,19 @@ describe('Configuration', () => { test('a token without a product is still not configured', () => { // Half-configured is the dangerous one: a checkout with no product to // sell would fail at the provider, after the person clicked pay. - Env.init({ POLAR_ACCESS_TOKEN: 'polar_at_notreal' }); + Env.init({ POLAR_ACCESS_TOKEN: 'polar_oat_notreal' }); Polar.reset(); expect(Polar.configured()).toBe(false); }); test('both together is configured', () => { - Env.init({ POLAR_ACCESS_TOKEN: 'polar_at_notreal', POLAR_PRODUCT_ID: 'prod_notreal' }); - Polar.reset(); expect(Polar.configured()).toBe(true); }); }); describe('Webhooks', () => { test('a body that is not signed is refused', () => { - Env.init({ - POLAR_ACCESS_TOKEN: 'polar_at_notreal', - POLAR_PRODUCT_ID: 'prod_notreal', - POLAR_WEBHOOK_SECRET: 'whsec_notreal' - }); - Polar.reset(); + configure({ POLAR_WEBHOOK_SECRET: 'whsec_notreal' }); expect(() => Polar.receive({ body: JSON.stringify({ type: 'subscription.active', data: {} }), @@ -105,8 +134,6 @@ describe('Webhooks', () => { // This route has no session in front of it. If the secret is missing the // only safe answer is to refuse, because accepting would mean anybody // who knows the URL can set anybody's plan. - Env.init({ POLAR_ACCESS_TOKEN: 'polar_at_notreal', POLAR_PRODUCT_ID: 'prod_notreal' }); - Polar.reset(); expect(() => Polar.receive({ body: '{}', headers: {} })).toThrow(/not configured/); }); }); diff --git a/packages/core/src/billing/polar.ts b/packages/core/src/billing/polar.ts index 98b8314b..46b462f6 100644 --- a/packages/core/src/billing/polar.ts +++ b/packages/core/src/billing/polar.ts @@ -45,6 +45,7 @@ export namespace Polar { accessToken: env.POLAR_ACCESS_TOKEN, server: Server.parse(env.POLAR_SERVER ?? 'sandbox'), productId: env.POLAR_PRODUCT_ID, + freeProductId: env.POLAR_FREE_PRODUCT_ID, webhookSecret: env.POLAR_WEBHOOK_SECRET }; } @@ -65,6 +66,51 @@ export namespace Polar { client.reset(); } + /** + * Put a team on the free plan with the provider, without a checkout. + * + * A subscription at nothing a month needs no payment, so it is created + * outright rather than by sending somebody to pay zero — a checkout for a + * free account is a step that exists only to be got through. + * + * The point of doing it at all is that every team then exists on their side, + * with our team id as its external id. Free accounts show up in the same + * places paid ones do, an upgrade changes a subscription rather than + * inventing a customer, and there is one question to ask about anybody + * rather than two. + * + * **Idempotent, and quiet when it fails.** It runs after a team is created + * and must never be able to undo that: signing up is not allowed to depend + * on a third party being reachable, so a failure here leaves a team that is + * free anyway — which is exactly what it would have been — and the next call + * fixes it. That is also why it is safe to call on a team that already has + * one. + */ + export const ensureFree = fn(z.object({ teamId: z.string() }), async (input) => { + const { freeProductId } = settings(); + if (!freeProductId) { + return { created: false, reason: 'no free product configured' as const }; + } + + try { + const existing = await client().customers.getStateExternal({ + externalId: input.teamId + }); + if (existing.activeSubscriptions.length > 0) { + return { created: false, reason: 'already subscribed' as const }; + } + } catch { + // No such customer yet, which is the ordinary case the first time. + // Creating the subscription below makes one. + } + + await client().subscriptions.create({ + productId: freeProductId, + externalCustomerId: input.teamId + }); + return { created: true, reason: 'created' as const }; + }); + /** * A checkout for a team, as the customer they already are. * @@ -142,18 +188,38 @@ export namespace Polar { * changed somebody's plan would be a default that eventually cancels an * account nobody cancelled. */ - export function standingFor(eventType: string): Standing | null { + export function standingFor(eventType: string, productId: string | null): Standing | null { + // Which plan a subscription *is* comes from the product, never from the + // event. Free is a real subscription here, so it announces itself with + // the same `subscription.created` a paid one does — reading the type + // alone would put every new signup on the paid allowance. + const { productId: paidProduct, freeProductId } = settings(); + const plan: Standing['plan'] | null = + productId && productId === paidProduct + ? 'paid' + : productId && productId === freeProductId + ? 'free' + : null; + + // A product we do not recognise is left alone rather than guessed at. + // Somebody selling something else through the same account should not be + // able to change what a team may run by doing so. + if (!plan) { + return null; + } + switch (eventType) { case 'subscription.created': case 'subscription.active': case 'subscription.updated': case 'subscription.uncanceled': - return { plan: 'paid', status: 'active' }; + return { plan, status: 'active' }; case 'subscription.canceled': - return { plan: 'paid', status: 'canceled' }; + return { plan, status: 'canceled' }; case 'subscription.past_due': - return { plan: 'paid', status: 'past_due' }; + return { plan, status: 'past_due' }; case 'subscription.revoked': + // Whatever it was, it is over. Free is where everybody lands. return { plan: 'free', status: 'revoked' }; default: return null; @@ -207,12 +273,18 @@ export namespace Polar { const data = (event as { data?: Record }).data ?? {}; const customer = data.customer as { externalId?: string | null } | undefined; - // `externalId` is the team id we sent at checkout. A delivery without - // one is about a customer created some other way — by hand in their - // dashboard, most likely — and there is nothing here it can change. + // `externalId` is the team id we put on the customer. A delivery + // without one is about a customer created some other way — by hand in + // their dashboard, most likely — and there is nothing here it can + // change. const teamId = customer?.externalId ?? null; - return { type: event.type, teamId, standing: standingFor(event.type) }; + // Both spellings, because which one a payload carries depends on + // whether the product was expanded into it. + const product = data.product as { id?: string } | undefined; + const productId = (data.productId as string | undefined) ?? product?.id ?? null; + + return { type: event.type, teamId, standing: standingFor(event.type, productId) }; } ); } diff --git a/packages/core/src/env.ts b/packages/core/src/env.ts index cc737ec4..9e63ae84 100644 --- a/packages/core/src/env.ts +++ b/packages/core/src/env.ts @@ -47,6 +47,8 @@ export namespace Env { POLAR_ACCESS_TOKEN: z.string().optional(), POLAR_WEBHOOK_SECRET: z.string().optional(), POLAR_PRODUCT_ID: z.string().optional(), + /** The product a team is put on at signup, priced at nothing. */ + POLAR_FREE_PRODUCT_ID: z.string().optional(), POLAR_SERVER: z.enum(['sandbox', 'production']).optional(), DATABASE_URL: z.string().optional() diff --git a/packages/core/src/team/index.ts b/packages/core/src/team/index.ts index 4d9de8fe..a85d82ca 100644 --- a/packages/core/src/team/index.ts +++ b/packages/core/src/team/index.ts @@ -2,6 +2,7 @@ import { eq, and, isNull, sql } from 'drizzle-orm'; import z from 'zod'; import { Actor } from '../actor.js'; +import { Polar } from '../billing/polar.js'; import { Database } from '../db/index.js'; import { Examples } from '../examples.js'; import { fn } from '../fn.js'; @@ -75,6 +76,28 @@ export namespace Team { role: 'owner' }); }); + + // Register the team with the payment provider, *after* the rows are + // committed and without being able to affect them. + // + // Every team exists on their side, free ones included, so that an + // upgrade changes a subscription rather than inventing a customer and + // there is one question to ask about anybody rather than two. + // + // **Signing up is not allowed to depend on a third party.** So this + // cannot run inside the transaction, cannot fail the call, and does not + // retry: a team that misses it is free, which is what it would have been + // anyway, and the next call puts it right because the operation is + // idempotent. + Database.effect(async () => { + try { + await Polar.ensureFree({ teamId: input.id }); + } catch (error) { + // eslint-disable-next-line no-console + console.error('could not register team with the payment provider:', error); + } + }); + return input.id; });