diff --git a/apps/web/src/views/boards/components/ImportBoardsForm.tsx b/apps/web/src/views/boards/components/ImportBoardsForm.tsx index 83e98127..c0e64376 100644 --- a/apps/web/src/views/boards/components/ImportBoardsForm.tsx +++ b/apps/web/src/views/boards/components/ImportBoardsForm.tsx @@ -4,7 +4,7 @@ import { t } from "@lingui/core/macro"; import { Plural, Trans } from "@lingui/react/macro"; import { Fragment, useEffect, useState } from "react"; import { Controller, useForm } from "react-hook-form"; -import { FaTrello } from "react-icons/fa"; +import { FaGithub, FaTrello } from "react-icons/fa"; import { HiChevronUpDown, HiMiniArrowTopRightOnSquare, @@ -27,12 +27,23 @@ const integrationProviders: Record< name: "Trello", icon: , }, + github: { + name: "GitHub", + icon: , + }, }; -const SelectSource = ({ handleNextStep }: { handleNextStep: () => void }) => { +const SelectSource = ({ + handleNextStep, +}: { + handleNextStep: (provider: string) => void; +}) => { const { data: integrations, refetch: refetchIntegrations } = api.integration.providers.useQuery(); - const { control, handleSubmit } = useForm({ + const { data: githubStatus, refetch: refetchGithubStatus } = + api.integration.getGitHubStatus.useQuery(); + + const { control, handleSubmit, watch } = useForm({ defaultValues: { source: integrations?.[0]?.provider ?? "trello", }, @@ -47,23 +58,36 @@ const SelectSource = ({ handleNextStep }: { handleNextStep: () => void }) => { }, ); - const hasIntegrations = integrations && integrations.length > 0; + const availableIntegrations = [ + ...(integrations ?? []), + ...(githubStatus?.connected ? [{ provider: "github" }] : []), + ]; + + const hasIntegrations = availableIntegrations.length > 0; useEffect(() => { const handleFocus = () => { - refetchIntegrations(); + void refetchIntegrations(); + void refetchGithubStatus(); }; window.addEventListener("focus", handleFocus); return () => { window.removeEventListener("focus", handleFocus); }; - }, [refetchIntegrations]); + }, [refetchIntegrations, refetchGithubStatus]); const onSubmit = () => { - if (!hasIntegrations && trelloUrl) { - window.open(trelloUrl.url, "trello_auth", "height=800,width=600"); + const selected = watch("source"); + if ( + selected === "trello" && + !integrations?.some((i) => i.provider === "trello") + ) { + if (trelloUrl) + window.open(trelloUrl.url, "trello_auth", "height=800,width=600"); + } else if (selected === "github" && !githubStatus?.connected) { + window.open("/settings/integrations", "_blank"); } else { - handleNextStep(); + handleNextStep(selected); } }; @@ -102,7 +126,7 @@ const SelectSource = ({ handleNextStep }: { handleNextStep: () => void }) => { > {hasIntegrations ? ( - integrations.map((integration, index) => ( + availableIntegrations.map((integration, index) => ( void }) => { )) ) : ( - -
- {integrationProviders.trello?.icon} - - {integrationProviders.trello?.name} - -
-
+ <> + +
+ {integrationProviders.trello?.icon} + + {integrationProviders.trello?.name} + +
+
+ +
+ {integrationProviders.github?.icon} + + {integrationProviders.github?.name} + +
+
+ )}
@@ -154,7 +192,159 @@ const SelectSource = ({ handleNextStep }: { handleNextStep: () => void }) => { !hasIntegrations ? : undefined } > - {hasIntegrations ? t`Select source` : t`Connect Trello`} + {hasIntegrations ? t`Select source` : t`Connect`} + + + + + ); +}; + +const ImportGithub: React.FC = () => { + const utils = api.useUtils(); + const { closeModal } = useModal(); + const { workspace } = useWorkspace(); + const { showPopup } = usePopup(); + const [isSelectAllEnabled, setIsSelectAllEnabled] = useState(false); + + const refetchBoards = () => utils.board.all.refetch(); + + const { data: projects, isLoading: projectsLoading } = + api.import.github.getProjects.useQuery(); + + const { + register: registerProjects, + handleSubmit: handleSubmitProjects, + setValue, + watch, + } = useForm({ + defaultValues: Object.fromEntries( + projects?.map((project) => [project.id, true]) ?? [], + ), + }); + + const importProjects = api.import.github.importProjects.useMutation({ + onSuccess: async () => { + showPopup({ + header: t`Import complete`, + message: t`Your projects have been imported.`, + icon: "success", + }); + try { + await refetchBoards(); + closeModal(); + } catch (e) { + console.log(e); + } + }, + onError: () => { + showPopup({ + header: t`Import failed`, + message: t`Please try again later, or contact customer support.`, + icon: "error", + }); + }, + }); + + const projectWatchers = projects?.map((project) => ({ + id: project.id, + value: watch(project.id), + })); + + const projectCount = + projectWatchers?.filter((w) => w.value === true).length ?? 0; + + const onSubmitProjects = (values: Record) => { + const projectIds = Object.keys(values).filter( + (key) => values[key] === true, + ); + + importProjects.mutate({ + projectIds, + workspacePublicId: workspace.publicId, + }); + }; + + const renderContent = () => { + if (projectsLoading) { + return ( +
+
+
+
+
+ ); + } + + if (!projects?.length) { + return ( +
+

+ {t`No projects found`} +

+
+ ); + } + + return projects.map((project) => ( +
+ +
+ )); + }; + + return ( +
+
{renderContent()}
+ +
+ { + const newState = !isSelectAllEnabled; + setIsSelectAllEnabled(newState); + + for (const project of projects ?? []) { + setValue(project.id, newState); + } + }} + /> +
+
@@ -213,7 +403,7 @@ const ImportTrello: React.FC = () => { value: watch(board.id), })); - const boardCount = boardWatchers?.filter((w) => w.value === true).length || 0; + const boardCount = boardWatchers?.filter((w) => w.value === true).length ?? 0; const onSubmitBoards = (values: Record) => { const boardIds = Object.keys(values).filter((key) => values[key] === true); @@ -267,7 +457,7 @@ const ImportTrello: React.FC = () => { return ( -
{renderContent()}
+
{renderContent()}
{ const newState = !isSelectAllEnabled; setIsSelectAllEnabled(newState); - for (const board of boards || []) { + for (const board of boards ?? []) { setValue(board.id, newState); } }} @@ -313,6 +503,7 @@ const ImportTrello: React.FC = () => { export function ImportBoardsForm() { const { closeModal } = useModal(); const [step, setStep] = useState(1); + const [provider, setProvider] = useState(null); return (
@@ -339,8 +530,16 @@ export function ImportBoardsForm() {
- {step === 1 && setStep(step + 1)} />} - {step === 2 && } + {step === 1 && ( + { + setProvider(p); + setStep(step + 1); + }} + /> + )} + {step === 2 && provider === "trello" && } + {step === 2 && provider === "github" && }
); } diff --git a/apps/web/src/views/settings/IntegrationsSettings.tsx b/apps/web/src/views/settings/IntegrationsSettings.tsx index ce11b3da..05e280ee 100644 --- a/apps/web/src/views/settings/IntegrationsSettings.tsx +++ b/apps/web/src/views/settings/IntegrationsSettings.tsx @@ -1,9 +1,13 @@ +import { zodResolver } from "@hookform/resolvers/zod"; import { t } from "@lingui/core/macro"; import { useEffect } from "react"; +import { useForm } from "react-hook-form"; import { HiMiniArrowTopRightOnSquare } from "react-icons/hi2"; +import { z } from "zod"; import Button from "~/components/Button"; import FeedbackModal from "~/components/FeedbackModal"; +import Input from "~/components/Input"; import Modal from "~/components/modal"; import { NewWorkspaceForm } from "~/components/NewWorkspaceForm"; import { PageHead } from "~/components/PageHead"; @@ -11,10 +15,28 @@ import { useModal } from "~/providers/modal"; import { usePopup } from "~/providers/popup"; import { api } from "~/utils/api"; +const githubTokenSchema = z.object({ + token: z.string().min(1, { message: t`Token is required` }), +}); + +type GitHubTokenFormValues = z.infer; + export default function IntegrationsSettings() { const { modalContentType, isOpen } = useModal(); const { showPopup } = usePopup(); + const { + register, + handleSubmit, + formState: { isDirty, errors }, + reset, + } = useForm({ + resolver: zodResolver(githubTokenSchema), + defaultValues: { + token: "", + }, + }); + const { data: integrations, refetch: refetchIntegrations, @@ -34,21 +56,25 @@ export default function IntegrationsSettings() { }, ); + const { data: githubStatus, refetch: refetchGithubStatus } = + api.integration.getGitHubStatus.useQuery(); + useEffect(() => { const handleFocus = () => { - refetchIntegrations(); + void refetchIntegrations(); + void refetchGithubStatus(); }; window.addEventListener("focus", handleFocus); return () => { window.removeEventListener("focus", handleFocus); }; - }, [refetchIntegrations]); + }, [refetchIntegrations, refetchGithubStatus]); const { mutateAsync: disconnectTrello } = api.integration.disconnect.useMutation({ onSuccess: () => { - refetchIntegrations(); - refetchTrelloUrl(); + void refetchIntegrations(); + void refetchTrelloUrl(); showPopup({ header: t`Trello disconnected`, message: t`Your Trello account has been disconnected.`, @@ -64,6 +90,49 @@ export default function IntegrationsSettings() { }, }); + const { mutateAsync: saveGithubToken, isPending: isSavingGithubToken } = + api.integration.saveGitHubToken.useMutation({ + onSuccess: () => { + void refetchGithubStatus(); + reset(); + showPopup({ + header: t`GitHub connected`, + message: t`Your GitHub account has been connected.`, + icon: "success", + }); + }, + onError: () => { + showPopup({ + header: t`Error connecting GitHub`, + message: t`An error occurred while connecting your GitHub account.`, + icon: "error", + }); + }, + }); + + const onSubmitGithubToken = (data: GitHubTokenFormValues) => { + void saveGithubToken({ token: data.token }); + }; + + const { mutateAsync: disconnectGithub } = + api.integration.disconnectGitHub.useMutation({ + onSuccess: () => { + void refetchGithubStatus(); + showPopup({ + header: t`GitHub disconnected`, + message: t`Your GitHub account has been disconnected.`, + icon: "success", + }); + }, + onError: () => { + showPopup({ + header: t`Error disconnecting GitHub`, + message: t`An error occurred while disconnecting your GitHub account.`, + icon: "error", + }); + }, + }); + return ( <> @@ -112,6 +181,51 @@ export default function IntegrationsSettings() { )}
+
+

+ {t`GitHub`} +

+ {!githubStatus?.connected ? ( + <> +

+ {t`Connect your GitHub account to import projects.`} +

+ +
+ +
+
+ +
+ + + ) : ( + <> +

+ {t`Your GitHub account is connected.`} +

+ + + )} +
+ {/* Global modals */} { + const user = ctx.user; + + if (!user) + throw new TRPCError({ + message: "User not authenticated", + code: "UNAUTHORIZED", + }); + + const integration = await integrationsRepo.getProviderForUser( + ctx.db, + user.id, + "github", + ); + + if (!integration) + throw new TRPCError({ + message: "GitHub token not found", + code: "UNAUTHORIZED", + }); + + const token = decryptToken(integration.accessToken); + + // GraphQL query to fetch Projects V2 for the user and their organizations + const query = ` + query { + viewer { + projectsV2(first: 20) { + nodes { + id + title + } + } + organizations(first: 10) { + nodes { + projectsV2(first: 10) { + nodes { + id + title + } + } + } + } + } + } + `; + + const response = await fetch("https://api.github.com/graphql", { + method: "POST", + headers: { + Authorization: `token ${token}`, + "Content-Type": "application/json", + "User-Agent": "Kan-App", + }, + body: JSON.stringify({ query }), + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error( + `GitHub API Error: ${response.status} ${response.statusText}`, + ); + console.error(`GitHub API Response: ${errorText}`); + + throw new TRPCError({ + message: `Failed to fetch GitHub projects: ${response.status} ${response.statusText}`, + code: "INTERNAL_SERVER_ERROR", + }); + } + + const result = (await response.json()) as GitHubProjectsResponse; + + if (result.errors) { + console.error("GitHub GraphQL Errors:", result.errors); + throw new TRPCError({ + message: "Failed to fetch GitHub projects (GraphQL Error)", + code: "INTERNAL_SERVER_ERROR", + }); + } + + const userProjects = result.data.viewer.projectsV2.nodes ?? []; + const orgProjects = result.data.viewer.organizations.nodes.flatMap( + (org) => org.projectsV2.nodes ?? [], + ); + + const allProjects = [...userProjects, ...orgProjects]; + + return allProjects.map((project) => ({ + id: project.id, + name: project.title, + })); + }), + + importProjects: protectedProcedure + .meta({ + openapi: { + summary: "Import projects from GitHub", + method: "POST", + path: "/imports/github/projects", + description: "Imports projects from GitHub", + tags: ["Imports"], + protect: true, + }, + }) + .input( + z.object({ + projectIds: z.array(z.string()), + workspacePublicId: z.string().min(12), + }), + ) + .output(z.object({ projectsImported: z.number() })) + .mutation(async ({ ctx, input }) => { + const userId = ctx.user?.id; + if (!userId) throw new TRPCError({ code: "UNAUTHORIZED" }); + + const integration = await integrationsRepo.getProviderForUser( + ctx.db, + userId, + "github", + ); + + if (!integration) + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "GitHub token not found", + }); + + const token = decryptToken(integration.accessToken); + + const workspace = await workspaceRepo.getByPublicId( + ctx.db, + input.workspacePublicId, + ); + if (!workspace) + throw new TRPCError({ + code: "NOT_FOUND", + message: "Workspace not found", + }); + + await assertUserInWorkspace(ctx.db, userId, workspace.id); + + const newImport = await importRepo.create(ctx.db, { + source: "github", + createdBy: userId, + }); + + if (!newImport) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to create import record", + }); + } + + const newImportId = newImport.id; + let projectsImported = 0; + + for (const projectId of input.projectIds) { + // GraphQL query to fetch Project V2 details, status options, area options, and items + const query = ` + query($id: ID!) { + node(id: $id) { + ... on ProjectV2 { + title + field(name: "Status") { + ... on ProjectV2SingleSelectField { + options { + id + name + } + } + } + areaField: field(name: "Area") { + ... on ProjectV2SingleSelectField { + options { + id + name + color + } + } + } + items(first: 100) { + nodes { + fieldValueByName(name: "Status") { + ... on ProjectV2ItemFieldSingleSelectValue { + name + } + } + areaValue: fieldValueByName(name: "Area") { + ... on ProjectV2ItemFieldSingleSelectValue { + name + } + } + content { + ... on Issue { + title + body + } + ... on PullRequest { + title + body + } + ... on DraftIssue { + title + body + } + } + } + } + } + } + } + `; + + const response = await fetch("https://api.github.com/graphql", { + method: "POST", + headers: { + Authorization: `token ${token}`, + "Content-Type": "application/json", + "User-Agent": "Kan-App", + }, + body: JSON.stringify({ query, variables: { id: projectId } }), + }); + + const result = (await response.json()) as GitHubGraphQLResponse; + if (result.errors || !result.data?.node) continue; + + const projectData = result.data.node; + const statusOptions = projectData.field?.options ?? []; + const areaOptions = projectData.areaField?.options ?? []; + const items = projectData.items?.nodes ?? []; + + const boardPublicId = generateUID(); + const board = await boardRepo.create(ctx.db, { + publicId: boardPublicId, + name: projectData.title, + workspaceId: workspace.id, + slug: generateSlug(projectData.title), + createdBy: userId, + importId: newImportId, + }); + + if (!board) continue; + + // Prepare Labels + const labelsInsert = areaOptions.map((option) => { + let colourCode = "#0d9488"; // Default Teal + const ghColor = option.color; + + // Map GitHub colors to Kan colors + if (ghColor === "BLUE") colourCode = "#0284c7"; + else if (ghColor === "GREEN") colourCode = "#65a30d"; + else if (ghColor === "YELLOW") colourCode = "#ca8a04"; + else if (ghColor === "ORANGE") colourCode = "#ea580c"; + else if (ghColor === "RED") colourCode = "#dc2626"; + else if (ghColor === "PINK") colourCode = "#db2777"; + else if (ghColor === "PURPLE") colourCode = "#4f46e5"; + else if (ghColor === "GRAY") colourCode = "#0d9488"; + + return { + publicId: generateUID(), + name: option.name, + colourCode, + createdBy: userId, + boardId: board.id, + importId: newImportId, + }; + }); + + const createdLabels = await labelRepo.bulkCreate( + ctx.db, + labelsInsert, + ); + const labelMap = new Map(); + + createdLabels.forEach((label, index) => { + const originalName = areaOptions[index]?.name; + if (originalName) { + labelMap.set(originalName, label.id); + } + }); + + // Prepare Lists + const listsInsert: { + publicId: string; + name: string; + createdBy: string; + boardId: number; + index: number; + importId: number; + }[] = []; + + if (statusOptions.length === 0) { + listsInsert.push({ + publicId: generateUID(), + name: "To Do", + createdBy: userId, + boardId: board.id, + index: 0, + importId: newImportId, + }); + } else { + statusOptions.forEach((option, index) => { + listsInsert.push({ + publicId: generateUID(), + name: option.name, + createdBy: userId, + boardId: board.id, + index: index, + importId: newImportId, + }); + }); + } + + const createdLists = await listRepo.bulkCreate(ctx.db, listsInsert); + const listIdMap = new Map(); + createdLists.forEach((list, index) => { + const originalName = listsInsert[index]?.name; + if (originalName) { + listIdMap.set(originalName, list.id); + } + }); + + // Prepare Cards + const itemsToInsert: { + item: NonNullable< + NonNullable< + NonNullable["nodes"] + >[number] + >; + listId: number; + title: string; + description: string; + }[] = []; + + for (const item of items) { + const statusName = item.fieldValueByName?.name; + const content = item.content ?? {}; + const title = content.title ?? "Untitled Card"; + const description = content.body ?? ""; + + let listId = statusName ? listIdMap.get(statusName) : undefined; + + // Fallback to first list + if (!listId && createdLists.length > 0) { + listId = createdLists[0]?.id; + } + + if (listId) { + itemsToInsert.push({ + item, + listId, + title, + description, + }); + } + } + + const cardsInput = itemsToInsert.map((data, index) => ({ + publicId: generateUID(), + title: data.title, + description: data.description, + createdBy: userId, + listId: data.listId, + index: index, + importId: newImportId, + })); + + const createdCards = await cardRepo.bulkCreate(ctx.db, cardsInput); + + // Create Activities + const activities = createdCards.map((card) => ({ + type: "card.created" as const, + cardId: card.id, + createdBy: userId, + })); + + if (activities.length > 0) { + await cardActivityRepo.bulkCreate(ctx.db, activities); + } + + // Link Labels + const cardLabelRelations: { cardId: number; labelId: number }[] = []; + createdCards.forEach((card, index) => { + const originalItem = itemsToInsert[index]?.item; + const areaName = originalItem?.areaValue?.name; + + if (areaName) { + const labelId = labelMap.get(areaName); + if (labelId) { + cardLabelRelations.push({ + cardId: card.id, + labelId: labelId, + }); + } + } + }); + + if (cardLabelRelations.length > 0) { + await cardRepo.bulkCreateCardLabelRelationships( + ctx.db, + cardLabelRelations, + ); + } + + projectsImported++; + } + + if (projectsImported > 0 && newImportId) { + await importRepo.update( + ctx.db, + { status: "success" }, + { importId: newImportId }, + ); + } + + return { projectsImported }; + }), + }), }); diff --git a/packages/api/src/routers/integration.ts b/packages/api/src/routers/integration.ts index 1c4c364d..12b3079a 100644 --- a/packages/api/src/routers/integration.ts +++ b/packages/api/src/routers/integration.ts @@ -14,7 +14,65 @@ export const apiKeys = { trello: process.env.TRELLO_APP_API_KEY, }; +import { encryptToken } from "../utils/encryption"; + export const integrationRouter = createTRPCRouter({ + saveGitHubToken: protectedProcedure + .input(z.object({ token: z.string() })) + .mutation(async ({ ctx, input }) => { + const user = ctx.user; + + if (!user) + throw new TRPCError({ + message: "User not authenticated", + code: "UNAUTHORIZED", + }); + + const encryptedToken = encryptToken(input.token); + + const expiresAt = new Date(); + expiresAt.setFullYear(expiresAt.getFullYear() + 1); + + await integrationsRepo.createOrUpdateProvider(ctx.db, { + provider: "github", + userId: user.id, + accessToken: encryptedToken, + expiresAt, + }); + + return { success: true }; + }), + + disconnectGitHub: protectedProcedure.mutation(async ({ ctx }) => { + const user = ctx.user; + + if (!user) + throw new TRPCError({ + message: "User not authenticated", + code: "UNAUTHORIZED", + }); + + await integrationsRepo.deleteProviderForUser(ctx.db, user.id, "github"); + return { success: true }; + }), + + getGitHubStatus: protectedProcedure.query(async ({ ctx }) => { + const user = ctx.user; + + if (!user) + throw new TRPCError({ + message: "User not authenticated", + code: "UNAUTHORIZED", + }); + + const connected = await integrationsRepo.isProviderAvailableForUser( + ctx.db, + user.id, + "github", + ); + return { connected }; + }), + providers: protectedProcedure .meta({ openapi: { @@ -67,7 +125,7 @@ export const integrationRouter = createTRPCRouter({ protect: true, }, }) - .input(z.object({ provider: z.enum(["trello"]) })) + .input(z.object({ provider: z.enum(["trello", "github"]) })) .output(z.object({})) .mutation(async ({ ctx, input }) => { const user = ctx.user; diff --git a/packages/api/src/utils/encryption.ts b/packages/api/src/utils/encryption.ts new file mode 100644 index 00000000..b85b15fa --- /dev/null +++ b/packages/api/src/utils/encryption.ts @@ -0,0 +1,53 @@ +import crypto from "crypto"; + +const ALGORITHM = "aes-256-gcm"; +const SECRET_KEY = process.env.BETTER_AUTH_SECRET; + +if (!SECRET_KEY) { + throw new Error("Encryption key is missing. Set BETTER_AUTH_SECRET."); +} + +// Ensure the key is exactly 32 bytes +const key = crypto.createHash("sha256").update(String(SECRET_KEY)).digest(); + +export const encryptToken = (text: string) => { + const iv = crypto.randomBytes(12); // 12 bytes is the recommended IV size for GCM + const cipher = crypto.createCipheriv(ALGORITHM, key, iv); + + // buffer concat is faster/cleaner for raw binary manipulation + const encrypted = Buffer.concat([ + cipher.update(text, "utf8"), + cipher.final(), + ]); + + const authTag = cipher.getAuthTag(); + + // Combine IV + AuthTag + EncryptedData into one buffer + // This saves space compared to storing them as separate hex strings + const combined = Buffer.concat([iv, authTag, encrypted]); + + // Return as URL-safe Base64 (ideal for cookies) + return combined.toString("base64url"); +}; + +export const decryptToken = (text: string) => { + // Convert URL-safe Base64 back to a Buffer + const combined = Buffer.from(text, "base64url"); + + // Extract the parts based on fixed lengths + // IV is 12 bytes (standard for GCM) + // AuthTag is 16 bytes (standard for GCM) + const iv = combined.subarray(0, 12); + const authTag = combined.subarray(12, 28); + const encryptedText = combined.subarray(28); + + const decipher = crypto.createDecipheriv(ALGORITHM, key, iv); + decipher.setAuthTag(authTag); + + // If the cookie was tampered with, this will throw an error + const decrypted = Buffer.concat([ + decipher.update(encryptedText), + decipher.final(), + ]); + return decrypted.toString("utf8"); +}; diff --git a/packages/db/migrations/20260224105235_AddGitHubIntegrationSupport.sql b/packages/db/migrations/20260224105235_AddGitHubIntegrationSupport.sql new file mode 100644 index 00000000..e3a109e5 --- /dev/null +++ b/packages/db/migrations/20260224105235_AddGitHubIntegrationSupport.sql @@ -0,0 +1,2 @@ +ALTER TYPE "public"."source" ADD VALUE 'github';--> statement-breakpoint +ALTER TABLE "integration" ALTER COLUMN "accessToken" SET DATA TYPE text;--> statement-breakpoint \ No newline at end of file diff --git a/packages/db/migrations/meta/20260224105235_snapshot.json b/packages/db/migrations/meta/20260224105235_snapshot.json new file mode 100644 index 00000000..c3f16818 --- /dev/null +++ b/packages/db/migrations/meta/20260224105235_snapshot.json @@ -0,0 +1,2983 @@ +{ + "id": "dc863226-583e-4985-8b6a-4c360a3b9fa1", + "prevId": "4e1ebb8b-bd52-48c5-87d6-8e6e5eb8bbde", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "accountId": { + "name": "accountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "accessToken": { + "name": "accessToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idToken": { + "name": "idToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessTokenExpiresAt": { + "name": "accessTokenExpiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refreshTokenExpiresAt": { + "name": "refreshTokenExpiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.apiKey": { + "name": "apiKey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start": { + "name": "start", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "refillInterval": { + "name": "refillInterval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "refillAmount": { + "name": "refillAmount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "lastRefillAt": { + "name": "lastRefillAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "rateLimitEnabled": { + "name": "rateLimitEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "rateLimitTimeWindow": { + "name": "rateLimitTimeWindow", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rateLimitMax": { + "name": "rateLimitMax", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "requestCount": { + "name": "requestCount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "lastRequest": { + "name": "lastRequest", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "apiKey_userId_user_id_fk": { + "name": "apiKey_userId_user_id_fk", + "tableFrom": "apiKey", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.board": { + "name": "board", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "importId": { + "name": "importId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "workspaceId": { + "name": "workspaceId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "visibility": { + "name": "visibility", + "type": "board_visibility", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'private'" + }, + "type": { + "name": "type", + "type": "board_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'regular'" + }, + "sourceBoardId": { + "name": "sourceBoardId", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "board_visibility_idx": { + "name": "board_visibility_idx", + "columns": [ + { + "expression": "visibility", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_type_idx": { + "name": "board_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_source_idx": { + "name": "board_source_idx", + "columns": [ + { + "expression": "sourceBoardId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_slug_per_workspace": { + "name": "unique_slug_per_workspace", + "columns": [ + { + "expression": "workspaceId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"board\".\"deletedAt\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "board_createdBy_user_id_fk": { + "name": "board_createdBy_user_id_fk", + "tableFrom": "board", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "board_deletedBy_user_id_fk": { + "name": "board_deletedBy_user_id_fk", + "tableFrom": "board", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "board_importId_import_id_fk": { + "name": "board_importId_import_id_fk", + "tableFrom": "board", + "tableTo": "import", + "columnsFrom": [ + "importId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "board_workspaceId_workspace_id_fk": { + "name": "board_workspaceId_workspace_id_fk", + "tableFrom": "board", + "tableTo": "workspace", + "columnsFrom": [ + "workspaceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "board_publicId_unique": { + "name": "board_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.card_activity": { + "name": "card_activity", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "card_activity_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "cardId": { + "name": "cardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "fromIndex": { + "name": "fromIndex", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "toIndex": { + "name": "toIndex", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fromListId": { + "name": "fromListId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "toListId": { + "name": "toListId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "labelId": { + "name": "labelId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "workspaceMemberId": { + "name": "workspaceMemberId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "fromTitle": { + "name": "fromTitle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "toTitle": { + "name": "toTitle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fromDescription": { + "name": "fromDescription", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "toDescription": { + "name": "toDescription", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "commentId": { + "name": "commentId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "fromComment": { + "name": "fromComment", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "toComment": { + "name": "toComment", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fromDueDate": { + "name": "fromDueDate", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "toDueDate": { + "name": "toDueDate", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "sourceBoardId": { + "name": "sourceBoardId", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "card_activity_cardId_card_id_fk": { + "name": "card_activity_cardId_card_id_fk", + "tableFrom": "card_activity", + "tableTo": "card", + "columnsFrom": [ + "cardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_activity_fromListId_list_id_fk": { + "name": "card_activity_fromListId_list_id_fk", + "tableFrom": "card_activity", + "tableTo": "list", + "columnsFrom": [ + "fromListId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_activity_toListId_list_id_fk": { + "name": "card_activity_toListId_list_id_fk", + "tableFrom": "card_activity", + "tableTo": "list", + "columnsFrom": [ + "toListId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_activity_labelId_label_id_fk": { + "name": "card_activity_labelId_label_id_fk", + "tableFrom": "card_activity", + "tableTo": "label", + "columnsFrom": [ + "labelId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_activity_workspaceMemberId_workspace_members_id_fk": { + "name": "card_activity_workspaceMemberId_workspace_members_id_fk", + "tableFrom": "card_activity", + "tableTo": "workspace_members", + "columnsFrom": [ + "workspaceMemberId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "card_activity_createdBy_user_id_fk": { + "name": "card_activity_createdBy_user_id_fk", + "tableFrom": "card_activity", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "card_activity_commentId_card_comments_id_fk": { + "name": "card_activity_commentId_card_comments_id_fk", + "tableFrom": "card_activity", + "tableTo": "card_comments", + "columnsFrom": [ + "commentId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_activity_sourceBoardId_board_id_fk": { + "name": "card_activity_sourceBoardId_board_id_fk", + "tableFrom": "card_activity", + "tableTo": "board", + "columnsFrom": [ + "sourceBoardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "card_activity_publicId_unique": { + "name": "card_activity_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.card_attachment": { + "name": "card_attachment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "cardId": { + "name": "cardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "originalFilename": { + "name": "originalFilename", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentType": { + "name": "contentType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "s3Key": { + "name": "s3Key", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "card_attachment_cardId_card_id_fk": { + "name": "card_attachment_cardId_card_id_fk", + "tableFrom": "card_attachment", + "tableTo": "card", + "columnsFrom": [ + "cardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_attachment_createdBy_user_id_fk": { + "name": "card_attachment_createdBy_user_id_fk", + "tableFrom": "card_attachment", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "card_attachment_publicId_unique": { + "name": "card_attachment_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public._card_workspace_members": { + "name": "_card_workspace_members", + "schema": "", + "columns": { + "cardId": { + "name": "cardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "workspaceMemberId": { + "name": "workspaceMemberId", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "_card_workspace_members_cardId_card_id_fk": { + "name": "_card_workspace_members_cardId_card_id_fk", + "tableFrom": "_card_workspace_members", + "tableTo": "card", + "columnsFrom": [ + "cardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "_card_workspace_members_workspaceMemberId_workspace_members_id_fk": { + "name": "_card_workspace_members_workspaceMemberId_workspace_members_id_fk", + "tableFrom": "_card_workspace_members", + "tableTo": "workspace_members", + "columnsFrom": [ + "workspaceMemberId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "_card_workspace_members_cardId_workspaceMemberId_pk": { + "name": "_card_workspace_members_cardId_workspaceMemberId_pk", + "columns": [ + "cardId", + "workspaceMemberId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.card": { + "name": "card", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "index": { + "name": "index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "listId": { + "name": "listId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "importId": { + "name": "importId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "dueDate": { + "name": "dueDate", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "card_createdBy_user_id_fk": { + "name": "card_createdBy_user_id_fk", + "tableFrom": "card", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "card_deletedBy_user_id_fk": { + "name": "card_deletedBy_user_id_fk", + "tableFrom": "card", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "card_listId_list_id_fk": { + "name": "card_listId_list_id_fk", + "tableFrom": "card", + "tableTo": "list", + "columnsFrom": [ + "listId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_importId_import_id_fk": { + "name": "card_importId_import_id_fk", + "tableFrom": "card", + "tableTo": "import", + "columnsFrom": [ + "importId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "card_publicId_unique": { + "name": "card_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public._card_labels": { + "name": "_card_labels", + "schema": "", + "columns": { + "cardId": { + "name": "cardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "labelId": { + "name": "labelId", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "_card_labels_cardId_card_id_fk": { + "name": "_card_labels_cardId_card_id_fk", + "tableFrom": "_card_labels", + "tableTo": "card", + "columnsFrom": [ + "cardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "_card_labels_labelId_label_id_fk": { + "name": "_card_labels_labelId_label_id_fk", + "tableFrom": "_card_labels", + "tableTo": "label", + "columnsFrom": [ + "labelId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "_card_labels_cardId_labelId_pk": { + "name": "_card_labels_cardId_labelId_pk", + "columns": [ + "cardId", + "labelId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.card_comments": { + "name": "card_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cardId": { + "name": "cardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "card_comments_cardId_card_id_fk": { + "name": "card_comments_cardId_card_id_fk", + "tableFrom": "card_comments", + "tableTo": "card", + "columnsFrom": [ + "cardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_comments_createdBy_user_id_fk": { + "name": "card_comments_createdBy_user_id_fk", + "tableFrom": "card_comments", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "card_comments_deletedBy_user_id_fk": { + "name": "card_comments_deletedBy_user_id_fk", + "tableFrom": "card_comments", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "card_comments_publicId_unique": { + "name": "card_comments_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.card_checklist_item": { + "name": "card_checklist_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "completed": { + "name": "completed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "index": { + "name": "index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "checklistId": { + "name": "checklistId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "card_checklist_item_checklistId_card_checklist_id_fk": { + "name": "card_checklist_item_checklistId_card_checklist_id_fk", + "tableFrom": "card_checklist_item", + "tableTo": "card_checklist", + "columnsFrom": [ + "checklistId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_checklist_item_createdBy_user_id_fk": { + "name": "card_checklist_item_createdBy_user_id_fk", + "tableFrom": "card_checklist_item", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "card_checklist_item_deletedBy_user_id_fk": { + "name": "card_checklist_item_deletedBy_user_id_fk", + "tableFrom": "card_checklist_item", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "card_checklist_item_publicId_unique": { + "name": "card_checklist_item_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.card_checklist": { + "name": "card_checklist", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "index": { + "name": "index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "cardId": { + "name": "cardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "card_checklist_cardId_card_id_fk": { + "name": "card_checklist_cardId_card_id_fk", + "tableFrom": "card_checklist", + "tableTo": "card", + "columnsFrom": [ + "cardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_checklist_createdBy_user_id_fk": { + "name": "card_checklist_createdBy_user_id_fk", + "tableFrom": "card_checklist", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "card_checklist_deletedBy_user_id_fk": { + "name": "card_checklist_deletedBy_user_id_fk", + "tableFrom": "card_checklist", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "card_checklist_publicId_unique": { + "name": "card_checklist_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.feedback": { + "name": "feedback", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reviewed": { + "name": "reviewed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "feedback_createdBy_user_id_fk": { + "name": "feedback_createdBy_user_id_fk", + "tableFrom": "feedback", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.import": { + "name": "import", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "import_createdBy_user_id_fk": { + "name": "import_createdBy_user_id_fk", + "tableFrom": "import", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "import_publicId_unique": { + "name": "import_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.label": { + "name": "label", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "colourCode": { + "name": "colourCode", + "type": "varchar(12)", + "primaryKey": false, + "notNull": false + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boardId": { + "name": "boardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "importId": { + "name": "importId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "label_createdBy_user_id_fk": { + "name": "label_createdBy_user_id_fk", + "tableFrom": "label", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "label_boardId_board_id_fk": { + "name": "label_boardId_board_id_fk", + "tableFrom": "label", + "tableTo": "board", + "columnsFrom": [ + "boardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "label_importId_import_id_fk": { + "name": "label_importId_import_id_fk", + "tableFrom": "label", + "tableTo": "import", + "columnsFrom": [ + "importId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "label_deletedBy_user_id_fk": { + "name": "label_deletedBy_user_id_fk", + "tableFrom": "label", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "label_publicId_unique": { + "name": "label_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.list": { + "name": "list", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "index": { + "name": "index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "boardId": { + "name": "boardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "importId": { + "name": "importId", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "list_createdBy_user_id_fk": { + "name": "list_createdBy_user_id_fk", + "tableFrom": "list", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "list_deletedBy_user_id_fk": { + "name": "list_deletedBy_user_id_fk", + "tableFrom": "list", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "list_boardId_board_id_fk": { + "name": "list_boardId_board_id_fk", + "tableFrom": "list", + "tableTo": "board", + "columnsFrom": [ + "boardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "list_importId_import_id_fk": { + "name": "list_importId_import_id_fk", + "tableFrom": "list", + "tableTo": "import", + "columnsFrom": [ + "importId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "list_publicId_unique": { + "name": "list_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuid_generate_v4()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "stripeCustomerId": { + "name": "stripeCustomerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.integration": { + "name": "integration", + "schema": "", + "columns": { + "provider": { + "name": "provider", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "accessToken": { + "name": "accessToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refreshToken": { + "name": "refreshToken", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "integration_userId_user_id_fk": { + "name": "integration_userId_user_id_fk", + "tableFrom": "integration", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integration_pkey": { + "name": "integration_pkey", + "columns": [ + "userId", + "provider" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.workspace_slug_checks": { + "name": "workspace_slug_checks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "available": { + "name": "available", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "reserved": { + "name": "reserved", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "workspaceId": { + "name": "workspaceId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_slug_checks_workspaceId_workspace_id_fk": { + "name": "workspace_slug_checks_workspaceId_workspace_id_fk", + "tableFrom": "workspace_slug_checks", + "tableTo": "workspace", + "columnsFrom": [ + "workspaceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_slug_checks_createdBy_user_id_fk": { + "name": "workspace_slug_checks_createdBy_user_id_fk", + "tableFrom": "workspace_slug_checks", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.workspace_slugs": { + "name": "workspace_slugs", + "schema": "", + "columns": { + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "slug_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_slugs_slug_unique": { + "name": "workspace_slugs_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_members": { + "name": "workspace_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "workspaceId": { + "name": "workspaceId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "member_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'invited'" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_members_userId_user_id_fk": { + "name": "workspace_members_userId_user_id_fk", + "tableFrom": "workspace_members", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_members_workspaceId_workspace_id_fk": { + "name": "workspace_members_workspaceId_workspace_id_fk", + "tableFrom": "workspace_members", + "tableTo": "workspace", + "columnsFrom": [ + "workspaceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_members_deletedBy_user_id_fk": { + "name": "workspace_members_deletedBy_user_id_fk", + "tableFrom": "workspace_members", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_members_publicId_unique": { + "name": "workspace_members_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "workspace_plan", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "showEmailsToMembers": { + "name": "showEmailsToMembers", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_createdBy_user_id_fk": { + "name": "workspace_createdBy_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_deletedBy_user_id_fk": { + "name": "workspace_deletedBy_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_publicId_unique": { + "name": "workspace_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + }, + "workspace_slug_unique": { + "name": "workspace_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.subscription": { + "name": "subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "referenceId": { + "name": "referenceId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": false + }, + "stripeCustomerId": { + "name": "stripeCustomerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "stripeSubscriptionId": { + "name": "stripeSubscriptionId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "periodStart": { + "name": "periodStart", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "periodEnd": { + "name": "periodEnd", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancelAtPeriodEnd": { + "name": "cancelAtPeriodEnd", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "unlimitedSeats": { + "name": "unlimitedSeats", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trialStart": { + "name": "trialStart", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trialEnd": { + "name": "trialEnd", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "subscription_referenceId_workspace_publicId_fk": { + "name": "subscription_referenceId_workspace_publicId_fk", + "tableFrom": "subscription", + "tableTo": "workspace", + "columnsFrom": [ + "referenceId" + ], + "columnsTo": [ + "publicId" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.workspace_invite_links": { + "name": "workspace_invite_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "workspaceId": { + "name": "workspaceId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "invite_link_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updatedBy": { + "name": "updatedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_invite_links_workspaceId_workspace_id_fk": { + "name": "workspace_invite_links_workspaceId_workspace_id_fk", + "tableFrom": "workspace_invite_links", + "tableTo": "workspace", + "columnsFrom": [ + "workspaceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_invite_links_createdBy_user_id_fk": { + "name": "workspace_invite_links_createdBy_user_id_fk", + "tableFrom": "workspace_invite_links", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_invite_links_updatedBy_user_id_fk": { + "name": "workspace_invite_links_updatedBy_user_id_fk", + "tableFrom": "workspace_invite_links", + "tableTo": "user", + "columnsFrom": [ + "updatedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_invite_links_publicId_unique": { + "name": "workspace_invite_links_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + }, + "workspace_invite_links_code_unique": { + "name": "workspace_invite_links_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": { + "public.board_type": { + "name": "board_type", + "schema": "public", + "values": [ + "regular", + "template" + ] + }, + "public.board_visibility": { + "name": "board_visibility", + "schema": "public", + "values": [ + "private", + "public" + ] + }, + "public.card_activity_type": { + "name": "card_activity_type", + "schema": "public", + "values": [ + "card.created", + "card.updated.title", + "card.updated.description", + "card.updated.index", + "card.updated.list", + "card.updated.label.added", + "card.updated.label.removed", + "card.updated.member.added", + "card.updated.member.removed", + "card.updated.comment.added", + "card.updated.comment.updated", + "card.updated.comment.deleted", + "card.updated.checklist.added", + "card.updated.checklist.renamed", + "card.updated.checklist.deleted", + "card.updated.checklist.item.added", + "card.updated.checklist.item.updated", + "card.updated.checklist.item.completed", + "card.updated.checklist.item.uncompleted", + "card.updated.checklist.item.deleted", + "card.updated.attachment.added", + "card.updated.attachment.removed", + "card.updated.dueDate.added", + "card.updated.dueDate.updated", + "card.updated.dueDate.removed", + "card.archived" + ] + }, + "public.source": { + "name": "source", + "schema": "public", + "values": [ + "trello", + "github" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "started", + "success", + "failed" + ] + }, + "public.role": { + "name": "role", + "schema": "public", + "values": [ + "admin", + "member", + "guest" + ] + }, + "public.member_status": { + "name": "member_status", + "schema": "public", + "values": [ + "invited", + "active", + "removed", + "paused" + ] + }, + "public.slug_type": { + "name": "slug_type", + "schema": "public", + "values": [ + "reserved", + "premium" + ] + }, + "public.workspace_plan": { + "name": "workspace_plan", + "schema": "public", + "values": [ + "free", + "pro", + "enterprise" + ] + }, + "public.invite_link_status": { + "name": "invite_link_status", + "schema": "public", + "values": [ + "active", + "inactive" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index e697ad3a..4fc49f2d 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -197,6 +197,13 @@ "when": 1772049587901, "tag": "20260225195947_AddIsArchivedToBoard", "breakpoints": true + }, + { + "idx": 28, + "version": "7", + "when": 1771930355536, + "tag": "20260224105235_AddGitHubIntegrationSupport", + "breakpoints": true } ] } diff --git a/packages/db/src/repository/integration.repo.ts b/packages/db/src/repository/integration.repo.ts index f1df7240..2cf1ae11 100644 --- a/packages/db/src/repository/integration.repo.ts +++ b/packages/db/src/repository/integration.repo.ts @@ -46,6 +46,35 @@ export const getProvidersForUser = async (db: dbClient, userId: string) => { return integration; }; +export const createOrUpdateProvider = async ( + db: dbClient, + data: { + userId: string; + provider: string; + accessToken: string; + refreshToken?: string | null; + expiresAt: Date; + }, +) => { + await db + .insert(integrations) + .values({ + provider: data.provider, + userId: data.userId, + accessToken: data.accessToken, + refreshToken: data.refreshToken ?? null, + expiresAt: data.expiresAt, + }) + .onConflictDoUpdate({ + target: [integrations.userId, integrations.provider], + set: { + accessToken: data.accessToken, + refreshToken: data.refreshToken ?? null, + expiresAt: data.expiresAt, + }, + }); +}; + export const deleteProviderForUser = async ( db: dbClient, userId: string, diff --git a/packages/db/src/schema/imports.ts b/packages/db/src/schema/imports.ts index 0b9cd9da..9acf329a 100644 --- a/packages/db/src/schema/imports.ts +++ b/packages/db/src/schema/imports.ts @@ -14,7 +14,7 @@ import { labels } from "./labels"; import { lists } from "./lists"; import { users } from "./users"; -export const importSourceEnum = pgEnum("source", ["trello"]); +export const importSourceEnum = pgEnum("source", ["trello", "github"]); export const importStatusEnum = pgEnum("status", [ "started", "success", diff --git a/packages/db/src/schema/integrations.ts b/packages/db/src/schema/integrations.ts index 2afeef1b..a451fe06 100644 --- a/packages/db/src/schema/integrations.ts +++ b/packages/db/src/schema/integrations.ts @@ -2,6 +2,7 @@ import { relations } from "drizzle-orm"; import { pgTable, primaryKey, + text, timestamp, uuid, varchar, @@ -16,7 +17,7 @@ export const integrations = pgTable( userId: uuid("userId") .notNull() .references(() => users.id, { onDelete: "cascade" }), - accessToken: varchar("accessToken", { length: 255 }).notNull(), + accessToken: text("accessToken").notNull(), refreshToken: varchar("refreshToken", { length: 255 }), expiresAt: timestamp("expiresAt").notNull(), createdAt: timestamp("createdAt")