diff --git a/.oxlintrc.json b/.oxlintrc.json index 1f94a12..10405cf 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -30,6 +30,7 @@ "eslint/eqeqeq": ["off", "smart"], "eslint/func-style": ["off"], "eslint/max-lines-per-function": "off", + "eslint/max-lines": "off", "eslint/id-length": ["warn", { "exceptionPatterns": ["^_", "^[Tertv]$"] }], "eslint/init-declarations": "off", "eslint/max-params": ["warn", { "max": 4 }], @@ -69,6 +70,7 @@ "import/prefer-default-export": "off", "jsdoc/require-param-type": "off", "jsdoc/require-returns-type": "off", + "node/no-top-level-await": "off", "promise/avoid-new": "warn" }, "overrides": [ diff --git a/mise.toml b/mise.toml index 5154641..a8e44c3 100644 --- a/mise.toml +++ b/mise.toml @@ -127,6 +127,7 @@ flag "--name " help="Migration file name" flag "--custom" help="Prepare empty migration file for custom SQL" """ dir = "packages/models" +raw = true run = """ #!/usr/bin/env nu let name = (if "usage_name" in $env { ["--name" $env.usage_name] } else { [] }) diff --git a/packages/drfed/src/index.ts b/packages/drfed/src/index.ts index 077b548..9b633cd 100644 --- a/packages/drfed/src/index.ts +++ b/packages/drfed/src/index.ts @@ -44,8 +44,8 @@ async function runServer(options: ServerOptions) { if (options.seed) { await seedData(options.drizzle.db); } - const { mailer } = options; - const yogaServer = createYogaServer(options.drizzle.db, { mailer }); + const { mailer, root } = options; + const yogaServer = createYogaServer(options.drizzle.db, { root, mailer }); const server = serve({ fetch: yogaServer.fetch.bind(yogaServer), hostname: options.address.host, @@ -76,7 +76,6 @@ async function runSchemaGenerator( await writeFile(options.outputFile, schemaCode, { encoding: "utf-8" }); } -// oxlint-disable-next-line max-lines-per-function export async function main(): Promise { const options: Options = run(program, { help: "option", diff --git a/packages/drfed/src/parser.ts b/packages/drfed/src/parser.ts index 0828e36..b7beff0 100644 --- a/packages/drfed/src/parser.ts +++ b/packages/drfed/src/parser.ts @@ -22,7 +22,7 @@ import { message, optionNames } from "@optique/core/message"; import { map, optional, withDefault } from "@optique/core/modifiers"; import type { InferValue } from "@optique/core/parser"; import { flag, option } from "@optique/core/primitives"; -import { socketAddress, url } from "@optique/core/valueparser"; +import { domain, socketAddress, url } from "@optique/core/valueparser"; import { loggingOptions } from "@optique/logtape"; import { path } from "@optique/run/valueparser"; import { LogTapeTransport } from "@upyo/logtape"; @@ -105,6 +105,12 @@ const seedParser = option("--dev-seed", { hidden: true, }); +const rootParser = optional( + option("--root-domain", "-r", domain({ lowercase: true }), { + description: message`The root domain of host.`, + }), +); + const serverParser = object("DrFed server", { address: withDefault( option("--listen", "-l", socketAddress({ requirePort: true }), { @@ -126,6 +132,7 @@ const serverParser = object("DrFed server", { ), }), ), + root: rootParser, mailer: smtpParser, seed: seedParser, }); diff --git a/packages/graphql/src/account.test.ts b/packages/graphql/src/account.test.ts index 060dc6a..bc00e39 100644 --- a/packages/graphql/src/account.test.ts +++ b/packages/graphql/src/account.test.ts @@ -52,7 +52,7 @@ const accountInstancesQuery = ` admin node { uuid - slug + host } } } @@ -82,7 +82,7 @@ const accountInstancesResponse = { admin: true, node: { uuid: acceptedInstanceId, - slug: "test-instance", + host: "test-instance.drfed.org", }, }, ], @@ -148,20 +148,7 @@ async function seedAccounts(db: Database): Promise { async function seedMembershipGraph(db: Database): Promise { await seedAccounts(db); - await db.insert(schema.instances).values([ - { - id: acceptedInstanceId, - slug: "test-instance", - created, - expires, - }, - { - id: pendingInstanceId, - slug: "pending-instance", - created, - expires, - }, - ]); + await seedLocalInstances(db); await db.insert(schema.instanceMembers).values([ { accountId, @@ -193,3 +180,32 @@ async function seedMembershipGraph(db: Database): Promise { }, ]); } + +async function seedLocalInstances(db: Database): Promise { + await db.insert(schema.localInstances).values([ + { + id: acceptedInstanceId, + slug: "test-instance", + expires, + }, + { + id: pendingInstanceId, + slug: "pending-instance", + expires, + }, + ]); + await db.insert(schema.instances).values([ + { + id: acceptedInstanceId, + localId: acceptedInstanceId, + host: "test-instance.drfed.org", + created, + }, + { + id: pendingInstanceId, + localId: pendingInstanceId, + host: "pending-instance.drfed.org", + created, + }, + ]); +} diff --git a/packages/graphql/src/account.ts b/packages/graphql/src/account.ts index 0451807..f07b118 100644 --- a/packages/graphql/src/account.ts +++ b/packages/graphql/src/account.ts @@ -79,7 +79,6 @@ const accountInstancesConnection = drizzleConnectionHelpers( }, ); -// oxlint-disable-next-line max-lines-per-function builder.drizzleObjectField(AccountRef, "instances", (t) => t.connection( { diff --git a/packages/graphql/src/auth.test.ts b/packages/graphql/src/auth.test.ts index 88673ec..fc5dd4f 100644 --- a/packages/graphql/src/auth.test.ts +++ b/packages/graphql/src/auth.test.ts @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -// oxlint-disable max-lines-per-function max-statements no-magic-numbers +// oxlint-disable max-statements no-magic-numbers import { deepEqual, equal, ok } from "node:assert/strict"; diff --git a/packages/graphql/src/builder.ts b/packages/graphql/src/builder.ts index 2a08a30..d6925cc 100644 --- a/packages/graphql/src/builder.ts +++ b/packages/graphql/src/builder.ts @@ -56,6 +56,11 @@ export interface ServerContext { * Origin list. */ readonly origins: ReadonlySet; + + /** + * Root domain. + */ + readonly root: string; } /** diff --git a/packages/graphql/src/index.ts b/packages/graphql/src/index.ts index 2105fcc..afd8ddd 100644 --- a/packages/graphql/src/index.ts +++ b/packages/graphql/src/index.ts @@ -47,6 +47,11 @@ export interface YogaServerOptions { * Origin list. */ origins?: ReadonlySet; + + /** + * Root domain. + */ + root?: string | undefined; } /** @@ -98,6 +103,7 @@ const fillOptions = (opt: YogaServerOptions): Required => ({ emailFrom: opt?.emailFrom ?? "noreply@drfed.org", // FIXME: Properly parametrize the following allowlist: origins: opt?.origins ?? new Set(["https://drfed.org"]), + root: opt?.root ?? "drfed.org", }); const getAccessToken = (headers: Headers) => diff --git a/packages/graphql/src/instance.test.ts b/packages/graphql/src/instance.test.ts index cc32334..d707ce6 100644 --- a/packages/graphql/src/instance.test.ts +++ b/packages/graphql/src/instance.test.ts @@ -14,11 +14,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -// oxlint-disable max-lines max-lines-per-function no-underscore-dangle +// oxlint-disable no-underscore-dangle import assert from "node:assert/strict"; import { type Database, schema } from "@drfed/models"; import { describe, it } from "@logtape/testing-node/autoload"; +import { DrizzleQueryError } from "drizzle-orm"; import { withTestHarness } from "./harness.test.ts"; @@ -31,9 +32,25 @@ const accountId = "00000000-0000-4000-8000-000000000001"; const memberId = "00000000-0000-4000-8000-000000000002"; const pendingMemberId = "00000000-0000-4000-8000-000000000003"; const instanceId = "00000000-0000-4000-8000-000000000101"; +const duplicateRemoteInstanceId = "00000000-0000-4000-8000-000000000102"; const sessionId = "00000000-0000-4000-8000-000000000201"; const accessToken = "test-access-token"; +const remoteInstanceQuery = ` + query RemoteInstance($uuid: UUID!) { + accountByUuid(uuid: $uuid) { + instances { + edges { + node { + uuid + host + } + } + } + } + } +`; + const instanceMembersQuery = ` query InstanceMembers($uuid: UUID!) { accountByUuid(uuid: $uuid) { @@ -122,7 +139,7 @@ const createInstanceMutation = ` __typename ... on Instance { uuid - slug + host } ... on CreateInstanceError { type @@ -146,12 +163,17 @@ describe("Mutation.createInstance", () => { const body = await response.json(); assert.equal(body.errors, undefined); assert.equal(body.data.createInstance.__typename, "Instance"); - assert.equal(body.data.createInstance.slug, "my-instance"); + assert.equal(body.data.createInstance.host, "my-instance.drfed.org"); assert.equal(typeof body.data.createInstance.uuid, "string"); const instances = await db.select().from(schema.instances); assert.equal(instances.length, 1); - assert.equal(instances[0]?.slug, "my-instance"); + const instance = instances[0]!; + assert.equal(typeof instance.localId, "string"); + const local = await db.query.localInstances.findFirst({ + where: { id: instance.localId! }, + }); + assert.equal(local?.slug, "my-instance"); const members = await db.select().from(schema.instanceMembers); assert.equal(members.length, 1); @@ -226,6 +248,55 @@ describe("Mutation.createInstance", () => { }); }); +describe("Remote instance", () => { + it("returns a created remote instance", async () => { + await withTestHarness(async ({ db, post }) => { + await seedRemoteInstance(db); + + const response = await post({ + query: remoteInstanceQuery, + variables: { uuid: accountId }, + }); + + assert.equal(response.status, ok); + assert.deepEqual(await response.json(), { + data: { + accountByUuid: { + instances: { + edges: [ + { + node: { + uuid: instanceId, + host: "remote.example.com", + }, + }, + ], + }, + }, + }, + }); + }); + }); + + it("requires a unique host", async () => { + await withTestHarness(async ({ db }) => { + await seedRemoteInstance(db); + + await assert.rejects( + db.insert(schema.instances).values({ + id: duplicateRemoteInstanceId, + host: "remote.example.com", + }), + (error: unknown) => + error instanceof DrizzleQueryError && + error.cause != null && + "constraint" in error.cause && + error.cause.constraint === "instances_host_key", + ); + }); + }); +}); + /** * Seeds an account and an authenticated session, then returns the request * options carrying the session's bearer token. @@ -267,7 +338,6 @@ async function hashSecret(raw: string): Promise { ).toHex(); } -// oxlint-disable-next-line max-lines-per-function async function seedInstanceMembers(db: Database): Promise { await db.insert(schema.accounts).values([ { @@ -292,12 +362,17 @@ async function seedInstanceMembers(db: Database): Promise { created, }, ]); - await db.insert(schema.instances).values({ + await db.insert(schema.localInstances).values({ id: instanceId, slug: "test-instance", - created, expires, }); + await db.insert(schema.instances).values({ + id: instanceId, + localId: instanceId, + created, + host: "test-instance.drfed.org", + }); await db.insert(schema.instanceMembers).values([ { accountId, @@ -322,3 +397,23 @@ async function seedInstanceMembers(db: Database): Promise { }, ]); } + +async function seedRemoteInstance(db: Database): Promise { + await db.insert(schema.accounts).values({ + id: accountId, + email: "owner@example.com", + name: "Owner", + created, + }); + await db.insert(schema.instances).values({ + id: instanceId, + created, + host: "remote.example.com", + }); + await db.insert(schema.instanceMembers).values({ + accountId, + instanceId, + accepted, + created, + }); +} diff --git a/packages/graphql/src/instance.ts b/packages/graphql/src/instance.ts index 32cdd5c..f778f83 100644 --- a/packages/graphql/src/instance.ts +++ b/packages/graphql/src/instance.ts @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -// oxlint-disable max-lines-per-function import { schema } from "@drfed/models"; import { instanceMembers } from "@drfed/models/schema"; import { drizzleConnectionHelpers } from "@pothos/plugin-drizzle"; @@ -38,22 +37,51 @@ const InstanceRef = builder.drizzleNode("instances", { fields: (t) => ({ uuid: t.expose("id", { type: "UUID", - description: "The UUID of the `Instance`.", - }), - slug: t.exposeString("slug"), - expires: t.expose("expires", { - type: "DateTime", - description: "The expiration date/time of the `Instance`.", }), + host: t.exposeString("host"), created: t.expose("created", { type: "DateTime", description: "The creation date/time of the `Instance`.", }), + nodeInfoUrl: t.exposeString("nodeInfoUrl", { + nullable: true, + }), + software: t.exposeString("software", { + nullable: true, + }), + softwareVersion: t.exposeString("softwareVersion", { + nullable: true, + }), }), }); export const Instance: DrFedObjectRef = InstanceRef; +const LocalInstanceRef = builder.drizzleNode("localInstances", { + name: "LocalInstance", + description: "Represents an `Instance` in the DrFed platform.", + id: { + column(instance) { + return instance.id; + }, + description: "The unique identifier of the `Instance`.", + }, + fields: (t) => ({ + uuid: t.expose("id", { + type: "UUID", + description: "The UUID of the `Instance`.", + }), + slug: t.exposeString("slug"), + expires: t.expose("expires", { + type: "DateTime", + description: "The expire date of the instance.", + }), + maxActors: t.exposeInt("maxActors"), + }), +}); + +export const LocalInstance: DrFedObjectRef = LocalInstanceRef; + const instanceMembersConnection = drizzleConnectionHelpers( builder, "instanceMembers", @@ -77,7 +105,6 @@ const instanceMembersConnection = drizzleConnectionHelpers( }, ); -// oxlint-disable-next-line max-lines-per-function builder.drizzleObjectField(InstanceRef, "members", (t) => t.connection( { @@ -218,16 +245,24 @@ builder.mutationFields((t) => ({ let tooManyInstances = false; try { return await ctx.db.transaction(async (tx) => { - const [instance] = await tx - .insert(schema.instances) + const [local] = await tx + .insert(schema.localInstances) .values({ id: uuid(), slug, expires: new Date( - Temporal.Now.instant().add({ hours: 8750 }).toString(), + Temporal.Now.instant().add({ hours: YEAR_BY_HOURS }).toString(), ), }) .returning(); + if (local == null) { + throw new Error("Failed to create local instance."); + } + const host = `${slug}.${ctx.root}`; + const [instance] = await tx + .insert(schema.instances) + .values({ id: uuid(), localId: local.id, host }) + .returning(); if (instance == null) throw new Error("Failed to create instance."); await tx.insert(schema.instanceMembers).values({ instanceId: instance.id, @@ -254,7 +289,7 @@ builder.mutationFields((t) => ({ e instanceof DrizzleQueryError && e.cause != null && "constraint" in e.cause && - e.cause.constraint === "instances_slug_key" + e.cause.constraint === "local_instances_slug_key" ) { return { message: `The slug ${JSON.stringify(slug)} is already taken.`, @@ -266,3 +301,5 @@ builder.mutationFields((t) => ({ }, }), })); + +const YEAR_BY_HOURS = 8760; diff --git a/packages/models/drizzle/20260802205417_separate-instance-local-remote/migration.sql b/packages/models/drizzle/20260802205417_separate-instance-local-remote/migration.sql new file mode 100644 index 0000000..7b30cef --- /dev/null +++ b/packages/models/drizzle/20260802205417_separate-instance-local-remote/migration.sql @@ -0,0 +1,26 @@ +CREATE TYPE "location" AS ENUM('Local', 'Remote');--> statement-breakpoint +CREATE TABLE "local_instances" ( + "id" uuid PRIMARY KEY, + "slug" varchar(63) NOT NULL UNIQUE, + "expires" timestamp with time zone NOT NULL, + "maxActors" integer DEFAULT 10 NOT NULL, + CONSTRAINT "instances_slug_check" CHECK ("slug" ~ '^[a-z0-9-]{4,63}$'), + CONSTRAINT "instances_max_actors_check" CHECK ("maxActors" > 0) +); +--> statement-breakpoint +CREATE TABLE "remote_instances" ( + "id" uuid PRIMARY KEY, + "host" varchar(100) NOT NULL UNIQUE, + "nodeInfoUrl" text, + "software" text, + "softwareVersion" text +); +--> statement-breakpoint +ALTER TABLE "instances" DROP CONSTRAINT "instances_slug_key";--> statement-breakpoint +ALTER TABLE "instances" DROP CONSTRAINT "instances_slug_check";--> statement-breakpoint +ALTER TABLE "instances" DROP CONSTRAINT "instances_expires_check";--> statement-breakpoint +ALTER TABLE "instances" ADD COLUMN "location" "location" NOT NULL;--> statement-breakpoint +ALTER TABLE "instances" DROP COLUMN "slug";--> statement-breakpoint +ALTER TABLE "instances" DROP COLUMN "expires";--> statement-breakpoint +ALTER TABLE "local_instances" ADD CONSTRAINT "local_instances_id_instances_id_fkey" FOREIGN KEY ("id") REFERENCES "instances"("id") ON DELETE CASCADE;--> statement-breakpoint +ALTER TABLE "remote_instances" ADD CONSTRAINT "remote_instances_id_instances_id_fkey" FOREIGN KEY ("id") REFERENCES "instances"("id") ON DELETE CASCADE; \ No newline at end of file diff --git a/packages/models/drizzle/20260802205417_separate-instance-local-remote/snapshot.json b/packages/models/drizzle/20260802205417_separate-instance-local-remote/snapshot.json new file mode 100644 index 0000000..fa02ce8 --- /dev/null +++ b/packages/models/drizzle/20260802205417_separate-instance-local-remote/snapshot.json @@ -0,0 +1,768 @@ +{ + "version": "8", + "dialect": "postgres", + "id": "08f1dfcb-1110-4550-9777-36d017ecfb10", + "prevIds": ["d11b25cc-8a54-4c9f-95c5-1891ce2a133a"], + "ddl": [ + { + "values": ["Local", "Remote"], + "name": "location", + "entityType": "enums", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "accounts", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "instance_members", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "instances", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "local_instances", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "login_tokens", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "remote_instances", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "sessions", + "entityType": "tables", + "schema": "public" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "varchar(100)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "10", + "generated": null, + "identity": null, + "name": "max_instances", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "admin", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP", + "generated": null, + "identity": null, + "name": "created", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "accountId", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "instanceId", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "admin", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "accepted", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP", + "generated": null, + "identity": null, + "name": "created", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "location", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "location", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP", + "generated": null, + "identity": null, + "name": "created", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "local_instances" + }, + { + "type": "varchar(63)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "local_instances" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires", + "entityType": "columns", + "schema": "public", + "table": "local_instances" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "10", + "generated": null, + "identity": null, + "name": "maxActors", + "entityType": "columns", + "schema": "public", + "table": "local_instances" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "accountId", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tokenHash", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "codeHash", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP", + "generated": null, + "identity": null, + "name": "created", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP + INTERVAL '15 minutes'", + "generated": null, + "identity": null, + "name": "expires", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "consumed", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "remote_instances" + }, + { + "type": "varchar(100)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "host", + "entityType": "columns", + "schema": "public", + "table": "remote_instances" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "nodeInfoUrl", + "entityType": "columns", + "schema": "public", + "table": "remote_instances" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "software", + "entityType": "columns", + "schema": "public", + "table": "remote_instances" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "softwareVersion", + "entityType": "columns", + "schema": "public", + "table": "remote_instances" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "accountId", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tokenHash", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP", + "generated": null, + "identity": null, + "name": "created", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP + INTERVAL '1 month'", + "generated": null, + "identity": null, + "name": "expires", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "accountId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": "\"accepted\" IS NOT NULL", + "with": "", + "method": "btree", + "concurrently": false, + "name": "instance_members_accountId_index", + "entityType": "indexes", + "schema": "public", + "table": "instance_members" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "instanceId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": "\"accepted\" IS NOT NULL", + "with": "", + "method": "btree", + "concurrently": false, + "name": "instance_members_instanceId_index", + "entityType": "indexes", + "schema": "public", + "table": "instance_members" + }, + { + "nameExplicit": false, + "columns": ["accountId"], + "schemaTo": "public", + "tableTo": "accounts", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "instance_members_accountId_accounts_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "instance_members" + }, + { + "nameExplicit": false, + "columns": ["instanceId"], + "schemaTo": "public", + "tableTo": "instances", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "instance_members_instanceId_instances_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "instance_members" + }, + { + "nameExplicit": false, + "columns": ["id"], + "schemaTo": "public", + "tableTo": "instances", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "local_instances_id_instances_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "local_instances" + }, + { + "nameExplicit": false, + "columns": ["accountId"], + "schemaTo": "public", + "tableTo": "accounts", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "login_tokens_accountId_accounts_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "login_tokens" + }, + { + "nameExplicit": false, + "columns": ["id"], + "schemaTo": "public", + "tableTo": "instances", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "remote_instances_id_instances_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "remote_instances" + }, + { + "nameExplicit": false, + "columns": ["accountId"], + "schemaTo": "public", + "tableTo": "accounts", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "sessions_accountId_accounts_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "sessions" + }, + { + "columns": ["instanceId", "accountId"], + "nameExplicit": false, + "name": "instance_members_pkey", + "entityType": "pks", + "schema": "public", + "table": "instance_members" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "accounts_pkey", + "schema": "public", + "table": "accounts", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "instances_pkey", + "schema": "public", + "table": "instances", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "local_instances_pkey", + "schema": "public", + "table": "local_instances", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "login_tokens_pkey", + "schema": "public", + "table": "login_tokens", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "remote_instances_pkey", + "schema": "public", + "table": "remote_instances", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "sessions_pkey", + "schema": "public", + "table": "sessions", + "entityType": "pks" + }, + { + "nameExplicit": false, + "columns": ["email"], + "nullsNotDistinct": false, + "name": "accounts_email_key", + "schema": "public", + "table": "accounts", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["slug"], + "nullsNotDistinct": false, + "name": "local_instances_slug_key", + "schema": "public", + "table": "local_instances", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["tokenHash"], + "nullsNotDistinct": false, + "name": "login_tokens_tokenHash_key", + "schema": "public", + "table": "login_tokens", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["host"], + "nullsNotDistinct": false, + "name": "remote_instances_host_key", + "schema": "public", + "table": "remote_instances", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["tokenHash"], + "nullsNotDistinct": false, + "name": "sessions_tokenHash_key", + "schema": "public", + "table": "sessions", + "entityType": "uniques" + }, + { + "value": "\"email\" ~ '^[^@]+@[^@]+\\.[^@]+$'", + "name": "accounts_email_check", + "entityType": "checks", + "schema": "public", + "table": "accounts" + }, + { + "value": "\"max_instances\" >= 0", + "name": "accounts_max_instances_check", + "entityType": "checks", + "schema": "public", + "table": "accounts" + }, + { + "value": "trim(both from \"name\") <> ''", + "name": "accounts_name_check", + "entityType": "checks", + "schema": "public", + "table": "accounts" + }, + { + "value": "\"slug\" ~ '^[a-z0-9-]{4,63}$'", + "name": "instances_slug_check", + "entityType": "checks", + "schema": "public", + "table": "local_instances" + }, + { + "value": "\"maxActors\" > 0", + "name": "instances_max_actors_check", + "entityType": "checks", + "schema": "public", + "table": "local_instances" + } + ], + "renames": [] +} diff --git a/packages/models/drizzle/20260804061119_instances-local-remote-constraint/migration.sql b/packages/models/drizzle/20260804061119_instances-local-remote-constraint/migration.sql new file mode 100644 index 0000000..563fe43 --- /dev/null +++ b/packages/models/drizzle/20260804061119_instances-local-remote-constraint/migration.sql @@ -0,0 +1,7 @@ +ALTER TABLE "local_instances" ADD COLUMN "location" "location" DEFAULT 'Local'::"location" NOT NULL;--> statement-breakpoint +ALTER TABLE "remote_instances" ADD COLUMN "location" "location" DEFAULT 'Remote'::"location" NOT NULL;--> statement-breakpoint +ALTER TABLE "instances" ADD CONSTRAINT "instances_id_location_key" UNIQUE("id","location");--> statement-breakpoint +ALTER TABLE "local_instances" ADD CONSTRAINT "local_instance_fk" FOREIGN KEY ("id","location") REFERENCES "instances"("id","location") ON DELETE CASCADE;--> statement-breakpoint +ALTER TABLE "remote_instances" ADD CONSTRAINT "remote_instance_fk" FOREIGN KEY ("id","location") REFERENCES "instances"("id","location") ON DELETE CASCADE;--> statement-breakpoint +ALTER TABLE "local_instances" ADD CONSTRAINT "local_check" CHECK ("location" = 'Local');--> statement-breakpoint +ALTER TABLE "remote_instances" ADD CONSTRAINT "remote_check" CHECK ("location" = 'Remote'); \ No newline at end of file diff --git a/packages/models/drizzle/20260804061119_instances-local-remote-constraint/snapshot.json b/packages/models/drizzle/20260804061119_instances-local-remote-constraint/snapshot.json new file mode 100644 index 0000000..c980d7c --- /dev/null +++ b/packages/models/drizzle/20260804061119_instances-local-remote-constraint/snapshot.json @@ -0,0 +1,843 @@ +{ + "version": "8", + "dialect": "postgres", + "id": "c40a9b41-e120-4f01-865b-68583fbb921c", + "prevIds": ["08f1dfcb-1110-4550-9777-36d017ecfb10"], + "ddl": [ + { + "values": ["Local", "Remote"], + "name": "location", + "entityType": "enums", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "accounts", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "instance_members", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "instances", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "local_instances", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "login_tokens", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "remote_instances", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "sessions", + "entityType": "tables", + "schema": "public" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "varchar(100)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "10", + "generated": null, + "identity": null, + "name": "max_instances", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "admin", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP", + "generated": null, + "identity": null, + "name": "created", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "accountId", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "instanceId", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "admin", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "accepted", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP", + "generated": null, + "identity": null, + "name": "created", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "location", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "location", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP", + "generated": null, + "identity": null, + "name": "created", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "local_instances" + }, + { + "type": "varchar(63)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "local_instances" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires", + "entityType": "columns", + "schema": "public", + "table": "local_instances" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "10", + "generated": null, + "identity": null, + "name": "maxActors", + "entityType": "columns", + "schema": "public", + "table": "local_instances" + }, + { + "type": "location", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'Local'", + "generated": null, + "identity": null, + "name": "location", + "entityType": "columns", + "schema": "public", + "table": "local_instances" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "accountId", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tokenHash", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "codeHash", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP", + "generated": null, + "identity": null, + "name": "created", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP + INTERVAL '15 minutes'", + "generated": null, + "identity": null, + "name": "expires", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "consumed", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "remote_instances" + }, + { + "type": "varchar(100)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "host", + "entityType": "columns", + "schema": "public", + "table": "remote_instances" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "nodeInfoUrl", + "entityType": "columns", + "schema": "public", + "table": "remote_instances" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "software", + "entityType": "columns", + "schema": "public", + "table": "remote_instances" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "softwareVersion", + "entityType": "columns", + "schema": "public", + "table": "remote_instances" + }, + { + "type": "location", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'Remote'", + "generated": null, + "identity": null, + "name": "location", + "entityType": "columns", + "schema": "public", + "table": "remote_instances" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "accountId", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tokenHash", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP", + "generated": null, + "identity": null, + "name": "created", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP + INTERVAL '1 month'", + "generated": null, + "identity": null, + "name": "expires", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "accountId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": "\"accepted\" IS NOT NULL", + "with": "", + "method": "btree", + "concurrently": false, + "name": "instance_members_accountId_index", + "entityType": "indexes", + "schema": "public", + "table": "instance_members" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "instanceId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": "\"accepted\" IS NOT NULL", + "with": "", + "method": "btree", + "concurrently": false, + "name": "instance_members_instanceId_index", + "entityType": "indexes", + "schema": "public", + "table": "instance_members" + }, + { + "nameExplicit": false, + "columns": ["accountId"], + "schemaTo": "public", + "tableTo": "accounts", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "instance_members_accountId_accounts_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "instance_members" + }, + { + "nameExplicit": false, + "columns": ["instanceId"], + "schemaTo": "public", + "tableTo": "instances", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "instance_members_instanceId_instances_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "instance_members" + }, + { + "nameExplicit": false, + "columns": ["id"], + "schemaTo": "public", + "tableTo": "instances", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "local_instances_id_instances_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "local_instances" + }, + { + "nameExplicit": true, + "columns": ["id", "location"], + "schemaTo": "public", + "tableTo": "instances", + "columnsTo": ["id", "location"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "local_instance_fk", + "entityType": "fks", + "schema": "public", + "table": "local_instances" + }, + { + "nameExplicit": false, + "columns": ["accountId"], + "schemaTo": "public", + "tableTo": "accounts", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "login_tokens_accountId_accounts_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "login_tokens" + }, + { + "nameExplicit": false, + "columns": ["id"], + "schemaTo": "public", + "tableTo": "instances", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "remote_instances_id_instances_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "remote_instances" + }, + { + "nameExplicit": true, + "columns": ["id", "location"], + "schemaTo": "public", + "tableTo": "instances", + "columnsTo": ["id", "location"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "remote_instance_fk", + "entityType": "fks", + "schema": "public", + "table": "remote_instances" + }, + { + "nameExplicit": false, + "columns": ["accountId"], + "schemaTo": "public", + "tableTo": "accounts", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "sessions_accountId_accounts_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "sessions" + }, + { + "columns": ["instanceId", "accountId"], + "nameExplicit": false, + "name": "instance_members_pkey", + "entityType": "pks", + "schema": "public", + "table": "instance_members" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "accounts_pkey", + "schema": "public", + "table": "accounts", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "instances_pkey", + "schema": "public", + "table": "instances", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "local_instances_pkey", + "schema": "public", + "table": "local_instances", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "login_tokens_pkey", + "schema": "public", + "table": "login_tokens", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "remote_instances_pkey", + "schema": "public", + "table": "remote_instances", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "sessions_pkey", + "schema": "public", + "table": "sessions", + "entityType": "pks" + }, + { + "nameExplicit": true, + "columns": ["id", "location"], + "nullsNotDistinct": false, + "name": "instances_id_location_key", + "entityType": "uniques", + "schema": "public", + "table": "instances" + }, + { + "nameExplicit": false, + "columns": ["email"], + "nullsNotDistinct": false, + "name": "accounts_email_key", + "schema": "public", + "table": "accounts", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["slug"], + "nullsNotDistinct": false, + "name": "local_instances_slug_key", + "schema": "public", + "table": "local_instances", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["tokenHash"], + "nullsNotDistinct": false, + "name": "login_tokens_tokenHash_key", + "schema": "public", + "table": "login_tokens", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["host"], + "nullsNotDistinct": false, + "name": "remote_instances_host_key", + "schema": "public", + "table": "remote_instances", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["tokenHash"], + "nullsNotDistinct": false, + "name": "sessions_tokenHash_key", + "schema": "public", + "table": "sessions", + "entityType": "uniques" + }, + { + "value": "\"email\" ~ '^[^@]+@[^@]+\\.[^@]+$'", + "name": "accounts_email_check", + "entityType": "checks", + "schema": "public", + "table": "accounts" + }, + { + "value": "\"max_instances\" >= 0", + "name": "accounts_max_instances_check", + "entityType": "checks", + "schema": "public", + "table": "accounts" + }, + { + "value": "trim(both from \"name\") <> ''", + "name": "accounts_name_check", + "entityType": "checks", + "schema": "public", + "table": "accounts" + }, + { + "value": "\"slug\" ~ '^[a-z0-9-]{4,63}$'", + "name": "instances_slug_check", + "entityType": "checks", + "schema": "public", + "table": "local_instances" + }, + { + "value": "\"maxActors\" > 0", + "name": "instances_max_actors_check", + "entityType": "checks", + "schema": "public", + "table": "local_instances" + }, + { + "value": "\"location\" = 'Local'", + "name": "local_check", + "entityType": "checks", + "schema": "public", + "table": "local_instances" + }, + { + "value": "\"location\" = 'Remote'", + "name": "remote_check", + "entityType": "checks", + "schema": "public", + "table": "remote_instances" + } + ], + "renames": [] +} diff --git a/packages/models/drizzle/20260818080259_dark_night_thrasher/migration.sql b/packages/models/drizzle/20260818080259_dark_night_thrasher/migration.sql new file mode 100644 index 0000000..8cda542 --- /dev/null +++ b/packages/models/drizzle/20260818080259_dark_night_thrasher/migration.sql @@ -0,0 +1,15 @@ +ALTER TABLE "local_instances" DROP CONSTRAINT "local_instances_id_instances_id_fkey";--> statement-breakpoint +ALTER TABLE "local_instances" DROP CONSTRAINT "local_instance_fk";--> statement-breakpoint +DROP TABLE "remote_instances";--> statement-breakpoint +ALTER TABLE "instances" DROP CONSTRAINT "instances_id_location_key";--> statement-breakpoint +ALTER TABLE "local_instances" DROP CONSTRAINT "local_check";--> statement-breakpoint +ALTER TABLE "instances" ADD COLUMN "localId" uuid;--> statement-breakpoint +ALTER TABLE "instances" ADD COLUMN "host" varchar(100) NOT NULL;--> statement-breakpoint +ALTER TABLE "instances" ADD COLUMN "nodeInfoUrl" text;--> statement-breakpoint +ALTER TABLE "instances" ADD COLUMN "software" text;--> statement-breakpoint +ALTER TABLE "instances" ADD COLUMN "softwareVersion" text;--> statement-breakpoint +ALTER TABLE "instances" DROP COLUMN "location";--> statement-breakpoint +ALTER TABLE "local_instances" DROP COLUMN "location";--> statement-breakpoint +ALTER TABLE "instances" ADD CONSTRAINT "instances_host_key" UNIQUE("host");--> statement-breakpoint +ALTER TABLE "instances" ADD CONSTRAINT "instances_localId_local_instances_id_fkey" FOREIGN KEY ("localId") REFERENCES "local_instances"("id") ON DELETE CASCADE;--> statement-breakpoint +DROP TYPE "location"; \ No newline at end of file diff --git a/packages/models/drizzle/20260818080259_dark_night_thrasher/snapshot.json b/packages/models/drizzle/20260818080259_dark_night_thrasher/snapshot.json new file mode 100644 index 0000000..7464399 --- /dev/null +++ b/packages/models/drizzle/20260818080259_dark_night_thrasher/snapshot.json @@ -0,0 +1,722 @@ +{ + "version": "8", + "dialect": "postgres", + "id": "d8ac955f-1e74-4493-ade9-a25c40b01c20", + "prevIds": ["c40a9b41-e120-4f01-865b-68583fbb921c"], + "ddl": [ + { + "isRlsEnabled": false, + "name": "accounts", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "instance_members", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "instances", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "local_instances", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "login_tokens", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "sessions", + "entityType": "tables", + "schema": "public" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "varchar(100)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "10", + "generated": null, + "identity": null, + "name": "max_instances", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "admin", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP", + "generated": null, + "identity": null, + "name": "created", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "accountId", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "instanceId", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "admin", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "accepted", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP", + "generated": null, + "identity": null, + "name": "created", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "localId", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP", + "generated": null, + "identity": null, + "name": "created", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "varchar(100)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "host", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "nodeInfoUrl", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "software", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "softwareVersion", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "local_instances" + }, + { + "type": "varchar(63)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "local_instances" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires", + "entityType": "columns", + "schema": "public", + "table": "local_instances" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "10", + "generated": null, + "identity": null, + "name": "maxActors", + "entityType": "columns", + "schema": "public", + "table": "local_instances" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "accountId", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tokenHash", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "codeHash", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP", + "generated": null, + "identity": null, + "name": "created", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP + INTERVAL '15 minutes'", + "generated": null, + "identity": null, + "name": "expires", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "consumed", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "accountId", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tokenHash", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP", + "generated": null, + "identity": null, + "name": "created", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP + INTERVAL '1 month'", + "generated": null, + "identity": null, + "name": "expires", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "accountId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": "\"accepted\" IS NOT NULL", + "with": "", + "method": "btree", + "concurrently": false, + "name": "instance_members_accountId_index", + "entityType": "indexes", + "schema": "public", + "table": "instance_members" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "instanceId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": "\"accepted\" IS NOT NULL", + "with": "", + "method": "btree", + "concurrently": false, + "name": "instance_members_instanceId_index", + "entityType": "indexes", + "schema": "public", + "table": "instance_members" + }, + { + "nameExplicit": false, + "columns": ["accountId"], + "schemaTo": "public", + "tableTo": "accounts", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "instance_members_accountId_accounts_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "instance_members" + }, + { + "nameExplicit": false, + "columns": ["instanceId"], + "schemaTo": "public", + "tableTo": "instances", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "instance_members_instanceId_instances_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "instance_members" + }, + { + "nameExplicit": false, + "columns": ["localId"], + "schemaTo": "public", + "tableTo": "local_instances", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "instances_localId_local_instances_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "instances" + }, + { + "nameExplicit": false, + "columns": ["accountId"], + "schemaTo": "public", + "tableTo": "accounts", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "login_tokens_accountId_accounts_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "login_tokens" + }, + { + "nameExplicit": false, + "columns": ["accountId"], + "schemaTo": "public", + "tableTo": "accounts", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "sessions_accountId_accounts_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "sessions" + }, + { + "columns": ["instanceId", "accountId"], + "nameExplicit": false, + "name": "instance_members_pkey", + "entityType": "pks", + "schema": "public", + "table": "instance_members" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "accounts_pkey", + "schema": "public", + "table": "accounts", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "instances_pkey", + "schema": "public", + "table": "instances", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "local_instances_pkey", + "schema": "public", + "table": "local_instances", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "login_tokens_pkey", + "schema": "public", + "table": "login_tokens", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "sessions_pkey", + "schema": "public", + "table": "sessions", + "entityType": "pks" + }, + { + "nameExplicit": false, + "columns": ["email"], + "nullsNotDistinct": false, + "name": "accounts_email_key", + "schema": "public", + "table": "accounts", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["host"], + "nullsNotDistinct": false, + "name": "instances_host_key", + "schema": "public", + "table": "instances", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["slug"], + "nullsNotDistinct": false, + "name": "local_instances_slug_key", + "schema": "public", + "table": "local_instances", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["tokenHash"], + "nullsNotDistinct": false, + "name": "login_tokens_tokenHash_key", + "schema": "public", + "table": "login_tokens", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["tokenHash"], + "nullsNotDistinct": false, + "name": "sessions_tokenHash_key", + "schema": "public", + "table": "sessions", + "entityType": "uniques" + }, + { + "value": "\"email\" ~ '^[^@]+@[^@]+\\.[^@]+$'", + "name": "accounts_email_check", + "entityType": "checks", + "schema": "public", + "table": "accounts" + }, + { + "value": "\"max_instances\" >= 0", + "name": "accounts_max_instances_check", + "entityType": "checks", + "schema": "public", + "table": "accounts" + }, + { + "value": "trim(both from \"name\") <> ''", + "name": "accounts_name_check", + "entityType": "checks", + "schema": "public", + "table": "accounts" + }, + { + "value": "\"slug\" ~ '^[a-z0-9-]{4,63}$'", + "name": "instances_slug_check", + "entityType": "checks", + "schema": "public", + "table": "local_instances" + }, + { + "value": "\"maxActors\" > 0", + "name": "instances_max_actors_check", + "entityType": "checks", + "schema": "public", + "table": "local_instances" + } + ], + "renames": [] +} diff --git a/packages/models/src/relations.ts b/packages/models/src/relations.ts index c1848df..299db4c 100644 --- a/packages/models/src/relations.ts +++ b/packages/models/src/relations.ts @@ -13,11 +13,11 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . + import { defineRelations } from "drizzle-orm"; import * as schema from "./schema.ts"; -// oxlint-disable-next-line eslint/max-lines-per-function export const relations = defineRelations(schema, (r) => ({ accounts: { instances: r.many.instances({ @@ -72,6 +72,16 @@ export const relations = defineRelations(schema, (r) => ({ accepted: { isNotNull: true }, }, }), + localInstances: r.one.localInstances({ + from: r.instances.localId, + to: r.localInstances.id, + }), + }, + localInstance: { + instances: r.one.instances({ + from: r.localInstances.id, + to: r.instances.localId, + }), }, sessions: { account: r.one.accounts({ diff --git a/packages/models/src/schema.ts b/packages/models/src/schema.ts index 6693785..d68bc1a 100644 --- a/packages/models/src/schema.ts +++ b/packages/models/src/schema.ts @@ -13,6 +13,7 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . + import { sql } from "drizzle-orm"; import { boolean, @@ -21,11 +22,14 @@ import { integer, pgTable, primaryKey, + text, timestamp, uuid, varchar, } from "drizzle-orm/pg-core"; +const currentTimestamp = sql`CURRENT_TIMESTAMP`; + /** * The database table to represent accounts. */ @@ -39,7 +43,7 @@ export const accounts = pgTable( admin: boolean().notNull().default(false), created: timestamp({ withTimezone: true }) .notNull() - .default(sql`CURRENT_TIMESTAMP`), + .default(currentTimestamp), }, (table) => [ check( @@ -57,27 +61,39 @@ export type NewAccount = typeof accounts.$inferInsert; /** * The database table to represent instances. */ -export const instances = pgTable( - "instances", +export const instances = pgTable("instances", { + id: uuid().primaryKey(), + localId: uuid().references(() => localInstances.id, { + onDelete: "cascade", + }), + created: timestamp({ withTimezone: true }) + .notNull() + .default(currentTimestamp), + host: varchar({ length: 100 }).notNull().unique(), + nodeInfoUrl: text(), + software: text(), + softwareVersion: text(), +}); + +export type Instance = typeof instances.$inferSelect; +export type NewInstance = typeof instances.$inferInsert; + +export const localInstances = pgTable( + "local_instances", { id: uuid().primaryKey(), - slug: varchar({ length: 100 }).notNull().unique(), + slug: varchar({ length: 63 }).notNull().unique(), expires: timestamp({ withTimezone: true }).notNull(), - created: timestamp({ withTimezone: true }) - .notNull() - .default(sql`CURRENT_TIMESTAMP`), + maxActors: integer().notNull().default(10), }, (table) => [ - check("instances_slug_check", sql`${table.slug} ~ '^[a-z0-9-]{4,100}$'`), - check( - "instances_expires_check", - sql`${table.expires} < (${table.created} + INTERVAL '1 year')`, - ), + check("instances_slug_check", sql`${table.slug} ~ '^[a-z0-9-]{4,63}$'`), + check("instances_max_actors_check", sql`${table.maxActors} > 0`), ], ); -export type Instance = typeof instances.$inferSelect; -export type NewInstance = typeof instances.$inferInsert; +export type LocalInstance = typeof localInstances.$inferSelect; +export type NewLocalInstance = typeof localInstances.$inferInsert; /** * The association table between instances and its member accounts. @@ -97,7 +113,7 @@ export const instanceMembers = pgTable( accepted: timestamp({ withTimezone: true }), created: timestamp({ withTimezone: true }) .notNull() - .default(sql`CURRENT_TIMESTAMP`), + .default(currentTimestamp), }, (table) => [ primaryKey({ columns: [table.instanceId, table.accountId] }), @@ -128,7 +144,7 @@ export const loginTokens = pgTable("login_tokens", { codeHash: varchar({ length: 64 }).notNull(), created: timestamp({ withTimezone: true }) .notNull() - .default(sql`CURRENT_TIMESTAMP`), + .default(currentTimestamp), expires: timestamp({ withTimezone: true }) .notNull() .default(sql`CURRENT_TIMESTAMP + INTERVAL '15 minutes'`), @@ -150,7 +166,7 @@ export const sessions = pgTable("sessions", { tokenHash: varchar({ length: 64 }).notNull().unique(), created: timestamp({ withTimezone: true }) .notNull() - .default(sql`CURRENT_TIMESTAMP`), + .default(currentTimestamp), expires: timestamp({ withTimezone: true }) .notNull() .default(sql`CURRENT_TIMESTAMP + INTERVAL '1 month'`), diff --git a/packages/web/.env.example b/packages/web/.env.example new file mode 100644 index 0000000..3a69b97 --- /dev/null +++ b/packages/web/.env.example @@ -0,0 +1 @@ +VITE_BACKEND_URL=http://127.0.0.1:8888 diff --git a/packages/web/src/routes/workspace/create/instance.tsx b/packages/web/src/routes/workspace/create/instance.tsx index a01d340..7e09a6b 100644 --- a/packages/web/src/routes/workspace/create/instance.tsx +++ b/packages/web/src/routes/workspace/create/instance.tsx @@ -31,7 +31,7 @@ const createInstanceMutation = graphql` ) { createInstance(slug: $slug) { ... on Instance { - slug + id } } } @@ -40,7 +40,7 @@ const createInstanceMutation = graphql` type CreateInstanceResult = | { payload: { - slug: string; + id: string; }; status: "success"; } @@ -82,15 +82,15 @@ const createInstanceAction = action(async (formData: FormData) => { message: errorMessage, status: "error", }); - } else if (response.createInstance.slug === undefined) { + } else if (response.createInstance.id === undefined) { resolve({ - message: "Empty Slug Returned", + message: "Empty ID Returned", status: "error", }); } else { resolve({ payload: { - slug: response.createInstance.slug, + id: response.createInstance.id, }, status: "success", }); diff --git a/scripts/dev.mts b/scripts/dev.mts index 4d94252..595642d 100644 --- a/scripts/dev.mts +++ b/scripts/dev.mts @@ -13,7 +13,7 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -// oxlint-disable no-console no-magic-numbers eslin/max-lines node/no-top-level-await +// oxlint-disable no-console no-magic-numbers node/no-top-level-await import { type ChildProcess, spawn } from "node:child_process"; import { existsSync } from "node:fs"; import { readFile, readdir, rm } from "node:fs/promises"; @@ -306,6 +306,7 @@ try { "../../.pgdata", "--listen=0.0.0.0:8888", "--log-format=color", + "--root-domain=drfed.org", ]; const logLevel = process.env.usage_log_level;