diff --git a/.github/workflows/create_issue_security_txt.yml b/.github/workflows/create_issue_security_txt.yml new file mode 100644 index 00000000..7c3eac33 --- /dev/null +++ b/.github/workflows/create_issue_security_txt.yml @@ -0,0 +1,27 @@ +name: Create an issue to remind updating of security.txt + +on: + workflow_dispatch: + schedule: + - cron: "0 0 1 6,12 *" # Every year on 6/1 and 12/1 at 9:00am JST + +jobs: + get_date: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Get this month + id: get_this_month + run: | + echo "this_month=$(date +%Y/%m)" >> "$GITHUB_OUTPUT" + outputs: + this_month: ${{ steps.get_this_month.outputs.this_month }} + create_issue: + needs: get_date + uses: route06/actions/.github/workflows/create_gh_issue.yml@v2 + permissions: + contents: read + issues: write + with: + title: "[Action Required] Update security.txt - ${{ needs.get_date.outputs.this_month }} Maintenance" + description_template_path: .github/workflows/templates/create_issue_security_txt.md diff --git a/.github/workflows/templates/create_issue_security_txt.md b/.github/workflows/templates/create_issue_security_txt.md new file mode 100644 index 00000000..851c6331 --- /dev/null +++ b/.github/workflows/templates/create_issue_security_txt.md @@ -0,0 +1,14 @@ +## Summary + +This issue reminds you to update your `security.txt` file to comply with the latest standards and maintain its relevance. + +🔗 [RFC 9116: A File Format to Aid in Security Vulnerability Disclosure](https://www.rfc-editor.org/rfc/rfc9116) + +> The "Expires" field indicates the date and time after which the data contained in the "security.txt" file is considered stale and should not be used (as per Section 5.3). (snip) It is RECOMMENDED that the value of this field be less than a year into the future to avoid staleness. + +## What to do + +Please create a pull request to address the following tasks: + +* Update the `Expires` field in `security.txt` to a date less than a year in the future +* Review and update any other fields in the file to ensure they remain accurate and relevant diff --git a/app/(auth)/actions.ts b/app/(auth)/actions.ts index 4977f96d..33db72c3 100644 --- a/app/(auth)/actions.ts +++ b/app/(auth)/actions.ts @@ -1,24 +1,35 @@ "use server"; -import { getAuthCallbackUrl } from "@/app/(auth)/lib"; +import { type Provider, getAuthCallbackUrl } from "@/app/(auth)/lib"; +import { logger } from "@/lib/logger"; import { createClient } from "@/lib/supabase"; import { redirect } from "next/navigation"; -export async function authorizeGitHub() { +async function authorizeOAuth(provider: Provider) { const supabase = await createClient(); - const { data, error } = await supabase.auth.signInWithOAuth({ - provider: "github", + provider, options: { - redirectTo: getAuthCallbackUrl(), + redirectTo: getAuthCallbackUrl({ provider }), }, }); + logger.debug(`authorized with ${provider}`); if (error != null) { const { code, message, name, status } = error; throw new Error(`${name} occurred: ${code} (${status}): ${message}`); } + logger.debug({ data: data }, `OAuth data got from ${provider}`); + if (data.url) { redirect(data.url); } } + +export async function authorizeGitHub() { + return authorizeOAuth("github"); +} + +export async function authorizeGoogle() { + return authorizeOAuth("google"); +} diff --git a/app/(auth)/auth/callback/route.ts b/app/(auth)/auth/callback/[provider]/route.ts similarity index 79% rename from app/(auth)/auth/callback/route.ts rename to app/(auth)/auth/callback/[provider]/route.ts index 20bef712..d0bb5a81 100644 --- a/app/(auth)/auth/callback/route.ts +++ b/app/(auth)/auth/callback/[provider]/route.ts @@ -1,13 +1,28 @@ // The client you created from the Server-Side Auth instructions import { db, oauthCredentials, supabaseUserMappings, users } from "@/drizzle"; +import { logger } from "@/lib/logger"; import { createClient } from "@/lib/supabase"; import { initializeAccount } from "@/services/accounts"; import type { Session, User } from "@supabase/supabase-js"; import { eq } from "drizzle-orm"; -import { NextResponse } from "next/server"; +import { type NextRequest, NextResponse } from "next/server"; +import type { Provider } from "../../../lib/"; -export async function GET(request: Request) { +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ provider: Provider }> }, +) { const { searchParams, origin } = new URL(request.url); + const { provider } = await params; + + logger.debug( + { + searchParams, + origin, + url: request.url, + }, + "'searchParams' and 'origin' got from request", + ); const errorMessage = checkError(searchParams); if (errorMessage) { return new Response(errorMessage, { @@ -16,6 +31,7 @@ export async function GET(request: Request) { } const code = searchParams.get("code"); + logger.debug({ code }, "code got from query param"); // if "next" is in param, use it as the redirect URL const next = searchParams.get("next") ?? "/"; if (!code) { @@ -31,10 +47,17 @@ export async function GET(request: Request) { }); } + logger.debug( + { + provider: data.session.user.app_metadata.provider, + providers: data.session.user.app_metadata.providers, + }, + "session data got from Supabase", + ); try { const { user, session } = data; await initializeUserIfNeeded(user); - await storeProviderTokens(user, session); + await storeProviderTokens(user, session, provider); } catch (error) { if (error instanceof Error) { return new Response(error.message, { status: 500 }); @@ -73,25 +96,22 @@ async function initializeUserIfNeeded(user: User) { } // store accessToken and refreshToken -async function storeProviderTokens(user: User, session: Session) { +async function storeProviderTokens( + user: User, + session: Session, + provider: string, +) { const { provider_token, provider_refresh_token } = session; if (!provider_token) { throw new Error("No provider token found"); } - let provider = ""; - // https://docs.github.com/ja/authentication/keeping-your-account-and-data-secure/about-authentication-to-github#github-%E3%81%AE%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%83%E3%83%88 - if (provider_token.startsWith("ghu_")) { - provider = "github"; - } - // TODO: add another logic for other providers - if (provider === "") { - throw new Error("No provider found"); - } + logger.debug(`provider: '${provider}'`); const identity = user.identities?.find((identity) => { return identity.provider === provider; }); + logger.debug({ currentProvider: provider }); if (!identity) { throw new Error(`No identity found for provider: ${provider}`); } diff --git a/app/(auth)/components/oauth-providers.tsx b/app/(auth)/components/oauth-providers.tsx index 39aef3a6..3956f2a6 100644 --- a/app/(auth)/components/oauth-providers.tsx +++ b/app/(auth)/components/oauth-providers.tsx @@ -1,34 +1,38 @@ import { Button } from "@/components/ui/button"; -import { SiGithub } from "@icons-pack/react-simple-icons"; +import { googleOauthFlag } from "@/flags"; +import { SiGithub, SiGoogle } from "@icons-pack/react-simple-icons"; import type { FC } from "react"; -import { authorizeGitHub } from "../actions"; +import { authorizeGitHub, authorizeGoogle } from "../actions"; type OauthProvidersProps = { labelPrefix: string; }; -export const OAuthProviders: FC = ({ labelPrefix }) => ( -
- {/* */} - {/****/} +export const OAuthProviders: FC = async ({ + labelPrefix, +}) => { + const displayGoogleOauth = await googleOauthFlag(); - - - -
-); + return ( +
+ {displayGoogleOauth && ( + + + + )} + + + +
+ ); +}; diff --git a/app/(auth)/lib/get-auth-callback-url.ts b/app/(auth)/lib/get-auth-callback-url.ts index 369b2a29..627782bd 100644 --- a/app/(auth)/lib/get-auth-callback-url.ts +++ b/app/(auth)/lib/get-auth-callback-url.ts @@ -1,10 +1,18 @@ // https://supabase.com/docs/guides/auth/redirect-urls -export function getAuthCallbackUrl({ next = "/" } = {}): string { +import type { Provider } from "./types"; + +export function getAuthCallbackUrl({ + next = "/", + provider, +}: { next?: string; provider: Provider }): string { + if (!provider) { + throw new Error("Provider is required"); + } let url = process.env.NEXT_PUBLIC_SITE_URL ?? process.env.NEXT_PUBLIC_VERCEL_URL ?? "http://localhost:3000/"; url = url.startsWith("http") ? url : `https://${url}`; url = url.endsWith("/") ? url : `${url}/`; - return `${url}auth/callback?next=${encodeURIComponent(next)}`; + return `${url}auth/callback/${provider}?next=${encodeURIComponent(next)}`; } diff --git a/app/(auth)/lib/get-current-subscription.ts b/app/(auth)/lib/get-current-subscription.ts index 795e93d9..a886e6f8 100644 --- a/app/(auth)/lib/get-current-subscription.ts +++ b/app/(auth)/lib/get-current-subscription.ts @@ -2,7 +2,7 @@ import { db, - organizations, + subscriptions, supabaseUserMappings, teamMemberships, teams, @@ -12,17 +12,18 @@ import { eq } from "drizzle-orm"; export const getUserSubscriptionId = async () => { const user = await getUser(); + // TODO: When team plans are released, a user may belong to multiple teams, so we need to handle that case. + // e.g., fetch team id through agents or so. const [subscription] = await db - .select({ - organizationId: organizations.dbId, // todo: replace with 'subscriptionId' if subscriptions table is created in the future - }) - .from(organizations) - .innerJoin(teams, eq(teams.organizationDbId, organizations.dbId)) + .select({ id: subscriptions.dbId }) + .from(subscriptions) + .innerJoin(teams, eq(teams.dbId, subscriptions.teamDbId)) .innerJoin(teamMemberships, eq(teamMemberships.teamDbId, teams.dbId)) .innerJoin( supabaseUserMappings, eq(supabaseUserMappings.userDbId, teamMemberships.userDbId), ) .where(eq(supabaseUserMappings.supabaseUserId, user.id)); - return subscription.organizationId; + + return subscription?.id ?? "sub_hotfix"; }; diff --git a/app/(auth)/lib/get-oauth-credential.ts b/app/(auth)/lib/get-oauth-credential.ts index f48c5014..9047d221 100644 --- a/app/(auth)/lib/get-oauth-credential.ts +++ b/app/(auth)/lib/get-oauth-credential.ts @@ -1,8 +1,7 @@ import { db, oauthCredentials, supabaseUserMappings, users } from "@/drizzle"; import { getUser } from "@/lib/supabase"; import { and, eq } from "drizzle-orm"; - -type Provider = "github"; +import type { Provider } from "./types"; export async function getOauthCredential(provider: Provider) { const supabaseUser = await getUser(); diff --git a/app/(auth)/lib/index.ts b/app/(auth)/lib/index.ts index 96752ce3..de8664ed 100644 --- a/app/(auth)/lib/index.ts +++ b/app/(auth)/lib/index.ts @@ -6,3 +6,4 @@ export { getCurrentTeam } from "./get-current-team"; export { getOauthCredential } from "./get-oauth-credential"; export { isRoute06User } from "./is-route06-user"; export { refreshOauthCredential } from "./refresh-oauth-credential"; +export type { Provider } from "./types"; diff --git a/app/(auth)/lib/types.ts b/app/(auth)/lib/types.ts new file mode 100644 index 00000000..39fe665e --- /dev/null +++ b/app/(auth)/lib/types.ts @@ -0,0 +1 @@ +export type Provider = "github" | "google"; diff --git a/app/(main)/settings/account/actions.ts b/app/(main)/settings/account/actions.ts index 833df750..c6248560 100644 --- a/app/(main)/settings/account/actions.ts +++ b/app/(main)/settings/account/actions.ts @@ -1,30 +1,32 @@ "use server"; +import type { Provider } from "@/app/(auth)/lib"; import { deleteOauthCredential, getAuthCallbackUrl } from "@/app/(auth)/lib"; +import { logger } from "@/lib/logger"; import { createClient, getUser } from "@/lib/supabase"; import { redirect } from "next/navigation"; -export async function connectGitHubIdentity() { +async function connectIdentity(provider: Provider) { const supabase = await createClient(); // Manual linking allows the user to link multiple same-provider identities. // But it introduces additional complexity, and not suitable for most use cases. - // We should check if the user already has a GitHub identity here. + // We should check if the user already has the given provider identity here. // https://supabase.com/docs/guides/auth/auth-identity-linking#manual-linking-beta const supabaseUser = await getUser(); if (supabaseUser.identities) { - const githubIdentity = supabaseUser.identities.find( - (it) => it.provider === "github", + const existingIdentity = supabaseUser.identities.find( + (it) => it.provider === provider, ); - if (githubIdentity) { - throw new Error("Already linked to GitHub"); + if (existingIdentity) { + throw new Error(`Already linked to ${provider}`); } } const { data, error } = await supabase.auth.linkIdentity({ - provider: "github", + provider, options: { - redirectTo: getAuthCallbackUrl({ next: "/settings/account" }), + redirectTo: getAuthCallbackUrl({ next: "/settings/account", provider }), }, }); @@ -37,12 +39,12 @@ export async function connectGitHubIdentity() { } } -export async function reconnectGitHubIdentity() { +async function reconnectIdentity(provider: Provider) { const supabase = await createClient(); const { data, error } = await supabase.auth.signInWithOAuth({ - provider: "github", + provider, options: { - redirectTo: getAuthCallbackUrl({ next: "/settings/account" }), + redirectTo: getAuthCallbackUrl({ next: "/settings/account", provider }), }, }); @@ -55,7 +57,7 @@ export async function reconnectGitHubIdentity() { } } -export async function disconnectGitHubIdentity() { +async function disconnectIdentity(provider: Provider) { const supabaseUser = await getUser(); const supabase = await createClient(); if (!supabaseUser.identities) { @@ -64,16 +66,40 @@ export async function disconnectGitHubIdentity() { if (supabaseUser.identities.length === 1) { throw new Error("Cannot unlink last identity"); } - const githubIdentity = supabaseUser.identities.find( - (it) => it.provider === "github", + const identity = supabaseUser.identities.find( + (it) => it.provider === provider, ); - if (!githubIdentity) { - throw new Error("No github identity"); + if (!identity) { + throw new Error(`No ${provider} identity`); } - const { error } = await supabase.auth.unlinkIdentity(githubIdentity); + const { error } = await supabase.auth.unlinkIdentity(identity); if (error) { throw new Error("Failed to unlink identity", { cause: error }); } - await deleteOauthCredential("github"); + await deleteOauthCredential(provider); +} + +export async function connectGoogleIdentity() { + return connectIdentity("google"); +} + +export async function connectGitHubIdentity() { + return connectIdentity("github"); +} + +export async function reconnectGoogleIdentity() { + return reconnectIdentity("google"); +} + +export async function reconnectGitHubIdentity() { + return reconnectIdentity("github"); +} + +export async function disconnectGoogleIdentity() { + return disconnectIdentity("google"); +} + +export async function disconnectGitHubIdentity() { + return disconnectIdentity("github"); } diff --git a/app/(main)/settings/account/github-authentication.tsx b/app/(main)/settings/account/github-authentication.tsx index f7e5a352..c34ffa3c 100644 --- a/app/(main)/settings/account/github-authentication.tsx +++ b/app/(main)/settings/account/github-authentication.tsx @@ -1,3 +1,5 @@ +"use server"; + import { getOauthCredential } from "@/app/(auth)/lib"; import { getUser } from "@/lib/supabase"; import { @@ -5,18 +7,20 @@ import { needsAuthorization, } from "@/services/external/github"; import { TriangleAlert } from "lucide-react"; -import { GitHubAuthentcationPresentation } from "../components/github-authentication-presentation"; -import { GitHubConnectionButton } from "../components/github-connection-button"; +import { GitHubAuthenticationPresentation } from "../components/github-authentication-presentation"; +import { ProviderConnectionButton } from "../components/provider-connection-button"; import { connectGitHubIdentity, disconnectGitHubIdentity, reconnectGitHubIdentity, } from "./actions"; +const provider = "github"; + export async function GitHubAuthentication() { - const credential = await getOauthCredential("github"); + const credential = await getOauthCredential(provider); if (!credential) { - return ; + return ; } const gitHubClient = buildGitHubUserClient(credential); @@ -27,7 +31,7 @@ export async function GitHubAuthentication() { supabaseUser.identities && supabaseUser.identities.length > 1; return ( - @@ -35,7 +39,7 @@ export async function GitHubAuthentication() { } catch (error) { if (needsAuthorization(error)) { return ( - @@ -47,32 +51,32 @@ export async function GitHubAuthentication() { function GitHubConnectButton() { return ( - + Connect - + ); } function GitHubReconnectButton() { return (
- Reconnect - +
); } function GitHubDisconnectButton() { return ( - Disconnect - + ); } diff --git a/app/(main)/settings/account/google-authentication.tsx b/app/(main)/settings/account/google-authentication.tsx new file mode 100644 index 00000000..49cb41b7 --- /dev/null +++ b/app/(main)/settings/account/google-authentication.tsx @@ -0,0 +1,85 @@ +import { getOauthCredential } from "@/app/(auth)/lib"; +import { logger } from "@/lib/logger"; +import { getUser } from "@/lib/supabase"; +import { GoogleAuthenticationPresentation } from "../components/google-authentication-presentation"; +import { ProviderConnectionButton } from "../components/provider-connection-button"; + +import { + buildGoogleUserClient, + needsAuthorization, +} from "@/services/external/google"; + +import { TriangleAlert } from "lucide-react"; +import { + connectGoogleIdentity, + disconnectGoogleIdentity, + reconnectGoogleIdentity, +} from "./actions"; + +const provider = "google"; + +export async function GoogleAuthentication() { + const credential = await getOauthCredential(provider); + + if (!credential) { + return ; + } + logger.debug({ credential }, "google credential"); + + const googleClient = buildGoogleUserClient(credential); + try { + const googleUser = await googleClient.getUser(); + const supabaseUser = await getUser(); + const unlinkable = + supabaseUser.identities && supabaseUser.identities.length > 1; + logger.debug({ googleUser }, "google user"); + return ( + + ); + } catch (error) { + if (needsAuthorization(error)) { + return ( + + ); + } + throw error; + } +} + +function GoogleConnectButton() { + return ( + + Connect + + ); +} + +function GoogleReconnectButton() { + return ( +
+ + Reconnect + +
+ ); +} + +function GoogleDisconnectButton() { + return ( + + Disconnect + + ); +} diff --git a/app/(main)/settings/account/page.tsx b/app/(main)/settings/account/page.tsx index ff18bfb6..e241b454 100644 --- a/app/(main)/settings/account/page.tsx +++ b/app/(main)/settings/account/page.tsx @@ -1,14 +1,17 @@ import { ClickableText } from "@/components/ui/clicable-text"; import { Field } from "@/components/ui/field"; import { Skeleton } from "@/components/ui/skeleton"; +import { googleOauthFlag } from "@/flags"; import { getUser } from "@/lib/supabase"; import Link from "next/link"; import { Suspense } from "react"; import { Card } from "../components/card"; import { GitHubAuthentication } from "./github-authentication"; +import { GoogleAuthentication } from "./google-authentication"; export default async function AccountSettingPage() { const user = await getUser(); + const displayGoogleOauth = await googleOauthFlag(); return (

+ {displayGoogleOauth && ( + + } + > + + + )} >; -type GitHubAuthentcationPresentationProps = { +type GitHubAuthenticationPresentationProps = { gitHubUser?: GitHubUser; button?: () => React.ReactNode; alert?: string; }; -export function GitHubAuthentcationPresentation({ +export function GitHubAuthenticationPresentation({ gitHubUser, button, alert, -}: GitHubAuthentcationPresentationProps) { +}: GitHubAuthenticationPresentationProps) { return (
{alert && ( diff --git a/app/(main)/settings/components/google-authentication-presentation.tsx b/app/(main)/settings/components/google-authentication-presentation.tsx new file mode 100644 index 00000000..ec7b161a --- /dev/null +++ b/app/(main)/settings/components/google-authentication-presentation.tsx @@ -0,0 +1,49 @@ +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { ClickableText } from "@/components/ui/clicable-text"; +import type { + GoogleUserClient, + GoogleUserData, +} from "@/services/external/google"; +import { SiGoogle } from "@icons-pack/react-simple-icons"; +import { TriangleAlertIcon } from "lucide-react"; +import Link from "next/link"; + +type GoogleUser = Awaited; + +type GoogleAuthenticationPresentationProps = { + googleUser?: GoogleUser; + button?: () => React.ReactNode; + alert?: string; +}; +export function GoogleAuthenticationPresentation({ + googleUser, + button, + alert, +}: GoogleAuthenticationPresentationProps) { + return ( +
+ {alert && ( + + + Authentication Error + {alert} + + )} +
+
+ +
+
Google
+ + {googleUser && ( +
+ {googleUser.name} ({googleUser.email}) +
+ )} +
+
+ {button?.()} +
+
+ ); +} diff --git a/app/(main)/settings/components/github-connection-button.tsx b/app/(main)/settings/components/provider-connection-button.tsx similarity index 94% rename from app/(main)/settings/components/github-connection-button.tsx rename to app/(main)/settings/components/provider-connection-button.tsx index 94bdf1cb..b9f0d896 100644 --- a/app/(main)/settings/components/github-connection-button.tsx +++ b/app/(main)/settings/components/provider-connection-button.tsx @@ -11,7 +11,7 @@ type ButtonWithActionProps = { className?: string; }; -export function GitHubConnectionButton({ +export function ProviderConnectionButton({ action, children, className, diff --git a/app/(main)/settings/team/actions.ts b/app/(main)/settings/team/actions.ts new file mode 100644 index 00000000..6233ec6d --- /dev/null +++ b/app/(main)/settings/team/actions.ts @@ -0,0 +1,63 @@ +"use server"; + +import { + UserId, + db, + supabaseUserMappings, + teamMemberships, + teams, +} from "@/drizzle"; +import { getUser } from "@/lib/supabase"; +import { eq } from "drizzle-orm"; +import { revalidatePath } from "next/cache"; + +export async function getTeamName() { + const user = await getUser(); + + // TODO: In the future, this query will be changed to retrieve from the selected team ID + const _teams = await db + .select({ dbId: teams.dbId, name: teams.name }) + .from(teams) + .innerJoin(teamMemberships, eq(teams.dbId, teamMemberships.teamDbId)) + .innerJoin( + supabaseUserMappings, + eq(teamMemberships.userDbId, supabaseUserMappings.userDbId), + ) + .where(eq(supabaseUserMappings.supabaseUserId, user.id)); + + return _teams[0].name; +} + +export async function updateTeamName(formData: FormData) { + const newName = formData.get("name") as string; + const user = await getUser(); + + try { + const team = await db + .select({ dbId: teams.dbId }) + .from(teams) + .innerJoin(teamMemberships, eq(teams.dbId, teamMemberships.teamDbId)) + .innerJoin( + supabaseUserMappings, + eq(teamMemberships.userDbId, supabaseUserMappings.userDbId), + ) + .where(eq(supabaseUserMappings.supabaseUserId, user.id)); + + if (team.length === 0) { + throw new Error("Team not found"); + } + + await db + .update(teams) + .set({ name: newName }) + .where(eq(teams.dbId, team[0].dbId)) + .execute(); + + revalidatePath("/settings/team"); + + return { success: true }; + } catch (error) { + console.error("Failed to update team name:", error); + return { success: false, error }; + } +} diff --git a/app/(main)/settings/team/page.tsx b/app/(main)/settings/team/page.tsx index 33cccee8..73f4d45f 100644 --- a/app/(main)/settings/team/page.tsx +++ b/app/(main)/settings/team/page.tsx @@ -1,3 +1,7 @@ +import { Skeleton } from "@/components/ui/skeleton"; +import { Suspense } from "react"; +import { TeamName } from "./team-name"; + export default function TeamPage() { return (
@@ -7,6 +11,16 @@ export default function TeamPage() { > Team

+ + + +
+ } + > + + ); } diff --git a/app/(main)/settings/team/team-name-form.tsx b/app/(main)/settings/team/team-name-form.tsx new file mode 100644 index 00000000..7dc5d293 --- /dev/null +++ b/app/(main)/settings/team/team-name-form.tsx @@ -0,0 +1,121 @@ +"use client"; + +import { Card } from "@/app/(main)/settings/components/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import type { teams } from "@/drizzle"; +import { Check, Pencil, X } from "lucide-react"; +import { useState } from "react"; +import { + type InferInput, + maxLength, + minLength, + parse, + pipe, + string, +} from "valibot"; +import { updateTeamName } from "./actions"; + +const TeamNameSchema = pipe( + string(), + minLength(1, "Team name is required"), + maxLength(256, "Team name must be 256 characters or less"), +); + +type TeamNameSchema = InferInput; + +export function TeamNameForm({ + name, +}: { name: typeof teams.$inferSelect.name }) { + const [isEditingName, setIsEditingName] = useState(false); + const [teamName, setTeamName] = useState(name); + const [tempTeamName, setTempTeamName] = useState(teamName); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(""); + + const handleSaveTeamName = async () => { + setError(""); + + try { + const validatedName = parse(TeamNameSchema, tempTeamName); + + setIsLoading(true); + + const formData = new FormData(); + formData.append("name", validatedName); + + const result = await updateTeamName(formData); + + if (result.success) { + setTeamName(validatedName); + setIsEditingName(false); + } else { + setError("Failed to update team name"); + console.error("Failed to update team name"); + } + } catch (error) { + if (error instanceof Error) { + setError(error.message); + } + console.error("Error:", error); + } finally { + setIsLoading(false); + } + }; + + const handleCancelTeamName = () => { + setTempTeamName(teamName); + setIsEditingName(false); + setError(""); + }; + + const handleChange = (e: React.ChangeEvent) => { + setError(""); + setTempTeamName(e.target.value); + }; + + return ( + +
+
+ {isEditingName ? ( + <> + + + + + ) : ( + <> + {teamName} + + + )} +
+ + {error &&

{error}

} +
+
+ ); +} diff --git a/app/(main)/settings/team/team-name.tsx b/app/(main)/settings/team/team-name.tsx new file mode 100644 index 00000000..77e25338 --- /dev/null +++ b/app/(main)/settings/team/team-name.tsx @@ -0,0 +1,8 @@ +import { getTeamName } from "./actions"; +import { TeamNameForm } from "./team-name-form"; + +export async function TeamName() { + const teamName = await getTeamName(); + + return ; +} diff --git a/app/(playground)/p/[agentId]/beta-proto/artifact/server-actions.ts b/app/(playground)/p/[agentId]/beta-proto/artifact/server-actions.ts index 3abec3bb..bb50be43 100644 --- a/app/(playground)/p/[agentId]/beta-proto/artifact/server-actions.ts +++ b/app/(playground)/p/[agentId]/beta-proto/artifact/server-actions.ts @@ -6,7 +6,7 @@ import { createStreamableValue } from "ai/rsc"; import { getUserSubscriptionId, isRoute06User } from "@/app/(auth)/lib"; import { langfuseModel } from "@/lib/llm"; -import { logger } from "@/lib/logger"; +import { createLogger } from "@/lib/opentelemetry"; import { metrics } from "@opentelemetry/api"; import { waitUntil } from "@vercel/functions"; import { Langfuse } from "langfuse"; @@ -26,10 +26,12 @@ type GenerateArtifactStreamParams = { export async function generateArtifactStream( params: GenerateArtifactStreamParams, ) { + const startTime = performance.now(); const lf = new Langfuse(); const trace = lf.trace({ id: `giselle-${Date.now()}`, }); + const logger = createLogger("generate-artifact"); const sources = await sourceIndexesToSources({ input: { agentId: params.agentId, @@ -71,6 +73,7 @@ ${sourcesToText(sources)} prompt: params.userPrompt, schema: artifactSchema, onFinish: async (result) => { + const duration = performance.now() - startTime; const meter = metrics.getMeter(params.modelConfiguration.provider); const tokenCounter = meter.createCounter("token_consumed", { description: "Number of OpenAI API tokens consumed by each request", @@ -85,8 +88,11 @@ ${sourcesToText(sources)} output: result, }); - logger.debug( - { tokenConsumed: result.usage.totalTokens }, + logger.info( + { + tokenConsumed: result.usage.totalTokens, + duration, + }, "response obtained", ); diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml index b1667770..9bc99ee5 100644 --- a/config/dependency_decisions.yml +++ b/config/dependency_decisions.yml @@ -80,3 +80,11 @@ :why: To not conflict with Apache-2.0 license. https://creativecommons.org/licenses/by/4.0/ :versions: [] :when: 2024-10-22 07:23:14.573790000 Z +- - :approve + - url-template + - :who: + :why: The BSD license is ambiguous. The license is now BSD 3-Clause from url-template + v3.1.0. See https://github.com/bramstein/url-template/pull/47 + :versions: + - 2.0.8 + :when: 2024-11-22 02:39:34.598816000 Z diff --git a/docs/packages-license.md b/docs/packages-license.md index 8a1213bd..8b88e4e8 100644 --- a/docs/packages-license.md +++ b/docs/packages-license.md @@ -1,12 +1,12 @@ # giselle -As of November 20, 2024 9:28am. 885 total +As of November 28, 2024 3:18am. 901 total ## Summary -* 651 MIT -* 119 Apache 2.0 +* 659 MIT +* 125 Apache 2.0 * 61 ISC -* 23 New BSD +* 24 New BSD * 12 Simplified BSD * 3 MIT OR Apache-2.0 * 3 BlueOak-1.0.0 @@ -14,6 +14,7 @@ As of November 20, 2024 9:28am. 885 total * 2 BSD Zero Clause License * 2 The Unlicense * 1 (MIT AND Zlib) +* 1 BSD * 1 CC0 1.0 Universal * 1 unknown * 1 CC-BY-4.0 @@ -3803,6 +3804,17 @@ Support for import attributes in acorn Turn a function into an `http.Agent` instance + +### agent-base v7.1.1 (dependencies) +#### + +##### Paths +* /home/runner/work/giselle/giselle + +MIT permitted + +Turn a function into an `http.Agent` instance + ### agentkeepalive v4.5.0 (dependencies) #### @@ -4133,6 +4145,17 @@ asynchronous before/error/after hooks for internal functionality An arbitrary length integer library for Javascript + +### bignumber.js v9.1.2 (dependencies) +#### + +##### Paths +* /home/runner/work/giselle/giselle + +MIT permitted + +A library for arbitrary-precision decimal and non-decimal arithmetic + ### binary-extensions v2.3.0 (dependencies, devDependencies) #### @@ -4210,6 +4233,17 @@ Bash-like brace expansion, implemented in JavaScript. Safer than other brace exp Share target browsers between different front-end tools, like Autoprefixer, Stylelint and babel-env-preset + +### buffer-equal-constant-time v1.0.1 (dependencies) +#### + +##### Paths +* /home/runner/work/giselle/giselle + +New BSD permitted + +Constant-time comparison of Buffers + ### buffer-from v1.1.2 (dependencies, devDependencies) #### @@ -5046,6 +5080,17 @@ Drizzle ORM package for SQL databases Get East Asian Width from a character. + +### ecdsa-sig-formatter v1.0.11 (dependencies) +#### + +##### Paths +* /home/runner/work/giselle/giselle + +Apache 2.0 permitted + +Translate ECDSA signatures between ASN.1/DER and JOSE-style concatenation + ### electron-to-chromium v1.5.11 (dependencies) #### @@ -5530,6 +5575,28 @@ Use node's fs.realpath, but fall back to the JS implementation if the native one Implementation of Function.prototype.bind + +### gaxios v6.7.1 (dependencies) +#### + +##### Paths +* /home/runner/work/giselle/giselle + +Apache 2.0 permitted + +A simple common HTTP client specifically for Google APIs and services. + + +### gcp-metadata v6.1.0 (dependencies) +#### + +##### Paths +* /home/runner/work/giselle/giselle + +Apache 2.0 permitted + +Get the metadata from a Google Cloud Platform environment + ### gensync v1.0.0-beta.2 (dependencies) #### @@ -5662,6 +5729,39 @@ Convert globs to regular expressions Global identifiers from different JavaScript environments + +### google-auth-library v9.15.0 (dependencies) +#### + +##### Paths +* /home/runner/work/giselle/giselle + +Apache 2.0 permitted + +Google APIs Authentication Client Library for Node.js + + +### googleapis v144.0.0 (dependencies) +#### + +##### Paths +* /home/runner/work/giselle/giselle + +Apache 2.0 permitted + +Google APIs Client Library for Node.js + + +### googleapis-common v7.2.0 (dependencies) +#### + +##### Paths +* /home/runner/work/giselle/giselle + +Apache 2.0 permitted + +A common tooling library used by the googleapis npm module. You probably don't want to use this directly. + ### gopd v1.0.1 (dependencies) #### @@ -5684,6 +5784,17 @@ Global identifiers from different JavaScript environments A drop-in replacement for fs, making various improvements. + +### gtoken v7.1.0 (dependencies) +#### + +##### Paths +* /home/runner/work/giselle/giselle + +MIT permitted + +Node.js Google Authentication Service Account Tokens + ### has-flag v3.0.0 (dependencies) #### @@ -5827,6 +5938,17 @@ List of HTML void tag names An HTTP(s) proxy `http.Agent` implementation for HTTPS + +### https-proxy-agent v7.0.5 (dependencies) +#### + +##### Paths +* /home/runner/work/giselle/giselle + +MIT permitted + +An HTTP(s) proxy `http.Agent` implementation for HTTPS + ### humanize-ms v1.2.1 (dependencies) #### @@ -6080,6 +6202,17 @@ Determine whether an AST node is a reference Determine whether an AST node is a reference + +### is-stream v2.0.1 (dependencies) +#### + +##### Paths +* /home/runner/work/giselle/giselle + +MIT permitted + +Check if something is a Node.js stream + ### is-typedarray v1.0.0 (dependencies) #### @@ -6234,6 +6367,17 @@ A regex that tokenizes JavaScript. Given some data, jsesc returns the shortest possible stringified & ASCII-safe representation of that data. + +### json-bigint v1.0.0 (dependencies) +#### + +##### Paths +* /home/runner/work/giselle/giselle + +MIT permitted + +JSON.parse with bigints support + ### json-parse-even-better-errors v2.3.1 (dependencies) #### @@ -6289,6 +6433,28 @@ JSON for Humans Diff & Patch for Javascript objects + +### jwa v2.0.0 (dependencies) +#### + +##### Paths +* /home/runner/work/giselle/giselle + +MIT permitted + +JWA implementation (supports all JWS algorithms) + + +### jws v4.0.0 (dependencies) +#### + +##### Paths +* /home/runner/work/giselle/giselle + +MIT permitted + +Implementation of JSON Web Signatures + ### langfuse v3.26.0 (dependencies) #### @@ -9314,6 +9480,21 @@ CLI tool to update caniuse-lite to refresh target browsers from Browserslist con An RFC 3986/3987 compliant, scheme extendable URI/IRI parsing/validating/resolving library for JavaScript. + +### url-template v2.0.8 (dependencies) +#### + +##### Paths +* /home/runner/work/giselle/giselle + +BSD manually approved + +>The BSD license is ambiguous. The license is now BSD 3-Clause from url-template v3.1.0. See https://github.com/bramstein/url-template/pull/47 + +> 2024-11-22 + + + ### use-callback-ref v1.3.2 (dependencies) #### diff --git a/drizzle/schema.ts b/drizzle/schema.ts index 5e1cc99c..b5a936c4 100644 --- a/drizzle/schema.ts +++ b/drizzle/schema.ts @@ -1,9 +1,6 @@ import type { GiselleNodeId } from "@/app/(playground)/p/[agentId]/beta-proto/giselle-node/types"; import type { GitHubIntegrationId } from "@/app/(playground)/p/[agentId]/beta-proto/github-integration/types"; -import { - type Graph, - playgroundModes, -} from "@/app/(playground)/p/[agentId]/beta-proto/graph/types"; +import type { Graph } from "@/app/(playground)/p/[agentId]/beta-proto/graph/types"; import type { FileId, KnowledgeContentId, @@ -50,23 +47,13 @@ import type { VectorStoreFile } from "openai/resources/beta/vector-stores/files" import type { VectorStore } from "openai/resources/beta/vector-stores/vector-stores"; import type { Stripe } from "stripe"; -export const organizations = pgTable("organizations", { - dbId: serial("db_id").primaryKey(), - name: text("name").notNull(), - createdAt: timestamp("created_at").defaultNow().notNull(), - updatedAt: timestamp("updated_at") - .defaultNow() - .notNull() - .$onUpdate(() => new Date()), -}); - export const subscriptions = pgTable("subscriptions", { // Subscription ID from Stripe, e.g. sub_1234. id: text("id").notNull().unique(), dbId: serial("db_id").primaryKey(), - organizationDbId: integer("organization_db_id") + teamDbId: integer("team_db_id") .notNull() - .references(() => organizations.dbId), + .references(() => teams.dbId), status: text("status").$type().notNull(), cancelAtPeriodEnd: boolean("cancel_at_period_end").notNull(), cancelAt: timestamp("cancel_at"), @@ -81,9 +68,6 @@ export const subscriptions = pgTable("subscriptions", { export const teams = pgTable("teams", { dbId: serial("db_id").primaryKey(), - organizationDbId: integer("organization_db_id") - .notNull() - .references(() => organizations.dbId), name: text("name").notNull(), createdAt: timestamp("created_at").defaultNow().notNull(), updatedAt: timestamp("updated_at") diff --git a/flags.ts b/flags.ts index 22c1b81c..32063cf2 100644 --- a/flags.ts +++ b/flags.ts @@ -64,6 +64,7 @@ export const freePlanFlag = flag({ { value: true, label: "Enable" }, ], }); + export const playgroundV2Flag = flag({ key: "playground-v2", async decide() { @@ -76,3 +77,16 @@ export const playgroundV2Flag = flag({ { value: true, label: "Enable" }, ], }); + +export const googleOauthFlag = flag({ + key: "google-oauth", + async decide() { + return takeLocalEnv("GOOGLE_OAUTH_FLAG"); + }, + description: "Enable Google OAuth", + defaultValue: false, + options: [ + { value: false, label: "disable" }, + { value: true, label: "Enable" }, + ], +}); diff --git a/lib/logger/pino.ts b/lib/logger/pino.ts index 46d179f4..b26bc4be 100644 --- a/lib/logger/pino.ts +++ b/lib/logger/pino.ts @@ -1,7 +1,26 @@ import pino from "pino"; +const partialCensor = (value: string) => { + if (!value || typeof value !== "string") return value; + return `${value.slice(0, 3)}***`; +}; + const baseConfig = { level: process.env.LOGLEVEL || "info", + redact: { + paths: [ + "code", + "credential.accessToken", + "credential.refreshToken", + "data.email", + "data.name", + "data.given_name", + "data.family_name", + "googleUser.name", + "googleUser.email", + ], + censor: partialCensor, + }, }; export const logger = (() => { diff --git a/lib/opentelemetry/index.ts b/lib/opentelemetry/index.ts index e1e746f6..0b893ea1 100644 --- a/lib/opentelemetry/index.ts +++ b/lib/opentelemetry/index.ts @@ -1,3 +1,4 @@ export * from "./log"; export * from "./metric"; export * from "./trace"; +export * from "./types"; diff --git a/lib/opentelemetry/log.ts b/lib/opentelemetry/log.ts index c0a7b8ee..24ff5315 100644 --- a/lib/opentelemetry/log.ts +++ b/lib/opentelemetry/log.ts @@ -1,22 +1,172 @@ +import { logger as pinoLogger } from "@/lib/logger"; +import type { TokenConsumedSchema } from "@/lib/opentelemetry/types"; import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http"; + +import type { AnyValue, Logger } from "@opentelemetry/api-logs"; +import { Resource } from "@opentelemetry/resources"; import { + type LogRecord as BaseLogRecord, BatchLogRecordProcessor, ConsoleLogRecordExporter, LoggerProvider, } from "@opentelemetry/sdk-logs"; import { headers } from "./base"; +interface LogRecord extends BaseLogRecord { + severityText: SeverityText; +} +type SeverityText = "INFO" | "ERROR" | "DEBUG"; +type LogMethod = "info" | "error" | "debug"; + +class PinoLogRecordExporter extends ConsoleLogRecordExporter { + onEmit(log: LogRecord) { + const { severityText, body, attributes } = log; + + const message = body?.toString() || ""; + const attrs = attributes ? this.convertAttributes(attributes) : undefined; + + const logMethod = this.severityToMethod(severityText); + const logger = pinoLogger[logMethod]; + + if (attrs) { + logger(attrs, message); + } else { + logger(message); + } + } + private convertAttributes( + attributes: Record, + ): Record { + const result: Record = {}; + + for (const [key, value] of Object.entries(attributes)) { + if (value === null || value === undefined) { + continue; + } + + if (typeof value === "object") { + result[key] = JSON.stringify(value); + } else { + result[key] = value; + } + } + + return result; + } + + private severityToMethod = ( + severity: SeverityText | undefined, + ): LogMethod => { + if (!severity) return "info"; + return severity.toLowerCase() as LogMethod; + }; +} + const logExporter = new OTLPLogExporter({ url: "https://ingest.us.signoz.cloud:443/v1/logs", headers, }); export const logRecordProcessor = new BatchLogRecordProcessor(logExporter); - -const loggerProvider = new LoggerProvider(); -loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter)); -loggerProvider.addLogRecordProcessor( - new BatchLogRecordProcessor(new ConsoleLogRecordExporter()), +export const pinoLogRecordProcessor = new BatchLogRecordProcessor( + new PinoLogRecordExporter(), ); -export const logger = loggerProvider.getLogger("giselles"); +let sharedLoggerProvider: LoggerProvider | null = null; +function getOrCreateLoggerProvider() { + if (!sharedLoggerProvider) { + sharedLoggerProvider = new LoggerProvider({ + resource: Resource.default(), + }); + + sharedLoggerProvider.addLogRecordProcessor(logRecordProcessor); + sharedLoggerProvider.addLogRecordProcessor(pinoLogRecordProcessor); + } + return sharedLoggerProvider; +} + +type LogSchema = TokenConsumedSchema; + +interface OtelLoggerWrapper { + info: (obj: LogSchema, msg?: string) => void; + error: (obj: LogSchema | Error, msg?: string) => void; + debug: (obj: LogSchema, msg?: string) => void; +} + +function createEmitLog(otelLogger: Logger) { + return function emitLog( + severity: SeverityText, + obj: object | string | Error, + msg?: string, + ) { + if (obj instanceof Error) { + const errorAttributes: Record = { + name: obj.name, + message: obj.message, + stack: obj.stack || "", + }; + + for (const [key, value] of Object.entries(obj)) { + if ( + typeof value === "string" || + typeof value === "number" || + typeof value === "boolean" + ) { + errorAttributes[key] = String(value); + } + } + + otelLogger.emit({ + severityText: severity, + body: obj.message, + attributes: errorAttributes, + }); + } else if (typeof obj === "string") { + otelLogger.emit({ + severityText: severity, + body: obj, + }); + } else { + otelLogger.emit({ + severityText: severity, + body: msg || "", + attributes: obj as Record, + }); + } + }; +} + +function getSchemaUrl() { + switch (process.env.NEXT_PUBLIC_VERCEL_ENV) { + case "production": + return "https://raw.githubusercontent.com/giselles-ai/giselle/main/lib/opentelemetry/types.ts"; + case "preview": + return `https://raw.githubusercontent.com/giselles-ai/giselle/refs/heads/${process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF}/lib/opentelemetry/types.ts`; + default: // development + return "@/lib/opentelemetry/types.ts"; + } +} + +const getVersion = () => { + return undefined; // to be implemented +}; + +export function createLogger(name: string): OtelLoggerWrapper { + const loggerProvider = getOrCreateLoggerProvider(); + const otelLogger = loggerProvider.getLogger(name, getVersion(), { + schemaUrl: getSchemaUrl(), + }); + const emitLog = createEmitLog(otelLogger); + + return { + info: (obj: LogSchema, msg?: string) => { + emitLog("INFO", obj, msg); + }, + error: (obj: LogSchema | Error, msg?: string) => { + emitLog("ERROR", obj, msg); + }, + debug: (obj: LogSchema, msg?: string) => { + emitLog("DEBUG", obj, msg); + }, + }; +} diff --git a/lib/opentelemetry/types.ts b/lib/opentelemetry/types.ts new file mode 100644 index 00000000..8dccde78 --- /dev/null +++ b/lib/opentelemetry/types.ts @@ -0,0 +1,8 @@ +import { z } from "zod"; + +const TokenConsumedSchema = z.object({ + tokenConsumed: z.number(), //Number of tokens consumed by the API request + duration: z.number().min(0), // Time taken for text generation in milliseconds +}); + +export type TokenConsumedSchema = z.infer; diff --git a/middleware.ts b/middleware.ts index dddb31f7..2a38aa6f 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,5 +1,6 @@ import { retrieveActiveStripeSubscriptionBySupabaseUserId } from "@/services/accounts/actions"; import { NextResponse } from "next/server"; +import { freePlanFlag } from "./flags"; import { supabaseMiddleware } from "./lib/supabase"; import { isEmailFromRoute06 } from "./lib/utils"; @@ -11,6 +12,12 @@ export default supabaseMiddleware(async (user, request) => { return NextResponse.redirect(url); } + // Users can use giselle without subscription if the free plan is enabled + const freePlanEnabled = await freePlanFlag(); + if (freePlanEnabled) { + return; + } + // Proceeding to check the user's subscription status since the email is not from the route06.co.jp if (!isEmailFromRoute06(user.email ?? "")) { const subscription = await retrieveActiveStripeSubscriptionBySupabaseUserId( diff --git a/migrations/0009_fuzzy_santa_claus.sql b/migrations/0009_fuzzy_santa_claus.sql new file mode 100644 index 00000000..8df1525d --- /dev/null +++ b/migrations/0009_fuzzy_santa_claus.sql @@ -0,0 +1,22 @@ +-- Add team_db_id column without NOT NULL constraint first +ALTER TABLE "subscriptions" ADD COLUMN "team_db_id" integer; +--> statement-breakpoint + +-- Migrate data: Set team_db_id based on organization relationship +UPDATE subscriptions +SET team_db_id = ( + SELECT db_id + FROM teams + WHERE organization_db_id = subscriptions.organization_db_id +); +--> statement-breakpoint + +-- Add NOT NULL constraint after data migration +ALTER TABLE "subscriptions" ALTER COLUMN "team_db_id" SET NOT NULL; +--> statement-breakpoint + +DO $$ BEGIN + ALTER TABLE "subscriptions" ADD CONSTRAINT "subscriptions_team_db_id_teams_db_id_fk" FOREIGN KEY ("team_db_id") REFERENCES "public"."teams"("db_id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/migrations/0010_classy_jackpot.sql b/migrations/0010_classy_jackpot.sql new file mode 100644 index 00000000..63721941 --- /dev/null +++ b/migrations/0010_classy_jackpot.sql @@ -0,0 +1,8 @@ +ALTER TABLE "subscriptions" DROP CONSTRAINT "subscriptions_organization_db_id_organizations_db_id_fk"; +--> statement-breakpoint +ALTER TABLE "teams" DROP CONSTRAINT "teams_organization_db_id_organizations_db_id_fk"; +--> statement-breakpoint +ALTER TABLE "subscriptions" DROP COLUMN IF EXISTS "organization_db_id";--> statement-breakpoint +ALTER TABLE "teams" DROP COLUMN IF EXISTS "organization_db_id"; + +DROP TABLE "organizations";--> statement-breakpoint diff --git a/migrations/meta/0009_snapshot.json b/migrations/meta/0009_snapshot.json new file mode 100644 index 00000000..56af7d17 --- /dev/null +++ b/migrations/meta/0009_snapshot.json @@ -0,0 +1,1757 @@ +{ + "id": "19fcd87c-cbad-488c-bc9b-b3448e49e0d6", + "prevId": "9bc9085d-008b-4718-a01e-b955134914be", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "team_db_id": { + "name": "team_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "graphv2": { + "name": "graphv2", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "graph": { + "name": "graph", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"nodes\":[],\"edges\":[],\"viewport\":{\"x\":0,\"y\":0,\"zoom\":1}}'::jsonb" + }, + "graph_hash": { + "name": "graph_hash", + "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": { + "agents_team_db_id_teams_db_id_fk": { + "name": "agents_team_db_id_teams_db_id_fk", + "tableFrom": "agents", + "tableTo": "teams", + "columnsFrom": ["team_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "agents_id_unique": { + "name": "agents_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + }, + "agents_graph_hash_unique": { + "name": "agents_graph_hash_unique", + "nullsNotDistinct": false, + "columns": ["graph_hash"] + } + } + }, + "public.builds": { + "name": "builds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "graph": { + "name": "graph", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "graph_hash": { + "name": "graph_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_db_id": { + "name": "agent_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "before_id": { + "name": "before_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "after_id": { + "name": "after_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "builds_agent_db_id_agents_db_id_fk": { + "name": "builds_agent_db_id_agents_db_id_fk", + "tableFrom": "builds", + "tableTo": "agents", + "columnsFrom": ["agent_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "builds_id_unique": { + "name": "builds_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + }, + "builds_graph_hash_unique": { + "name": "builds_graph_hash_unique", + "nullsNotDistinct": false, + "columns": ["graph_hash"] + } + } + }, + "public.edges": { + "name": "edges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "build_db_id": { + "name": "build_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_port_db_id": { + "name": "target_port_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_port_db_id": { + "name": "source_port_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "edges_build_db_id_builds_db_id_fk": { + "name": "edges_build_db_id_builds_db_id_fk", + "tableFrom": "edges", + "tableTo": "builds", + "columnsFrom": ["build_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "edges_target_port_db_id_ports_db_id_fk": { + "name": "edges_target_port_db_id_ports_db_id_fk", + "tableFrom": "edges", + "tableTo": "ports", + "columnsFrom": ["target_port_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "edges_source_port_db_id_ports_db_id_fk": { + "name": "edges_source_port_db_id_ports_db_id_fk", + "tableFrom": "edges", + "tableTo": "ports", + "columnsFrom": ["source_port_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "edges_target_port_db_id_source_port_db_id_unique": { + "name": "edges_target_port_db_id_source_port_db_id_unique", + "nullsNotDistinct": false, + "columns": ["target_port_db_id", "source_port_db_id"] + }, + "edges_id_build_db_id_unique": { + "name": "edges_id_build_db_id_unique", + "nullsNotDistinct": false, + "columns": ["id", "build_db_id"] + } + } + }, + "public.file_openai_file_representations": { + "name": "file_openai_file_representations", + "schema": "", + "columns": { + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "file_db_id": { + "name": "file_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "openai_file_id": { + "name": "openai_file_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "file_openai_file_representations_file_db_id_files_db_id_fk": { + "name": "file_openai_file_representations_file_db_id_files_db_id_fk", + "tableFrom": "file_openai_file_representations", + "tableTo": "files", + "columnsFrom": ["file_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "file_openai_file_representations_openai_file_id_unique": { + "name": "file_openai_file_representations_openai_file_id_unique", + "nullsNotDistinct": false, + "columns": ["openai_file_id"] + } + } + }, + "public.files": { + "name": "files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_type": { + "name": "file_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "blob_url": { + "name": "blob_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "files_id_unique": { + "name": "files_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + } + } + }, + "public.github_integrations": { + "name": "github_integrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "agent_db_id": { + "name": "agent_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "repository_full_name": { + "name": "repository_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "call_sign": { + "name": "call_sign", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "start_node_id": { + "name": "start_node_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "end_node_id": { + "name": "end_node_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "next_action": { + "name": "next_action", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "github_integrations_repository_full_name_index": { + "name": "github_integrations_repository_full_name_index", + "columns": [ + { + "expression": "repository_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_integrations_agent_db_id_agents_db_id_fk": { + "name": "github_integrations_agent_db_id_agents_db_id_fk", + "tableFrom": "github_integrations", + "tableTo": "agents", + "columnsFrom": ["agent_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "github_integrations_id_unique": { + "name": "github_integrations_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + } + } + }, + "public.knowledge_content_openai_vector_store_file_representations": { + "name": "knowledge_content_openai_vector_store_file_representations", + "schema": "", + "columns": { + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "knowledge_content_db_id": { + "name": "knowledge_content_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "openai_vector_store_file_id": { + "name": "openai_vector_store_file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "openai_vector_store_status": { + "name": "openai_vector_store_status", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knowledge_content_openai_vector_store_file_representations_knowledge_content_db_id_knowledge_contents_db_id_fk": { + "name": "knowledge_content_openai_vector_store_file_representations_knowledge_content_db_id_knowledge_contents_db_id_fk", + "tableFrom": "knowledge_content_openai_vector_store_file_representations", + "tableTo": "knowledge_contents", + "columnsFrom": ["knowledge_content_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kcovsfr_knowledge_content_db_id_unique": { + "name": "kcovsfr_knowledge_content_db_id_unique", + "nullsNotDistinct": false, + "columns": ["knowledge_content_db_id"] + }, + "kcovsfr_openai_vector_store_file_id_unique": { + "name": "kcovsfr_openai_vector_store_file_id_unique", + "nullsNotDistinct": false, + "columns": ["openai_vector_store_file_id"] + } + } + }, + "public.knowledge_contents": { + "name": "knowledge_contents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "knowledge_content_type": { + "name": "knowledge_content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "knowledge_db_id": { + "name": "knowledge_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "file_db_id": { + "name": "file_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knowledge_contents_knowledge_db_id_knowledges_db_id_fk": { + "name": "knowledge_contents_knowledge_db_id_knowledges_db_id_fk", + "tableFrom": "knowledge_contents", + "tableTo": "knowledges", + "columnsFrom": ["knowledge_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_contents_file_db_id_files_db_id_fk": { + "name": "knowledge_contents_file_db_id_files_db_id_fk", + "tableFrom": "knowledge_contents", + "tableTo": "files", + "columnsFrom": ["file_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knowledge_contents_id_unique": { + "name": "knowledge_contents_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + }, + "knowledge_contents_file_db_id_knowledge_db_id_unique": { + "name": "knowledge_contents_file_db_id_knowledge_db_id_unique", + "nullsNotDistinct": false, + "columns": ["file_db_id", "knowledge_db_id"] + } + } + }, + "public.knowledge_openai_vector_store_representations": { + "name": "knowledge_openai_vector_store_representations", + "schema": "", + "columns": { + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "knowledge_db_id": { + "name": "knowledge_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "openai_vector_store_id": { + "name": "openai_vector_store_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knowledge_openai_vector_store_representations_knowledge_db_id_knowledges_db_id_fk": { + "name": "knowledge_openai_vector_store_representations_knowledge_db_id_knowledges_db_id_fk", + "tableFrom": "knowledge_openai_vector_store_representations", + "tableTo": "knowledges", + "columnsFrom": ["knowledge_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knowledge_openai_vector_store_representations_openai_vector_store_id_unique": { + "name": "knowledge_openai_vector_store_representations_openai_vector_store_id_unique", + "nullsNotDistinct": false, + "columns": ["openai_vector_store_id"] + } + } + }, + "public.knowledges": { + "name": "knowledges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_db_id": { + "name": "agent_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knowledges_agent_db_id_agents_db_id_fk": { + "name": "knowledges_agent_db_id_agents_db_id_fk", + "tableFrom": "knowledges", + "tableTo": "agents", + "columnsFrom": ["agent_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knowledges_id_unique": { + "name": "knowledges_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + } + } + }, + "public.nodes": { + "name": "nodes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "build_db_id": { + "name": "build_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "class_name": { + "name": "class_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "graph": { + "name": "graph", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "nodes_build_db_id_builds_db_id_fk": { + "name": "nodes_build_db_id_builds_db_id_fk", + "tableFrom": "nodes", + "tableTo": "builds", + "columnsFrom": ["build_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "nodes_id_build_db_id_unique": { + "name": "nodes_id_build_db_id_unique", + "nullsNotDistinct": false, + "columns": ["id", "build_db_id"] + } + } + }, + "public.oauth_credentials": { + "name": "oauth_credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "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": { + "oauth_credentials_user_id_users_db_id_fk": { + "name": "oauth_credentials_user_id_users_db_id_fk", + "tableFrom": "oauth_credentials", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_credentials_user_id_provider_provider_account_id_unique": { + "name": "oauth_credentials_user_id_provider_provider_account_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id", "provider", "provider_account_id"] + } + } + }, + "public.organizations": { + "name": "organizations", + "schema": "", + "columns": { + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "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, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.ports": { + "name": "ports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "node_db_id": { + "name": "node_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ports_node_db_id_nodes_db_id_fk": { + "name": "ports_node_db_id_nodes_db_id_fk", + "tableFrom": "ports", + "tableTo": "nodes", + "columnsFrom": ["node_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ports_id_node_db_id_unique": { + "name": "ports_id_node_db_id_unique", + "nullsNotDistinct": false, + "columns": ["id", "node_db_id"] + } + } + }, + "public.request_port_messages": { + "name": "request_port_messages", + "schema": "", + "columns": { + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_db_id": { + "name": "request_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "port_db_id": { + "name": "port_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "request_port_messages_request_db_id_requests_db_id_fk": { + "name": "request_port_messages_request_db_id_requests_db_id_fk", + "tableFrom": "request_port_messages", + "tableTo": "requests", + "columnsFrom": ["request_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "request_port_messages_port_db_id_ports_db_id_fk": { + "name": "request_port_messages_port_db_id_ports_db_id_fk", + "tableFrom": "request_port_messages", + "tableTo": "ports", + "columnsFrom": ["port_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "request_port_messages_request_db_id_port_db_id_unique": { + "name": "request_port_messages_request_db_id_port_db_id_unique", + "nullsNotDistinct": false, + "columns": ["request_db_id", "port_db_id"] + } + } + }, + "public.request_results": { + "name": "request_results", + "schema": "", + "columns": { + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_db_id": { + "name": "request_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "request_results_request_db_id_requests_db_id_fk": { + "name": "request_results_request_db_id_requests_db_id_fk", + "tableFrom": "request_results", + "tableTo": "requests", + "columnsFrom": ["request_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "request_results_request_db_id_unique": { + "name": "request_results_request_db_id_unique", + "nullsNotDistinct": false, + "columns": ["request_db_id"] + } + } + }, + "public.request_runners": { + "name": "request_runners", + "schema": "", + "columns": { + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_db_id": { + "name": "request_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "runner_id": { + "name": "runner_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "request_runners_request_db_id_requests_db_id_fk": { + "name": "request_runners_request_db_id_requests_db_id_fk", + "tableFrom": "request_runners", + "tableTo": "requests", + "columnsFrom": ["request_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "request_runners_runner_id_unique": { + "name": "request_runners_runner_id_unique", + "nullsNotDistinct": false, + "columns": ["runner_id"] + } + } + }, + "public.request_stack_runners": { + "name": "request_stack_runners", + "schema": "", + "columns": { + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_stack_db_id": { + "name": "request_stack_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "runner_id": { + "name": "runner_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "request_stack_runners_request_stack_db_id_request_stacks_db_id_fk": { + "name": "request_stack_runners_request_stack_db_id_request_stacks_db_id_fk", + "tableFrom": "request_stack_runners", + "tableTo": "request_stacks", + "columnsFrom": ["request_stack_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "request_stack_runners_runner_id_unique": { + "name": "request_stack_runners_runner_id_unique", + "nullsNotDistinct": false, + "columns": ["runner_id"] + } + } + }, + "public.request_stacks": { + "name": "request_stacks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_db_id": { + "name": "request_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "start_node_db_id": { + "name": "start_node_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "end_node_db_id": { + "name": "end_node_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "request_stacks_request_db_id_requests_db_id_fk": { + "name": "request_stacks_request_db_id_requests_db_id_fk", + "tableFrom": "request_stacks", + "tableTo": "requests", + "columnsFrom": ["request_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "request_stacks_start_node_db_id_nodes_db_id_fk": { + "name": "request_stacks_start_node_db_id_nodes_db_id_fk", + "tableFrom": "request_stacks", + "tableTo": "nodes", + "columnsFrom": ["start_node_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "request_stacks_end_node_db_id_nodes_db_id_fk": { + "name": "request_stacks_end_node_db_id_nodes_db_id_fk", + "tableFrom": "request_stacks", + "tableTo": "nodes", + "columnsFrom": ["end_node_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "request_stacks_id_unique": { + "name": "request_stacks_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + } + } + }, + "public.request_steps": { + "name": "request_steps", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_stack_db_id": { + "name": "request_stack_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "node_db_id": { + "name": "node_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'in_progress'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "request_steps_request_stack_db_id_request_stacks_db_id_fk": { + "name": "request_steps_request_stack_db_id_request_stacks_db_id_fk", + "tableFrom": "request_steps", + "tableTo": "request_stacks", + "columnsFrom": ["request_stack_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "request_steps_node_db_id_nodes_db_id_fk": { + "name": "request_steps_node_db_id_nodes_db_id_fk", + "tableFrom": "request_steps", + "tableTo": "nodes", + "columnsFrom": ["node_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "request_steps_id_unique": { + "name": "request_steps_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + } + } + }, + "public.requests": { + "name": "requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "build_db_id": { + "name": "build_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "requests_build_db_id_builds_db_id_fk": { + "name": "requests_build_db_id_builds_db_id_fk", + "tableFrom": "requests", + "tableTo": "builds", + "columnsFrom": ["build_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "requests_id_unique": { + "name": "requests_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + } + } + }, + "public.stripe_user_mappings": { + "name": "stripe_user_mappings", + "schema": "", + "columns": { + "user_db_id": { + "name": "user_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "stripe_user_mappings_user_db_id_users_db_id_fk": { + "name": "stripe_user_mappings_user_db_id_users_db_id_fk", + "tableFrom": "stripe_user_mappings", + "tableTo": "users", + "columnsFrom": ["user_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "stripe_user_mappings_user_db_id_unique": { + "name": "stripe_user_mappings_user_db_id_unique", + "nullsNotDistinct": false, + "columns": ["user_db_id"] + }, + "stripe_user_mappings_stripe_customer_id_unique": { + "name": "stripe_user_mappings_stripe_customer_id_unique", + "nullsNotDistinct": false, + "columns": ["stripe_customer_id"] + } + } + }, + "public.subscriptions": { + "name": "subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "organization_db_id": { + "name": "organization_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "team_db_id": { + "name": "team_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "cancel_at": { + "name": "cancel_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "current_period_start": { + "name": "current_period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "current_period_end": { + "name": "current_period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_start": { + "name": "trial_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_end": { + "name": "trial_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "subscriptions_organization_db_id_organizations_db_id_fk": { + "name": "subscriptions_organization_db_id_organizations_db_id_fk", + "tableFrom": "subscriptions", + "tableTo": "organizations", + "columnsFrom": ["organization_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "subscriptions_team_db_id_teams_db_id_fk": { + "name": "subscriptions_team_db_id_teams_db_id_fk", + "tableFrom": "subscriptions", + "tableTo": "teams", + "columnsFrom": ["team_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "subscriptions_id_unique": { + "name": "subscriptions_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + } + } + }, + "public.supabase_user_mappings": { + "name": "supabase_user_mappings", + "schema": "", + "columns": { + "user_db_id": { + "name": "user_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "supabase_user_id": { + "name": "supabase_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "supabase_user_mappings_user_db_id_users_db_id_fk": { + "name": "supabase_user_mappings_user_db_id_users_db_id_fk", + "tableFrom": "supabase_user_mappings", + "tableTo": "users", + "columnsFrom": ["user_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "supabase_user_mappings_user_db_id_unique": { + "name": "supabase_user_mappings_user_db_id_unique", + "nullsNotDistinct": false, + "columns": ["user_db_id"] + }, + "supabase_user_mappings_supabase_user_id_unique": { + "name": "supabase_user_mappings_supabase_user_id_unique", + "nullsNotDistinct": false, + "columns": ["supabase_user_id"] + } + } + }, + "public.team_memberships": { + "name": "team_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_db_id": { + "name": "user_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "team_db_id": { + "name": "team_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "team_memberships_user_db_id_users_db_id_fk": { + "name": "team_memberships_user_db_id_users_db_id_fk", + "tableFrom": "team_memberships", + "tableTo": "users", + "columnsFrom": ["user_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "team_memberships_team_db_id_teams_db_id_fk": { + "name": "team_memberships_team_db_id_teams_db_id_fk", + "tableFrom": "team_memberships", + "tableTo": "teams", + "columnsFrom": ["team_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_memberships_user_db_id_team_db_id_unique": { + "name": "team_memberships_user_db_id_team_db_id_unique", + "nullsNotDistinct": false, + "columns": ["user_db_id", "team_db_id"] + } + } + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "organization_db_id": { + "name": "organization_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "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, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "teams_organization_db_id_organizations_db_id_fk": { + "name": "teams_organization_db_id_organizations_db_id_fk", + "tableFrom": "teams", + "tableTo": "organizations", + "columnsFrom": ["organization_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.trigger_nodes": { + "name": "trigger_nodes", + "schema": "", + "columns": { + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "build_db_id": { + "name": "build_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "node_db_id": { + "name": "node_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "trigger_nodes_build_db_id_builds_db_id_fk": { + "name": "trigger_nodes_build_db_id_builds_db_id_fk", + "tableFrom": "trigger_nodes", + "tableTo": "builds", + "columnsFrom": ["build_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "trigger_nodes_node_db_id_nodes_db_id_fk": { + "name": "trigger_nodes_node_db_id_nodes_db_id_fk", + "tableFrom": "trigger_nodes", + "tableTo": "nodes", + "columnsFrom": ["node_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "trigger_nodes_build_db_id_unique": { + "name": "trigger_nodes_build_db_id_unique", + "nullsNotDistinct": false, + "columns": ["build_db_id"] + } + } + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_id_unique": { + "name": "users_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + } + } + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/migrations/meta/0010_snapshot.json b/migrations/meta/0010_snapshot.json new file mode 100644 index 00000000..d8786cd1 --- /dev/null +++ b/migrations/meta/0010_snapshot.json @@ -0,0 +1,1690 @@ +{ + "id": "124b069d-5baa-4e06-a4b8-c4641c6145e8", + "prevId": "19fcd87c-cbad-488c-bc9b-b3448e49e0d6", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "team_db_id": { + "name": "team_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "graphv2": { + "name": "graphv2", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "graph": { + "name": "graph", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"nodes\":[],\"edges\":[],\"viewport\":{\"x\":0,\"y\":0,\"zoom\":1}}'::jsonb" + }, + "graph_hash": { + "name": "graph_hash", + "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": { + "agents_team_db_id_teams_db_id_fk": { + "name": "agents_team_db_id_teams_db_id_fk", + "tableFrom": "agents", + "tableTo": "teams", + "columnsFrom": ["team_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "agents_id_unique": { + "name": "agents_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + }, + "agents_graph_hash_unique": { + "name": "agents_graph_hash_unique", + "nullsNotDistinct": false, + "columns": ["graph_hash"] + } + } + }, + "public.builds": { + "name": "builds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "graph": { + "name": "graph", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "graph_hash": { + "name": "graph_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_db_id": { + "name": "agent_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "before_id": { + "name": "before_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "after_id": { + "name": "after_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "builds_agent_db_id_agents_db_id_fk": { + "name": "builds_agent_db_id_agents_db_id_fk", + "tableFrom": "builds", + "tableTo": "agents", + "columnsFrom": ["agent_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "builds_id_unique": { + "name": "builds_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + }, + "builds_graph_hash_unique": { + "name": "builds_graph_hash_unique", + "nullsNotDistinct": false, + "columns": ["graph_hash"] + } + } + }, + "public.edges": { + "name": "edges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "build_db_id": { + "name": "build_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_port_db_id": { + "name": "target_port_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_port_db_id": { + "name": "source_port_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "edges_build_db_id_builds_db_id_fk": { + "name": "edges_build_db_id_builds_db_id_fk", + "tableFrom": "edges", + "tableTo": "builds", + "columnsFrom": ["build_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "edges_target_port_db_id_ports_db_id_fk": { + "name": "edges_target_port_db_id_ports_db_id_fk", + "tableFrom": "edges", + "tableTo": "ports", + "columnsFrom": ["target_port_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "edges_source_port_db_id_ports_db_id_fk": { + "name": "edges_source_port_db_id_ports_db_id_fk", + "tableFrom": "edges", + "tableTo": "ports", + "columnsFrom": ["source_port_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "edges_target_port_db_id_source_port_db_id_unique": { + "name": "edges_target_port_db_id_source_port_db_id_unique", + "nullsNotDistinct": false, + "columns": ["target_port_db_id", "source_port_db_id"] + }, + "edges_id_build_db_id_unique": { + "name": "edges_id_build_db_id_unique", + "nullsNotDistinct": false, + "columns": ["id", "build_db_id"] + } + } + }, + "public.file_openai_file_representations": { + "name": "file_openai_file_representations", + "schema": "", + "columns": { + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "file_db_id": { + "name": "file_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "openai_file_id": { + "name": "openai_file_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "file_openai_file_representations_file_db_id_files_db_id_fk": { + "name": "file_openai_file_representations_file_db_id_files_db_id_fk", + "tableFrom": "file_openai_file_representations", + "tableTo": "files", + "columnsFrom": ["file_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "file_openai_file_representations_openai_file_id_unique": { + "name": "file_openai_file_representations_openai_file_id_unique", + "nullsNotDistinct": false, + "columns": ["openai_file_id"] + } + } + }, + "public.files": { + "name": "files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_type": { + "name": "file_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "blob_url": { + "name": "blob_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "files_id_unique": { + "name": "files_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + } + } + }, + "public.github_integrations": { + "name": "github_integrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "agent_db_id": { + "name": "agent_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "repository_full_name": { + "name": "repository_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "call_sign": { + "name": "call_sign", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "start_node_id": { + "name": "start_node_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "end_node_id": { + "name": "end_node_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "next_action": { + "name": "next_action", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "github_integrations_repository_full_name_index": { + "name": "github_integrations_repository_full_name_index", + "columns": [ + { + "expression": "repository_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_integrations_agent_db_id_agents_db_id_fk": { + "name": "github_integrations_agent_db_id_agents_db_id_fk", + "tableFrom": "github_integrations", + "tableTo": "agents", + "columnsFrom": ["agent_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "github_integrations_id_unique": { + "name": "github_integrations_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + } + } + }, + "public.knowledge_content_openai_vector_store_file_representations": { + "name": "knowledge_content_openai_vector_store_file_representations", + "schema": "", + "columns": { + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "knowledge_content_db_id": { + "name": "knowledge_content_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "openai_vector_store_file_id": { + "name": "openai_vector_store_file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "openai_vector_store_status": { + "name": "openai_vector_store_status", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knowledge_content_openai_vector_store_file_representations_knowledge_content_db_id_knowledge_contents_db_id_fk": { + "name": "knowledge_content_openai_vector_store_file_representations_knowledge_content_db_id_knowledge_contents_db_id_fk", + "tableFrom": "knowledge_content_openai_vector_store_file_representations", + "tableTo": "knowledge_contents", + "columnsFrom": ["knowledge_content_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kcovsfr_knowledge_content_db_id_unique": { + "name": "kcovsfr_knowledge_content_db_id_unique", + "nullsNotDistinct": false, + "columns": ["knowledge_content_db_id"] + }, + "kcovsfr_openai_vector_store_file_id_unique": { + "name": "kcovsfr_openai_vector_store_file_id_unique", + "nullsNotDistinct": false, + "columns": ["openai_vector_store_file_id"] + } + } + }, + "public.knowledge_contents": { + "name": "knowledge_contents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "knowledge_content_type": { + "name": "knowledge_content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "knowledge_db_id": { + "name": "knowledge_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "file_db_id": { + "name": "file_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knowledge_contents_knowledge_db_id_knowledges_db_id_fk": { + "name": "knowledge_contents_knowledge_db_id_knowledges_db_id_fk", + "tableFrom": "knowledge_contents", + "tableTo": "knowledges", + "columnsFrom": ["knowledge_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_contents_file_db_id_files_db_id_fk": { + "name": "knowledge_contents_file_db_id_files_db_id_fk", + "tableFrom": "knowledge_contents", + "tableTo": "files", + "columnsFrom": ["file_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knowledge_contents_id_unique": { + "name": "knowledge_contents_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + }, + "knowledge_contents_file_db_id_knowledge_db_id_unique": { + "name": "knowledge_contents_file_db_id_knowledge_db_id_unique", + "nullsNotDistinct": false, + "columns": ["file_db_id", "knowledge_db_id"] + } + } + }, + "public.knowledge_openai_vector_store_representations": { + "name": "knowledge_openai_vector_store_representations", + "schema": "", + "columns": { + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "knowledge_db_id": { + "name": "knowledge_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "openai_vector_store_id": { + "name": "openai_vector_store_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knowledge_openai_vector_store_representations_knowledge_db_id_knowledges_db_id_fk": { + "name": "knowledge_openai_vector_store_representations_knowledge_db_id_knowledges_db_id_fk", + "tableFrom": "knowledge_openai_vector_store_representations", + "tableTo": "knowledges", + "columnsFrom": ["knowledge_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knowledge_openai_vector_store_representations_openai_vector_store_id_unique": { + "name": "knowledge_openai_vector_store_representations_openai_vector_store_id_unique", + "nullsNotDistinct": false, + "columns": ["openai_vector_store_id"] + } + } + }, + "public.knowledges": { + "name": "knowledges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_db_id": { + "name": "agent_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knowledges_agent_db_id_agents_db_id_fk": { + "name": "knowledges_agent_db_id_agents_db_id_fk", + "tableFrom": "knowledges", + "tableTo": "agents", + "columnsFrom": ["agent_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knowledges_id_unique": { + "name": "knowledges_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + } + } + }, + "public.nodes": { + "name": "nodes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "build_db_id": { + "name": "build_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "class_name": { + "name": "class_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "graph": { + "name": "graph", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "nodes_build_db_id_builds_db_id_fk": { + "name": "nodes_build_db_id_builds_db_id_fk", + "tableFrom": "nodes", + "tableTo": "builds", + "columnsFrom": ["build_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "nodes_id_build_db_id_unique": { + "name": "nodes_id_build_db_id_unique", + "nullsNotDistinct": false, + "columns": ["id", "build_db_id"] + } + } + }, + "public.oauth_credentials": { + "name": "oauth_credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "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": { + "oauth_credentials_user_id_users_db_id_fk": { + "name": "oauth_credentials_user_id_users_db_id_fk", + "tableFrom": "oauth_credentials", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_credentials_user_id_provider_provider_account_id_unique": { + "name": "oauth_credentials_user_id_provider_provider_account_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id", "provider", "provider_account_id"] + } + } + }, + "public.ports": { + "name": "ports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "node_db_id": { + "name": "node_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ports_node_db_id_nodes_db_id_fk": { + "name": "ports_node_db_id_nodes_db_id_fk", + "tableFrom": "ports", + "tableTo": "nodes", + "columnsFrom": ["node_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ports_id_node_db_id_unique": { + "name": "ports_id_node_db_id_unique", + "nullsNotDistinct": false, + "columns": ["id", "node_db_id"] + } + } + }, + "public.request_port_messages": { + "name": "request_port_messages", + "schema": "", + "columns": { + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_db_id": { + "name": "request_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "port_db_id": { + "name": "port_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "request_port_messages_request_db_id_requests_db_id_fk": { + "name": "request_port_messages_request_db_id_requests_db_id_fk", + "tableFrom": "request_port_messages", + "tableTo": "requests", + "columnsFrom": ["request_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "request_port_messages_port_db_id_ports_db_id_fk": { + "name": "request_port_messages_port_db_id_ports_db_id_fk", + "tableFrom": "request_port_messages", + "tableTo": "ports", + "columnsFrom": ["port_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "request_port_messages_request_db_id_port_db_id_unique": { + "name": "request_port_messages_request_db_id_port_db_id_unique", + "nullsNotDistinct": false, + "columns": ["request_db_id", "port_db_id"] + } + } + }, + "public.request_results": { + "name": "request_results", + "schema": "", + "columns": { + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_db_id": { + "name": "request_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "request_results_request_db_id_requests_db_id_fk": { + "name": "request_results_request_db_id_requests_db_id_fk", + "tableFrom": "request_results", + "tableTo": "requests", + "columnsFrom": ["request_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "request_results_request_db_id_unique": { + "name": "request_results_request_db_id_unique", + "nullsNotDistinct": false, + "columns": ["request_db_id"] + } + } + }, + "public.request_runners": { + "name": "request_runners", + "schema": "", + "columns": { + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_db_id": { + "name": "request_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "runner_id": { + "name": "runner_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "request_runners_request_db_id_requests_db_id_fk": { + "name": "request_runners_request_db_id_requests_db_id_fk", + "tableFrom": "request_runners", + "tableTo": "requests", + "columnsFrom": ["request_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "request_runners_runner_id_unique": { + "name": "request_runners_runner_id_unique", + "nullsNotDistinct": false, + "columns": ["runner_id"] + } + } + }, + "public.request_stack_runners": { + "name": "request_stack_runners", + "schema": "", + "columns": { + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_stack_db_id": { + "name": "request_stack_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "runner_id": { + "name": "runner_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "request_stack_runners_request_stack_db_id_request_stacks_db_id_fk": { + "name": "request_stack_runners_request_stack_db_id_request_stacks_db_id_fk", + "tableFrom": "request_stack_runners", + "tableTo": "request_stacks", + "columnsFrom": ["request_stack_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "request_stack_runners_runner_id_unique": { + "name": "request_stack_runners_runner_id_unique", + "nullsNotDistinct": false, + "columns": ["runner_id"] + } + } + }, + "public.request_stacks": { + "name": "request_stacks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_db_id": { + "name": "request_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "start_node_db_id": { + "name": "start_node_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "end_node_db_id": { + "name": "end_node_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "request_stacks_request_db_id_requests_db_id_fk": { + "name": "request_stacks_request_db_id_requests_db_id_fk", + "tableFrom": "request_stacks", + "tableTo": "requests", + "columnsFrom": ["request_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "request_stacks_start_node_db_id_nodes_db_id_fk": { + "name": "request_stacks_start_node_db_id_nodes_db_id_fk", + "tableFrom": "request_stacks", + "tableTo": "nodes", + "columnsFrom": ["start_node_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "request_stacks_end_node_db_id_nodes_db_id_fk": { + "name": "request_stacks_end_node_db_id_nodes_db_id_fk", + "tableFrom": "request_stacks", + "tableTo": "nodes", + "columnsFrom": ["end_node_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "request_stacks_id_unique": { + "name": "request_stacks_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + } + } + }, + "public.request_steps": { + "name": "request_steps", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_stack_db_id": { + "name": "request_stack_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "node_db_id": { + "name": "node_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'in_progress'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "request_steps_request_stack_db_id_request_stacks_db_id_fk": { + "name": "request_steps_request_stack_db_id_request_stacks_db_id_fk", + "tableFrom": "request_steps", + "tableTo": "request_stacks", + "columnsFrom": ["request_stack_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "request_steps_node_db_id_nodes_db_id_fk": { + "name": "request_steps_node_db_id_nodes_db_id_fk", + "tableFrom": "request_steps", + "tableTo": "nodes", + "columnsFrom": ["node_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "request_steps_id_unique": { + "name": "request_steps_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + } + } + }, + "public.requests": { + "name": "requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "build_db_id": { + "name": "build_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "requests_build_db_id_builds_db_id_fk": { + "name": "requests_build_db_id_builds_db_id_fk", + "tableFrom": "requests", + "tableTo": "builds", + "columnsFrom": ["build_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "requests_id_unique": { + "name": "requests_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + } + } + }, + "public.stripe_user_mappings": { + "name": "stripe_user_mappings", + "schema": "", + "columns": { + "user_db_id": { + "name": "user_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "stripe_user_mappings_user_db_id_users_db_id_fk": { + "name": "stripe_user_mappings_user_db_id_users_db_id_fk", + "tableFrom": "stripe_user_mappings", + "tableTo": "users", + "columnsFrom": ["user_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "stripe_user_mappings_user_db_id_unique": { + "name": "stripe_user_mappings_user_db_id_unique", + "nullsNotDistinct": false, + "columns": ["user_db_id"] + }, + "stripe_user_mappings_stripe_customer_id_unique": { + "name": "stripe_user_mappings_stripe_customer_id_unique", + "nullsNotDistinct": false, + "columns": ["stripe_customer_id"] + } + } + }, + "public.subscriptions": { + "name": "subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "team_db_id": { + "name": "team_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "cancel_at": { + "name": "cancel_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "current_period_start": { + "name": "current_period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "current_period_end": { + "name": "current_period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_start": { + "name": "trial_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_end": { + "name": "trial_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "subscriptions_team_db_id_teams_db_id_fk": { + "name": "subscriptions_team_db_id_teams_db_id_fk", + "tableFrom": "subscriptions", + "tableTo": "teams", + "columnsFrom": ["team_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "subscriptions_id_unique": { + "name": "subscriptions_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + } + } + }, + "public.supabase_user_mappings": { + "name": "supabase_user_mappings", + "schema": "", + "columns": { + "user_db_id": { + "name": "user_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "supabase_user_id": { + "name": "supabase_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "supabase_user_mappings_user_db_id_users_db_id_fk": { + "name": "supabase_user_mappings_user_db_id_users_db_id_fk", + "tableFrom": "supabase_user_mappings", + "tableTo": "users", + "columnsFrom": ["user_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "supabase_user_mappings_user_db_id_unique": { + "name": "supabase_user_mappings_user_db_id_unique", + "nullsNotDistinct": false, + "columns": ["user_db_id"] + }, + "supabase_user_mappings_supabase_user_id_unique": { + "name": "supabase_user_mappings_supabase_user_id_unique", + "nullsNotDistinct": false, + "columns": ["supabase_user_id"] + } + } + }, + "public.team_memberships": { + "name": "team_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_db_id": { + "name": "user_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "team_db_id": { + "name": "team_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "team_memberships_user_db_id_users_db_id_fk": { + "name": "team_memberships_user_db_id_users_db_id_fk", + "tableFrom": "team_memberships", + "tableTo": "users", + "columnsFrom": ["user_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "team_memberships_team_db_id_teams_db_id_fk": { + "name": "team_memberships_team_db_id_teams_db_id_fk", + "tableFrom": "team_memberships", + "tableTo": "teams", + "columnsFrom": ["team_db_id"], + "columnsTo": ["db_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "team_memberships_user_db_id_team_db_id_unique": { + "name": "team_memberships_user_db_id_team_db_id_unique", + "nullsNotDistinct": false, + "columns": ["user_db_id", "team_db_id"] + } + } + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "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, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.trigger_nodes": { + "name": "trigger_nodes", + "schema": "", + "columns": { + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "build_db_id": { + "name": "build_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "node_db_id": { + "name": "node_db_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "trigger_nodes_build_db_id_builds_db_id_fk": { + "name": "trigger_nodes_build_db_id_builds_db_id_fk", + "tableFrom": "trigger_nodes", + "tableTo": "builds", + "columnsFrom": ["build_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "trigger_nodes_node_db_id_nodes_db_id_fk": { + "name": "trigger_nodes_node_db_id_nodes_db_id_fk", + "tableFrom": "trigger_nodes", + "tableTo": "nodes", + "columnsFrom": ["node_db_id"], + "columnsTo": ["db_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "trigger_nodes_build_db_id_unique": { + "name": "trigger_nodes_build_db_id_unique", + "nullsNotDistinct": false, + "columns": ["build_db_id"] + } + } + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "db_id": { + "name": "db_id", + "type": "serial", + "primaryKey": true, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_id_unique": { + "name": "users_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + } + } + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json index 9172e2e6..aff29732 100644 --- a/migrations/meta/_journal.json +++ b/migrations/meta/_journal.json @@ -64,6 +64,20 @@ "when": 1731653081961, "tag": "0008_cloudy_squadron_supreme", "breakpoints": true + }, + { + "idx": 9, + "version": "7", + "when": 1732504730844, + "tag": "0009_fuzzy_santa_claus", + "breakpoints": true + }, + { + "idx": 10, + "version": "7", + "when": 1732514526781, + "tag": "0010_classy_jackpot", + "breakpoints": true } ] } diff --git a/package.json b/package.json index 9149d04e..7d0a2784 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,8 @@ "drizzle-orm": "0.32.1", "framer-motion": "12.0.0-alpha.0", "handlebars": "4.7.8", + "google-auth-library": "9.15.0", + "googleapis": "144.0.0", "import-in-the-middle": "1.11.0", "input-otp": "^1.2.4", "langfuse": "3.26.0", diff --git a/public/.well-known/security.txt b/public/.well-known/security.txt new file mode 100644 index 00000000..06435a36 --- /dev/null +++ b/public/.well-known/security.txt @@ -0,0 +1,6 @@ +Contact: mailto:security@giselles.ai +Expires: 2024-12-31T23:59:59Z + +Preferred-Languages: en, ja +Policy: https://github.com/giselles-ai/giselle/security/policy +Canonical: https://studio.giselles.ai/.well-known/security.txt diff --git a/services/accounts/actions/initialize-account.ts b/services/accounts/actions/initialize-account.ts index b6b0533c..0c9299c9 100644 --- a/services/accounts/actions/initialize-account.ts +++ b/services/accounts/actions/initialize-account.ts @@ -2,7 +2,6 @@ import { db, - organizations, supabaseUserMappings, teamMemberships, teams, @@ -26,18 +25,9 @@ export const initializeAccount = async (supabaseUserId: User["id"]) => { userDbId: user.dbId, supabaseUserId, }); - const [organization] = await tx - .insert(organizations) - .values({ - name: "default", - }) - .returning({ - id: organizations.dbId, - }); const [team] = await tx .insert(teams) .values({ - organizationDbId: organization.id, name: "default", }) .returning({ diff --git a/services/accounts/actions/retrieve-stripe-subscription.ts b/services/accounts/actions/retrieve-stripe-subscription.ts index c4265e96..8a204bb0 100644 --- a/services/accounts/actions/retrieve-stripe-subscription.ts +++ b/services/accounts/actions/retrieve-stripe-subscription.ts @@ -1,6 +1,5 @@ import { db, - organizations, subscriptions, supabaseUserMappings, teamMemberships, @@ -12,14 +11,12 @@ import { and, eq } from "drizzle-orm"; export const retrieveActiveStripeSubscriptionBySupabaseUserId = async ( supabaseUserId: string, ) => { + // TODO: When team plans are released, a user may belong to multiple teams, so we need to handle that case. + // One supabase user can have multiple teams which have pro plan subscription. const [subscription] = await db .selectDistinct({ id: subscriptions.id }) .from(subscriptions) - .innerJoin( - organizations, - eq(organizations.dbId, subscriptions.organizationDbId), - ) - .innerJoin(teams, eq(teams.organizationDbId, organizations.dbId)) + .innerJoin(teams, eq(teams.dbId, subscriptions.teamDbId)) .innerJoin(teamMemberships, eq(teamMemberships.teamDbId, teams.dbId)) .innerJoin(users, eq(users.dbId, teamMemberships.userDbId)) .innerJoin( diff --git a/services/external/google/index.ts b/services/external/google/index.ts new file mode 100644 index 00000000..a98431b3 --- /dev/null +++ b/services/external/google/index.ts @@ -0,0 +1,6 @@ +export { + buildGoogleUserClient, + needsAuthorization, + type GoogleUserClient, + type GoogleUserData, +} from "./user-client"; diff --git a/services/external/google/user-client.ts b/services/external/google/user-client.ts new file mode 100644 index 00000000..0a0b4663 --- /dev/null +++ b/services/external/google/user-client.ts @@ -0,0 +1,134 @@ +import { logger } from "@/lib/logger"; +import { GaxiosError } from "gaxios"; +import { google } from "googleapis"; + +export function buildGoogleUserClient(token: GoogleUserCredential) { + const clientId = process.env.GOOGLE_OAUTH_CLIENT_ID; + if (!clientId) { + throw new Error("GOOGLE_OAUTH_CLIENT_ID is empty"); + } + const clientSecret = process.env.GOOGLE_OAUTH_CLIENT_SECRET; + if (!clientSecret) { + throw new Error("GOOGLE_OAUTH_CLIENT_SECRET is empty"); + } + + return new GoogleUserClient(token, clientId, clientSecret); +} + +// MARK: Errors + +class GoogleTokenRefreshError extends Error { + constructor(message: string, options?: { cause?: unknown }) { + super(message, options); + this.name = this.constructor.name; + Object.setPrototypeOf(this, GoogleTokenRefreshError.prototype); + } +} + +/** + * Determines if the given error requires authorization. + * if return value is true, the user should be redirected to the authorization page. + * + * see {@link https://supabase.com/docs/reference/javascript/auth-signinwithoauth} + * + * @param error - The error to check. + * @returns True if the error requires authorization, false otherwise. + */ + +export function needsAuthorization(error: unknown) { + if (error instanceof GoogleTokenRefreshError) { + return true; + } + logger.debug({ error }, "error------------"); + if (error instanceof GaxiosError) { + return error.status === 401 || error.status === 403 || error.status === 404; + } + return false; +} + +export type GoogleUserData = { + name: string; + email: string; +}; + +class GoogleUserClient { + private clientId: string; + private clientSecret: string; + + constructor( + private token: GoogleUserCredential, + clientId: string, + clientSecret: string, + ) { + this.clientId = clientId; + this.clientSecret = clientSecret; + } + + async getUser(): Promise { + try { + const authClient = await this.buildClient(); + logger.debug( + { + hasAccessToken: !!this.token.accessToken, + tokenPrefix: this.token.accessToken?.substring(0, 5), + }, + "getting user info", + ); + + const oauth2 = google.oauth2({ + version: "v2", + auth: authClient, + }); + const { data } = await oauth2.userinfo.get(); + logger.debug({ data }, "userinfo.get() response"); + + const user_data = { + first_name: data.given_name, + last_name: data.family_name, + email: data.email, + }; + + return { + name: `${user_data.first_name} ${user_data.last_name}`, + email: user_data.email, + } as GoogleUserData; + } catch (error) { + logger.error( + { + error, + tokenState: { + hasAccessToken: !!this.token.accessToken, + hasRefreshToken: !!this.token.refreshToken, + }, + }, + "Failed to get user info", + ); + throw error; + } + } + + private async buildClient() { + const client = new google.auth.OAuth2( + this.clientId, + this.clientSecret, + process.env.NEXT_PUBLIC_SITE_URL, + ); + logger.debug("OAuth client initialized"); + + client.setCredentials({ + access_token: this.token.accessToken, + refresh_token: this.token.refreshToken ?? undefined, + }); + logger.debug("credentials set to OAuth client"); + + return client; + } +} + +type GoogleUserCredential = { + accessToken: string; + expiresAt: Date | null; + refreshToken: string | null; +}; + +export type { GoogleUserClient }; diff --git a/services/external/stripe/actions/upsert-subscription.ts b/services/external/stripe/actions/upsert-subscription.ts index 7426483b..45789e77 100644 --- a/services/external/stripe/actions/upsert-subscription.ts +++ b/services/external/stripe/actions/upsert-subscription.ts @@ -1,6 +1,5 @@ import { db, - organizations, stripeUserMappings, subscriptions, teamMemberships, @@ -18,10 +17,11 @@ export const upsertSubscription = async ( ) => { const subscription = await stripe.subscriptions.retrieve(subscriptionId); - const [organization] = await db - .selectDistinct({ dbId: organizations.dbId }) - .from(organizations) - .innerJoin(teams, eq(teams.organizationDbId, organizations.dbId)) + // TODO: When team plans are released, a user may belong to multiple teams, so we need to handle that case. + const [team] = await db + .select({ dbId: teams.dbId }) + .from(teams) + .innerJoin(teams, eq(teams.dbId, subscriptions.teamDbId)) .innerJoin(teamMemberships, eq(teamMemberships.teamDbId, teams.dbId)) .innerJoin(users, eq(users.dbId, teamMemberships.userDbId)) .innerJoin(stripeUserMappings, eq(stripeUserMappings.userDbId, users.dbId)) @@ -29,7 +29,7 @@ export const upsertSubscription = async ( const upsertValues: typeof subscriptions.$inferInsert = { id: subscription.id, - organizationDbId: organization.dbId, + teamDbId: team.dbId, status: subscription.status, cancelAtPeriodEnd: subscription.cancel_at_period_end, cancelAt: