diff --git a/.env.example b/.env.example index 0672a8b..3c6360d 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,5 @@ NETLIFY_DATABASE_URL=postgresql://user:password@host:5432/db +# Schema: use `pnpm db:generate` then `pnpm db:migrate` (see drizzle/README.md). Avoid `db:push` on DBs with data. BETTER_AUTH_SECRET=generate-with-openssl-rand-base64-32 BETTER_AUTH_URL=http://localhost:3001 GOOGLE_CLIENT_ID= @@ -13,3 +14,17 @@ SYNC_APP_ID= SYNC_PUBLISH_KEY= UPLOADTHING_TOKEN= SYNC_ENGINE_URL= +# GitHub App integration (optional) +# In the GitHub App settings set Setup URL and Callback URL to: +# http://localhost:3001/api/git/github/callback (local) +# Webhook URL points at smee/ngrok → /api/webhooks/github (not the callback). +# Personal account linking also uses the Callback URL (user-to-server OAuth). +GITHUB_APP_ID= +GITHUB_APP_PRIVATE_KEY= +GITHUB_APP_WEBHOOK_SECRET= +GITHUB_APP_CLIENT_ID= +GITHUB_APP_CLIENT_SECRET= +GITHUB_APP_SLUG=project-y +GIT_TOKEN_ENCRYPTION_KEY= +# Log GitHub API payloads to the dev server console (on by default outside production) +# GITHUB_API_DEBUG=true diff --git a/.gitignore b/.gitignore index 2d458e4..e930c60 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +dumps/ + node_modules package-lock.json yarn.lock diff --git a/drizzle.baseline.config.ts b/drizzle.baseline.config.ts new file mode 100644 index 0000000..f265e86 --- /dev/null +++ b/drizzle.baseline.config.ts @@ -0,0 +1,21 @@ +import { type Config } from "drizzle-kit"; + +export default { + schema: ["./drizzle/baseline-schema/schema.ts", "./drizzle/baseline-schema/auth-schema.ts"], + out: "./drizzle", + dialect: "postgresql", + dbCredentials: { + url: process.env.NETLIFY_DATABASE_URL as string, + }, + tablesFilter: [ + "project-y_*", + "user", + "session", + "account", + "two_factor", + "verification", + "organization", + "member", + "invitation", + ], +} satisfies Config; diff --git a/drizzle.config.ts b/drizzle.config.ts index 5414ef8..9a5246d 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -2,6 +2,7 @@ import { type Config } from "drizzle-kit"; export default { schema: ["./src/db/schema.ts", "./src/db/auth-schema.ts"], + out: "./drizzle", dialect: "postgresql", dbCredentials: { url: process.env.NETLIFY_DATABASE_URL as string, diff --git a/drizzle/0000_baseline.sql b/drizzle/0000_baseline.sql new file mode 100644 index 0000000..21b85d1 --- /dev/null +++ b/drizzle/0000_baseline.sql @@ -0,0 +1,3 @@ +-- Baseline migration: production databases already contain this schema. +-- This migration exists so Drizzle can diff against the pre-git snapshot in meta/. +SELECT 1; diff --git a/drizzle/0001_git_integration.sql b/drizzle/0001_git_integration.sql new file mode 100644 index 0000000..2a88e2c --- /dev/null +++ b/drizzle/0001_git_integration.sql @@ -0,0 +1,174 @@ +CREATE TYPE "public"."git_branch_state" AS ENUM('active', 'deleted');--> statement-breakpoint +CREATE TYPE "public"."git_pr_state" AS ENUM('open', 'closed', 'merged', 'draft');--> statement-breakpoint +CREATE TYPE "public"."git_provider" AS ENUM('github', 'gitlab', 'gitlab_self_hosted');--> statement-breakpoint +CREATE TABLE "project-y_git_commit_status_application" ( + "delivery_id" varchar(64) NOT NULL, + "commit_sha" varchar(64) NOT NULL, + "rule_id" uuid NOT NULL, + "task_id" uuid NOT NULL, + "created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + CONSTRAINT "project-y_git_commit_status_application_delivery_id_commit_sha_rule_id_task_id_pk" PRIMARY KEY("delivery_id","commit_sha","rule_id","task_id") +); +--> statement-breakpoint +CREATE TABLE "project-y_git_connection" ( + "id" uuid PRIMARY KEY NOT NULL, + "owner" varchar(256) NOT NULL, + "provider" "git_provider" NOT NULL, + "installation_id" varchar(64), + "account_login" varchar(256), + "account_type" varchar(32), + "instance_url" varchar(512), + "credentials" text, + "connected_by_user_id" varchar(256), + "created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "project-y_git_repository" ( + "id" uuid PRIMARY KEY NOT NULL, + "connection_id" uuid NOT NULL, + "owner" varchar(256) NOT NULL, + "external_id" varchar(64) NOT NULL, + "full_name" varchar(512) NOT NULL, + "default_branch" varchar(256) DEFAULT 'main' NOT NULL, + "html_url" varchar(512) NOT NULL, + "is_archived" boolean DEFAULT false NOT NULL, + "created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "project-y_git_status_rule" ( + "id" uuid PRIMARY KEY NOT NULL, + "owner" varchar(256) NOT NULL, + "project_id" uuid, + "pattern" text NOT NULL, + "target_status_id" uuid NOT NULL, + "priority" integer DEFAULT 0 NOT NULL, + "created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "project-y_git_user_link" ( + "id" uuid PRIMARY KEY NOT NULL, + "user_id" varchar(256) NOT NULL, + "provider" "git_provider" NOT NULL, + "provider_user_id" varchar(256) NOT NULL, + "provider_login" varchar(256), + "access_token" text NOT NULL, + "refresh_token" text, + "token_expires_at" timestamp with time zone, + "scopes" text, + "created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "project-y_git_webhook_delivery" ( + "id" varchar(64) PRIMARY KEY NOT NULL, + "provider" "git_provider" NOT NULL, + "event_type" varchar(128) NOT NULL, + "processed_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL +); +--> statement-breakpoint +CREATE TABLE "project-y_project_git_repository" ( + "project_id" uuid NOT NULL, + "repository_id" uuid NOT NULL, + "owner" varchar(256) NOT NULL, + "is_primary" boolean DEFAULT false NOT NULL, + "default_base_branch" varchar(256), + "created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + CONSTRAINT "project-y_project_git_repository_project_id_repository_id_pk" PRIMARY KEY("project_id","repository_id") +); +--> statement-breakpoint +CREATE TABLE "project-y_task_git_activity" ( + "id" uuid PRIMARY KEY NOT NULL, + "task_id" uuid NOT NULL, + "owner" varchar(256) NOT NULL, + "type" varchar(64) NOT NULL, + "payload" jsonb NOT NULL, + "actor_login" varchar(256), + "occurred_at" timestamp with time zone NOT NULL, + "created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL +); +--> statement-breakpoint +CREATE TABLE "project-y_task_git_branch" ( + "id" uuid PRIMARY KEY NOT NULL, + "task_id" uuid NOT NULL, + "repository_id" uuid NOT NULL, + "owner" varchar(256) NOT NULL, + "ref" varchar(512) NOT NULL, + "sha" varchar(64), + "state" "git_branch_state" DEFAULT 'active' NOT NULL, + "created_by_user_id" varchar(256), + "created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "project-y_task_git_pull_request" ( + "id" uuid PRIMARY KEY NOT NULL, + "task_id" uuid NOT NULL, + "repository_id" uuid NOT NULL, + "branch_id" uuid, + "owner" varchar(256) NOT NULL, + "number" integer NOT NULL, + "provider_pr_id" varchar(64) NOT NULL, + "url" varchar(512) NOT NULL, + "title" varchar(512) NOT NULL, + "state" "git_pr_state" DEFAULT 'open' NOT NULL, + "head_ref" varchar(512) NOT NULL, + "base_ref" varchar(512) NOT NULL, + "checks_status" varchar(32), + "merged_at" timestamp with time zone, + "closed_at" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +ALTER TABLE "project-y_project" ADD COLUMN "task_key_prefix" varchar(16);--> statement-breakpoint +ALTER TABLE "project-y_project" ADD COLUMN "next_task_number" integer DEFAULT 1 NOT NULL;--> statement-breakpoint +ALTER TABLE "project-y_task" ADD COLUMN "number" integer;--> statement-breakpoint +UPDATE "project-y_task" AS t +SET "number" = numbered.rn +FROM ( + SELECT + id, + ROW_NUMBER() OVER ( + PARTITION BY owner, project_id + ORDER BY created_at + ) AS rn + FROM "project-y_task" +) AS numbered +WHERE t.id = numbered.id;--> statement-breakpoint +UPDATE "project-y_project" AS p +SET "next_task_number" = COALESCE( + ( + SELECT MAX(t."number") + 1 + FROM "project-y_task" AS t + WHERE t.project_id = p.id + AND t.owner = p.owner + ), + 1 +);--> statement-breakpoint +ALTER TABLE "project-y_task" ALTER COLUMN "number" SET NOT NULL;--> statement-breakpoint +ALTER TABLE "project-y_git_repository" ADD CONSTRAINT "project-y_git_repository_connection_id_project-y_git_connection_id_fk" FOREIGN KEY ("connection_id") REFERENCES "public"."project-y_git_connection"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "project-y_project_git_repository" ADD CONSTRAINT "project-y_project_git_repository_repository_id_project-y_git_repository_id_fk" FOREIGN KEY ("repository_id") REFERENCES "public"."project-y_git_repository"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "project-y_task_git_branch" ADD CONSTRAINT "project-y_task_git_branch_repository_id_project-y_git_repository_id_fk" FOREIGN KEY ("repository_id") REFERENCES "public"."project-y_git_repository"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "project-y_task_git_pull_request" ADD CONSTRAINT "project-y_task_git_pull_request_repository_id_project-y_git_repository_id_fk" FOREIGN KEY ("repository_id") REFERENCES "public"."project-y_git_repository"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "project-y_task_git_pull_request" ADD CONSTRAINT "project-y_task_git_pull_request_branch_id_project-y_task_git_branch_id_fk" FOREIGN KEY ("branch_id") REFERENCES "public"."project-y_task_git_branch"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "git_connection_owner_idx" ON "project-y_git_connection" USING btree ("owner");--> statement-breakpoint +CREATE UNIQUE INDEX "git_connection_owner_provider_install_idx" ON "project-y_git_connection" USING btree ("owner","provider","installation_id");--> statement-breakpoint +CREATE INDEX "git_repository_connection_idx" ON "project-y_git_repository" USING btree ("connection_id");--> statement-breakpoint +CREATE INDEX "git_repository_owner_idx" ON "project-y_git_repository" USING btree ("owner");--> statement-breakpoint +CREATE UNIQUE INDEX "git_repository_connection_external_idx" ON "project-y_git_repository" USING btree ("connection_id","external_id");--> statement-breakpoint +CREATE INDEX "git_status_rule_owner_idx" ON "project-y_git_status_rule" USING btree ("owner");--> statement-breakpoint +CREATE INDEX "git_user_link_user_idx" ON "project-y_git_user_link" USING btree ("user_id");--> statement-breakpoint +CREATE UNIQUE INDEX "git_user_link_user_provider_idx" ON "project-y_git_user_link" USING btree ("user_id","provider");--> statement-breakpoint +CREATE INDEX "git_webhook_delivery_provider_idx" ON "project-y_git_webhook_delivery" USING btree ("provider");--> statement-breakpoint +CREATE INDEX "project_git_repository_owner_idx" ON "project-y_project_git_repository" USING btree ("owner");--> statement-breakpoint +CREATE INDEX "task_git_activity_task_idx" ON "project-y_task_git_activity" USING btree ("task_id");--> statement-breakpoint +CREATE INDEX "task_git_branch_task_idx" ON "project-y_task_git_branch" USING btree ("task_id");--> statement-breakpoint +CREATE INDEX "task_git_branch_owner_ref_idx" ON "project-y_task_git_branch" USING btree ("owner","ref");--> statement-breakpoint +CREATE UNIQUE INDEX "task_git_branch_task_repo_active_idx" ON "project-y_task_git_branch" USING btree ("task_id","repository_id");--> statement-breakpoint +CREATE INDEX "task_git_pr_task_idx" ON "project-y_task_git_pull_request" USING btree ("task_id");--> statement-breakpoint +CREATE UNIQUE INDEX "task_git_pr_repo_number_idx" ON "project-y_task_git_pull_request" USING btree ("repository_id","number");--> statement-breakpoint +CREATE UNIQUE INDEX "project_owner_task_key_prefix_idx" ON "project-y_project" USING btree ("owner","task_key_prefix");--> statement-breakpoint +CREATE UNIQUE INDEX "task_owner_project_number_idx" ON "project-y_task" USING btree ("owner","project_id","number"); \ No newline at end of file diff --git a/drizzle/README.md b/drizzle/README.md new file mode 100644 index 0000000..79863d4 --- /dev/null +++ b/drizzle/README.md @@ -0,0 +1,30 @@ +# Database migrations + +Schema changes are managed with [Drizzle Kit](https://orm.drizzle.team/kit-docs/overview). **Do not hand-write migration files or edit `_journal.json`** — use the CLI so Drizzle keeps snapshots and the journal in sync. + +## Day to day + +1. Point `NETLIFY_DATABASE_URL` in `.env` at a database whose schema matches the latest applied migration (usually your local Docker Postgres after `pnpm db:switch-local`). +2. Change `src/db/schema.ts` (and related schema files). +3. Generate: `pnpm db:generate --name descriptive_name` +4. Review the generated SQL in `drizzle/`. If a migration must backfill data before `NOT NULL` or unique constraints, adjust the **generated** file (add nullable column → `UPDATE` → `SET NOT NULL`) — do not rewrite the migration from scratch. +5. Apply: `pnpm db:migrate` + +## Netlify deploys + +Production and preview builds run `pnpm db:migrate` before `pnpm build` (see `netlify.toml`). Ensure `NETLIFY_DATABASE_URL` is set in the Netlify site environment. Preview deploys must use a database whose schema matches the last applied migration (or a branch DB), not production unless you intend to migrate production from previews. + +## Existing databases (production / staging) + +Use **`pnpm db:migrate`**, not `pnpm db:push`. `db:push` diffs directly against the schema and can fail or drop data on columns like `tasks.number` without a backfill. + +- **`0000_baseline`**: no-op for databases that already had the pre–git-integration schema. +- **`0001_git_integration`**: adds git tables, task keys, and backfills `tasks.number`. + +## Fresh empty database + +Run `pnpm db:migrate` (applies baseline + git integration), or use `pnpm db:push` only when you are sure the database is empty and you accept schema-as-code without migration history. + +## Regenerating a baseline (rare) + +If you need a new baseline snapshot (e.g. after a major schema reset), use `drizzle.baseline.config.ts` and the copy under `drizzle/baseline-schema/` — see git history for the two-step `generate` flow. This is a one-time setup aid, not part of normal development. diff --git a/drizzle/baseline-schema/auth-schema.ts b/drizzle/baseline-schema/auth-schema.ts new file mode 100644 index 0000000..6cd312a --- /dev/null +++ b/drizzle/baseline-schema/auth-schema.ts @@ -0,0 +1,209 @@ +import { relations } from "drizzle-orm"; +import { + pgTable, + text, + timestamp, + boolean, + index, + uniqueIndex, +} from "drizzle-orm/pg-core"; + +export const user = pgTable("user", { + id: text("id").primaryKey(), + firstname: text("firstname").notNull(), + lastname: text("lastname").notNull(), + name: text("name").notNull(), + email: text("email").notNull().unique(), + emailVerified: boolean("email_verified").default(false).notNull(), + twoFactorEnabled: boolean("two_factor_enabled").default(false).notNull(), + image: text("image"), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .defaultNow() + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), +}); + +export const session = pgTable( + "session", + { + id: text("id").primaryKey(), + expiresAt: timestamp("expires_at").notNull(), + token: text("token").notNull().unique(), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + ipAddress: text("ip_address"), + userAgent: text("user_agent"), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + activeOrganizationId: text("active_organization_id"), + }, + (table) => [index("session_userId_idx").on(table.userId)], +); + +export const account = pgTable( + "account", + { + id: text("id").primaryKey(), + accountId: text("account_id").notNull(), + providerId: text("provider_id").notNull(), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + accessToken: text("access_token"), + refreshToken: text("refresh_token"), + idToken: text("id_token"), + accessTokenExpiresAt: timestamp("access_token_expires_at"), + refreshTokenExpiresAt: timestamp("refresh_token_expires_at"), + scope: text("scope"), + password: text("password"), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + }, + (table) => [index("account_userId_idx").on(table.userId)], +); + +export const twoFactor = pgTable( + "two_factor", + { + id: text("id").primaryKey(), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + secret: text("secret").notNull(), + backupCodes: text("backup_codes").notNull(), + verified: boolean("verified").default(false).notNull(), + }, + (table) => [index("two_factor_userId_idx").on(table.userId)], +); + +export const verification = pgTable( + "verification", + { + id: text("id").primaryKey(), + identifier: text("identifier").notNull(), + value: text("value").notNull(), + expiresAt: timestamp("expires_at").notNull(), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .defaultNow() + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + }, + (table) => [index("verification_identifier_idx").on(table.identifier)], +); + +export const organization = pgTable( + "organization", + { + id: text("id").primaryKey(), + name: text("name").notNull(), + slug: text("slug").notNull().unique(), + logo: text("logo"), + createdAt: timestamp("created_at").notNull(), + metadata: text("metadata"), + }, + (table) => [uniqueIndex("organization_slug_uidx").on(table.slug)], +); + +export const member = pgTable( + "member", + { + id: text("id").primaryKey(), + organizationId: text("organization_id") + .notNull() + .references(() => organization.id, { onDelete: "cascade" }), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + role: text("role").default("member").notNull(), + createdAt: timestamp("created_at").notNull(), + }, + (table) => [ + index("member_organizationId_idx").on(table.organizationId), + index("member_userId_idx").on(table.userId), + ], +); + +export const invitation = pgTable( + "invitation", + { + id: text("id").primaryKey(), + organizationId: text("organization_id") + .notNull() + .references(() => organization.id, { onDelete: "cascade" }), + email: text("email").notNull(), + role: text("role"), + status: text("status").default("pending").notNull(), + expiresAt: timestamp("expires_at").notNull(), + createdAt: timestamp("created_at").defaultNow().notNull(), + inviterId: text("inviter_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + }, + (table) => [ + index("invitation_organizationId_idx").on(table.organizationId), + index("invitation_email_idx").on(table.email), + ], +); + +export const userRelations = relations(user, ({ many }) => ({ + sessions: many(session), + accounts: many(account), + twoFactors: many(twoFactor), + members: many(member), + invitations: many(invitation), +})); + +export const twoFactorRelations = relations(twoFactor, ({ one }) => ({ + user: one(user, { + fields: [twoFactor.userId], + references: [user.id], + }), +})); + +export const sessionRelations = relations(session, ({ one }) => ({ + user: one(user, { + fields: [session.userId], + references: [user.id], + }), +})); + +export const accountRelations = relations(account, ({ one }) => ({ + user: one(user, { + fields: [account.userId], + references: [user.id], + }), +})); + +export const organizationRelations = relations(organization, ({ many }) => ({ + members: many(member), + invitations: many(invitation), +})); + +export const memberRelations = relations(member, ({ one }) => ({ + organization: one(organization, { + fields: [member.organizationId], + references: [organization.id], + }), + user: one(user, { + fields: [member.userId], + references: [user.id], + }), +})); + +export const invitationRelations = relations(invitation, ({ one }) => ({ + organization: one(organization, { + fields: [invitation.organizationId], + references: [organization.id], + }), + user: one(user, { + fields: [invitation.inviterId], + references: [user.id], + }), +})); diff --git a/drizzle/baseline-schema/schema.ts b/drizzle/baseline-schema/schema.ts new file mode 100644 index 0000000..5eda446 --- /dev/null +++ b/drizzle/baseline-schema/schema.ts @@ -0,0 +1,524 @@ +// Example model schema from the Drizzle docs +// https://orm.drizzle.team/docs/sql-schema-declaration + +import { relations, sql } from "drizzle-orm"; +import { + pgTableCreator, + uuid, + timestamp, + varchar, + text, + integer, + pgEnum, + primaryKey, + index, + boolean, + jsonb, +} from "drizzle-orm/pg-core"; +import { createInsertSchema } from "drizzle-zod"; +import { z } from "zod"; + +/** + * This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same + * database instance for multiple projects. + * + * @see https://orm.drizzle.team/docs/goodies#multi-project-schema + */ +export const createTable = pgTableCreator((name) => `project-y_${name}`); + +export const colorsEnum = pgEnum("color", [ + "red", + "orange", + "yellow", + "green", + "emerald", + "teal", + "cyan", + "blue", + "indigo", + "violet", + "purple", + "fuchsia", + "pink", + "rose", + "neutral", +]); +export const COLOR_VALUES = colorsEnum.enumValues; +export type Color = (typeof colorsEnum.enumValues)[number]; + +export const priorityEnum = pgEnum("priority", [ + "low", + "medium", + "high", + "critical", +]); +export const PRIORITY_VALUES = priorityEnum.enumValues; +export type Priority = (typeof priorityEnum.enumValues)[number]; + +export const tasks = createTable( + "task", + { + id: uuid("id").primaryKey(), + name: varchar("name", { length: 256 }).notNull(), + description: text("description"), + statusId: uuid("status_id"), + priority: priorityEnum("priority").notNull().default("medium"), + deadline: timestamp("deadline", { withTimezone: true }), + owner: varchar("owner", { length: 256 }).notNull(), + projectId: uuid("project_id").notNull(), + sprintId: uuid("sprint_id"), + createdAt: timestamp("created_at", { withTimezone: true }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull(), + }, + (example) => [ + index("task_owner_idx").on(example.owner), + index("task_owner_project_idx").on(example.owner, example.projectId), + index("task_owner_sprint_idx").on(example.owner, example.sprintId), + ] +); + +export const taskAssignees = createTable( + "task_assignees", + { + id: uuid("id").primaryKey(), + taskId: uuid("task_id") + .notNull() + .references(() => tasks.id, { onDelete: "cascade" }), + owner: varchar("owner", { length: 256 }).notNull(), + userId: varchar("user_id", { length: 256 }).notNull(), + assignedAt: timestamp("assigned_at").defaultNow(), + createdAt: timestamp("created_at", { withTimezone: true }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull(), + }, + (example) => [index("task_assignees_owner_idx").on(example.owner)] +); + +export const subTasks = createTable( + "sub_task", + { + id: uuid("id").primaryKey(), + description: text("description"), + taskId: uuid("task_id") + .notNull() + .references(() => tasks.id, { onDelete: "cascade" }), + done: boolean("done").notNull().default(false), + owner: varchar("owner", { length: 256 }).notNull(), + projectId: uuid("project_id").notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull(), + }, + (example) => [index("subtask_owner_idx").on(example.owner)] +); + +export const subTaskAssignees = createTable( + "sub_task_assignees", + { + id: uuid("id").primaryKey(), + subTaskId: uuid("sub_task_id") + .notNull() + .references(() => subTasks.id, { onDelete: "cascade" }), + owner: varchar("owner", { length: 256 }).notNull(), + userId: varchar("user_id", { length: 256 }).notNull(), + assignedAt: timestamp("assigned_at").defaultNow(), + createdAt: timestamp("created_at", { withTimezone: true }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull(), + }, + (example) => [index("sub_task_assignees_owner_idx").on(example.owner)] +); + +export const statuses = createTable( + "status", + { + id: uuid("id").primaryKey(), + name: varchar("name", { length: 256 }).notNull(), + color: colorsEnum("color").notNull().default("neutral"), + order: integer("order").notNull().default(0), + owner: varchar("owner", { length: 256 }).notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }), + }, + (example) => [index("status_owner_idx").on(example.owner)] +); + +export const labels = createTable( + "label", + { + id: uuid("id").primaryKey(), + name: varchar("name", { length: 256 }).notNull(), + color: colorsEnum("color").notNull().default("neutral"), + order: integer("order").notNull().default(0), + owner: varchar("owner", { length: 256 }).notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }), + }, + (example) => [index("label_owner_idx").on(example.owner)] +); + +export const projects = createTable( + "project", + { + id: uuid("id").primaryKey(), + name: varchar("name", { length: 256 }).notNull(), + description: text("description"), + owner: varchar("owner", { length: 256 }).notNull(), + logo: varchar("logo", { length: 256 }), + createdAt: timestamp("created_at", { withTimezone: true }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }), + }, + (example) => [index("project_owner_idx").on(example.owner)] +); + +export const comments = createTable( + "comment", + { + id: uuid("id").primaryKey(), + taskId: uuid("task_id") + .notNull() + .references(() => tasks.id, { onDelete: "cascade" }), + owner: varchar("owner", { length: 256 }).notNull(), + author: varchar("author", { length: 256 }).notNull(), + content: text("content").notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }), + }, + (example) => [ + index("comment_owner_idx").on(example.owner), + index("comment_task_owner_idx").on(example.taskId, example.owner), + ] +); + +export const attachments = createTable( + "attachment", + { + id: uuid("id").primaryKey(), + taskId: uuid("task_id") + .notNull() + .references(() => tasks.id), + owner: varchar("owner", { length: 256 }).notNull(), + name: varchar("name", { length: 256 }).notNull(), + size: integer("size").notNull(), + type: varchar("type", { length: 256 }).notNull(), + uploadedBy: varchar("uploaded_by", { length: 256 }).notNull(), + providerFileId: varchar("provider_file_id", { length: 256 }).notNull(), + url: varchar("url", { length: 256 }).notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }), + }, + (example) => [index("attachment_owner_idx").on(example.owner)] +); + +export const sprints = createTable( + "sprint", + { + id: uuid("id").primaryKey(), + name: varchar("name", { length: 256 }).notNull(), + start: timestamp("start", { withTimezone: true }).notNull(), + end: timestamp("end", { withTimezone: true }).notNull(), + owner: varchar("owner", { length: 256 }).notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }), + }, + (example) => [index("sprint_owner_idx").on(example.owner)] +); + +export const attachmentRelations = relations(attachments, ({ one }) => ({ + task: one(tasks, { + fields: [attachments.taskId], + references: [tasks.id], + }), +})); + +export const commentRelations = relations(comments, ({ one }) => ({ + task: one(tasks, { + fields: [comments.taskId], + references: [tasks.id], + }), +})); + +export const TASK_VIEW_MODES = ["board", "list"] as const; +export type TaskViewMode = (typeof TASK_VIEW_MODES)[number]; + +export const userPreferencesSchema = z.object({ + taskViewMode: z.enum(TASK_VIEW_MODES).default("board"), +}); +export type UserPreferences = z.infer; + +export const updateUserPreferencesValidator = userPreferencesSchema + .partial() + .refine((data) => Object.keys(data).length > 0, { + message: "At least one preference must be provided", + }); + +export const userPreferences = createTable("user_preferences", { + userId: text("user_id").primaryKey(), + preferences: jsonb("preferences") + .$type() + .notNull() + .default({ taskViewMode: "board" }), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), +}); + +export const labelsToTasks = createTable( + "labels_to_tasks", + { + labelId: uuid("label_id") + .notNull() + .references(() => labels.id), + taskId: uuid("task_id") + .notNull() + .references(() => tasks.id, { onDelete: "cascade" }), + }, + (t) => [primaryKey({ columns: [t.labelId, t.taskId] })] +); + +export const labelsToTasksRelations = relations(labelsToTasks, ({ one }) => ({ + task: one(tasks, { + fields: [labelsToTasks.taskId], + references: [tasks.id], + }), + label: one(labels, { + fields: [labelsToTasks.labelId], + references: [labels.id], + }), +})); + +export const taskRelations = relations(tasks, ({ one, many }) => ({ + status: one(statuses, { + fields: [tasks.statusId], + references: [statuses.id], + }), + labelsToTasks: many(labelsToTasks), + project: one(projects, { + fields: [tasks.projectId], + references: [projects.id], + }), + attachments: many(attachments), + assignees: many(taskAssignees), + subTasks: many(subTasks), + sprint: one(sprints, { + fields: [tasks.sprintId], + references: [sprints.id], + }), +})); + +export const taskAssigneesRelations = relations(taskAssignees, ({ one }) => ({ + task: one(tasks, { + fields: [taskAssignees.taskId], + references: [tasks.id], + }), +})); + +export const subTaskAssigneesRelations = relations( + subTaskAssignees, + ({ one }) => ({ + subTask: one(subTasks, { + fields: [subTaskAssignees.subTaskId], + references: [subTasks.id], + }), + }) +); + +export const subTaskRelations = relations(subTasks, ({ one, many }) => ({ + task: one(tasks, { + fields: [subTasks.taskId], + references: [tasks.id], + }), + assignees: many(subTaskAssignees), +})); + +export const labelRelations = relations(labels, ({ many }) => ({ + labelsToTasks: many(labelsToTasks), +})); + +export type Task = typeof tasks.$inferSelect; +export type TaskWithRelations = Task & { + labels: (typeof labels.$inferSelect)[]; + attachments: (typeof attachments.$inferSelect)[]; + project: Project; + sprint: Sprint | null; + assignees: (typeof taskAssignees.$inferSelect)[]; + subTasks: SubTask[]; +}; +export const insertTaskValidator = createInsertSchema(tasks, { + id: (schema) => schema.optional(), + owner: (schema) => schema.optional(), +}); +/** Partial task updates; dates are coerced because RPC serializes them as strings. */ +export const updateTaskValidator = z.object({ + id: z.string().uuid(), + name: z.string().min(1).max(256).optional(), + description: z.string().nullable().optional(), + statusId: z.string().uuid().nullable().optional(), + priority: z.enum(PRIORITY_VALUES).optional(), + deadline: z.coerce.date().nullable().optional(), + projectId: z.string().uuid().optional(), + sprintId: z.string().uuid().nullable().optional(), +}); +export type CreateTask = Omit< + typeof tasks.$inferInsert, + "id" | "createdAt" | "updatedAt" | "owner" +>; +export type UpdateTask = z.infer; + +export type SubTask = typeof subTasks.$inferSelect & { + assignees: (typeof subTaskAssignees.$inferSelect)[]; +}; +export const insertSubTaskValidator = createInsertSchema(subTasks, { + id: (schema) => schema.optional(), + owner: (schema) => schema.optional(), + updatedAt: (schema) => schema.optional(), +}); +export const updateSubTaskValidator = createInsertSchema(subTasks, { + id: (schema) => schema.optional(), + owner: (schema) => schema.optional(), + done: (schema) => schema.optional(), + taskId: (schema) => schema.optional(), + projectId: (schema) => schema.optional(), + updatedAt: (schema) => schema.optional(), +}); +export type CreateSubTask = Omit< + typeof subTasks.$inferInsert, + "id" | "createdAt" | "updatedAt" | "owner" +>; +export type UpdateSubTask = { + id: string; + description?: string; + taskId: string; + done?: boolean; + projectId: string; + owner?: string; + updatedAt?: Date; +}; + +export const assignTaskValidator = createInsertSchema(taskAssignees, { + id: (schema) => schema.optional(), + owner: (schema) => schema.optional(), + assignedAt: (schema) => schema.optional(), + updatedAt: (schema) => schema.optional(), +}); +export type AssignTask = Omit< + typeof taskAssignees.$inferInsert, + "id" | "createdAt" | "updatedAt" | "owner" +>; + +export type Comment = typeof comments.$inferSelect; +export const insertCommentValidator = createInsertSchema(comments, { + id: (schema) => schema.optional(), + owner: (schema) => schema.optional(), +}); +export type CreateComment = Omit< + typeof comments.$inferInsert, + "id" | "createdAt" | "updatedAt" | "owner" +>; +export type UpdateComment = { + id: string; + content?: string; +}; + +export type Attachment = typeof attachments.$inferSelect; +export const insertAttachmentValidator = createInsertSchema(attachments, { + id: (schema) => schema.optional(), + owner: (schema) => schema.optional(), +}); +export type CreateAttachment = Omit< + typeof attachments.$inferInsert, + "id" | "createdAt" | "updatedAt" | "owner" +>; +export type UpdateAttachment = { + id: string; + content?: string; +}; + +export type Sprint = typeof sprints.$inferSelect; +export const insertSprintValidator = createInsertSchema(sprints, { + id: (schema) => schema.optional(), + owner: (schema) => schema.optional(), +}); +export const updateSprintValidator = createInsertSchema(sprints, { + owner: (schema) => schema.optional(), + name: (schema) => schema.optional(), + start: (schema) => schema.optional(), + end: (schema) => schema.optional(), +}); +export type CreateSprint = Omit< + typeof sprints.$inferInsert, + "id" | "createdAt" | "updatedAt" | "owner" +>; +export type UpdateSprint = { + id: string; + content?: string; +}; + +export type Project = typeof projects.$inferSelect; +export const insertProjectValidator = createInsertSchema(projects, { + id: (schema) => schema.optional(), + owner: (schema) => schema.optional(), +}); +export const updateProjectValidator = createInsertSchema(projects, { + owner: (schema) => schema.optional(), + name: (schema) => schema.optional(), + description: (schema) => schema.optional(), + logo: (schema) => schema.optional(), +}); +export type CreateProject = Omit< + typeof projects.$inferInsert, + "id" | "createdAt" | "updatedAt" | "owner" +>; +export type UpdateProject = { + id: string; + content?: string; +}; + +export type Status = typeof statuses.$inferInsert; +export const insertStatusValidator = createInsertSchema(statuses, { + id: (schema) => schema.optional(), + owner: (schema) => schema.optional(), +}); +export const updateStatusValidator = createInsertSchema(statuses, { + id: (schema) => schema.optional(), + owner: (schema) => schema.optional(), +}); +export const updateMultipleStatusesValidator = updateStatusValidator.array(); +export type CreateStatus = Omit< + Status, + "id" | "createdAt" | "updatedAt" | "owner" +>; +export type UpdateStatus = Omit; + +export type Label = typeof labels.$inferInsert; +export const insertLabelValidator = createInsertSchema(labels, { + id: (schema) => schema.optional(), + owner: (schema) => schema.optional(), +}); +export const updateLabelValidator = createInsertSchema(labels, { + id: (schema) => schema.optional(), + owner: (schema) => schema.optional(), +}); +export const updateMultipleLabelsValidator = updateLabelValidator.array(); +export type CreateLabel = Omit< + Label, + "id" | "createdAt" | "updatedAt" | "owner" +>; +export type UpdateLabel = Omit; diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..6a5240d --- /dev/null +++ b/drizzle/meta/0000_snapshot.json @@ -0,0 +1,1812 @@ +{ + "id": "e0a26639-14be-401f-b3b0-d501e6ff64d9", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.project-y_attachment": { + "name": "project-y_attachment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "provider_file_id": { + "name": "provider_file_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "attachment_owner_idx": { + "name": "attachment_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project-y_attachment_task_id_project-y_task_id_fk": { + "name": "project-y_attachment_task_id_project-y_task_id_fk", + "tableFrom": "project-y_attachment", + "tableTo": "project-y_task", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_comment": { + "name": "project-y_comment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "author": { + "name": "author", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "comment_owner_idx": { + "name": "comment_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "comment_task_owner_idx": { + "name": "comment_task_owner_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project-y_comment_task_id_project-y_task_id_fk": { + "name": "project-y_comment_task_id_project-y_task_id_fk", + "tableFrom": "project-y_comment", + "tableTo": "project-y_task", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_label": { + "name": "project-y_label", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "color", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'neutral'" + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "label_owner_idx": { + "name": "label_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_labels_to_tasks": { + "name": "project-y_labels_to_tasks", + "schema": "", + "columns": { + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "project-y_labels_to_tasks_label_id_project-y_label_id_fk": { + "name": "project-y_labels_to_tasks_label_id_project-y_label_id_fk", + "tableFrom": "project-y_labels_to_tasks", + "tableTo": "project-y_label", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "project-y_labels_to_tasks_task_id_project-y_task_id_fk": { + "name": "project-y_labels_to_tasks_task_id_project-y_task_id_fk", + "tableFrom": "project-y_labels_to_tasks", + "tableTo": "project-y_task", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project-y_labels_to_tasks_label_id_task_id_pk": { + "name": "project-y_labels_to_tasks_label_id_task_id_pk", + "columns": [ + "label_id", + "task_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_project": { + "name": "project-y_project", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "project_owner_idx": { + "name": "project_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_sprint": { + "name": "project-y_sprint", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "start": { + "name": "start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "end": { + "name": "end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sprint_owner_idx": { + "name": "sprint_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_status": { + "name": "project-y_status", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "color", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'neutral'" + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "status_owner_idx": { + "name": "status_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_sub_task_assignees": { + "name": "project-y_sub_task_assignees", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "sub_task_id": { + "name": "sub_task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "sub_task_assignees_owner_idx": { + "name": "sub_task_assignees_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project-y_sub_task_assignees_sub_task_id_project-y_sub_task_id_fk": { + "name": "project-y_sub_task_assignees_sub_task_id_project-y_sub_task_id_fk", + "tableFrom": "project-y_sub_task_assignees", + "tableTo": "project-y_sub_task", + "columnsFrom": [ + "sub_task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_sub_task": { + "name": "project-y_sub_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "done": { + "name": "done", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "subtask_owner_idx": { + "name": "subtask_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project-y_sub_task_task_id_project-y_task_id_fk": { + "name": "project-y_sub_task_task_id_project-y_task_id_fk", + "tableFrom": "project-y_sub_task", + "tableTo": "project-y_task", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_task_assignees": { + "name": "project-y_task_assignees", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "task_assignees_owner_idx": { + "name": "task_assignees_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project-y_task_assignees_task_id_project-y_task_id_fk": { + "name": "project-y_task_assignees_task_id_project-y_task_id_fk", + "tableFrom": "project-y_task_assignees", + "tableTo": "project-y_task", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_task": { + "name": "project-y_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_id": { + "name": "status_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "priority", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "deadline": { + "name": "deadline", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sprint_id": { + "name": "sprint_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "task_owner_idx": { + "name": "task_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_owner_project_idx": { + "name": "task_owner_project_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_owner_sprint_idx": { + "name": "task_owner_sprint_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sprint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_user_preferences": { + "name": "project-y_user_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "preferences": { + "name": "preferences", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"taskViewMode\":\"board\"}'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "invitation_organizationId_idx": { + "name": "invitation_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "member_organizationId_idx": { + "name": "member_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_userId_idx": { + "name": "member_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "organization_slug_uidx": { + "name": "organization_slug_uidx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_slug_unique": { + "name": "organization_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "two_factor_userId_idx": { + "name": "two_factor_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "two_factor_user_id_user_id_fk": { + "name": "two_factor_user_id_user_id_fk", + "tableFrom": "two_factor", + "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": "text", + "primaryKey": true, + "notNull": true + }, + "firstname": { + "name": "firstname", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastname": { + "name": "lastname", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.color": { + "name": "color", + "schema": "public", + "values": [ + "red", + "orange", + "yellow", + "green", + "emerald", + "teal", + "cyan", + "blue", + "indigo", + "violet", + "purple", + "fuchsia", + "pink", + "rose", + "neutral" + ] + }, + "public.priority": { + "name": "priority", + "schema": "public", + "values": [ + "low", + "medium", + "high", + "critical" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..502b72d --- /dev/null +++ b/drizzle/meta/0001_snapshot.json @@ -0,0 +1,2975 @@ +{ + "id": "1d79b88e-4e52-4d43-a326-af68d8afb91e", + "prevId": "e0a26639-14be-401f-b3b0-d501e6ff64d9", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.project-y_attachment": { + "name": "project-y_attachment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "provider_file_id": { + "name": "provider_file_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "attachment_owner_idx": { + "name": "attachment_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project-y_attachment_task_id_project-y_task_id_fk": { + "name": "project-y_attachment_task_id_project-y_task_id_fk", + "tableFrom": "project-y_attachment", + "tableTo": "project-y_task", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_comment": { + "name": "project-y_comment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "author": { + "name": "author", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "comment_owner_idx": { + "name": "comment_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "comment_task_owner_idx": { + "name": "comment_task_owner_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project-y_comment_task_id_project-y_task_id_fk": { + "name": "project-y_comment_task_id_project-y_task_id_fk", + "tableFrom": "project-y_comment", + "tableTo": "project-y_task", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_label": { + "name": "project-y_label", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "color", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'neutral'" + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "label_owner_idx": { + "name": "label_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_labels_to_tasks": { + "name": "project-y_labels_to_tasks", + "schema": "", + "columns": { + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "project-y_labels_to_tasks_label_id_project-y_label_id_fk": { + "name": "project-y_labels_to_tasks_label_id_project-y_label_id_fk", + "tableFrom": "project-y_labels_to_tasks", + "tableTo": "project-y_label", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "project-y_labels_to_tasks_task_id_project-y_task_id_fk": { + "name": "project-y_labels_to_tasks_task_id_project-y_task_id_fk", + "tableFrom": "project-y_labels_to_tasks", + "tableTo": "project-y_task", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project-y_labels_to_tasks_label_id_task_id_pk": { + "name": "project-y_labels_to_tasks_label_id_task_id_pk", + "columns": [ + "label_id", + "task_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_project": { + "name": "project-y_project", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "task_key_prefix": { + "name": "task_key_prefix", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "next_task_number": { + "name": "next_task_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "project_owner_idx": { + "name": "project_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_owner_task_key_prefix_idx": { + "name": "project_owner_task_key_prefix_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_sprint": { + "name": "project-y_sprint", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "start": { + "name": "start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "end": { + "name": "end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sprint_owner_idx": { + "name": "sprint_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_status": { + "name": "project-y_status", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "color", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'neutral'" + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "status_owner_idx": { + "name": "status_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_sub_task_assignees": { + "name": "project-y_sub_task_assignees", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "sub_task_id": { + "name": "sub_task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "sub_task_assignees_owner_idx": { + "name": "sub_task_assignees_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project-y_sub_task_assignees_sub_task_id_project-y_sub_task_id_fk": { + "name": "project-y_sub_task_assignees_sub_task_id_project-y_sub_task_id_fk", + "tableFrom": "project-y_sub_task_assignees", + "tableTo": "project-y_sub_task", + "columnsFrom": [ + "sub_task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_sub_task": { + "name": "project-y_sub_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "done": { + "name": "done", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "subtask_owner_idx": { + "name": "subtask_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project-y_sub_task_task_id_project-y_task_id_fk": { + "name": "project-y_sub_task_task_id_project-y_task_id_fk", + "tableFrom": "project-y_sub_task", + "tableTo": "project-y_task", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_task_assignees": { + "name": "project-y_task_assignees", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "task_assignees_owner_idx": { + "name": "task_assignees_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project-y_task_assignees_task_id_project-y_task_id_fk": { + "name": "project-y_task_assignees_task_id_project-y_task_id_fk", + "tableFrom": "project-y_task_assignees", + "tableTo": "project-y_task", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_task": { + "name": "project-y_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "number": { + "name": "number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_id": { + "name": "status_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "priority", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "deadline": { + "name": "deadline", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sprint_id": { + "name": "sprint_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "task_owner_idx": { + "name": "task_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_owner_project_idx": { + "name": "task_owner_project_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_owner_sprint_idx": { + "name": "task_owner_sprint_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sprint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_owner_project_number_idx": { + "name": "task_owner_project_number_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_user_preferences": { + "name": "project-y_user_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "preferences": { + "name": "preferences", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"taskViewMode\":\"board\"}'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_git_commit_status_application": { + "name": "project-y_git_commit_status_application", + "schema": "", + "columns": { + "delivery_id": { + "name": "delivery_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "commit_sha": { + "name": "commit_sha", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "rule_id": { + "name": "rule_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "project-y_git_commit_status_application_delivery_id_commit_sha_rule_id_task_id_pk": { + "name": "project-y_git_commit_status_application_delivery_id_commit_sha_rule_id_task_id_pk", + "columns": [ + "delivery_id", + "commit_sha", + "rule_id", + "task_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_git_connection": { + "name": "project-y_git_connection", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "git_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "installation_id": { + "name": "installation_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "account_login": { + "name": "account_login", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "account_type": { + "name": "account_type", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "instance_url": { + "name": "instance_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "credentials": { + "name": "credentials", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connected_by_user_id": { + "name": "connected_by_user_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "git_connection_owner_idx": { + "name": "git_connection_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "git_connection_owner_provider_install_idx": { + "name": "git_connection_owner_provider_install_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_git_repository": { + "name": "project-y_git_repository", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "full_name": { + "name": "full_name", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "default_branch": { + "name": "default_branch", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true, + "default": "'main'" + }, + "html_url": { + "name": "html_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "is_archived": { + "name": "is_archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "git_repository_connection_idx": { + "name": "git_repository_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "git_repository_owner_idx": { + "name": "git_repository_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "git_repository_connection_external_idx": { + "name": "git_repository_connection_external_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project-y_git_repository_connection_id_project-y_git_connection_id_fk": { + "name": "project-y_git_repository_connection_id_project-y_git_connection_id_fk", + "tableFrom": "project-y_git_repository", + "tableTo": "project-y_git_connection", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_git_status_rule": { + "name": "project-y_git_status_rule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_status_id": { + "name": "target_status_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "git_status_rule_owner_idx": { + "name": "git_status_rule_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_git_user_link": { + "name": "project-y_git_user_link", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "git_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "provider_user_id": { + "name": "provider_user_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "provider_login": { + "name": "provider_login", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_expires_at": { + "name": "token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "git_user_link_user_idx": { + "name": "git_user_link_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "git_user_link_user_provider_idx": { + "name": "git_user_link_user_provider_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_git_webhook_delivery": { + "name": "project-y_git_webhook_delivery", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "git_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "git_webhook_delivery_provider_idx": { + "name": "git_webhook_delivery_provider_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_project_git_repository": { + "name": "project-y_project_git_repository", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "repository_id": { + "name": "repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default_base_branch": { + "name": "default_base_branch", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "project_git_repository_owner_idx": { + "name": "project_git_repository_owner_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project-y_project_git_repository_repository_id_project-y_git_repository_id_fk": { + "name": "project-y_project_git_repository_repository_id_project-y_git_repository_id_fk", + "tableFrom": "project-y_project_git_repository", + "tableTo": "project-y_git_repository", + "columnsFrom": [ + "repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project-y_project_git_repository_project_id_repository_id_pk": { + "name": "project-y_project_git_repository_project_id_repository_id_pk", + "columns": [ + "project_id", + "repository_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_task_git_activity": { + "name": "project-y_task_git_activity", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "actor_login": { + "name": "actor_login", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "task_git_activity_task_idx": { + "name": "task_git_activity_task_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_task_git_branch": { + "name": "project-y_task_git_branch", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "repository_id": { + "name": "repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "ref": { + "name": "ref", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "sha": { + "name": "sha", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "git_branch_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "task_git_branch_task_idx": { + "name": "task_git_branch_task_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_git_branch_owner_ref_idx": { + "name": "task_git_branch_owner_ref_idx", + "columns": [ + { + "expression": "owner", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_git_branch_task_repo_active_idx": { + "name": "task_git_branch_task_repo_active_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project-y_task_git_branch_repository_id_project-y_git_repository_id_fk": { + "name": "project-y_task_git_branch_repository_id_project-y_git_repository_id_fk", + "tableFrom": "project-y_task_git_branch", + "tableTo": "project-y_git_repository", + "columnsFrom": [ + "repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project-y_task_git_pull_request": { + "name": "project-y_task_git_pull_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "repository_id": { + "name": "repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "branch_id": { + "name": "branch_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner": { + "name": "owner", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "number": { + "name": "number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_pr_id": { + "name": "provider_pr_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "git_pr_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "head_ref": { + "name": "head_ref", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "base_ref": { + "name": "base_ref", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "checks_status": { + "name": "checks_status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "merged_at": { + "name": "merged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "task_git_pr_task_idx": { + "name": "task_git_pr_task_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_git_pr_repo_number_idx": { + "name": "task_git_pr_repo_number_idx", + "columns": [ + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project-y_task_git_pull_request_repository_id_project-y_git_repository_id_fk": { + "name": "project-y_task_git_pull_request_repository_id_project-y_git_repository_id_fk", + "tableFrom": "project-y_task_git_pull_request", + "tableTo": "project-y_git_repository", + "columnsFrom": [ + "repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project-y_task_git_pull_request_branch_id_project-y_task_git_branch_id_fk": { + "name": "project-y_task_git_pull_request_branch_id_project-y_task_git_branch_id_fk", + "tableFrom": "project-y_task_git_pull_request", + "tableTo": "project-y_task_git_branch", + "columnsFrom": [ + "branch_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "invitation_organizationId_idx": { + "name": "invitation_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "member_organizationId_idx": { + "name": "member_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_userId_idx": { + "name": "member_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "organization_slug_uidx": { + "name": "organization_slug_uidx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_slug_unique": { + "name": "organization_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "two_factor_userId_idx": { + "name": "two_factor_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "two_factor_user_id_user_id_fk": { + "name": "two_factor_user_id_user_id_fk", + "tableFrom": "two_factor", + "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": "text", + "primaryKey": true, + "notNull": true + }, + "firstname": { + "name": "firstname", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastname": { + "name": "lastname", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.color": { + "name": "color", + "schema": "public", + "values": [ + "red", + "orange", + "yellow", + "green", + "emerald", + "teal", + "cyan", + "blue", + "indigo", + "violet", + "purple", + "fuchsia", + "pink", + "rose", + "neutral" + ] + }, + "public.priority": { + "name": "priority", + "schema": "public", + "values": [ + "low", + "medium", + "high", + "critical" + ] + }, + "public.git_branch_state": { + "name": "git_branch_state", + "schema": "public", + "values": [ + "active", + "deleted" + ] + }, + "public.git_pr_state": { + "name": "git_pr_state", + "schema": "public", + "values": [ + "open", + "closed", + "merged", + "draft" + ] + }, + "public.git_provider": { + "name": "git_provider", + "schema": "public", + "values": [ + "github", + "gitlab", + "gitlab_self_hosted" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json new file mode 100644 index 0000000..8834308 --- /dev/null +++ b/drizzle/meta/_journal.json @@ -0,0 +1,20 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1779434449000, + "tag": "0000_baseline", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1779434451000, + "tag": "0001_git_integration", + "breakpoints": true + } + ] +} diff --git a/netlify.toml b/netlify.toml index fe33e82..ceea5a9 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,3 +1,3 @@ [build] - command = "pnpm build" + command = "pnpm db:migrate && pnpm build" publish = "dist/client" diff --git a/package.json b/package.json index b47509b..93ea7a1 100644 --- a/package.json +++ b/package.json @@ -10,14 +10,21 @@ "build": "vite build && tsc --noEmit", "lint": "eslint src --max-warnings 0", "preview": "vite preview", + "db:generate": "drizzle-kit generate", + "db:migrate": "drizzle-kit migrate", "db:push": "drizzle-kit push", - "db:studio": "drizzle-kit studio" + "db:studio": "drizzle-kit studio", + "db:switch-local": "./scripts/switch-to-local-db.sh" }, "dependencies": { "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", "@hookform/resolvers": "^5.1.1", + "@octokit/app": "^16.1.2", + "@octokit/rest": "^22.0.1", + "@pierre/diffs": "^1.2.1", + "@pierre/trees": "1.0.0-beta.4", "@radix-ui/react-alert-dialog": "^1.1.14", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-checkbox": "^1.3.3", @@ -58,13 +65,17 @@ "drizzle-zod": "^0.8.2", "lucide-react": "^0.525.0", "next-themes": "^0.4.6", + "node-mailjet": "^6.0.11", "postgres": "^3.4.7", "react": "^19.2.0", "react-day-picker": "^9.8.0", "react-dom": "^19.2.0", "react-hook-form": "^7.60.0", + "react-markdown": "^10.1.0", "redaxios": "^0.5.1", - "node-mailjet": "^6.0.11", + "rehype-raw": "^7.0.0", + "rehype-sanitize": "^6.0.0", + "remark-gfm": "^4.0.1", "sonner": "^2.0.6", "tailwind-merge": "^2.6.0", "tailwindcss": "^4.1.18", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9ea16ee..954a1d3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,18 @@ importers: '@hookform/resolvers': specifier: ^5.1.1 version: 5.2.2(react-hook-form@7.76.0(react@19.2.6)) + '@octokit/app': + specifier: ^16.1.2 + version: 16.1.2 + '@octokit/rest': + specifier: ^22.0.1 + version: 22.0.1 + '@pierre/diffs': + specifier: ^1.2.1 + version: 1.2.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@pierre/trees': + specifier: 1.0.0-beta.4 + version: 1.0.0-beta.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-alert-dialog': specifier: ^1.1.14 version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -158,9 +170,21 @@ importers: react-hook-form: specifier: ^7.60.0 version: 7.76.0(react@19.2.6) + react-markdown: + specifier: ^10.1.0 + version: 10.1.0(@types/react@19.2.14)(react@19.2.6) redaxios: specifier: ^0.5.1 version: 0.5.1 + rehype-raw: + specifier: ^7.0.0 + version: 7.0.0 + rehype-sanitize: + specifier: ^6.0.0 + version: 6.0.0 + remark-gfm: + specifier: ^4.0.1 + version: 4.0.1 sonner: specifier: ^2.0.6 version: 2.0.7(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -1377,6 +1401,105 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@octokit/app@16.1.2': + resolution: {integrity: sha512-8j7sEpUYVj18dxvh0KWj6W/l6uAiVRBl1JBDVRqH1VHKAO/G5eRVl4yEoYACjakWers1DjUkcCHyJNQK47JqyQ==} + engines: {node: '>= 20'} + + '@octokit/auth-app@8.2.0': + resolution: {integrity: sha512-vVjdtQQwomrZ4V46B9LaCsxsySxGoHsyw6IYBov/TqJVROrlYdyNgw5q6tQbB7KZt53v1l1W53RiqTvpzL907g==} + engines: {node: '>= 20'} + + '@octokit/auth-oauth-app@9.0.3': + resolution: {integrity: sha512-+yoFQquaF8OxJSxTb7rnytBIC2ZLbLqA/yb71I4ZXT9+Slw4TziV9j/kyGhUFRRTF2+7WlnIWsePZCWHs+OGjg==} + engines: {node: '>= 20'} + + '@octokit/auth-oauth-device@8.0.3': + resolution: {integrity: sha512-zh2W0mKKMh/VWZhSqlaCzY7qFyrgd9oTWmTmHaXnHNeQRCZr/CXy2jCgHo4e4dJVTiuxP5dLa0YM5p5QVhJHbw==} + engines: {node: '>= 20'} + + '@octokit/auth-oauth-user@6.0.2': + resolution: {integrity: sha512-qLoPPc6E6GJoz3XeDG/pnDhJpTkODTGG4kY0/Py154i/I003O9NazkrwJwRuzgCalhzyIeWQ+6MDvkUmKXjg/A==} + engines: {node: '>= 20'} + + '@octokit/auth-token@6.0.0': + resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==} + engines: {node: '>= 20'} + + '@octokit/auth-unauthenticated@7.0.3': + resolution: {integrity: sha512-8Jb1mtUdmBHL7lGmop9mU9ArMRUTRhg8vp0T1VtZ4yd9vEm3zcLwmjQkhNEduKawOOORie61xhtYIhTDN+ZQ3g==} + engines: {node: '>= 20'} + + '@octokit/core@7.0.6': + resolution: {integrity: sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==} + engines: {node: '>= 20'} + + '@octokit/endpoint@11.0.3': + resolution: {integrity: sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==} + engines: {node: '>= 20'} + + '@octokit/graphql@9.0.3': + resolution: {integrity: sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==} + engines: {node: '>= 20'} + + '@octokit/oauth-app@8.0.3': + resolution: {integrity: sha512-jnAjvTsPepyUaMu9e69hYBuozEPgYqP4Z3UnpmvoIzHDpf8EXDGvTY1l1jK0RsZ194oRd+k6Hm13oRU8EoDFwg==} + engines: {node: '>= 20'} + + '@octokit/oauth-authorization-url@8.0.0': + resolution: {integrity: sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==} + engines: {node: '>= 20'} + + '@octokit/oauth-methods@6.0.2': + resolution: {integrity: sha512-HiNOO3MqLxlt5Da5bZbLV8Zarnphi4y9XehrbaFMkcoJ+FL7sMxH/UlUsCVxpddVu4qvNDrBdaTVE2o4ITK8ng==} + engines: {node: '>= 20'} + + '@octokit/openapi-types@27.0.0': + resolution: {integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==} + + '@octokit/openapi-webhooks-types@12.1.0': + resolution: {integrity: sha512-WiuzhOsiOvb7W3Pvmhf8d2C6qaLHXrWiLBP4nJ/4kydu+wpagV5Fkz9RfQwV2afYzv3PB+3xYgp4mAdNGjDprA==} + + '@octokit/plugin-paginate-rest@14.0.0': + resolution: {integrity: sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-request-log@6.0.0': + resolution: {integrity: sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-rest-endpoint-methods@17.0.0': + resolution: {integrity: sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/request-error@7.1.0': + resolution: {integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==} + engines: {node: '>= 20'} + + '@octokit/request@10.0.9': + resolution: {integrity: sha512-o8Bi3f608eyM+7BmBiUWxFsdjLb3/ym1cQek5LZOv9KkZcxRrHCPhhRzm6xjO6HVZ85ItD6+sTsjxo821SVa/A==} + engines: {node: '>= 20'} + + '@octokit/rest@22.0.1': + resolution: {integrity: sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw==} + engines: {node: '>= 20'} + + '@octokit/types@16.0.0': + resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==} + + '@octokit/webhooks-methods@6.0.0': + resolution: {integrity: sha512-MFlzzoDJVw/GcbfzVC1RLR36QqkTLUf79vLVO3D+xn7r0QgxnFoLZgtrzxiQErAjFUOdH6fas2KeQJ1yr/qaXQ==} + engines: {node: '>= 20'} + + '@octokit/webhooks@14.2.0': + resolution: {integrity: sha512-da6KbdNCV5sr1/txD896V+6W0iamFWrvVl8cHkBSPT+YlvmT3DwXa4jxZnQc+gnuTEqSWbBeoSZYTayXH9wXcw==} + engines: {node: '>= 20'} + '@oozcitak/dom@2.0.2': resolution: {integrity: sha512-GjpKhkSYC3Mj4+lfwEyI1dqnsKTgwGy48ytZEhm4A/xnH/8z9M3ZVXKr/YGQi3uCLs1AEBS+x5T2JPiueEDW8w==} engines: {node: '>=20.0'} @@ -1668,6 +1791,22 @@ packages: resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} engines: {node: '>= 10.0.0'} + '@pierre/diffs@1.2.1': + resolution: {integrity: sha512-T9pYhh1KeaYLTwL1HMiAkeETj9CPjiKq6pqiUWRVJXvIkIvYutMrYyr3kN07cFWX2XPM8eqe6l2dknezs6K2AA==} + peerDependencies: + react: ^18.3.1 || ^19.0.0 + react-dom: ^18.3.1 || ^19.0.0 + + '@pierre/theme@1.0.3': + resolution: {integrity: sha512-sWHv11TMoqKxKDgTIk5VbhQjdPhs8DCcBxbjh3mRlS3YOM/OcrWoGX6MM8eBGn9cUu3M46Py0JnxsG2nJaFTuA==} + engines: {vscode: ^1.0.0} + + '@pierre/trees@1.0.0-beta.4': + resolution: {integrity: sha512-OfT1yk9ne8Te5+GB5zUY8yqE6B8BqjBHQJleH4lu8ltwNpoocZl4vXt1AzlEExpxI/pp+AFX5QG+lR3JjtTEag==} + peerDependencies: + react: ^18.3.1 || ^19.0.0 + react-dom: ^18.3.1 || ^19.0.0 + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -2288,6 +2427,30 @@ packages: rollup: optional: true + '@shikijs/core@3.23.0': + resolution: {integrity: sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==} + + '@shikijs/engine-javascript@3.23.0': + resolution: {integrity: sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==} + + '@shikijs/engine-oniguruma@3.23.0': + resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==} + + '@shikijs/langs@3.23.0': + resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==} + + '@shikijs/themes@3.23.0': + resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==} + + '@shikijs/transformers@3.23.0': + resolution: {integrity: sha512-F9msZVxdF+krQNSdQ4V+Ja5QemeAoTQ2jxt7nJCwhDsdF1JWS3KxIQXA3lQbyKwS3J61oHRUSv4jYWv3CkaKTQ==} + + '@shikijs/types@3.23.0': + resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@so-ric/colorspace@1.1.6': resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==} @@ -2826,12 +2989,24 @@ packages: '@tybys/wasm-util@0.10.2': resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + '@types/aws-lambda@8.10.161': + resolution: {integrity: sha512-rUYdp+MQwSFocxIOcSsYSF3YYYC/uUpMbCY/mbO21vGqfrEYvNSoPyKYDj6RhXXpPfS0KstW9RwG3qXh9sL7FQ==} + + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + '@types/esrecurse@4.3.1': resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -2841,9 +3016,15 @@ packages: '@types/markdown-it@14.1.2': resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/mdurl@2.0.0': resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/node@22.19.19': resolution: {integrity: sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==} @@ -2867,6 +3048,12 @@ packages: '@types/triple-beam@1.3.5': resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/use-sync-external-store@0.0.6': resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} @@ -2958,6 +3145,9 @@ packages: resolution: {integrity: sha512-U3gxVaDVnuZKhSspW/MzMxE1kq7zOdc072FcSNoqA1I9p8HyKbBFfEHoWckBAMgNMph4MamwS5iTVzFmrnt8TQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.1': + resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} + '@uploadthing/mime-types@0.3.6': resolution: {integrity: sha512-t3tTzgwFV9+1D7lNDYc7Lr7kBwotHaX0ZsvoCGe7xGnXKo9z0jG2Sjl/msll12FeoLj77nyhsxevXyGpQDBvLg==} @@ -3158,6 +3348,9 @@ packages: babel-dead-code-elimination@1.0.12: resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -3214,6 +3407,9 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + before-after-hook@4.0.0: + resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} + better-ajv-errors@1.2.0: resolution: {integrity: sha512-UW+IsFycygIo7bclP9h5ugkNH8EjCSgqyFB/yQ4Hqqa1OEYDtb0uFIkYE0b6+CjkgJYVM5UKI/pJPxjYe9EZlA==} engines: {node: '>= 12.13.0'} @@ -3353,6 +3549,9 @@ packages: caniuse-lite@1.0.30001793: resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -3361,6 +3560,18 @@ packages: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + cheerio-select@2.1.0: resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} @@ -3437,6 +3648,9 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@10.0.1: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} @@ -3466,6 +3680,10 @@ packages: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -3576,6 +3794,9 @@ packages: decache@4.6.2: resolution: {integrity: sha512-2LPqkLeu8XWHU8qNCS3kcF6sCcb5zIzvWaAHYSvPfwhdd7mHuah29NssMzrTYyHN4F5oFy2ko9OBYxegtU0FEw==} + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + dedent@1.7.2: resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} peerDependencies: @@ -3606,6 +3827,10 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + destr@2.0.5: resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} @@ -3662,6 +3887,13 @@ packages: dettle@1.0.5: resolution: {integrity: sha512-ZVyjhAJ7sCe1PNXEGveObOH9AC8QvMga3HJIghHawtG7mE4K5pW9nz/vDGAr/U7a3LWgdOzEE7ac9MURnyfaTA==} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + diff@8.0.3: + resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} + engines: {node: '>=0.3.1'} + diff@8.0.4: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} @@ -3965,6 +4197,9 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} @@ -3994,6 +4229,9 @@ packages: exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + extract-zip@2.0.1: resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} engines: {node: '>= 10.17.0'} @@ -4003,6 +4241,9 @@ packages: resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} engines: {node: '>=8.0.0'} + fast-content-type-parse@3.0.0: + resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -4266,6 +4507,33 @@ packages: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-raw@9.1.0: + resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} + + hast-util-sanitize@5.0.2: + resolution: {integrity: sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-to-parse5@8.0.1: + resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + hermes-estree@0.25.1: resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} @@ -4276,6 +4544,12 @@ packages: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} + html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + htmlparser2@10.1.0: resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} @@ -4337,6 +4611,9 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -4348,6 +4625,12 @@ packages: iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -4384,6 +4667,9 @@ packages: resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -4404,6 +4690,9 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -4548,6 +4837,9 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json-with-bigint@3.5.8: + resolution: {integrity: sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -4722,6 +5014,9 @@ packages: resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} engines: {node: '>= 12.0.0'} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -4732,6 +5027,9 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru_map@0.4.1: + resolution: {integrity: sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg==} + lucide-react@0.525.0: resolution: {integrity: sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==} peerDependencies: @@ -4752,10 +5050,58 @@ packages: resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==} hasBin: true + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + mdn-data@2.0.28: resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} @@ -4776,6 +5122,90 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -4982,6 +5412,12 @@ packages: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} + oniguruma-parser@0.12.2: + resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} + + oniguruma-to-es@4.3.6: + resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -5036,6 +5472,9 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse-gitignore@2.0.0: resolution: {integrity: sha512-RmVuCHWsfu0QPNW+mraxh/xjQVw/lhUCUru8Zni3Ctq3AoMhpDTq0OVdKS6iesd6Kqb7viCV3isAL43dciOSog==} engines: {node: '>=14'} @@ -5131,6 +5570,14 @@ packages: resolution: {integrity: sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw==} engines: {node: '>=12'} + preact-render-to-string@6.6.5: + resolution: {integrity: sha512-O6MHzYNIKYaiSX3bOw0gGZfEbOmlIDtDfWwN1JJdc/T3ihzRT6tGGSEWE088dWrEDGa1u7101q+6fzQnO9XCPA==} + peerDependencies: + preact: '>=10 || >= 11.0.0-0' + + preact@11.0.0-beta.0: + resolution: {integrity: sha512-IcODoASASYwJ9kxz7+MJeiJhvLriwSb4y4mHIyxdgaRZp6kPUud7xytrk/6GZw8U3y6EFJaRb5wi9SrEK+8+lg==} + precinct@12.3.2: resolution: {integrity: sha512-JbJevI1K80z8e/WIyDt/4vUN/4qcfBSKKqOjJA4mosPPPb7zODKRJQV7YN7apVWN3k58nZYm/vEsLgEGYmnxwg==} engines: {node: '>=18'} @@ -5157,6 +5604,9 @@ packages: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + prosemirror-changeset@2.4.1: resolution: {integrity: sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw==} @@ -5259,6 +5709,12 @@ packages: peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 + react-markdown@10.1.0: + resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + react-remove-scroll-bar@2.3.8: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} @@ -5334,10 +5790,37 @@ packages: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + regexp.prototype.flags@1.5.4: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} + rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + + rehype-sanitize@6.0.0: + resolution: {integrity: sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + remove-trailing-separator@1.1.0: resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==} @@ -5475,6 +5958,9 @@ packages: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} + shiki@3.23.0: + resolution: {integrity: sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==} + side-channel-list@1.0.1: resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} engines: {node: '>= 0.4'} @@ -5522,6 +6008,9 @@ packages: resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} engines: {node: '>= 12'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} @@ -5581,6 +6070,9 @@ packages: string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -5599,6 +6091,12 @@ packages: stubborn-utils@1.0.2: resolution: {integrity: sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==} + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -5666,6 +6164,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toad-cache@3.7.1: + resolution: {integrity: sha512-5DXWzE4Vz7xNHsv+xQ+MGfJYyC78Aok3tEr0MNwHoRf7vZnga1mQXZ4/Nsodld4VR6Wd+VhfmqnNrsRJyYPfrQ==} + engines: {node: '>=20'} + toml@3.0.0: resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} @@ -5675,10 +6177,16 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + triple-beam@1.4.1: resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} engines: {node: '>= 14.0.0'} + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-api-utils@2.5.0: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} @@ -5760,6 +6268,30 @@ packages: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} engines: {node: '>=18'} + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + + universal-github-app-jwt@2.2.2: + resolution: {integrity: sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==} + + universal-user-agent@7.0.3: + resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} + unixify@1.0.0: resolution: {integrity: sha512-6bc58dPYhCMHHuwxldQxO3RRNZ4eCogZ/st++0+fcC1nr0jiGUtAdBJ2qzmLQWSxbtz42pWt4QQMiZ9HvZf5cg==} engines: {node: '>=0.10.0'} @@ -5915,6 +6447,15 @@ packages: resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite@8.0.13: resolution: {integrity: sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5969,6 +6510,9 @@ packages: w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + web-streams-polyfill@3.3.3: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} @@ -6110,6 +6654,9 @@ packages: zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: '@babel/code-frame@7.27.1': @@ -7235,6 +7782,149 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 + '@octokit/app@16.1.2': + dependencies: + '@octokit/auth-app': 8.2.0 + '@octokit/auth-unauthenticated': 7.0.3 + '@octokit/core': 7.0.6 + '@octokit/oauth-app': 8.0.3 + '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.6) + '@octokit/types': 16.0.0 + '@octokit/webhooks': 14.2.0 + + '@octokit/auth-app@8.2.0': + dependencies: + '@octokit/auth-oauth-app': 9.0.3 + '@octokit/auth-oauth-user': 6.0.2 + '@octokit/request': 10.0.9 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + toad-cache: 3.7.1 + universal-github-app-jwt: 2.2.2 + universal-user-agent: 7.0.3 + + '@octokit/auth-oauth-app@9.0.3': + dependencies: + '@octokit/auth-oauth-device': 8.0.3 + '@octokit/auth-oauth-user': 6.0.2 + '@octokit/request': 10.0.9 + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + + '@octokit/auth-oauth-device@8.0.3': + dependencies: + '@octokit/oauth-methods': 6.0.2 + '@octokit/request': 10.0.9 + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + + '@octokit/auth-oauth-user@6.0.2': + dependencies: + '@octokit/auth-oauth-device': 8.0.3 + '@octokit/oauth-methods': 6.0.2 + '@octokit/request': 10.0.9 + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + + '@octokit/auth-token@6.0.0': {} + + '@octokit/auth-unauthenticated@7.0.3': + dependencies: + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + + '@octokit/core@7.0.6': + dependencies: + '@octokit/auth-token': 6.0.0 + '@octokit/graphql': 9.0.3 + '@octokit/request': 10.0.9 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + before-after-hook: 4.0.0 + universal-user-agent: 7.0.3 + + '@octokit/endpoint@11.0.3': + dependencies: + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + + '@octokit/graphql@9.0.3': + dependencies: + '@octokit/request': 10.0.9 + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + + '@octokit/oauth-app@8.0.3': + dependencies: + '@octokit/auth-oauth-app': 9.0.3 + '@octokit/auth-oauth-user': 6.0.2 + '@octokit/auth-unauthenticated': 7.0.3 + '@octokit/core': 7.0.6 + '@octokit/oauth-authorization-url': 8.0.0 + '@octokit/oauth-methods': 6.0.2 + '@types/aws-lambda': 8.10.161 + universal-user-agent: 7.0.3 + + '@octokit/oauth-authorization-url@8.0.0': {} + + '@octokit/oauth-methods@6.0.2': + dependencies: + '@octokit/oauth-authorization-url': 8.0.0 + '@octokit/request': 10.0.9 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + + '@octokit/openapi-types@27.0.0': {} + + '@octokit/openapi-webhooks-types@12.1.0': {} + + '@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/types': 16.0.0 + + '@octokit/plugin-request-log@6.0.0(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + + '@octokit/plugin-rest-endpoint-methods@17.0.0(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/types': 16.0.0 + + '@octokit/request-error@7.1.0': + dependencies: + '@octokit/types': 16.0.0 + + '@octokit/request@10.0.9': + dependencies: + '@octokit/endpoint': 11.0.3 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + content-type: 2.0.0 + fast-content-type-parse: 3.0.0 + json-with-bigint: 3.5.8 + universal-user-agent: 7.0.3 + + '@octokit/rest@22.0.1': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.6) + '@octokit/plugin-request-log': 6.0.0(@octokit/core@7.0.6) + '@octokit/plugin-rest-endpoint-methods': 17.0.0(@octokit/core@7.0.6) + + '@octokit/types@16.0.0': + dependencies: + '@octokit/openapi-types': 27.0.0 + + '@octokit/webhooks-methods@6.0.0': {} + + '@octokit/webhooks@14.2.0': + dependencies: + '@octokit/openapi-webhooks-types': 12.1.0 + '@octokit/request-error': 7.1.0 + '@octokit/webhooks-methods': 6.0.0 + '@oozcitak/dom@2.0.2': dependencies: '@oozcitak/infra': 2.0.2 @@ -7432,6 +8122,26 @@ snapshots: '@parcel/watcher-win32-ia32': 2.5.6 '@parcel/watcher-win32-x64': 2.5.6 + '@pierre/diffs@1.2.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@pierre/theme': 1.0.3 + '@shikijs/transformers': 3.23.0 + diff: 8.0.3 + hast-util-to-html: 9.0.5 + lru_map: 0.4.1 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + shiki: 3.23.0 + + '@pierre/theme@1.0.3': {} + + '@pierre/trees@1.0.0-beta.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + preact: 11.0.0-beta.0 + preact-render-to-string: 6.6.5(preact@11.0.0-beta.0) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + '@pkgjs/parseargs@0.11.0': optional: true @@ -7998,6 +8708,44 @@ snapshots: estree-walker: 2.0.2 picomatch: 4.0.4 + '@shikijs/core@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.6 + + '@shikijs/engine-oniguruma@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + + '@shikijs/themes@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + + '@shikijs/transformers@3.23.0': + dependencies: + '@shikijs/core': 3.23.0 + '@shikijs/types': 3.23.0 + + '@shikijs/types@3.23.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + '@so-ric/colorspace@1.1.6': dependencies: color: 5.0.3 @@ -8611,10 +9359,24 @@ snapshots: tslib: 2.8.1 optional: true + '@types/aws-lambda@8.10.161': {} + + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + '@types/esrecurse@4.3.1': {} + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.9 + '@types/estree@1.0.9': {} + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/json-schema@7.0.15': {} '@types/linkify-it@5.0.0': {} @@ -8624,8 +9386,14 @@ snapshots: '@types/linkify-it': 5.0.0 '@types/mdurl': 2.0.0 + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/mdurl@2.0.0': {} + '@types/ms@2.1.0': {} + '@types/node@22.19.19': dependencies: undici-types: 6.21.0 @@ -8646,6 +9414,10 @@ snapshots: '@types/triple-beam@1.3.5': {} + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + '@types/use-sync-external-store@0.0.6': {} '@types/yauzl@2.10.3': @@ -8779,6 +9551,8 @@ snapshots: '@typescript-eslint/types': 8.59.4 eslint-visitor-keys: 5.0.1 + '@ungap/structured-clone@1.3.1': {} + '@uploadthing/mime-types@0.3.6': {} '@uploadthing/react@7.3.3(react@19.2.6)(uploadthing@7.7.4(h3@1.15.11)(tailwindcss@4.3.0))': @@ -9030,6 +9804,8 @@ snapshots: transitivePeerDependencies: - supports-color + bail@2.0.2: {} + balanced-match@1.0.2: {} balanced-match@4.0.4: {} @@ -9070,6 +9846,8 @@ snapshots: baseline-browser-mapping@2.10.30: {} + before-after-hook@4.0.0: {} + better-ajv-errors@1.2.0(ajv@8.20.0): dependencies: '@babel/code-frame': 7.29.0 @@ -9182,6 +9960,8 @@ snapshots: caniuse-lite@1.0.30001793: {} + ccount@2.0.1: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -9189,6 +9969,14 @@ snapshots: chalk@5.6.2: {} + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + cheerio-select@2.1.0: dependencies: boolbase: 1.0.0 @@ -9291,6 +10079,8 @@ snapshots: dependencies: delayed-stream: 1.0.0 + comma-separated-tokens@2.0.3: {} + commander@10.0.1: {} commander@11.1.0: {} @@ -9313,6 +10103,8 @@ snapshots: consola@3.4.2: {} + content-type@2.0.0: {} + convert-source-map@2.0.0: {} cookie-es@1.2.3: {} @@ -9415,6 +10207,10 @@ snapshots: dependencies: callsite: 1.0.0 + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + dedent@1.7.2: {} deep-is@0.1.4: {} @@ -9437,6 +10233,8 @@ snapshots: delayed-stream@1.0.0: {} + dequal@2.0.3: {} + destr@2.0.5: {} detect-libc@2.1.2: {} @@ -9501,6 +10299,12 @@ snapshots: dettle@1.0.5: {} + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + diff@8.0.3: {} + diff@8.0.4: {} dom-serializer@2.0.0: @@ -9859,6 +10663,8 @@ snapshots: estraverse@5.3.0: {} + estree-util-is-identifier-name@3.0.0: {} + estree-walker@2.0.2: {} esutils@2.0.3: {} @@ -9889,6 +10695,8 @@ snapshots: exsolve@1.0.8: {} + extend@3.0.2: {} + extract-zip@2.0.1: dependencies: debug: 4.4.3 @@ -9903,6 +10711,8 @@ snapshots: dependencies: pure-rand: 6.1.0 + fast-content-type-parse@3.0.0: {} + fast-deep-equal@3.1.3: {} fast-fifo@1.3.2: {} @@ -10155,6 +10965,99 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.1.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-raw@9.1.0: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.3.1 + hast-util-from-parse5: 8.0.3 + hast-util-to-parse5: 8.0.1 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + parse5: 7.3.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-sanitize@5.0.2: + dependencies: + '@types/hast': 3.0.4 + '@ungap/structured-clone': 1.3.1 + unist-util-position: 5.0.0 + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.9 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-to-parse5@8.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + hermes-estree@0.25.1: {} hermes-parser@0.25.1: @@ -10165,6 +11068,10 @@ snapshots: dependencies: lru-cache: 10.4.3 + html-url-attributes@3.0.1: {} + + html-void-elements@3.0.0: {} + htmlparser2@10.1.0: dependencies: domelementtype: 2.3.0 @@ -10219,6 +11126,8 @@ snapshots: inherits@2.0.4: {} + inline-style-parser@0.2.7: {} + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -10267,6 +11176,13 @@ snapshots: iron-webcrypto@1.2.1: {} + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.9 @@ -10311,6 +11227,8 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-decimal@2.0.1: {} + is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -10331,6 +11249,8 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-hexadecimal@2.0.1: {} + is-map@2.0.3: {} is-negative-zero@2.0.3: {} @@ -10443,6 +11363,8 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + json-with-bigint@3.5.8: {} + json5@2.2.3: {} jsonpointer@5.0.1: {} @@ -10614,6 +11536,8 @@ snapshots: safe-stable-stringify: 2.5.0 triple-beam: 1.4.1 + longest-streak@3.1.0: {} + lru-cache@10.4.3: {} lru-cache@11.4.0: {} @@ -10622,6 +11546,8 @@ snapshots: dependencies: yallist: 3.1.1 + lru_map@0.4.1: {} + lucide-react@0.525.0(react@19.2.6): dependencies: react: 19.2.6 @@ -10643,8 +11569,163 @@ snapshots: punycode.js: 2.3.1 uc.micro: 2.1.0 + markdown-table@3.0.4: {} + math-intrinsics@1.1.0: {} + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.1 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdn-data@2.0.28: {} mdn-data@2.27.1: {} @@ -10659,6 +11740,197 @@ snapshots: merge2@1.4.1: {} + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -10860,6 +12132,14 @@ snapshots: dependencies: mimic-fn: 4.0.0 + oniguruma-parser@0.12.2: {} + + oniguruma-to-es@4.3.6: + dependencies: + oniguruma-parser: 0.12.2 + regex: 6.1.0 + regex-recursion: 6.0.2 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -10941,6 +12221,16 @@ snapshots: package-json-from-dist@1.0.1: {} + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + parse-gitignore@2.0.0: {} parse-imports@2.2.1: @@ -11023,6 +12313,12 @@ snapshots: postgres@3.4.9: {} + preact-render-to-string@6.6.5(preact@11.0.0-beta.0): + dependencies: + preact: 11.0.0-beta.0 + + preact@11.0.0-beta.0: {} + precinct@12.3.2: dependencies: '@dependents/detective-less': 5.0.3 @@ -11057,6 +12353,8 @@ snapshots: process@0.11.10: {} + property-information@7.1.0: {} + prosemirror-changeset@2.4.1: dependencies: prosemirror-transform: 1.12.0 @@ -11196,6 +12494,24 @@ snapshots: dependencies: react: 19.2.6 + react-markdown@10.1.0(@types/react@19.2.14)(react@19.2.6): + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/react': 19.2.14 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.1 + react: 19.2.6 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.5 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.6): dependencies: react: 19.2.6 @@ -11288,6 +12604,16 @@ snapshots: get-proto: 1.0.1 which-builtin-type: 1.2.1 + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + regexp.prototype.flags@1.5.4: dependencies: call-bind: 1.0.9 @@ -11297,6 +12623,51 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 + rehype-raw@7.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-raw: 9.1.0 + vfile: 6.0.3 + + rehype-sanitize@6.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-sanitize: 5.0.2 + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + remove-trailing-separator@1.1.0: {} require-directory@2.1.1: {} @@ -11464,6 +12835,17 @@ snapshots: shell-quote@1.8.3: {} + shiki@3.23.0: + dependencies: + '@shikijs/core': 3.23.0 + '@shikijs/engine-javascript': 3.23.0 + '@shikijs/engine-oniguruma': 3.23.0 + '@shikijs/langs': 3.23.0 + '@shikijs/themes': 3.23.0 + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + side-channel-list@1.0.1: dependencies: es-errors: 1.3.0 @@ -11518,6 +12900,8 @@ snapshots: source-map@0.7.6: {} + space-separated-tokens@2.0.2: {} + spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 @@ -11597,6 +12981,11 @@ snapshots: dependencies: safe-buffer: 5.2.1 + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -11613,6 +13002,14 @@ snapshots: stubborn-utils@1.0.2: {} + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -11697,14 +13094,20 @@ snapshots: dependencies: is-number: 7.0.0 + toad-cache@3.7.1: {} + toml@3.0.0: {} tomlify-j0.4@3.0.0: {} tr46@0.0.3: {} + trim-lines@3.0.1: {} + triple-beam@1.4.1: {} + trough@2.2.0: {} + ts-api-utils@2.5.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -11792,6 +13195,43 @@ snapshots: unicorn-magic@0.1.0: {} + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + universal-github-app-jwt@2.2.2: {} + + universal-user-agent@7.0.3: {} + unixify@1.0.0: dependencies: normalize-path: 2.1.1 @@ -11881,6 +13321,21 @@ snapshots: validate-npm-package-name@5.0.1: {} + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + vite@8.0.13(@types/node@22.19.19)(esbuild@0.28.0)(jiti@2.7.0)(tsx@4.22.1)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 @@ -11902,6 +13357,8 @@ snapshots: w3c-keyname@2.2.8: {} + web-namespaces@2.0.1: {} + web-streams-polyfill@3.3.3: {} webidl-conversions@3.0.1: {} @@ -12058,3 +13515,5 @@ snapshots: zod@3.25.76: {} zod@4.4.3: {} + + zwitch@2.0.4: {} diff --git a/scripts/migrate-clerk-images.sql b/scripts/migrate-clerk-images.sql deleted file mode 100644 index 0bdd03b..0000000 --- a/scripts/migrate-clerk-images.sql +++ /dev/null @@ -1,22 +0,0 @@ --- Restore profile images from Clerk (one-time). --- Replace the two URL variables below, then run. - -DO $$ -DECLARE - clerk_user_image_url text := 'https://REPLACE_WITH_CLERK_USER_IMAGE_URL'; - clerk_org_logo_url text := 'https://REPLACE_WITH_CLERK_ORG_IMAGE_URL'; -BEGIN - IF clerk_user_image_url LIKE '%REPLACE_WITH%' - OR clerk_org_logo_url LIKE '%REPLACE_WITH%' THEN - RAISE EXCEPTION - 'Replace clerk_user_image_url and clerk_org_logo_url with real Clerk image URLs before running this migration.'; - END IF; - - UPDATE "user" - SET image = clerk_user_image_url - WHERE id = 'F2LaBzrU26nMG3dVl8uBsBbNq69FeaT2'; - - UPDATE organization - SET logo = clerk_org_logo_url - WHERE id = 'YVWL19ooaw0qTkFISrp6ng3d0I648Ykn'; -END $$; diff --git a/scripts/switch-to-local-db.sh b/scripts/switch-to-local-db.sh new file mode 100755 index 0000000..2546e21 --- /dev/null +++ b/scripts/switch-to-local-db.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env bash +# Dump the current (remote) database, switch .env to local Postgres, start Docker, and import. +# +# Usage: ./scripts/switch-to-local-db.sh +# Requires: docker, and either local pg_dump/psql or the postgres Docker image. + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT_DIR" + +DB_CONTAINER_NAME="project-y-postgres" +DUMP_DIR="$ROOT_DIR/dumps" +TIMESTAMP="$(date +%Y%m%d-%H%M%S)" +DUMP_FILE="$DUMP_DIR/project-y-$TIMESTAMP.sql" + +if [[ ! -f .env ]]; then + echo "Missing .env in $ROOT_DIR" + exit 1 +fi + +strip_quotes() { + local value="$1" + value="${value#\"}" + value="${value%\"}" + value="${value#\'}" + value="${value%\'}" + printf '%s' "$value" +} + +read_env_url() { + local line="$1" + line="${line#NETLIFY_DATABASE_URL=}" + strip_quotes "$line" +} + +read_active_database_url() { + local line + line="$(grep -E '^NETLIFY_DATABASE_URL=' .env | head -1 || true)" + if [[ -z "$line" ]]; then + return 1 + fi + read_env_url "$line" +} + +is_local_url() { + local url="$1" + [[ "$url" == *"@localhost:"* || "$url" == *"@127.0.0.1:"* ]] +} + +extract_local_url_from_env() { + local line url + while IFS= read -r line; do + if [[ "$line" =~ ^#[[:space:]]*NETLIFY_DATABASE_URL= ]]; then + url="$(read_env_url "${line#\# }")" + if is_local_url "$url"; then + printf '%s' "$url" + return 0 + fi + fi + done < .env + return 1 +} + +REMOTE_URL="$(read_active_database_url)" + +if is_local_url "$REMOTE_URL"; then + echo "NETLIFY_DATABASE_URL already points at a local database." + echo "Re-run after switching back to your remote URL, or import manually from $DUMP_DIR" + exit 1 +fi + +if LOCAL_URL="$(extract_local_url_from_env)"; then + echo "Using local URL from commented line in .env" +else + echo "No commented local NETLIFY_DATABASE_URL found in .env." + echo "Add a line like:" + echo ' # NETLIFY_DATABASE_URL="postgresql://postgres:YOUR_PASSWORD@localhost:5432/project-y"' + exit 1 +fi + +mkdir -p "$DUMP_DIR" + +echo "Dumping remote database to $DUMP_FILE ..." +if command -v pg_dump >/dev/null 2>&1; then + pg_dump "$REMOTE_URL" \ + --no-owner \ + --no-acl \ + --clean \ + --if-exists \ + -F p \ + -f "$DUMP_FILE" +else + docker run --rm \ + -e "REMOTE_URL=$REMOTE_URL" \ + docker.io/postgres \ + sh -c 'pg_dump "$REMOTE_URL" --no-owner --no-acl --clean --if-exists -F p' \ + >"$DUMP_FILE" +fi + +echo "Updating .env to use local database ..." +tmp_env="$(mktemp)" +remote_written=false +while IFS= read -r line || [[ -n "$line" ]]; do + if [[ "$line" =~ ^NETLIFY_DATABASE_URL= ]]; then + if [[ "$remote_written" == false ]]; then + printf '# NETLIFY_DATABASE_URL="%s"\n' "$REMOTE_URL" >>"$tmp_env" + printf 'NETLIFY_DATABASE_URL="%s"\n' "$LOCAL_URL" >>"$tmp_env" + remote_written=true + fi + continue + fi + if [[ "$line" =~ ^#[[:space:]]*NETLIFY_DATABASE_URL= ]]; then + continue + fi + printf '%s\n' "$line" >>"$tmp_env" +done < .env + +if [[ "$remote_written" == false ]]; then + rm -f "$tmp_env" + echo "Could not find NETLIFY_DATABASE_URL in .env" + exit 1 +fi + +mv "$tmp_env" .env + +echo "Starting local database container ..." +bash "$ROOT_DIR/start-database.sh" + +echo "Waiting for Postgres to accept connections ..." +ready=false +for _ in $(seq 1 60); do + if docker exec "$DB_CONTAINER_NAME" pg_isready -U postgres -d project-y >/dev/null 2>&1; then + ready=true + break + fi + sleep 1 +done + +if [[ "$ready" != true ]]; then + echo "Postgres did not become ready in time." + exit 1 +fi + +echo "Importing dump into local database ..." +if command -v psql >/dev/null 2>&1; then + psql "$LOCAL_URL" -v ON_ERROR_STOP=1 -f "$DUMP_FILE" +else + docker exec -i "$DB_CONTAINER_NAME" \ + psql -U postgres -d project-y -v ON_ERROR_STOP=1 \ + <"$DUMP_FILE" +fi + +echo "" +echo "Done." +echo " Dump: $DUMP_FILE" +echo " Local: $LOCAL_URL" +echo "Remote URL is preserved as a comment in .env." diff --git a/src/components/OpenTask.tsx b/src/components/OpenTask.tsx index 7dac849..f2c8873 100644 --- a/src/components/OpenTask.tsx +++ b/src/components/OpenTask.tsx @@ -40,8 +40,10 @@ import { Calendar, CircleDashed, ClockFading, + Code2, Ellipsis, Flag, + LayoutList, Tags, Trash2, Users, @@ -66,6 +68,9 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs"; import { SubTasks } from "./SubTasks"; import { Badge } from "./ui/badge"; import { SprintSelect } from "./SprintSelect"; +import { TaskDevelopmentSection } from "~/components/git/TaskDevelopmentSection"; +import { TaskPullRequestReviewFeed } from "~/components/git/TaskPullRequestReviewFeed"; +import { TaskGitReviewNavProvider } from "~/lib/git/task-git-review-nav"; export function OpenTask({ task, @@ -102,7 +107,6 @@ export function OpenTask({ ); const currentStatus = statuses.find((status) => status.id === task?.statusId); const [isDeleting, setIsDeleting] = useState(false); - const handleUpdateTitle = useCallback( async (content: string) => { if (!task?.id || !task.projectId) return; @@ -158,6 +162,9 @@ export function OpenTask({ const owner = useCurrentOwningIdentity(); const [preventClose, setPreventClose] = useState(false); + const [dialogView, setDialogView] = useState<"overview" | "development">( + "overview" + ); const [taskTab, setTaskTab] = useState<"attachments" | "subtasks">( "attachments" ); @@ -176,6 +183,7 @@ export function OpenTask({ useEffect(() => { if (!task?.id) return; + setDialogView("overview"); setTaskTab(task.subTasks.length > 0 ? "subtasks" : "attachments"); clearFileDragOver(); // Intentionally only when switching tasks — not when subtask count changes. @@ -235,7 +243,7 @@ export function OpenTask({ ref={dialogContentRef} size="large" isLoading={isDeleting} - className="gap-0 rounded-md p-0" + className="flex max-h-full flex-col gap-0 overflow-hidden rounded-md p-0" onDragEnter={handleTaskPanelDragEnter} onDragLeave={handleTaskPanelDragLeave} onDragOver={handleTaskPanelDragOver} @@ -248,7 +256,7 @@ export function OpenTask({ }} > {task?.name} - +
@@ -309,181 +317,233 @@ export function OpenTask({
{task && currentStatus ? ( -
-
- - - - - - - { - handleUpdateTask({ - id: task.id, - statusId, - projectId: task.projectId, - }); - }} - /> - - - { - handleUpdateTask({ - id: task.id, - priority, - projectId: task.projectId, - }); - }} - /> - - setDialogView("development")} + > +
+
+ + setDialogView(value as "overview" | "development") + } + className="flex min-h-0 flex-1 flex-col gap-0" + > +
+ + + + Overview + + + + Development + + +
+ + - { - if (!task) return; +
+ +
+
+ + + + + + { + handleUpdateTask({ + id: task.id, + statusId, + projectId: task.projectId, + }); + }} + /> + + + { + handleUpdateTask({ + id: task.id, + priority, + projectId: task.projectId, + }); + }} + /> + + + { + handleUpdateTask({ + id: task.id, + deadline: date ?? null, + projectId: task.projectId, + }); + }} + /> + + + assignee.userId + )} + onValueChange={async (ids) => { + setIsAssigningUser(true); + const add = ids.filter( + (id) => + !task.assignees.find( + (assignee) => assignee.userId === id + ) + ); + const remove = task.assignees + .map((a) => a.userId) + .filter((assignee) => !ids.includes(assignee)); + + await Promise.all([ + assignTask(task, add), + unassignTask(task, remove), + ]); + setIsAssigningUser(false); + }} + /> + + + { + setIsAssigningSprint(true); + try { + await updateTask({ + id: task.id, + sprintId: sprintId ?? null, + }); + } finally { + setIsAssigningSprint(false); + } + }} + /> + + +
+ { handleUpdateTask({ id: task.id, - deadline: date ?? null, + description: data.text, projectId: task.projectId, }); }} /> - - - assignee.userId - )} - onValueChange={async (ids) => { - if (!task) return; - setIsAssigningUser(true); - const add = ids.filter( - (id) => - !task.assignees.find( - (assignee) => assignee.userId === id - ) - ); - const remove = task.assignees - .map((a) => a.userId) - .filter((assignee) => !ids.includes(assignee)); - await Promise.all([ - await assignTask(task, add), - await unassignTask(task, remove), - ]); - setIsAssigningUser(false); - }} - /> - - - { - if (!task) return; - setIsAssigningSprint(true); - try { - await updateTask({ - id: task.id, - sprintId: sprintId ?? null, - }); - } finally { - setIsAssigningSprint(false); - } - }} - /> - - -
- { - if (!task) return; - handleUpdateTask({ - id: task.id, - description: data.text, - projectId: task.projectId, - }); - }} - /> + + setTaskTab(value as "attachments" | "subtasks") + } + > + + + Attachments + + {task.attachments.length} + + + + Subtasks + + {task.subTasks.length} + + + + + + + + + + + - - setTaskTab(value as "attachments" | "subtasks") - } - > - - - Attachments - - {task.attachments.length} - - - - Subtasks - - {task.subTasks.length} - - - - - + - - -
-
- Conversation - ({ - ...comment, - author: comment.author || "Unknown", - authorAvatar: comment.authorAvatar ?? "", - }))} - /> - -
+ +
+ ) : null} diff --git a/src/components/TaskCard.tsx b/src/components/TaskCard.tsx index 405fe68..0658776 100644 --- a/src/components/TaskCard.tsx +++ b/src/components/TaskCard.tsx @@ -11,9 +11,13 @@ import { Flag, Folder, Paperclip, + GitBranch, + GitPullRequest, TextIcon, Users, } from "lucide-react"; +import { formatTaskKey } from "~/lib/git/task-key"; +import type { TaskGitSummary } from "~/db/queries/git"; import { DateDisplay } from "./ui/date-display"; import { Avatar, AvatarFallback, AvatarImage, AvatarList } from "./ui/avatar"; import { useUsersQuery } from "~/db/queries/users"; @@ -40,12 +44,14 @@ export default function TaskCard({ showSprint, showProject, taskLinkTo, + gitSummary, }: { task: TaskWithRelations; columnTaskIds: string[]; showSprint?: boolean; showProject?: boolean; taskLinkTo?: "project" | "sprint" | "all"; + gitSummary?: TaskGitSummary; }) { const { attributes, listeners, setNodeRef, isDragging } = useDraggable({ id: `task:${task.id}`, @@ -107,6 +113,7 @@ export default function TaskCard({ taskLinkTo={taskLinkTo} isSelected={isSelected} isHovered={isHovered} + gitSummary={gitSummary} />
@@ -187,6 +194,7 @@ export const TaskCardComponent = ({ taskLinkTo, isSelected = false, isHovered = false, + gitSummary, }: { task: TaskWithRelations; columnTaskIds?: string[]; @@ -195,8 +203,13 @@ export const TaskCardComponent = ({ taskLinkTo?: "project" | "sprint" | "all"; isSelected?: boolean; isHovered?: boolean; + gitSummary?: TaskGitSummary; }) => { const isOverdue = task.deadline && task.deadline < new Date(); + const taskKeyLabel = + task.project.taskKeyPrefix != null + ? formatTaskKey(task.project.taskKeyPrefix, task.number) + : null; const usersQuery = useUsersQuery(); const { data: session } = authClient.useSession(); const currentUserId = session?.user.id; @@ -287,7 +300,22 @@ export const TaskCardComponent = ({ ))}
) : null} - {task.name} +
+ {task.name} +
+ {taskKeyLabel ? ( + + {taskKeyLabel} + + ) : null} + {gitSummary?.hasBranch ? ( + + ) : null} + {gitSummary?.hasOpenPr ? ( + + ) : null} +
+
{task.subTasks.length ? ( diff --git a/src/components/app-sidebar.tsx b/src/components/app-sidebar.tsx index 4daedbd..e81167e 100644 --- a/src/components/app-sidebar.tsx +++ b/src/components/app-sidebar.tsx @@ -8,6 +8,7 @@ import { Flag, Folder, FolderPlus, + GitBranch, LayoutDashboard, LucideIcon, Tag, @@ -53,6 +54,11 @@ const settingsNav = [ url: "/settings/organization", icon: Building2, }, + { + name: "Integrations", + url: "/settings/integrations", + icon: GitBranch, + }, { name: "Statuses", url: "/statuses", diff --git a/src/components/auth/google-sign-in-button.tsx b/src/components/auth/google-sign-in-button.tsx index 99e8597..d08bea8 100644 --- a/src/components/auth/google-sign-in-button.tsx +++ b/src/components/auth/google-sign-in-button.tsx @@ -4,6 +4,7 @@ import { toast } from "sonner"; type GoogleSignInButtonProps = { disabled?: boolean; + callbackURL?: string; }; function GoogleIcon() { @@ -29,12 +30,15 @@ function GoogleIcon() { ); } -export function GoogleSignInButton({ disabled }: GoogleSignInButtonProps) { +export function GoogleSignInButton({ + disabled, + callbackURL = "/dashboard", +}: GoogleSignInButtonProps) { const handleGoogleSignIn = async () => { try { const { error } = await authClient.signIn.social({ provider: "google", - callbackURL: "/dashboard", + callbackURL, }); if (error) { diff --git a/src/components/forms/ProjectForm.tsx b/src/components/forms/ProjectForm.tsx index 60c740e..1e6d2c5 100644 --- a/src/components/forms/ProjectForm.tsx +++ b/src/components/forms/ProjectForm.tsx @@ -23,6 +23,7 @@ import { } from "~/components/ui/surface-styles"; import { cn } from "~/lib/utils"; import { toast } from "sonner"; +import { ProjectGitSection } from "~/components/git/ProjectGitSection"; type FormLayout = "page" | "sheet"; @@ -352,6 +353,7 @@ export function ProjectFormEdit({ )} /> + ) : ( <> diff --git a/src/components/git/CommitList.tsx b/src/components/git/CommitList.tsx new file mode 100644 index 0000000..473c6a0 --- /dev/null +++ b/src/components/git/CommitList.tsx @@ -0,0 +1,113 @@ +import type { GitCommit } from "~/lib/git/types"; +import { formatRelativeTime } from "~/lib/format-relative-time"; +import { cn } from "~/lib/utils"; + +/** Visible strip rows before the list scrolls (`h-10` per row → 12.5rem total). */ +const STRIP_VISIBLE_ROWS = 5; + +export function CommitList({ + commits, + selectedSha, + onSelect, + layout = "sidebar", + className, +}: { + commits: GitCommit[]; + selectedSha?: string | null; + onSelect: (sha: string) => void; + /** `strip` = compact rows for a list above the diff (email-client style). */ + layout?: "sidebar" | "strip"; + className?: string; +}) { + if (commits.length === 0) { + return ( +

+ No commits yet. +

+ ); + } + + if (layout === "strip") { + const scrollable = commits.length > STRIP_VISIBLE_ROWS; + + return ( +
+
    + {commits.map((commit, index) => { + const selected = selectedSha === commit.sha; + const selectCommit = () => onSelect(commit.sha); + const tabIndex = + selected || (selectedSha == null && index === 0) ? 0 : -1; + return ( +
  • { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + selectCommit(); + } + }} + className={cn( + "flex h-10 w-full cursor-pointer items-center gap-3 px-3 text-left transition-colors outline-none", + "hover:bg-muted/50 focus-visible:ring-2 focus-visible:ring-ring/50", + selected && + "bg-primary/10 ring-1 ring-inset ring-primary/25" + )} + > + + {commit.sha.slice(0, 7)} + + + {commit.message} + + + {commit.authorLogin ?? "Unknown"} + + + {formatRelativeTime(commit.committedAt)} + +
  • + ); + })} +
+
+ ); + } + + return ( +
    + {commits.map((commit) => ( +
  • + +
  • + ))} +
+ ); +} diff --git a/src/components/git/DiffFileTree.tsx b/src/components/git/DiffFileTree.tsx new file mode 100644 index 0000000..f4ca3af --- /dev/null +++ b/src/components/git/DiffFileTree.tsx @@ -0,0 +1,105 @@ +import { FileTree, useFileTree } from "@pierre/trees/react"; +import { themeToTreeStyles } from "@pierre/trees"; +import type { GitReviewComment } from "~/lib/git/types"; +import type { GitDiffFile } from "~/lib/git/types"; +import { + diffFilesToGitStatus, + diffFilesToTreePaths, +} from "~/lib/git/diff-file-tree"; +import { useTheme } from "next-themes"; +import { useEffect, useMemo, useRef } from "react"; +import { cn } from "~/lib/utils"; + +export function DiffFileTree({ + files, + activePath, + onSelectPath, + commentCountByPath, + className, +}: { + files: GitDiffFile[]; + activePath: string | null; + onSelectPath: (path: string) => void; + commentCountByPath?: Map; + className?: string; +}) { + const { resolvedTheme } = useTheme(); + const paths = useMemo(() => diffFilesToTreePaths(files), [files]); + const gitStatus = useMemo(() => diffFilesToGitStatus(files), [files]); + const pathSet = useMemo(() => new Set(paths), [paths]); + const onSelectPathRef = useRef(onSelectPath); + onSelectPathRef.current = onSelectPath; + + const { model } = useFileTree({ + paths, + gitStatus, + flattenEmptyDirectories: true, + initialExpansion: 1, + icons: "standard", + density: "compact", + initialSelectedPaths: activePath ? [activePath] : [], + onSelectionChange: (selected) => { + const filePath = [...selected] + .reverse() + .find((p) => pathSet.has(p)); + if (filePath) onSelectPathRef.current(filePath); + }, + renderRowDecoration: commentCountByPath + ? (ctx) => { + const count = commentCountByPath.get(ctx.item.path); + if (!count) return null; + return { text: String(count), title: `${count} comment${count === 1 ? "" : "s"}` }; + } + : undefined, + }); + + useEffect(() => { + model.resetPaths(paths); + model.setGitStatus(gitStatus); + }, [model, paths, gitStatus]); + + useEffect(() => { + if (!activePath || !pathSet.has(activePath)) return; + const selected = model.getSelectedPaths(); + if (!selected.includes(activePath)) { + for (const p of selected) { + model.getItem(p)?.deselect(); + } + model.getItem(activePath)?.select(); + } + model.scrollToPath(activePath, { focus: false }); + }, [model, activePath, pathSet]); + + const hostStyle = useMemo( + () => + themeToTreeStyles({ + type: resolvedTheme === "dark" ? "dark" : "light", + }), + [resolvedTheme] + ); + + return ( +
+ +
+ ); +} + +export function buildCommentCountByPath( + comments: GitReviewComment[] +): Map { + const counts = new Map(); + for (const c of comments) { + counts.set(c.path, (counts.get(c.path) ?? 0) + 1); + } + return counts; +} diff --git a/src/components/git/DiffInlineReviewComment.tsx b/src/components/git/DiffInlineReviewComment.tsx new file mode 100644 index 0000000..d16c55d --- /dev/null +++ b/src/components/git/DiffInlineReviewComment.tsx @@ -0,0 +1,169 @@ +import { GitHubMarkdownBody } from "~/components/git/GitHubMarkdownBody"; +import { ReviewThreadResolveButton } from "~/components/git/ReviewThreadResolveButton"; +import { ReviewThreadResolvedShell } from "~/components/git/ReviewThreadResolvedShell"; +import type { GitReviewComment } from "~/lib/git/types"; +import { cn } from "~/lib/utils"; + +function looksLikeMarkdown(body: string): boolean { + return /(^|\n)\s{0,3}#{1,6}\s|```|]|/g, "") + // GitHub-only fenced blocks → plain code fences for display + .replace(/```suggestion\n/g, "```\n") + .replace(/```suggestion\r\n/g, "```\r\n") + .trim() + ); +} diff --git a/src/lib/git/resolve-project-link.ts b/src/lib/git/resolve-project-link.ts new file mode 100644 index 0000000..f077f5c --- /dev/null +++ b/src/lib/git/resolve-project-link.ts @@ -0,0 +1,62 @@ +import "@tanstack/react-start/server-only"; + +import { and, desc, eq } from "drizzle-orm"; +import { db } from "~/db"; +import { projectGitRepositories, taskGitBranches } from "~/db/schema"; +import { MULTI_REPO_SELECTION_MESSAGE } from "./constants"; + +export { MULTI_REPO_SELECTION_MESSAGE }; + +/** + * Resolves a project↔repo link for git operations on a task. + * Uses explicit repositoryId, else the task's linked branch repo, else the only linked repo. + */ +export async function resolveProjectRepositoryLink( + projectId: string, + owner: string, + options?: { repositoryId?: string; taskId?: string } +) { + const links = await db.query.projectGitRepositories.findMany({ + where: eq(projectGitRepositories.projectId, projectId), + with: { repository: { with: { connection: true } } }, + }); + + const withConnection = links.filter((l) => l.repository?.connection); + if (withConnection.length === 0) { + throw new Error("No repository linked to this project"); + } + + if (options?.repositoryId) { + const link = withConnection.find( + (l) => l.repositoryId === options.repositoryId + ); + if (!link) { + throw new Error("Repository is not linked to this project"); + } + return link; + } + + if (options?.taskId) { + const branches = await db.query.taskGitBranches.findMany({ + where: and( + eq(taskGitBranches.taskId, options.taskId), + eq(taskGitBranches.owner, owner) + ), + orderBy: [desc(taskGitBranches.updatedAt)], + }); + const active = + branches.find((b) => b.state === "active") ?? branches[0]; + if (active) { + const link = withConnection.find( + (l) => l.repositoryId === active.repositoryId + ); + if (link) return link; + } + } + + if (withConnection.length === 1) { + return withConnection[0]; + } + + throw new Error(MULTI_REPO_SELECTION_MESSAGE); +} diff --git a/src/lib/git/resolve-task-repo.ts b/src/lib/git/resolve-task-repo.ts new file mode 100644 index 0000000..5bb0852 --- /dev/null +++ b/src/lib/git/resolve-task-repo.ts @@ -0,0 +1,101 @@ +import "@tanstack/react-start/server-only"; + +import { and, eq } from "drizzle-orm"; +import { db } from "~/db"; +import { + gitRepositories, + projectGitRepositories, + taskGitBranches, + taskGitPullRequests, + tasks, +} from "~/db/schema"; +import { resolveProjectRepositoryLink } from "./resolve-project-link"; +import type { GitRepo } from "./types"; + +export function toGitRepo( + repository: typeof gitRepositories.$inferSelect +): GitRepo { + return { + externalId: repository.externalId, + fullName: repository.fullName, + defaultBranch: repository.defaultBranch, + htmlUrl: repository.htmlUrl, + isArchived: repository.isArchived, + }; +} + +export async function resolveTaskRepository( + owner: string, + taskId: string, + options?: { repositoryId?: string; pullRequestId?: string } +) { + const task = await db.query.tasks.findFirst({ + where: and(eq(tasks.id, taskId), eq(tasks.owner, owner)), + with: { project: true }, + }); + if (!task) throw new Error("Task not found"); + + if (options?.pullRequestId) { + const pr = await db.query.taskGitPullRequests.findFirst({ + where: and( + eq(taskGitPullRequests.id, options.pullRequestId), + eq(taskGitPullRequests.taskId, taskId), + eq(taskGitPullRequests.owner, owner) + ), + with: { + repository: { with: { connection: true } }, + branch: true, + }, + }); + if (!pr?.repository?.connection) throw new Error("Pull request not found"); + return { + task, + repository: pr.repository, + connection: pr.repository.connection, + branch: pr.branch ?? null, + pullRequest: pr, + }; + } + + const branchWhere = options?.repositoryId + ? and( + eq(taskGitBranches.taskId, taskId), + eq(taskGitBranches.owner, owner), + eq(taskGitBranches.repositoryId, options.repositoryId) + ) + : and(eq(taskGitBranches.taskId, taskId), eq(taskGitBranches.owner, owner)); + + const branches = await db.query.taskGitBranches.findMany({ + where: branchWhere, + with: { repository: { with: { connection: true } } }, + orderBy: (b, { desc: d }) => [d(b.updatedAt)], + }); + const branch = branches[0]; + + if (branch?.repository?.connection) { + return { + task, + repository: branch.repository, + connection: branch.repository.connection, + branch, + pullRequest: null as null, + }; + } + + const projectLink = await resolveProjectRepositoryLink( + task.projectId, + owner, + { + repositoryId: options?.repositoryId, + taskId, + } + ); + + return { + task, + repository: projectLink.repository, + connection: projectLink.repository.connection, + branch: null as null, + pullRequest: null as null, + }; +} diff --git a/src/lib/git/retry.ts b/src/lib/git/retry.ts new file mode 100644 index 0000000..421bda0 --- /dev/null +++ b/src/lib/git/retry.ts @@ -0,0 +1,3 @@ +export function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/src/lib/git/review-comment-annotation.ts b/src/lib/git/review-comment-annotation.ts new file mode 100644 index 0000000..82ff5c5 --- /dev/null +++ b/src/lib/git/review-comment-annotation.ts @@ -0,0 +1,91 @@ +import type { FileDiffMetadata } from "@pierre/diffs"; +import type { DiffLineAnnotation } from "@pierre/diffs"; +import type { GitReviewComment } from "~/lib/git/types"; + +export type DiffLineIndex = { + deletions: Set; + additions: Set; +}; + +export function buildDiffLineIndex(fileDiff: FileDiffMetadata): DiffLineIndex { + const deletions = new Set(); + const additions = new Set(); + for (const hunk of fileDiff.hunks) { + for (let i = 0; i < hunk.deletionCount; i++) { + deletions.add(hunk.deletionStart + i); + } + for (let i = 0; i < hunk.additionCount; i++) { + additions.add(hunk.additionStart + i); + } + } + return { deletions, additions }; +} + +export function inferCommentSide( + comment: GitReviewComment +): "LEFT" | "RIGHT" { + if (comment.side === "LEFT" || comment.side === "RIGHT") { + return comment.side; + } + if (comment.originalLine != null && comment.line == null) return "LEFT"; + return "RIGHT"; +} + +function lineForSide( + comment: GitReviewComment, + side: "LEFT" | "RIGHT" +): number | null { + if (side === "LEFT") { + return comment.originalLine ?? comment.line; + } + return comment.line ?? comment.originalLine; +} + +function pierreSideForGitSide(side: "LEFT" | "RIGHT"): "deletions" | "additions" { + return side === "LEFT" ? "deletions" : "additions"; +} + +/** Prefer comments that still have line/side after GitHub refetch. */ +export function pickRicherReviewComment( + a: GitReviewComment, + b: GitReviewComment +): GitReviewComment { + const score = (c: GitReviewComment) => + (c.line != null ? 2 : 0) + + (c.originalLine != null ? 2 : 0) + + (c.side != null ? 1 : 0); + const winner = score(a) >= score(b) ? a : b; + const other = winner === a ? b : a; + return { + ...winner, + inReplyToId: winner.inReplyToId ?? other.inReplyToId, + }; +} + +export function commentToDiffAnnotation( + comment: GitReviewComment, + index?: DiffLineIndex +): DiffLineAnnotation | null { + const gitSide = inferCommentSide(comment); + let line = lineForSide(comment, gitSide); + if (line == null) return null; + + let pierreSide = pierreSideForGitSide(gitSide); + + if (index) { + const primary = index[pierreSide]; + if (!primary.has(line)) { + const altGitSide = gitSide === "LEFT" ? "RIGHT" : "LEFT"; + const altLine = lineForSide(comment, altGitSide); + const altPierreSide = pierreSideForGitSide(altGitSide); + if (altLine != null && index[altPierreSide].has(altLine)) { + line = altLine; + pierreSide = altPierreSide; + } else if (!primary.has(line)) { + return null; + } + } + } + + return { side: pierreSide, lineNumber: line, metadata: comment }; +} diff --git a/src/lib/git/review-thread.ts b/src/lib/git/review-thread.ts new file mode 100644 index 0000000..fdf4820 --- /dev/null +++ b/src/lib/git/review-thread.ts @@ -0,0 +1,33 @@ +import type { GitReviewComment, GitReviewThread } from "~/lib/git/types"; + +export function applyReviewThreadMetadata( + comments: GitReviewComment[], + threads: GitReviewThread[] +): GitReviewComment[] { + const threadByCommentId = new Map(); + for (const thread of threads) { + for (const id of thread.commentIds) { + threadByCommentId.set(id, thread); + } + } + + return comments.map((comment) => { + const thread = threadByCommentId.get(comment.id); + if (!thread) return comment; + return { + ...comment, + threadNodeId: thread.nodeId, + threadIsResolved: thread.isResolved, + }; + }); +} + +/** First comment in a thread (for resolve UI — one control per conversation). */ +export function isReviewThreadRoot( + comment: GitReviewComment, + comments: GitReviewComment[] +): boolean { + if (!comment.threadNodeId) return false; + if (comment.inReplyToId == null) return true; + return !comments.some((c) => c.id === comment.inReplyToId); +} diff --git a/src/lib/git/scroll-diff-to-line.ts b/src/lib/git/scroll-diff-to-line.ts new file mode 100644 index 0000000..54f67e7 --- /dev/null +++ b/src/lib/git/scroll-diff-to-line.ts @@ -0,0 +1,37 @@ +/** Best-effort scroll to a line in a Pierre / unified diff container. */ +export function scrollDiffToLine( + container: HTMLElement | null, + line: number, + side: "LEFT" | "RIGHT" +): boolean { + if (!container) return false; + + const lineText = String(line); + + const rows = container.querySelectorAll("tr"); + for (const row of rows) { + const cells = row.querySelectorAll("td, th"); + for (const cell of cells) { + if (cell.textContent?.trim() !== lineText) continue; + const inDeletion = + cell.className.includes("deletion") || + cell.closest('[class*="deletion"]') != null; + const inAddition = + cell.className.includes("addition") || + cell.closest('[class*="addition"]') != null; + if (side === "LEFT" && !inDeletion && inAddition) continue; + if (side === "RIGHT" && !inAddition && inDeletion) continue; + row.scrollIntoView({ block: "center", behavior: "smooth" }); + return true; + } + } + + for (const el of container.querySelectorAll("td, span, div")) { + if (el.textContent?.trim() === lineText) { + el.scrollIntoView({ block: "center", behavior: "smooth" }); + return true; + } + } + + return false; +} diff --git a/src/lib/git/status-rules.ts b/src/lib/git/status-rules.ts new file mode 100644 index 0000000..1fa3033 --- /dev/null +++ b/src/lib/git/status-rules.ts @@ -0,0 +1,79 @@ +import type { GitStatusRule } from "~/db/schema"; +import { extractTaskKeysFromText } from "./task-key"; + +export type StatusRuleMatch = { + rule: GitStatusRule; + taskKey: { prefix: string; number: number }; +}; + +/** + * Placeholder in commit status rule patterns. Replaced with the literal task key + * (e.g. PY-12). Use `#?KEY` when the hash is optional: `fixes #PY-12` or `fixes PY-12`. + */ +export const STATUS_RULE_KEY_PLACEHOLDER = "KEY"; + +/** GitHub-style closing keywords (https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue). */ +export const CLOSING_KEYWORDS = + "fix|fixes|fixed|close|closes|closed|resolve|resolves|resolved"; + +/** + * Shared convention for commit messages and status rules: + * - Reference tasks as `PY-12` or `#PY-12` + * - Close/move with ` #?KEY` (e.g. `fixes #PY-12`, `resolves PY-12`) + * Patterns are JavaScript regex; matching is case-insensitive. Do not use `(?i)`. + */ +export const COMMIT_STATUS_RULE_CONVENTION = { + taskReference: "PY-12 or #PY-12", + closeExample: "fixes #PY-12", + wipExample: "#PY-12 wip", + placeholder: STATUS_RULE_KEY_PLACEHOLDER, +} as const; + +/** `(?i)` is PCRE-style; JavaScript RegExp uses the `i` flag instead. */ +export function normalizeStatusRulePattern(pattern: string): string { + return pattern.replace(/^\(\?i\)/, "").trim(); +} + +export function buildRuleRegex(pattern: string, taskKey: string): RegExp { + const escapedKey = taskKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const withKey = normalizeStatusRulePattern(pattern).replaceAll( + STATUS_RULE_KEY_PLACEHOLDER, + escapedKey + ); + return new RegExp(withKey, "i"); +} + +export function matchStatusRules( + message: string, + rules: GitStatusRule[], + taskKeys: { prefix: string; number: number }[] +): StatusRuleMatch | null { + const keys = + taskKeys.length > 0 ? taskKeys : extractTaskKeysFromText(message); + + const sortedRules = [...rules].sort((a, b) => b.priority - a.priority); + + for (const taskKey of keys) { + const displayKey = `${taskKey.prefix}-${taskKey.number}`; + for (const rule of sortedRules) { + let regex: RegExp; + try { + regex = buildRuleRegex(rule.pattern, displayKey); + } catch { + continue; + } + if (regex.test(message)) { + return { rule, taskKey }; + } + } + } + + return null; +} + +export const DEFAULT_STATUS_RULE_PATTERNS = { + /** e.g. fixes #PY-12, resolves PY-12 */ + close: `(?:${CLOSING_KEYWORDS})\\s+#?${STATUS_RULE_KEY_PLACEHOLDER}\\b`, + /** e.g. #PY-12 wip, PY-12 in progress */ + wip: `#?${STATUS_RULE_KEY_PLACEHOLDER}\\b\\s+(?:wip|in[- ]?progress)`, +} as const; diff --git a/src/lib/git/task-dev-phase.ts b/src/lib/git/task-dev-phase.ts new file mode 100644 index 0000000..5d8bcf5 --- /dev/null +++ b/src/lib/git/task-dev-phase.ts @@ -0,0 +1,79 @@ +import type { TaskGitPullRequest } from "~/db/schema"; + +type ProjectRepoLink = { + repositoryId: string; + defaultBaseBranch: string | null; + repository: { defaultBranch: string }; +}; + +type BranchRow = { + id: string; + ref: string; + sha: string | null; + state: string; + repositoryId: string; + repository: { fullName: string; htmlUrl: string; defaultBranch: string }; +}; + +type PrRow = TaskGitPullRequest & { + updatedAt: Date | string; +}; + +export type TaskDevPhase = + | "no_repo" + | "not_started" + | "branch_only" + | "open_pr" + | "closed_pr"; + +export type TaskDevPhaseContext = { + projectRepos: ProjectRepoLink[]; + branches: BranchRow[]; + pullRequests: PrRow[]; +}; + +export function getActiveBranch(ctx: TaskDevPhaseContext) { + return ctx.branches.find((b) => b.state === "active") ?? ctx.branches[0]; +} + +export function getOpenPr(ctx: TaskDevPhaseContext) { + return ctx.pullRequests.find( + (p) => p.state === "open" || p.state === "draft" + ); +} + +export function getLatestPr(ctx: TaskDevPhaseContext) { + return ctx.pullRequests[0] ?? null; +} + +export function getTaskDevPhase(ctx: TaskDevPhaseContext): TaskDevPhase { + if (ctx.projectRepos.length === 0) return "no_repo"; + if (getOpenPr(ctx)) return "open_pr"; + if (getActiveBranch(ctx)) return "branch_only"; + if (getLatestPr(ctx)) return "closed_pr"; + return "not_started"; +} + +export function resolveBaseRef( + ctx: TaskDevPhaseContext, + branch: BranchRow | undefined, + pr: PrRow | null | undefined +): string { + if (pr?.baseRef) return pr.baseRef; + if (branch) { + const link = ctx.projectRepos.find( + (r) => r.repositoryId === branch.repositoryId + ); + if (link?.defaultBaseBranch) return link.defaultBaseBranch; + return branch.repository.defaultBranch; + } + if (ctx.projectRepos.length === 1) { + const link = ctx.projectRepos[0]; + return link?.defaultBaseBranch ?? link?.repository.defaultBranch ?? "main"; + } + return "main"; +} + +export function canReviewInline(phase: TaskDevPhase): boolean { + return phase === "open_pr"; +} diff --git a/src/lib/git/task-git-review-nav.tsx b/src/lib/git/task-git-review-nav.tsx new file mode 100644 index 0000000..81a2e48 --- /dev/null +++ b/src/lib/git/task-git-review-nav.tsx @@ -0,0 +1,60 @@ +import { + createContext, + useCallback, + useContext, + useMemo, + useState, + type ReactNode, +} from "react"; + +export type TaskGitReviewLineFocus = { + path: string; + line: number; + side: "LEFT" | "RIGHT"; +}; + +type TaskGitReviewNavContextValue = { + lineFocus: TaskGitReviewLineFocus | null; + focusLineInDiff: (target: TaskGitReviewLineFocus) => void; + clearLineFocus: () => void; +}; + +const TaskGitReviewNavContext = + createContext(null); + +export function TaskGitReviewNavProvider({ + children, + onOpenDevelopment, +}: { + children: ReactNode; + onOpenDevelopment: () => void; +}) { + const [lineFocus, setLineFocus] = useState( + null + ); + + const focusLineInDiff = useCallback( + (target: TaskGitReviewLineFocus) => { + onOpenDevelopment(); + setLineFocus(target); + }, + [onOpenDevelopment] + ); + + const clearLineFocus = useCallback(() => setLineFocus(null), []); + + const value = useMemo( + () => ({ lineFocus, focusLineInDiff, clearLineFocus }), + [lineFocus, focusLineInDiff, clearLineFocus] + ); + + return ( + + {children} + + ); +} + +export function useTaskGitReviewNav() { + return useContext(TaskGitReviewNavContext); +} diff --git a/src/lib/git/task-key.ts b/src/lib/git/task-key.ts new file mode 100644 index 0000000..ceef30c --- /dev/null +++ b/src/lib/git/task-key.ts @@ -0,0 +1,68 @@ +/** Suggest a task key prefix from a project name (e.g. "Project Y" → "PY"). */ +export function suggestTaskKeyPrefix(projectName: string): string { + const words = projectName + .trim() + .split(/\s+/) + .filter(Boolean); + if (words.length === 0) return "TASK"; + if (words.length === 1) { + const w = words[0].replace(/[^a-zA-Z0-9]/g, ""); + return (w.slice(0, 4) || "TASK").toUpperCase(); + } + return words + .map((w) => w[0]) + .join("") + .replace(/[^a-zA-Z0-9]/g, "") + .slice(0, 6) + .toUpperCase() || "TASK"; +} + +export function formatTaskKey(prefix: string | null | undefined, number: number) { + if (!prefix) return `#${number}`; + return `${prefix}-${number}`; +} + +/** Matches PY-12 and GitHub-style #PY-12 (case-insensitive). */ +const TASK_KEY_REGEX = /#?([A-Za-z][A-Za-z0-9]*)-(\d+)\b/gi; + +export function extractTaskKeysFromText(text: string): { prefix: string; number: number }[] { + const results: { prefix: string; number: number }[] = []; + const seen = new Set(); + for (const match of text.matchAll(TASK_KEY_REGEX)) { + const prefix = match[1].toUpperCase(); + const number = parseInt(match[2], 10); + const key = `${prefix}-${number}`; + if (!seen.has(key)) { + seen.add(key); + results.push({ prefix, number }); + } + } + return results; +} + +export function slugifyForBranch(text: string, maxLength = 40): string { + return text + .toLowerCase() + .normalize("NFKD") + .replace(/[\u0300-\u036f]/g, "") + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, "") + .slice(0, maxLength) + .replace(/-+$/, ""); +} + +export function defaultBranchName( + taskKey: string, + taskName: string +): string { + const slug = slugifyForBranch(taskName); + const base = taskKey.toLowerCase(); + return slug ? `${base}-${slug}` : base; +} + +/** True when a git branch ref belongs to this task key (e.g. py-42 or py-42-fix-login). */ +export function branchMatchesTaskKey(branchRef: string, taskKey: string): boolean { + const prefix = taskKey.toLowerCase(); + const ref = branchRef.toLowerCase(); + return ref === prefix || ref.startsWith(`${prefix}-`); +} diff --git a/src/lib/git/types.ts b/src/lib/git/types.ts new file mode 100644 index 0000000..7dfe4d4 --- /dev/null +++ b/src/lib/git/types.ts @@ -0,0 +1,331 @@ +import type { GitConnection, GitProviderType } from "~/db/schema"; + +export type GitRepo = { + externalId: string; + fullName: string; + defaultBranch: string; + htmlUrl: string; + isArchived: boolean; +}; + +export type GitBranch = { + ref: string; + sha: string; + htmlUrl: string; +}; + +export type GitPullRequest = { + providerPrId: string; + number: number; + url: string; + title: string; + body: string | null; + state: "open" | "closed" | "merged" | "draft"; + headRef: string; + baseRef: string; + headSha: string; + mergedAt: Date | null; + closedAt: Date | null; +}; + +export type GitCommit = { + sha: string; + message: string; + authorLogin: string | null; + committedAt: Date; + htmlUrl: string; +}; + +export type GitCompareResult = { + commits: GitCommit[]; + files: GitDiffFile[]; + baseRef: string; + headRef: string; +}; + +export type GitReviewComment = { + id: number; + body: string; + path: string; + line: number | null; + originalLine: number | null; + side: "LEFT" | "RIGHT" | null; + commitId: string; + authorLogin: string; + authorAvatarUrl: string | null; + authorHtmlUrl: string | null; + createdAt: Date; + inReplyToId: number | null; + reviewId: number | null; + url: string; + /** GraphQL node id of the review thread (for resolve / unresolve). */ + threadNodeId?: string; + threadIsResolved?: boolean; +}; + +export type GitReviewThread = { + nodeId: string; + isResolved: boolean; + path: string; + line: number | null; + originalLine: number | null; + side: "LEFT" | "RIGHT" | null; + commentIds: number[]; +}; + +/** General PR conversation comment (timeline), not tied to a diff line. */ +export type GitPullRequestIssueComment = { + id: number; + body: string; + authorLogin: string; + authorAvatarUrl: string | null; + authorHtmlUrl: string | null; + isBot: boolean; + createdAt: Date; + updatedAt: Date | null; + url: string; +}; + +export type GitCheckConclusion = + | "success" + | "failure" + | "neutral" + | "cancelled" + | "skipped" + | "timed_out" + | "action_required" + | "stale" + | null; + +export type GitCheckStatus = + | "queued" + | "in_progress" + | "completed" + | "waiting" + | "pending" + | "requested"; + +export type GitPullRequestCheck = { + id: string; + name: string; + status: GitCheckStatus; + conclusion: GitCheckConclusion; + htmlUrl: string; + description: string | null; + source: "check_run" | "status"; + /** GitHub App slug when from a check run. */ + appSlug: string | null; + /** App or status creator avatar from GitHub. */ + avatarUrl: string | null; +}; + +export type GitPullRequestMergeStatus = { + state: GitPullRequest["state"]; + mergeable: boolean | null; + mergeableState: string; + checks: GitPullRequestCheck[]; +}; + +export type GitPullRequestReview = { + id: number; + body: string | null; + state: "COMMENTED" | "APPROVED" | "CHANGES_REQUESTED" | "DISMISSED" | "PENDING"; + commitId: string | null; + authorLogin: string; + authorAvatarUrl: string | null; + authorHtmlUrl: string | null; + submittedAt: Date | null; + url: string | null; +}; + +export type CreateReviewCommentInput = { + body: string; + commitId: string; + path: string; + line: number; + side?: "LEFT" | "RIGHT"; + inReplyTo?: number; + reviewId: number; +}; + +export type SubmitPullRequestReviewInput = { + reviewId?: number; + commitId: string; + body?: string; + event: "COMMENT" | "APPROVE" | "REQUEST_CHANGES"; +}; + +export type GitDiffFile = { + path: string; + oldPath?: string; + status: "added" | "removed" | "modified" | "renamed"; + patch?: string; + oldContent?: string; + newContent?: string; +}; + +export type GitWebhookEvent = { + provider: GitProviderType; + eventType: string; + deliveryId: string; + payload: unknown; +}; + +export interface GitProvider { + listAccessibleRepos(connection: GitConnection): Promise; + syncRepositories(connection: GitConnection): Promise; + createBranch( + connection: GitConnection, + repo: GitRepo, + options: { base: string; name: string; userAccessToken?: string } + ): Promise; + getPullRequest( + connection: GitConnection, + repo: GitRepo, + prNumber: number + ): Promise; + listPullRequestsForRef( + connection: GitConnection, + repo: GitRepo, + headRef: string + ): Promise; + createPullRequest( + connection: GitConnection, + repo: GitRepo, + options: { + title: string; + body: string; + head: string; + base: string; + userAccessToken?: string; + } + ): Promise; + getPullRequestDiff( + connection: GitConnection, + repo: GitRepo, + prNumber: number + ): Promise; + compareBranches( + connection: GitConnection, + repo: GitRepo, + base: string, + head: string + ): Promise; + listPullRequestCommits( + connection: GitConnection, + repo: GitRepo, + prNumber: number + ): Promise; + getCommitDiff( + connection: GitConnection, + repo: GitRepo, + sha: string + ): Promise; + listPullRequestIssueComments( + connection: GitConnection, + repo: GitRepo, + prNumber: number + ): Promise; + listPullRequestReviewComments( + connection: GitConnection, + repo: GitRepo, + prNumber: number + ): Promise; + /** Includes pending review comments visible only to the author until submit. */ + listPullRequestReviewCommentsAsUser( + connection: GitConnection, + repo: GitRepo, + prNumber: number, + userAccessToken: string + ): Promise; + listPullRequestReviewCommentsForReviewAsUser( + connection: GitConnection, + repo: GitRepo, + prNumber: number, + reviewId: number, + userAccessToken: string + ): Promise; + /** Line-accurate thread comments (GraphQL); best source for pending review placement. */ + listPullRequestReviewThreadCommentsAsUser( + connection: GitConnection, + repo: GitRepo, + prNumber: number, + userAccessToken: string + ): Promise; + listPullRequestReviewThreads( + connection: GitConnection, + repo: GitRepo, + prNumber: number + ): Promise; + setReviewThreadResolved( + threadNodeId: string, + resolved: boolean, + userAccessToken: string + ): Promise; + listPullRequestReviews( + connection: GitConnection, + repo: GitRepo, + prNumber: number + ): Promise; + findPendingReviewForUser( + connection: GitConnection, + repo: GitRepo, + prNumber: number, + authorLogin: string, + userAccessToken: string, + options?: { retries?: number } + ): Promise; + startPendingReview( + connection: GitConnection, + repo: GitRepo, + prNumber: number, + commitId: string, + userAccessToken: string, + authorLogin: string + ): Promise; + createPullRequestReviewComment( + connection: GitConnection, + repo: GitRepo, + prNumber: number, + input: CreateReviewCommentInput, + userAccessToken: string + ): Promise; + submitPullRequestReview( + connection: GitConnection, + repo: GitRepo, + prNumber: number, + input: SubmitPullRequestReviewInput, + userAccessToken: string + ): Promise; + discardPendingReview( + connection: GitConnection, + repo: GitRepo, + prNumber: number, + reviewId: number, + userAccessToken: string + ): Promise; + getPullRequestMergeStatus( + connection: GitConnection, + repo: GitRepo, + prNumber: number, + options?: { userAccessToken?: string } + ): Promise; + mergePullRequest( + connection: GitConnection, + repo: GitRepo, + prNumber: number, + userAccessToken: string, + options?: { mergeMethod?: "merge" | "squash" | "rebase" } + ): Promise; + closePullRequest( + connection: GitConnection, + repo: GitRepo, + prNumber: number, + userAccessToken: string + ): Promise; + parsePullRequestUrl(url: string): { + owner: string; + repo: string; + number: number; + } | null; +} diff --git a/src/lib/git/upsert-task-pull-request.ts b/src/lib/git/upsert-task-pull-request.ts new file mode 100644 index 0000000..19c8e1c --- /dev/null +++ b/src/lib/git/upsert-task-pull-request.ts @@ -0,0 +1,77 @@ +import "@tanstack/react-start/server-only"; + +import { and, eq } from "drizzle-orm"; +import { v7 as uuid } from "uuid"; +import { db } from "~/db"; +import { taskGitPullRequests, type GitPrState } from "~/db/schema"; + +export type UpsertTaskPullRequestInput = { + owner: string; + taskId: string; + repositoryId: string; + branchId?: string | null; + providerPrId: string; + number: number; + url: string; + title: string; + state: GitPrState; + headRef: string; + baseRef: string; + mergedAt: Date | null; + closedAt: Date | null; +}; + +/** Insert or update by (repositoryId, number) — matches webhook + manual link behavior. */ +export async function upsertTaskPullRequest( + input: UpsertTaskPullRequestInput +): Promise { + const existing = await db.query.taskGitPullRequests.findFirst({ + where: and( + eq(taskGitPullRequests.repositoryId, input.repositoryId), + eq(taskGitPullRequests.number, input.number) + ), + }); + + const now = new Date(); + + if (existing) { + await db + .update(taskGitPullRequests) + .set({ + taskId: input.taskId, + branchId: input.branchId ?? existing.branchId, + owner: input.owner, + providerPrId: input.providerPrId, + url: input.url, + title: input.title, + state: input.state, + headRef: input.headRef, + baseRef: input.baseRef, + mergedAt: input.mergedAt, + closedAt: input.closedAt, + updatedAt: now, + }) + .where(eq(taskGitPullRequests.id, existing.id)); + return existing.id; + } + + const id = uuid(); + await db.insert(taskGitPullRequests).values({ + id, + taskId: input.taskId, + repositoryId: input.repositoryId, + branchId: input.branchId ?? null, + owner: input.owner, + number: input.number, + providerPrId: input.providerPrId, + url: input.url, + title: input.title, + state: input.state, + headRef: input.headRef, + baseRef: input.baseRef, + mergedAt: input.mergedAt, + closedAt: input.closedAt, + updatedAt: now, + }); + return id; +} diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 1802a89..4170b78 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -20,12 +20,15 @@ import { Route as SignedInTasksRouteImport } from './routes/_signed-in/tasks' import { Route as SignedInStatusesRouteImport } from './routes/_signed-in/statuses' import { Route as SignedInLabelsRouteImport } from './routes/_signed-in/labels' import { Route as SignedInDashboardRouteImport } from './routes/_signed-in/dashboard' +import { Route as ApiWebhooksGithubRouteImport } from './routes/api/webhooks/github' import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$' import { Route as SignedInTasksTaskIdRouteImport } from './routes/_signed-in/tasks.$taskId' import { Route as SignedInSprintsNewRouteImport } from './routes/_signed-in/sprints.new' import { Route as SignedInSettingsOrganizationRouteImport } from './routes/_signed-in/settings.organization' +import { Route as SignedInSettingsIntegrationsRouteImport } from './routes/_signed-in/settings.integrations' import { Route as SignedInSettingsAccountRouteImport } from './routes/_signed-in/settings.account' import { Route as SignedInProjectsNewRouteImport } from './routes/_signed-in/projects.new' +import { Route as ApiGitGithubCallbackRouteImport } from './routes/api/git/github/callback' import { Route as SignedInSprintsSprintIdTasksRouteImport } from './routes/_signed-in/sprints.$sprintId.tasks' import { Route as SignedInSprintsSprintIdSettingsRouteImport } from './routes/_signed-in/sprints.$sprintId.settings' import { Route as SignedInSettingsOrganizationNewRouteImport } from './routes/_signed-in/settings.organization.new' @@ -88,6 +91,11 @@ const SignedInDashboardRoute = SignedInDashboardRouteImport.update({ path: '/dashboard', getParentRoute: () => SignedInRoute, } as any) +const ApiWebhooksGithubRoute = ApiWebhooksGithubRouteImport.update({ + id: '/api/webhooks/github', + path: '/api/webhooks/github', + getParentRoute: () => rootRouteImport, +} as any) const ApiAuthSplatRoute = ApiAuthSplatRouteImport.update({ id: '/api/auth/$', path: '/api/auth/$', @@ -109,6 +117,12 @@ const SignedInSettingsOrganizationRoute = path: '/settings/organization', getParentRoute: () => SignedInRoute, } as any) +const SignedInSettingsIntegrationsRoute = + SignedInSettingsIntegrationsRouteImport.update({ + id: '/settings/integrations', + path: '/settings/integrations', + getParentRoute: () => SignedInRoute, + } as any) const SignedInSettingsAccountRoute = SignedInSettingsAccountRouteImport.update({ id: '/settings/account', path: '/settings/account', @@ -119,6 +133,11 @@ const SignedInProjectsNewRoute = SignedInProjectsNewRouteImport.update({ path: '/projects/new', getParentRoute: () => SignedInRoute, } as any) +const ApiGitGithubCallbackRoute = ApiGitGithubCallbackRouteImport.update({ + id: '/api/git/github/callback', + path: '/api/git/github/callback', + getParentRoute: () => rootRouteImport, +} as any) const SignedInSprintsSprintIdTasksRoute = SignedInSprintsSprintIdTasksRouteImport.update({ id: '/sprints/$sprintId/tasks', @@ -175,15 +194,18 @@ export interface FileRoutesByFullPath { '/sign-in/$': typeof SignInSplatRoute '/projects/new': typeof SignedInProjectsNewRoute '/settings/account': typeof SignedInSettingsAccountRoute + '/settings/integrations': typeof SignedInSettingsIntegrationsRoute '/settings/organization': typeof SignedInSettingsOrganizationRouteWithChildren '/sprints/new': typeof SignedInSprintsNewRoute '/tasks/$taskId': typeof SignedInTasksTaskIdRoute '/api/auth/$': typeof ApiAuthSplatRoute + '/api/webhooks/github': typeof ApiWebhooksGithubRoute '/projects/$projectId/settings': typeof SignedInProjectsProjectIdSettingsRoute '/projects/$projectId/tasks': typeof SignedInProjectsProjectIdTasksRouteWithChildren '/settings/organization/new': typeof SignedInSettingsOrganizationNewRoute '/sprints/$sprintId/settings': typeof SignedInSprintsSprintIdSettingsRoute '/sprints/$sprintId/tasks': typeof SignedInSprintsSprintIdTasksRouteWithChildren + '/api/git/github/callback': typeof ApiGitGithubCallbackRoute '/projects/$projectId/tasks/$taskId': typeof SignedInProjectsProjectIdTasksTaskIdRoute '/sprints/$sprintId/tasks/$taskId': typeof SignedInSprintsSprintIdTasksTaskIdRoute } @@ -200,15 +222,18 @@ export interface FileRoutesByTo { '/sign-in/$': typeof SignInSplatRoute '/projects/new': typeof SignedInProjectsNewRoute '/settings/account': typeof SignedInSettingsAccountRoute + '/settings/integrations': typeof SignedInSettingsIntegrationsRoute '/settings/organization': typeof SignedInSettingsOrganizationRouteWithChildren '/sprints/new': typeof SignedInSprintsNewRoute '/tasks/$taskId': typeof SignedInTasksTaskIdRoute '/api/auth/$': typeof ApiAuthSplatRoute + '/api/webhooks/github': typeof ApiWebhooksGithubRoute '/projects/$projectId/settings': typeof SignedInProjectsProjectIdSettingsRoute '/projects/$projectId/tasks': typeof SignedInProjectsProjectIdTasksRouteWithChildren '/settings/organization/new': typeof SignedInSettingsOrganizationNewRoute '/sprints/$sprintId/settings': typeof SignedInSprintsSprintIdSettingsRoute '/sprints/$sprintId/tasks': typeof SignedInSprintsSprintIdTasksRouteWithChildren + '/api/git/github/callback': typeof ApiGitGithubCallbackRoute '/projects/$projectId/tasks/$taskId': typeof SignedInProjectsProjectIdTasksTaskIdRoute '/sprints/$sprintId/tasks/$taskId': typeof SignedInSprintsSprintIdTasksTaskIdRoute } @@ -227,15 +252,18 @@ export interface FileRoutesById { '/sign-in/$': typeof SignInSplatRoute '/_signed-in/projects/new': typeof SignedInProjectsNewRoute '/_signed-in/settings/account': typeof SignedInSettingsAccountRoute + '/_signed-in/settings/integrations': typeof SignedInSettingsIntegrationsRoute '/_signed-in/settings/organization': typeof SignedInSettingsOrganizationRouteWithChildren '/_signed-in/sprints/new': typeof SignedInSprintsNewRoute '/_signed-in/tasks/$taskId': typeof SignedInTasksTaskIdRoute '/api/auth/$': typeof ApiAuthSplatRoute + '/api/webhooks/github': typeof ApiWebhooksGithubRoute '/_signed-in/projects/$projectId/settings': typeof SignedInProjectsProjectIdSettingsRoute '/_signed-in/projects/$projectId/tasks': typeof SignedInProjectsProjectIdTasksRouteWithChildren '/_signed-in/settings/organization/new': typeof SignedInSettingsOrganizationNewRoute '/_signed-in/sprints/$sprintId/settings': typeof SignedInSprintsSprintIdSettingsRoute '/_signed-in/sprints/$sprintId/tasks': typeof SignedInSprintsSprintIdTasksRouteWithChildren + '/api/git/github/callback': typeof ApiGitGithubCallbackRoute '/_signed-in/projects/$projectId/tasks/$taskId': typeof SignedInProjectsProjectIdTasksTaskIdRoute '/_signed-in/sprints/$sprintId/tasks/$taskId': typeof SignedInSprintsSprintIdTasksTaskIdRoute } @@ -254,15 +282,18 @@ export interface FileRouteTypes { | '/sign-in/$' | '/projects/new' | '/settings/account' + | '/settings/integrations' | '/settings/organization' | '/sprints/new' | '/tasks/$taskId' | '/api/auth/$' + | '/api/webhooks/github' | '/projects/$projectId/settings' | '/projects/$projectId/tasks' | '/settings/organization/new' | '/sprints/$sprintId/settings' | '/sprints/$sprintId/tasks' + | '/api/git/github/callback' | '/projects/$projectId/tasks/$taskId' | '/sprints/$sprintId/tasks/$taskId' fileRoutesByTo: FileRoutesByTo @@ -279,15 +310,18 @@ export interface FileRouteTypes { | '/sign-in/$' | '/projects/new' | '/settings/account' + | '/settings/integrations' | '/settings/organization' | '/sprints/new' | '/tasks/$taskId' | '/api/auth/$' + | '/api/webhooks/github' | '/projects/$projectId/settings' | '/projects/$projectId/tasks' | '/settings/organization/new' | '/sprints/$sprintId/settings' | '/sprints/$sprintId/tasks' + | '/api/git/github/callback' | '/projects/$projectId/tasks/$taskId' | '/sprints/$sprintId/tasks/$taskId' id: @@ -305,15 +339,18 @@ export interface FileRouteTypes { | '/sign-in/$' | '/_signed-in/projects/new' | '/_signed-in/settings/account' + | '/_signed-in/settings/integrations' | '/_signed-in/settings/organization' | '/_signed-in/sprints/new' | '/_signed-in/tasks/$taskId' | '/api/auth/$' + | '/api/webhooks/github' | '/_signed-in/projects/$projectId/settings' | '/_signed-in/projects/$projectId/tasks' | '/_signed-in/settings/organization/new' | '/_signed-in/sprints/$sprintId/settings' | '/_signed-in/sprints/$sprintId/tasks' + | '/api/git/github/callback' | '/_signed-in/projects/$projectId/tasks/$taskId' | '/_signed-in/sprints/$sprintId/tasks/$taskId' fileRoutesById: FileRoutesById @@ -327,6 +364,8 @@ export interface RootRouteChildren { ApiUploadthingRoute: typeof ApiUploadthingRoute SignInSplatRoute: typeof SignInSplatRoute ApiAuthSplatRoute: typeof ApiAuthSplatRoute + ApiWebhooksGithubRoute: typeof ApiWebhooksGithubRoute + ApiGitGithubCallbackRoute: typeof ApiGitGithubCallbackRoute } declare module '@tanstack/react-router' { @@ -408,6 +447,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SignedInDashboardRouteImport parentRoute: typeof SignedInRoute } + '/api/webhooks/github': { + id: '/api/webhooks/github' + path: '/api/webhooks/github' + fullPath: '/api/webhooks/github' + preLoaderRoute: typeof ApiWebhooksGithubRouteImport + parentRoute: typeof rootRouteImport + } '/api/auth/$': { id: '/api/auth/$' path: '/api/auth/$' @@ -436,6 +482,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SignedInSettingsOrganizationRouteImport parentRoute: typeof SignedInRoute } + '/_signed-in/settings/integrations': { + id: '/_signed-in/settings/integrations' + path: '/settings/integrations' + fullPath: '/settings/integrations' + preLoaderRoute: typeof SignedInSettingsIntegrationsRouteImport + parentRoute: typeof SignedInRoute + } '/_signed-in/settings/account': { id: '/_signed-in/settings/account' path: '/settings/account' @@ -450,6 +503,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SignedInProjectsNewRouteImport parentRoute: typeof SignedInRoute } + '/api/git/github/callback': { + id: '/api/git/github/callback' + path: '/api/git/github/callback' + fullPath: '/api/git/github/callback' + preLoaderRoute: typeof ApiGitGithubCallbackRouteImport + parentRoute: typeof rootRouteImport + } '/_signed-in/sprints/$sprintId/tasks': { id: '/_signed-in/sprints/$sprintId/tasks' path: '/sprints/$sprintId/tasks' @@ -565,6 +625,7 @@ interface SignedInRouteChildren { SignedInTasksRoute: typeof SignedInTasksRouteWithChildren SignedInProjectsNewRoute: typeof SignedInProjectsNewRoute SignedInSettingsAccountRoute: typeof SignedInSettingsAccountRoute + SignedInSettingsIntegrationsRoute: typeof SignedInSettingsIntegrationsRoute SignedInSettingsOrganizationRoute: typeof SignedInSettingsOrganizationRouteWithChildren SignedInSprintsNewRoute: typeof SignedInSprintsNewRoute SignedInProjectsProjectIdSettingsRoute: typeof SignedInProjectsProjectIdSettingsRoute @@ -580,6 +641,7 @@ const SignedInRouteChildren: SignedInRouteChildren = { SignedInTasksRoute: SignedInTasksRouteWithChildren, SignedInProjectsNewRoute: SignedInProjectsNewRoute, SignedInSettingsAccountRoute: SignedInSettingsAccountRoute, + SignedInSettingsIntegrationsRoute: SignedInSettingsIntegrationsRoute, SignedInSettingsOrganizationRoute: SignedInSettingsOrganizationRouteWithChildren, SignedInSprintsNewRoute: SignedInSprintsNewRoute, @@ -605,6 +667,8 @@ const rootRouteChildren: RootRouteChildren = { ApiUploadthingRoute: ApiUploadthingRoute, SignInSplatRoute: SignInSplatRoute, ApiAuthSplatRoute: ApiAuthSplatRoute, + ApiWebhooksGithubRoute: ApiWebhooksGithubRoute, + ApiGitGithubCallbackRoute: ApiGitGithubCallbackRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/src/routes/_signed-in/settings.integrations.tsx b/src/routes/_signed-in/settings.integrations.tsx new file mode 100644 index 0000000..fbdb2de --- /dev/null +++ b/src/routes/_signed-in/settings.integrations.tsx @@ -0,0 +1,269 @@ +import { createFileRoute, Link } from "@tanstack/react-router"; +import { useQueryClient, useSuspenseQuery } from "@tanstack/react-query"; +import { Plus, Trash2 } from "lucide-react"; +import { PageLayout } from "~/components/PageLayout"; +import { PageSection, PageSectionContent } from "~/components/PageSection"; +import { Button } from "~/components/ui/button"; +import { Badge } from "~/components/ui/badge"; +import { + gitConnectionQueryOptions, + gitStatusRulesQueryOptions, +} from "~/db/queries/git"; +import { fetchGitHubInstallUrls } from "~/db/queries/git-install"; +import { + useCreateGitStatusRuleMutation, + useDeleteGitStatusRuleMutation, + useDisconnectGitHubMutation, + useSyncGitRepositoriesMutation, +} from "~/db/mutations/git"; +import { useStatusesQuery } from "~/db/queries/statuses"; +import { Input } from "~/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "~/components/ui/select"; +import { useEffect, useState } from "react"; +import { toast } from "sonner"; +import { z } from "zod"; +import { + COMMIT_STATUS_RULE_CONVENTION, + DEFAULT_STATUS_RULE_PATTERNS, +} from "~/lib/git/status-rules"; + +const integrationsSearchSchema = z.object({ + installed: z.enum(["1", "error"]).optional().catch(undefined), + user: z.enum(["connected", "error"]).optional().catch(undefined), + error: z.string().optional().catch(undefined), +}); + +export const Route = createFileRoute("/_signed-in/settings/integrations")({ + validateSearch: integrationsSearchSchema, + head: () => ({ + meta: [{ title: "Integrations" }], + }), + loader: async ({ context }) => { + await context.queryClient.ensureQueryData(gitConnectionQueryOptions()); + await context.queryClient.ensureQueryData(gitStatusRulesQueryOptions()); + }, + component: IntegrationsPage, +}); + +function IntegrationsPage() { + const search = Route.useSearch(); + const queryClient = useQueryClient(); + const { data: connectionData } = useSuspenseQuery(gitConnectionQueryOptions()); + const { data: rules } = useSuspenseQuery(gitStatusRulesQueryOptions()); + const { data: installUrls } = useSuspenseQuery({ + queryKey: ["git", "install-urls"], + queryFn: () => fetchGitHubInstallUrls(), + }); + const statusesQuery = useStatusesQuery(); + const syncRepos = useSyncGitRepositoriesMutation(); + const disconnect = useDisconnectGitHubMutation(); + const createRule = useCreateGitStatusRuleMutation(); + const deleteRule = useDeleteGitStatusRuleMutation(); + + const [newPattern, setNewPattern] = useState( + DEFAULT_STATUS_RULE_PATTERNS.close + ); + const [newStatusId, setNewStatusId] = useState(""); + + const connection = connectionData.connection; + const userLink = connectionData.userLink; + + useEffect(() => { + if (search.installed === "1") { + toast.success("GitHub App connected"); + void queryClient.invalidateQueries({ queryKey: ["git"] }); + } else if (search.installed === "error") { + toast.error("GitHub installation failed. Try Install again from this page."); + } + if (search.user === "connected") { + toast.success("GitHub account linked"); + void queryClient.invalidateQueries({ queryKey: ["git"] }); + } else if (search.user === "error") { + toast.error("GitHub account link failed"); + } + if (search.error) { + toast.error(`GitHub: ${search.error}`); + } + }, [ + search.installed, + search.user, + search.error, + queryClient, + ]); + + return ( + +

+ Connect Git hosting and automate workflows. +

+ + + {!installUrls.configured ? ( +

+ GitHub App is not configured. Set GITHUB_APP_* and GIT_TOKEN_ENCRYPTION_KEY + environment variables. +

+ ) : connection ? ( +
+
+ Connected + + {connection.accountLogin} ({connection.accountType}) + +
+
+ + +
+
+ ) : ( +
+

+ Install the GitHub App on your organization or account to link repositories, + branches, and pull requests to tasks. +

+ +
+ )} + + {installUrls.configured && ( +
+

Personal GitHub (optional)

+

+ Connect your GitHub account to create branches and PRs when the app lacks + permission. +

+ {!installUrls.userOAuthConfigured ? ( +

+ Set GITHUB_APP_CLIENT_ID and GITHUB_APP_CLIENT_SECRET, and add your + callback URL in the GitHub App settings. +

+ ) : userLink ? ( + Connected as @{userLink.providerLogin} + ) : ( + + )} +
+ )} +
+
+ + {connection && ( + + +

+ Reference tasks as{" "} + {COMMIT_STATUS_RULE_CONVENTION.taskReference}{" "} + in commit messages (same style as GitHub issue links). Use{" "} + {COMMIT_STATUS_RULE_CONVENTION.placeholder} in + patterns; it becomes the project key (e.g. PY-42). Optional hash:{" "} + #?KEY matches{" "} + {COMMIT_STATUS_RULE_CONVENTION.closeExample} and{" "} + fixes PY-42. +

+

+ Default close pattern:{" "} + {DEFAULT_STATUS_RULE_PATTERNS.close} +

+
    + {rules.map((rule) => { + const status = statusesQuery.data?.find( + (s) => s.id === rule.targetStatusId + ); + return ( +
  • + + {rule.pattern} + {" → "} + {status?.name ?? rule.targetStatusId} + + +
  • + ); + })} +
+
+
+ setNewPattern(e.target.value)} + placeholder="Pattern with KEY" + /> +
+ + +
+
+
+ )} + +

+ + Organization settings + {" "} + · GitLab support coming soon +

+
+ ); +} diff --git a/src/routes/api/git/github/callback.ts b/src/routes/api/git/github/callback.ts new file mode 100644 index 0000000..795ff1e --- /dev/null +++ b/src/routes/api/git/github/callback.ts @@ -0,0 +1,105 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { + runCompleteGitHubInstallation, + runExchangeGitHubOAuthCode, + runSaveGitHubUserLink, +} from "~/db/mutations/git-callback.server"; +import { getInstallationAccount } from "~/lib/git/github/installation-octokit"; +import { isGitHubConfigured } from "~/env"; +import { auth } from "~/lib/auth"; + +const redirectBase = "/settings/integrations"; + +function redirectToIntegrations( + origin: string, + params?: Record +) { + const target = new URL(redirectBase, origin); + if (params) { + for (const [key, value] of Object.entries(params)) { + target.searchParams.set(key, value); + } + } + return Response.redirect(target.toString(), 302); +} + +export const Route = createFileRoute("/api/git/github/callback")({ + server: { + handlers: { + GET: async ({ request }) => { + try { + if (!isGitHubConfigured()) { + return new Response("GitHub integration is not configured", { + status: 503, + }); + } + + const url = new URL(request.url); + const installationId = url.searchParams.get("installation_id"); + const setupAction = url.searchParams.get("setup_action"); + const code = url.searchParams.get("code"); + const state = url.searchParams.get("state"); + const error = url.searchParams.get("error"); + + if (error) { + return redirectToIntegrations(url.origin, { error }); + } + + const session = await auth.api.getSession({ + headers: request.headers, + }); + + const returnPath = `${url.pathname}${url.search}`; + + if (!session) { + const signInUrl = new URL("/sign-in", url.origin); + signInUrl.searchParams.set("redirectTo", returnPath); + return Response.redirect(signInUrl.toString(), 302); + } + + const shouldCompleteInstallation = + installationId && + (!setupAction || + setupAction === "install" || + setupAction === "update"); + + if (code && state === "user-oauth") { + try { + const tokenData = await runExchangeGitHubOAuthCode(code); + await runSaveGitHubUserLink(session.user.id, tokenData); + return redirectToIntegrations(url.origin, { user: "connected" }); + } catch (e) { + console.error("GitHub user OAuth failed", e); + return redirectToIntegrations(url.origin, { user: "error" }); + } + } + + if (shouldCompleteInstallation) { + try { + const { accountLogin, accountType } = await getInstallationAccount( + Number(installationId) + ); + + await runCompleteGitHubInstallation(session, { + installationId, + accountLogin, + accountType, + }); + + return redirectToIntegrations(url.origin, { installed: "1" }); + } catch (e) { + console.error("GitHub installation callback failed", e); + return redirectToIntegrations(url.origin, { installed: "error" }); + } + } + + return redirectToIntegrations(url.origin); + } catch (e) { + console.error("GitHub callback failed", e); + const url = new URL(request.url); + return redirectToIntegrations(url.origin, { user: "error" }); + } + }, + }, + }, +}); diff --git a/src/routes/api/webhooks/github.ts b/src/routes/api/webhooks/github.ts new file mode 100644 index 0000000..d114057 --- /dev/null +++ b/src/routes/api/webhooks/github.ts @@ -0,0 +1,43 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { + processGitHubWebhook, + verifyGitHubWebhookSignature, +} from "~/lib/git/github/webhooks"; + +export const Route = createFileRoute("/api/webhooks/github")({ + server: { + handlers: { + POST: async ({ request }) => { + const deliveryId = request.headers.get("X-GitHub-Delivery"); + const eventType = request.headers.get("X-GitHub-Event"); + const signature = request.headers.get("X-Hub-Signature-256"); + + if (!deliveryId || !eventType) { + return new Response("Missing GitHub headers", { status: 400 }); + } + + const payload = await request.text(); + if (!verifyGitHubWebhookSignature(payload, signature)) { + return new Response("Invalid signature", { status: 401 }); + } + + try { + const json = JSON.parse(payload) as Record; + await processGitHubWebhook(eventType, deliveryId, json); + return new Response("OK", { status: 200 }); + } catch (error) { + const detail = + error instanceof Error ? error.message : "Unknown error"; + console.error( + `GitHub webhook processing failed (${eventType}, ${deliveryId}):`, + error + ); + return new Response( + `Webhook handler error: ${detail}`, + { status: 500 } + ); + } + }, + }, + }, +}); diff --git a/src/routes/sign-in.$.tsx b/src/routes/sign-in.$.tsx index 52b0773..993a75f 100644 --- a/src/routes/sign-in.$.tsx +++ b/src/routes/sign-in.$.tsx @@ -14,6 +14,16 @@ import { VerifyEmailPending } from "~/components/auth/verify-email-pending"; const signInSearchSchema = z.object({ tab: z.enum(["sign-in", "sign-up"]).optional().catch(undefined), + redirectTo: z + .string() + .optional() + .refine( + (value) => + value === undefined || + (value.startsWith("/") && !value.startsWith("//")), + { message: "Invalid redirect" } + ) + .catch(undefined), }); export const Route = createFileRoute("/sign-in/$")({ @@ -23,7 +33,8 @@ export const Route = createFileRoute("/sign-in/$")({ function RouteComponent() { const navigate = useNavigate(); - const { tab } = Route.useSearch(); + const { tab, redirectTo } = Route.useSearch(); + const afterAuthPath = redirectTo ?? "/dashboard"; const [isLoading, setIsLoading] = useState(false); const [pendingVerificationEmail, setPendingVerificationEmail] = useState< string | null @@ -69,7 +80,11 @@ function RouteComponent() { return; } - navigate({ to: "/dashboard" }); + if (redirectTo) { + window.location.assign(redirectTo); + } else { + navigate({ to: "/dashboard" }); + } } catch (error) { toast.error(error instanceof Error ? error.message : "Sign in failed"); } finally { @@ -132,7 +147,10 @@ function RouteComponent() { - + ); diff --git a/src/styles/app.css b/src/styles/app.css index 8b9dcfc..17eed7a 100644 --- a/src/styles/app.css +++ b/src/styles/app.css @@ -180,3 +180,87 @@ } @plugin 'tailwind-scrollbar'; + +/* PR description panel (development tab) */ +.pr-description-markdown.gh-markdown { + font-size: 0.875rem; + line-height: 1.65; +} + +.pr-description-markdown.gh-markdown h1, +.pr-description-markdown.gh-markdown h2 { + margin-top: 0.5rem; + margin-bottom: 0.25rem; + font-size: 0.875rem; + font-weight: 600; +} + +.pr-description-markdown.gh-markdown > div > :first-child, +.pr-description-markdown.gh-markdown h1:first-child, +.pr-description-markdown.gh-markdown h2:first-child, +.pr-description-markdown.gh-markdown h3:first-child, +.pr-description-markdown.gh-markdown p:first-child, +.pr-description-markdown.gh-markdown .gh-markdown-p:first-child { + margin-top: 0; +} + +.pr-description-markdown.gh-markdown h3, +.pr-description-markdown.gh-markdown h4 { + margin-top: 0.375rem; + margin-bottom: 0.125rem; + font-size: 0.75rem; + font-weight: 600; +} + +.pr-description-markdown.gh-markdown p, +.pr-description-markdown.gh-markdown .gh-markdown-p { + margin-top: 0.375rem; + margin-bottom: 0.375rem; +} + +.pr-description-markdown.gh-markdown ul, +.pr-description-markdown.gh-markdown ol { + margin-top: 0.375rem; + margin-bottom: 0.375rem; +} + +.pr-description-markdown.gh-markdown pre { + margin-top: 0.5rem; + margin-bottom: 0.5rem; + font-size: 0.75rem; +} + +/* GitHub PR comment markdown (sidebar + review UI) */ +.gh-markdown details > :not(summary) { + padding: 0 0.625rem 0.625rem; +} + +.gh-markdown details pre { + margin-top: 0.5rem; +} + +/* Inline PR comments sit inside Pierre’s monospace diff — reset to UI sans */ +.diff-inline-comment-wrap, +.diff-inline-comment { + font-family: var(--font-sans); + letter-spacing: normal; +} + +.diff-inline-comment .gh-markdown, +.diff-inline-comment .gh-markdown p, +.diff-inline-comment .gh-markdown li, +.diff-inline-comment .gh-markdown summary, +.diff-inline-comment .gh-markdown h1, +.diff-inline-comment .gh-markdown h2, +.diff-inline-comment .gh-markdown h3, +.diff-inline-comment .gh-markdown h4 { + font-family: inherit; + color: inherit; +} + +.diff-inline-comment .gh-markdown pre, +.diff-inline-comment .gh-markdown pre code, +.diff-inline-comment .gh-markdown :not(pre) > code { + font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, + monospace; +} diff --git a/start-database.sh b/start-database.sh index 0945dc3..dc49e8b 100755 --- a/start-database.sh +++ b/start-database.sh @@ -27,9 +27,10 @@ if [ "$(docker ps -q -a -f name=$DB_CONTAINER_NAME)" ]; then exit 0 fi -# import env variables from .env -set -a -source .env +# Read database URL only (do not source .env — values may contain spaces). +NETLIFY_DATABASE_URL="$( + grep -E '^NETLIFY_DATABASE_URL=' .env | head -1 | sed -E 's/^NETLIFY_DATABASE_URL=//' | sed -E 's/^["'\'']|["'\'']$//g' +)" DB_PASSWORD=$(echo "$NETLIFY_DATABASE_URL" | awk -F':' '{print $3}' | awk -F'@' '{print $1}') DB_PORT=$(echo "$NETLIFY_DATABASE_URL" | awk -F':' '{print $4}' | awk -F'/' '{print $1}') diff --git a/vite.config.ts b/vite.config.ts index 2905bed..ddc9599 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -13,6 +13,8 @@ export default defineConfig({ }, server: { port: 3001, + // Listen on IPv4 too; default [::1] breaks smee/curl to 127.0.0.1 + host: true, }, plugins: [ devtools(),