diff --git a/packages/database/src/dbTypes.ts b/packages/database/src/dbTypes.ts index dc48a6a4c..9ece1617b 100644 --- a/packages/database/src/dbTypes.ts +++ b/packages/database/src/dbTypes.ts @@ -33,6 +33,13 @@ export type Database = { request_id?: string } Relationships: [ + { + foreignKeyName: "access_token_platform_account_id_fkey" + columns: ["platform_account_id"] + isOneToOne: false + referencedRelation: "my_accounts" + referencedColumns: ["id"] + }, { foreignKeyName: "access_token_platform_account_id_fkey" columns: ["platform_account_id"] @@ -62,6 +69,13 @@ export type Database = { value?: string } Relationships: [ + { + foreignKeyName: "AgentIdentifier_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "my_accounts" + referencedColumns: ["id"] + }, { foreignKeyName: "AgentIdentifier_account_id_fkey" columns: ["account_id"] @@ -124,6 +138,13 @@ export type Database = { space_id?: number } Relationships: [ + { + foreignKeyName: "Concept_author_id_fkey" + columns: ["author_id"] + isOneToOne: false + referencedRelation: "my_accounts" + referencedColumns: ["id"] + }, { foreignKeyName: "Concept_author_id_fkey" columns: ["author_id"] @@ -138,6 +159,13 @@ export type Database = { referencedRelation: "Content" referencedColumns: ["id"] }, + { + foreignKeyName: "Concept_represented_by_id_fkey" + columns: ["represented_by_id"] + isOneToOne: false + referencedRelation: "my_contents" + referencedColumns: ["id"] + }, { foreignKeyName: "Concept_schema_id_fkey" columns: ["schema_id"] @@ -145,6 +173,20 @@ export type Database = { referencedRelation: "Concept" referencedColumns: ["id"] }, + { + foreignKeyName: "Concept_schema_id_fkey" + columns: ["schema_id"] + isOneToOne: false + referencedRelation: "my_concepts" + referencedColumns: ["id"] + }, + { + foreignKeyName: "Concept_space_id_fkey" + columns: ["space_id"] + isOneToOne: false + referencedRelation: "my_spaces" + referencedColumns: ["id"] + }, { foreignKeyName: "Concept_space_id_fkey" columns: ["space_id"] @@ -175,6 +217,20 @@ export type Database = { referencedRelation: "Concept" referencedColumns: ["id"] }, + { + foreignKeyName: "concept_contributors_concept_id_fkey" + columns: ["concept_id"] + isOneToOne: false + referencedRelation: "my_concepts" + referencedColumns: ["id"] + }, + { + foreignKeyName: "concept_contributors_contributor_id_fkey" + columns: ["contributor_id"] + isOneToOne: false + referencedRelation: "my_accounts" + referencedColumns: ["id"] + }, { foreignKeyName: "concept_contributors_contributor_id_fkey" columns: ["contributor_id"] @@ -231,6 +287,13 @@ export type Database = { variant?: Database["public"]["Enums"]["ContentVariant"] } Relationships: [ + { + foreignKeyName: "Content_author_id_fkey" + columns: ["author_id"] + isOneToOne: false + referencedRelation: "my_accounts" + referencedColumns: ["id"] + }, { foreignKeyName: "Content_author_id_fkey" columns: ["author_id"] @@ -238,6 +301,13 @@ export type Database = { referencedRelation: "PlatformAccount" referencedColumns: ["id"] }, + { + foreignKeyName: "Content_creator_id_fkey" + columns: ["creator_id"] + isOneToOne: false + referencedRelation: "my_accounts" + referencedColumns: ["id"] + }, { foreignKeyName: "Content_creator_id_fkey" columns: ["creator_id"] @@ -252,6 +322,13 @@ export type Database = { referencedRelation: "Document" referencedColumns: ["id"] }, + { + foreignKeyName: "Content_document_id_fkey" + columns: ["document_id"] + isOneToOne: false + referencedRelation: "my_documents" + referencedColumns: ["id"] + }, { foreignKeyName: "Content_part_of_id_fkey" columns: ["part_of_id"] @@ -259,6 +336,20 @@ export type Database = { referencedRelation: "Content" referencedColumns: ["id"] }, + { + foreignKeyName: "Content_part_of_id_fkey" + columns: ["part_of_id"] + isOneToOne: false + referencedRelation: "my_contents" + referencedColumns: ["id"] + }, + { + foreignKeyName: "Content_space_id_fkey" + columns: ["space_id"] + isOneToOne: false + referencedRelation: "my_spaces" + referencedColumns: ["id"] + }, { foreignKeyName: "Content_space_id_fkey" columns: ["space_id"] @@ -289,6 +380,20 @@ export type Database = { referencedRelation: "Content" referencedColumns: ["id"] }, + { + foreignKeyName: "content_contributors_content_id_fkey" + columns: ["content_id"] + isOneToOne: false + referencedRelation: "my_contents" + referencedColumns: ["id"] + }, + { + foreignKeyName: "content_contributors_contributor_id_fkey" + columns: ["contributor_id"] + isOneToOne: false + referencedRelation: "my_accounts" + referencedColumns: ["id"] + }, { foreignKeyName: "content_contributors_contributor_id_fkey" columns: ["contributor_id"] @@ -325,6 +430,13 @@ export type Database = { referencedRelation: "Content" referencedColumns: ["id"] }, + { + foreignKeyName: "ContentEmbedding_openai_text_embedding_3_small_1_target_id_fkey" + columns: ["target_id"] + isOneToOne: true + referencedRelation: "my_contents" + referencedColumns: ["id"] + }, ] } Document: { @@ -362,6 +474,13 @@ export type Database = { url?: string | null } Relationships: [ + { + foreignKeyName: "Document_author_id_fkey" + columns: ["author_id"] + isOneToOne: false + referencedRelation: "my_accounts" + referencedColumns: ["id"] + }, { foreignKeyName: "Document_author_id_fkey" columns: ["author_id"] @@ -369,6 +488,13 @@ export type Database = { referencedRelation: "PlatformAccount" referencedColumns: ["id"] }, + { + foreignKeyName: "Document_space_id_fkey" + columns: ["space_id"] + isOneToOne: false + referencedRelation: "my_spaces" + referencedColumns: ["id"] + }, { foreignKeyName: "Document_space_id_fkey" columns: ["space_id"] @@ -452,6 +578,13 @@ export type Database = { space_id?: number } Relationships: [ + { + foreignKeyName: "SpaceAccess_account_id_fkey" + columns: ["account_id"] + isOneToOne: false + referencedRelation: "my_accounts" + referencedColumns: ["id"] + }, { foreignKeyName: "SpaceAccess_account_id_fkey" columns: ["account_id"] @@ -459,6 +592,13 @@ export type Database = { referencedRelation: "PlatformAccount" referencedColumns: ["id"] }, + { + foreignKeyName: "SpaceAccess_space_id_fkey" + columns: ["space_id"] + isOneToOne: false + referencedRelation: "my_spaces" + referencedColumns: ["id"] + }, { foreignKeyName: "SpaceAccess_space_id_fkey" columns: ["space_id"] @@ -509,7 +649,342 @@ export type Database = { } } Views: { - [_ in never]: never + my_accounts: { + Row: { + account_local_id: string | null + active: boolean | null + agent_type: Database["public"]["Enums"]["AgentType"] | null + dg_account: string | null + id: number | null + metadata: Json | null + name: string | null + platform: Database["public"]["Enums"]["Platform"] | null + write_permission: boolean | null + } + Relationships: [] + } + my_concepts: { + Row: { + arity: number | null + author_id: number | null + created: string | null + description: string | null + epistemic_status: + | Database["public"]["Enums"]["EpistemicStatus"] + | null + id: number | null + is_schema: boolean | null + last_modified: string | null + literal_content: Json | null + name: string | null + reference_content: Json | null + refs: number[] | null + represented_by_id: number | null + schema_id: number | null + space_id: number | null + } + Insert: { + arity?: number | null + author_id?: number | null + created?: string | null + description?: string | null + epistemic_status?: + | Database["public"]["Enums"]["EpistemicStatus"] + | null + id?: number | null + is_schema?: boolean | null + last_modified?: string | null + literal_content?: Json | null + name?: string | null + reference_content?: Json | null + refs?: number[] | null + represented_by_id?: number | null + schema_id?: number | null + space_id?: number | null + } + Update: { + arity?: number | null + author_id?: number | null + created?: string | null + description?: string | null + epistemic_status?: + | Database["public"]["Enums"]["EpistemicStatus"] + | null + id?: number | null + is_schema?: boolean | null + last_modified?: string | null + literal_content?: Json | null + name?: string | null + reference_content?: Json | null + refs?: number[] | null + represented_by_id?: number | null + schema_id?: number | null + space_id?: number | null + } + Relationships: [ + { + foreignKeyName: "Concept_author_id_fkey" + columns: ["author_id"] + isOneToOne: false + referencedRelation: "my_accounts" + referencedColumns: ["id"] + }, + { + foreignKeyName: "Concept_author_id_fkey" + columns: ["author_id"] + isOneToOne: false + referencedRelation: "PlatformAccount" + referencedColumns: ["id"] + }, + { + foreignKeyName: "Concept_represented_by_id_fkey" + columns: ["represented_by_id"] + isOneToOne: false + referencedRelation: "Content" + referencedColumns: ["id"] + }, + { + foreignKeyName: "Concept_represented_by_id_fkey" + columns: ["represented_by_id"] + isOneToOne: false + referencedRelation: "my_contents" + referencedColumns: ["id"] + }, + { + foreignKeyName: "Concept_schema_id_fkey" + columns: ["schema_id"] + isOneToOne: false + referencedRelation: "Concept" + referencedColumns: ["id"] + }, + { + foreignKeyName: "Concept_schema_id_fkey" + columns: ["schema_id"] + isOneToOne: false + referencedRelation: "my_concepts" + referencedColumns: ["id"] + }, + { + foreignKeyName: "Concept_space_id_fkey" + columns: ["space_id"] + isOneToOne: false + referencedRelation: "my_spaces" + referencedColumns: ["id"] + }, + { + foreignKeyName: "Concept_space_id_fkey" + columns: ["space_id"] + isOneToOne: false + referencedRelation: "Space" + referencedColumns: ["id"] + }, + ] + } + my_contents: { + Row: { + author_id: number | null + created: string | null + creator_id: number | null + document_id: number | null + id: number | null + last_modified: string | null + metadata: Json | null + part_of_id: number | null + scale: Database["public"]["Enums"]["Scale"] | null + source_local_id: string | null + space_id: number | null + text: string | null + variant: Database["public"]["Enums"]["ContentVariant"] | null + } + Insert: { + author_id?: number | null + created?: string | null + creator_id?: number | null + document_id?: number | null + id?: number | null + last_modified?: string | null + metadata?: Json | null + part_of_id?: number | null + scale?: Database["public"]["Enums"]["Scale"] | null + source_local_id?: string | null + space_id?: number | null + text?: string | null + variant?: Database["public"]["Enums"]["ContentVariant"] | null + } + Update: { + author_id?: number | null + created?: string | null + creator_id?: number | null + document_id?: number | null + id?: number | null + last_modified?: string | null + metadata?: Json | null + part_of_id?: number | null + scale?: Database["public"]["Enums"]["Scale"] | null + source_local_id?: string | null + space_id?: number | null + text?: string | null + variant?: Database["public"]["Enums"]["ContentVariant"] | null + } + Relationships: [ + { + foreignKeyName: "Content_author_id_fkey" + columns: ["author_id"] + isOneToOne: false + referencedRelation: "my_accounts" + referencedColumns: ["id"] + }, + { + foreignKeyName: "Content_author_id_fkey" + columns: ["author_id"] + isOneToOne: false + referencedRelation: "PlatformAccount" + referencedColumns: ["id"] + }, + { + foreignKeyName: "Content_creator_id_fkey" + columns: ["creator_id"] + isOneToOne: false + referencedRelation: "my_accounts" + referencedColumns: ["id"] + }, + { + foreignKeyName: "Content_creator_id_fkey" + columns: ["creator_id"] + isOneToOne: false + referencedRelation: "PlatformAccount" + referencedColumns: ["id"] + }, + { + foreignKeyName: "Content_document_id_fkey" + columns: ["document_id"] + isOneToOne: false + referencedRelation: "Document" + referencedColumns: ["id"] + }, + { + foreignKeyName: "Content_document_id_fkey" + columns: ["document_id"] + isOneToOne: false + referencedRelation: "my_documents" + referencedColumns: ["id"] + }, + { + foreignKeyName: "Content_part_of_id_fkey" + columns: ["part_of_id"] + isOneToOne: false + referencedRelation: "Content" + referencedColumns: ["id"] + }, + { + foreignKeyName: "Content_part_of_id_fkey" + columns: ["part_of_id"] + isOneToOne: false + referencedRelation: "my_contents" + referencedColumns: ["id"] + }, + { + foreignKeyName: "Content_space_id_fkey" + columns: ["space_id"] + isOneToOne: false + referencedRelation: "my_spaces" + referencedColumns: ["id"] + }, + { + foreignKeyName: "Content_space_id_fkey" + columns: ["space_id"] + isOneToOne: false + referencedRelation: "Space" + referencedColumns: ["id"] + }, + ] + } + my_documents: { + Row: { + author_id: number | null + contents: unknown | null + created: string | null + id: number | null + last_modified: string | null + metadata: Json | null + source_local_id: string | null + space_id: number | null + url: string | null + } + Insert: { + author_id?: number | null + contents?: unknown | null + created?: string | null + id?: number | null + last_modified?: string | null + metadata?: Json | null + source_local_id?: string | null + space_id?: number | null + url?: string | null + } + Update: { + author_id?: number | null + contents?: unknown | null + created?: string | null + id?: number | null + last_modified?: string | null + metadata?: Json | null + source_local_id?: string | null + space_id?: number | null + url?: string | null + } + Relationships: [ + { + foreignKeyName: "Document_author_id_fkey" + columns: ["author_id"] + isOneToOne: false + referencedRelation: "my_accounts" + referencedColumns: ["id"] + }, + { + foreignKeyName: "Document_author_id_fkey" + columns: ["author_id"] + isOneToOne: false + referencedRelation: "PlatformAccount" + referencedColumns: ["id"] + }, + { + foreignKeyName: "Document_space_id_fkey" + columns: ["space_id"] + isOneToOne: false + referencedRelation: "my_spaces" + referencedColumns: ["id"] + }, + { + foreignKeyName: "Document_space_id_fkey" + columns: ["space_id"] + isOneToOne: false + referencedRelation: "Space" + referencedColumns: ["id"] + }, + ] + } + my_spaces: { + Row: { + id: number | null + name: string | null + platform: Database["public"]["Enums"]["Platform"] | null + url: string | null + } + Insert: { + id?: number | null + name?: string | null + platform?: Database["public"]["Enums"]["Platform"] | null + url?: string | null + } + Update: { + id?: number | null + name?: string | null + platform?: Database["public"]["Enums"]["Platform"] | null + url?: string | null + } + Relationships: [] + } } Functions: { _local_concept_to_db_concept: { @@ -574,28 +1049,60 @@ export type Database = { Args: { p_account_id: number } Returns: boolean } + author_of_concept: { + Args: { concept: unknown } + Returns: { + account_local_id: string | null + active: boolean | null + agent_type: Database["public"]["Enums"]["AgentType"] | null + dg_account: string | null + id: number | null + metadata: Json | null + name: string | null + platform: Database["public"]["Enums"]["Platform"] | null + write_permission: boolean | null + }[] + } + author_of_content: { + Args: { content: unknown } + Returns: { + account_local_id: string | null + active: boolean | null + agent_type: Database["public"]["Enums"]["AgentType"] | null + dg_account: string | null + id: number | null + metadata: Json | null + name: string | null + platform: Database["public"]["Enums"]["Platform"] | null + write_permission: boolean | null + }[] + } compute_arity_local: { Args: { lit_content: Json; schema_id: number } Returns: number } concept_in_relations: { - Args: { concept: Database["public"]["Tables"]["Concept"]["Row"] } + Args: + | { concept: Database["public"]["Tables"]["Concept"]["Row"] } + | { concept: unknown } Returns: { arity: number | null author_id: number | null - created: string + created: string | null description: string | null - epistemic_status: Database["public"]["Enums"]["EpistemicStatus"] - id: number - is_schema: boolean - last_modified: string - literal_content: Json - name: string - reference_content: Json - refs: number[] + epistemic_status: + | Database["public"]["Enums"]["EpistemicStatus"] + | null + id: number | null + is_schema: boolean | null + last_modified: string | null + literal_content: Json | null + name: string | null + reference_content: Json | null + refs: number[] | null represented_by_id: number | null schema_id: number | null - space_id: number + space_id: number | null }[] } concept_in_space: { @@ -603,29 +1110,51 @@ export type Database = { Returns: boolean } concepts_of_relation: { - Args: { relation: Database["public"]["Tables"]["Concept"]["Row"] } + Args: + | { relation: Database["public"]["Tables"]["Concept"]["Row"] } + | { relation: unknown } Returns: { arity: number | null author_id: number | null - created: string + created: string | null description: string | null - epistemic_status: Database["public"]["Enums"]["EpistemicStatus"] - id: number - is_schema: boolean - last_modified: string - literal_content: Json - name: string - reference_content: Json - refs: number[] + epistemic_status: + | Database["public"]["Enums"]["EpistemicStatus"] + | null + id: number | null + is_schema: boolean | null + last_modified: string | null + literal_content: Json | null + name: string | null + reference_content: Json | null + refs: number[] | null represented_by_id: number | null schema_id: number | null - space_id: number + space_id: number | null }[] } content_in_space: { Args: { content_id: number } Returns: boolean } + content_of_concept: { + Args: { concept: unknown } + Returns: { + author_id: number | null + created: string | null + creator_id: number | null + document_id: number | null + id: number | null + last_modified: string | null + metadata: Json | null + part_of_id: number | null + scale: Database["public"]["Enums"]["Scale"] | null + source_local_id: string | null + space_id: number | null + text: string | null + variant: Database["public"]["Enums"]["ContentVariant"] | null + }[] + } create_account_in_space: { Args: { account_local_id_: string @@ -641,6 +1170,20 @@ export type Database = { Args: { document_id: number } Returns: boolean } + document_of_content: { + Args: { content: unknown } + Returns: { + author_id: number | null + contents: unknown | null + created: string | null + id: number | null + last_modified: string | null + metadata: Json | null + source_local_id: string | null + space_id: number | null + url: string | null + }[] + } end_sync_task: { Args: { s_function: string @@ -673,7 +1216,9 @@ export type Database = { Returns: boolean } instances_of_schema: { - Args: { schema: Database["public"]["Tables"]["Concept"]["Row"] } + Args: + | { schema: Database["public"]["Tables"]["Concept"]["Row"] } + | { schema: unknown } Returns: { arity: number | null author_id: number | null @@ -692,6 +1237,10 @@ export type Database = { space_id: number }[] } + is_my_account: { + Args: { account_id: number } + Returns: boolean + } match_content_embeddings: { Args: { current_document_id?: number @@ -716,8 +1265,12 @@ export type Database = { }[] } my_account: { - Args: { account_id: number } - Returns: boolean + Args: Record + Returns: number + } + my_space_ids: { + Args: Record + Returns: number[] } propose_sync_task: { Args: { @@ -730,7 +1283,9 @@ export type Database = { Returns: string } schema_of_concept: { - Args: { concept: Database["public"]["Tables"]["Concept"]["Row"] } + Args: + | { concept: Database["public"]["Tables"]["Concept"]["Row"] } + | { concept: unknown } Returns: { arity: number | null author_id: number | null diff --git a/packages/database/src/lib/contextFunctions.ts b/packages/database/src/lib/contextFunctions.ts index d82e0403e..dd93fa2f8 100644 --- a/packages/database/src/lib/contextFunctions.ts +++ b/packages/database/src/lib/contextFunctions.ts @@ -102,10 +102,17 @@ export const fetchOrCreateSpaceDirect = async ( }); if (result2.data === null) { - return asPostgrestFailure( - JSON.stringify(result2.error), - "Failed to create space", - ); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + let error: string = (result2.error?.message as string | undefined) || ""; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (result2.error?.context?.body) + try { + // eslint-disable-next-line + error += await new Response(result2.error.context.body).text(); + } catch (err) { + // could not parse, not important + } + return asPostgrestFailure(error, "Failed to create space"); } return { data: result2.data, diff --git a/packages/database/src/lib/queries.ts b/packages/database/src/lib/queries.ts index b9d1374ee..aa1f3921e 100644 --- a/packages/database/src/lib/queries.ts +++ b/packages/database/src/lib/queries.ts @@ -114,12 +114,14 @@ const composeConceptQuery = ({ if (contentFields.length > 0) { const args: string[] = contentFields.slice(); if (documentFields.length > 0) { - args.push("Document (\n" + documentFields.join(",\n") + ")"); + args.push( + `Document:my_documents!document_id${innerContent ? "!inner" : ""} (\n${documentFields.join(",\n")})`, + ); } - q += `,\nContent${innerContent ? "!inner" : ""} (\n${args.join(",\n")})`; + q += `,\nContent:my_contents!represented_by_id${innerContent ? "!inner" : ""} (\n${args.join(",\n")})`; } if (nodeAuthor !== undefined) { - q += ", author:author_id!inner(account_local_id)"; + q += ", author:my_accounts!author_id!inner(account_local_id)"; } if ( inRelsOfType !== undefined || @@ -139,16 +141,18 @@ const composeConceptQuery = ({ if (inRelsToNodesOfType !== undefined && !args2.includes("schema_id")) args2.push("schema_id"); if (inRelsToNodeLocalIds !== undefined) - args2.push("Content!inner(source_local_id)"); + args2.push( + "Content:my_contents!represented_by_id!inner(source_local_id)", + ); if (inRelsToNodesOfAuthor !== undefined) { if (!args2.includes("author_id")) args2.push("author_id"); - args2.push("author:author_id!inner(account_local_id)"); + args2.push("author:my_accounts!author_id!inner(account_local_id)"); } args.push(`subnodes:concepts_of_relation!inner(${args2.join(",\n")})`); } q += `, relations:concept_in_relations!inner(${args.join(",\n")})`; } - let query = supabase.from("Concept").select(q); + let query = supabase.from("my_concepts").select(q); if (fetchNodes === true) { query = query.eq("arity", 0); } else if (fetchNodes === false) { @@ -172,7 +176,7 @@ const composeConceptQuery = ({ else throw new Error("schemaDbIds should be a number or number[]"); } if (baseNodeLocalIds.length > 0) - query = query.in("content.source_local_id", baseNodeLocalIds); + query = query.in("Content.source_local_id", baseNodeLocalIds); if (inRelsOfType !== undefined && inRelsOfType.length > 0) query = query.in("relations.schema_id", inRelsOfType); if (inRelsToNodesOfType !== undefined && inRelsToNodesOfType.length > 0) diff --git a/packages/database/supabase/functions/create-space/index.ts b/packages/database/supabase/functions/create-space/index.ts index ccf8daea6..7c82ab120 100644 --- a/packages/database/supabase/functions/create-space/index.ts +++ b/packages/database/supabase/functions/create-space/index.ts @@ -114,6 +114,7 @@ const processAndGetOrCreateSpace = async ( return asPostgrestFailure(error.message, "authentication_error"); } anonymousUser = data.user; + await supabase.auth.signOut({ scope: "local" }); } if (anonymousUser === null) { const resultCreateAnonymousUser = await supabase.auth.admin.createUser({ @@ -185,7 +186,7 @@ const processAndGetOrCreateSpace = async ( const allowedOrigins = ["https://roamresearch.com", "http://localhost:3000"]; const isVercelPreviewUrl = (origin: string): boolean => - /^https:\/\/.*-discourse-graph-[a-z0-9]+\.vercel\.app$/.test(origin) + /^https:\/\/.*-discourse-graph-[a-z0-9]+\.vercel\.app$/.test(origin); const isAllowedOrigin = (origin: string): boolean => allowedOrigins.some((allowed) => origin.startsWith(allowed)) || @@ -193,20 +194,20 @@ const isAllowedOrigin = (origin: string): boolean => // @ts-ignore Deno is not visible to the IDE Deno.serve(async (req) => { - const origin = req.headers.get("origin"); - const originIsAllowed = origin && isAllowedOrigin(origin); - if (req.method === "OPTIONS") { - return new Response(null, { - status: 204, - headers: { - ...(originIsAllowed ? { "Access-Control-Allow-Origin": origin } : {}), - "Access-Control-Allow-Methods": "GET, POST, OPTIONS", - "Access-Control-Allow-Headers": - "Content-Type, Authorization, x-vercel-protection-bypass, x-client-info, apikey", - "Access-Control-Max-Age": "86400", - }, - }); - } + const origin = req.headers.get("origin"); + const originIsAllowed = origin && isAllowedOrigin(origin); + if (req.method === "OPTIONS") { + return new Response(null, { + status: 204, + headers: { + ...(originIsAllowed ? { "Access-Control-Allow-Origin": origin } : {}), + "Access-Control-Allow-Methods": "GET, POST, OPTIONS", + "Access-Control-Allow-Headers": + "Content-Type, Authorization, x-vercel-protection-bypass, x-client-info, apikey", + "Access-Control-Max-Age": "86400", + }, + }); + } const input = await req.json(); // @ts-ignore Deno is not visible to the IDE diff --git a/packages/database/supabase/migrations/20251008131345_security_views.sql b/packages/database/supabase/migrations/20251008131345_security_views.sql new file mode 100644 index 000000000..1821cfddc --- /dev/null +++ b/packages/database/supabase/migrations/20251008131345_security_views.sql @@ -0,0 +1,372 @@ +ALTER FUNCTION public.my_account RENAME TO is_my_account; + +DROP POLICY IF EXISTS access_token_policy ON public.access_token; +CREATE POLICY access_token_policy ON public.access_token FOR ALL USING (platform_account_id IS NULL OR public.is_my_account(platform_account_id)); + +CREATE OR REPLACE FUNCTION public.is_my_account(account_id BIGINT) RETURNS boolean +STABLE SECURITY DEFINER +SET search_path = '' +LANGUAGE sql +AS $$ + WITH u AS (SELECT auth.uid() LIMIT 1) + SELECT true FROM public."PlatformAccount" pa + JOIN u ON pa.dg_account = u.uid + WHERE pa.id = account_id; +$$; + +COMMENT ON FUNCTION public.is_my_account IS 'security utility: is this my own account?'; + +CREATE OR REPLACE FUNCTION public.my_account() RETURNS BIGINT +STABLE SECURITY DEFINER +SET search_path = '' +LANGUAGE sql +AS $$ + WITH u AS (SELECT auth.uid() LIMIT 1) + SELECT id FROM public."PlatformAccount" pa + JOIN u ON pa.dg_account = u.uid LIMIT 1; +$$; + +COMMENT ON FUNCTION public.my_account IS 'security utility: id of my account'; + +CREATE OR REPLACE FUNCTION public.my_space_ids() RETURNS BIGINT [] +STABLE SECURITY DEFINER +SET search_path = '' +LANGUAGE sql +AS $$ + WITH u AS (SELECT auth.uid() LIMIT 1) + SELECT COALESCE(array_agg(distinct sa.space_id), '{}') AS ids FROM public."SpaceAccess" AS sa + JOIN public."PlatformAccount" AS pa ON pa.id=sa.account_id + JOIN u ON pa.dg_account = u.uid; +$$; +COMMENT ON FUNCTION public.my_space_ids IS 'security utility: all spaces the user has access to'; + + +CREATE OR REPLACE FUNCTION public.in_space(space_id BIGINT) RETURNS boolean +STABLE SECURITY DEFINER +SET search_path = '' +LANGUAGE sql +AS $$ + WITH u AS (SELECT auth.uid() LIMIT 1), + pa AS (SELECT sa.space_id AS id FROM public."SpaceAccess" AS sa + JOIN public."PlatformAccount" AS pa ON pa.id=sa.account_id + JOIN u ON pa.dg_account = u.uid) + SELECT EXISTS (SELECT id FROM pa WHERE id = space_id ); +$$; + +COMMENT ON FUNCTION public.in_space IS 'security utility: does current user have access to this space?'; + + +CREATE OR REPLACE FUNCTION public.account_in_shared_space(p_account_id BIGINT) RETURNS boolean +STABLE SECURITY DEFINER +SET search_path = '' +LANGUAGE sql AS $$ + SELECT EXISTS ( + SELECT 1 + FROM public."SpaceAccess" AS sa + WHERE sa.account_id = p_account_id + AND sa.space_id = ANY(public.my_space_ids()) + ); +$$; + +COMMENT ON FUNCTION public.account_in_shared_space IS 'security utility: does current user share a space with this account?'; + +CREATE OR REPLACE FUNCTION public.unowned_account_in_shared_space(p_account_id BIGINT) RETURNS boolean +STABLE SECURITY DEFINER +SET search_path = '' +LANGUAGE sql AS $$ + SELECT EXISTS ( + SELECT 1 + FROM public."SpaceAccess" AS sa + JOIN public."PlatformAccount" AS pa ON (pa.id = sa.account_id) + WHERE sa.account_id = p_account_id + AND sa.space_id = ANY(public.my_space_ids()) + AND pa.dg_account IS NULL + ); +$$; + +COMMENT ON FUNCTION public.unowned_account_in_shared_space IS 'security utility: does current user share a space with this unowned account?'; + +DROP POLICY IF EXISTS platform_account_policy ON public."PlatformAccount"; + +DROP POLICY IF EXISTS platform_account_select_policy ON public."PlatformAccount"; +CREATE POLICY platform_account_select_policy ON public."PlatformAccount" FOR SELECT USING (dg_account = (SELECT auth.uid() LIMIT 1) OR public.account_in_shared_space(id)); + +DROP POLICY IF EXISTS platform_account_delete_policy ON public."PlatformAccount"; +CREATE POLICY platform_account_delete_policy ON public."PlatformAccount" FOR DELETE USING (dg_account = (SELECT auth.uid() LIMIT 1) OR (dg_account IS null AND public.unowned_account_in_shared_space(id))); + +DROP POLICY IF EXISTS platform_account_insert_policy ON public."PlatformAccount"; +CREATE POLICY platform_account_insert_policy ON public."PlatformAccount" FOR INSERT WITH CHECK (dg_account = (SELECT auth.uid() LIMIT 1) OR (dg_account IS null AND public.unowned_account_in_shared_space(id))); + +DROP POLICY IF EXISTS platform_account_update_policy ON public."PlatformAccount"; +CREATE POLICY platform_account_update_policy ON public."PlatformAccount" FOR UPDATE WITH CHECK (dg_account = (SELECT auth.uid() LIMIT 1) OR (dg_account IS null AND public.unowned_account_in_shared_space(id))); + +DROP POLICY IF EXISTS space_access_policy ON public."SpaceAccess"; + +DROP POLICY IF EXISTS space_access_select_policy ON public."SpaceAccess"; +CREATE POLICY space_access_select_policy ON public."SpaceAccess" FOR SELECT USING (public.in_space(space_id)); + +DROP POLICY IF EXISTS space_access_delete_policy ON public."SpaceAccess"; +CREATE POLICY space_access_delete_policy ON public."SpaceAccess" FOR DELETE USING (public.unowned_account_in_shared_space(account_id) OR account_id = public.my_account()); + +DROP POLICY IF EXISTS space_access_insert_policy ON public."SpaceAccess"; +CREATE POLICY space_access_insert_policy ON public."SpaceAccess" FOR INSERT WITH CHECK (public.unowned_account_in_shared_space(account_id) OR account_id = public.my_account()); + +DROP POLICY IF EXISTS space_access_update_policy ON public."SpaceAccess"; +CREATE POLICY space_access_update_policy ON public."SpaceAccess" FOR UPDATE WITH CHECK (public.unowned_account_in_shared_space(account_id) OR account_id = public.my_account()); + + +DROP POLICY IF EXISTS agent_identifier_policy ON public."AgentIdentifier"; + +DROP POLICY IF EXISTS agent_identifier_select_policy ON public."AgentIdentifier"; +CREATE POLICY agent_identifier_select_policy ON public."AgentIdentifier" FOR SELECT USING (public.account_in_shared_space(account_id)); + +DROP POLICY IF EXISTS agent_identifier_delete_policy ON public."AgentIdentifier"; +CREATE POLICY agent_identifier_delete_policy ON public."AgentIdentifier" FOR DELETE USING (public.unowned_account_in_shared_space(account_id) OR account_id = public.my_account()); + +DROP POLICY IF EXISTS agent_identifier_insert_policy ON public."AgentIdentifier"; +CREATE POLICY agent_identifier_insert_policy ON public."AgentIdentifier" FOR INSERT WITH CHECK (public.unowned_account_in_shared_space(account_id) OR account_id = public.my_account()); + +DROP POLICY IF EXISTS agent_identifier_update_policy ON public."AgentIdentifier"; +CREATE POLICY agent_identifier_update_policy ON public."AgentIdentifier" FOR UPDATE WITH CHECK (public.unowned_account_in_shared_space(account_id) OR account_id = public.my_account()); + +CREATE OR REPLACE VIEW public.my_spaces AS +SELECT + id, + url, + name, + platform +FROM public."Space" WHERE id = any(public.my_space_ids()); + +CREATE OR REPLACE VIEW public.my_accounts AS +SELECT + pa.id, + pa.name, + pa.platform, + pa.account_local_id, + pa.write_permission, + pa.active, + pa.agent_type, + pa.metadata, + pa.dg_account +FROM public."PlatformAccount" AS pa +WHERE EXISTS ( + SELECT 1 + FROM public."SpaceAccess" AS sa + WHERE sa.account_id = pa.id + AND sa.space_id = ANY(public.my_space_ids()) +); + + +CREATE OR REPLACE VIEW public.my_documents AS +SELECT + id, + space_id, + source_local_id, + url, + "created", + metadata, + last_modified, + author_id, + contents +FROM public."Document" WHERE space_id = any(public.my_space_ids()); + +CREATE OR REPLACE VIEW public.my_contents AS +SELECT + id, + document_id, + source_local_id, + variant, + author_id, + creator_id, + created, + text, + metadata, + scale, + space_id, + last_modified, + part_of_id +FROM public."Content" WHERE space_id = any(public.my_space_ids()); + +CREATE OR REPLACE VIEW public.my_concepts AS +SELECT + id, + epistemic_status, + name, + description, + author_id, + created, + last_modified, + space_id, + arity, + schema_id, + literal_content, + reference_content, + refs, + is_schema, + represented_by_id +FROM public."Concept" WHERE space_id = any(public.my_space_ids()); + + +CREATE OR REPLACE FUNCTION public.schema_of_concept(concept public.my_concepts) +RETURNS SETOF public.my_concepts STRICT STABLE +ROWS 1 +SET search_path = '' +LANGUAGE sql +AS $$ + SELECT * from public.my_concepts WHERE id=concept.schema_id; +$$; +COMMENT ON FUNCTION public.schema_of_concept(public.my_concepts) +IS 'Computed one-to-one: returns the schema Concept for a given Concept (by schema_id).'; + +CREATE OR REPLACE FUNCTION public.instances_of_schema(schema public.my_concepts) +RETURNS SETOF public.my_concepts STRICT STABLE +SET search_path = '' +LANGUAGE sql +AS $$ + SELECT * from public.my_concepts WHERE schema_id=schema.id; +$$; +COMMENT ON FUNCTION public.instances_of_schema(public.my_concepts) +IS 'Computed one-to-many: returns all Concept instances that are based on the given schema Concept.'; + + +CREATE OR REPLACE FUNCTION public.concept_in_relations(concept public.my_concepts) +RETURNS SETOF public.my_concepts STRICT STABLE +SET search_path = '' +LANGUAGE sql +AS $$ + SELECT * from public.my_concepts WHERE refs @> ARRAY[concept.id]; +$$; +COMMENT ON FUNCTION public.concept_in_relations(public.my_concepts) +IS 'Computed one-to-many: returns all Concept instances that are relations including the current concept.'; + +CREATE OR REPLACE FUNCTION public.concepts_of_relation(relation public.my_concepts) +RETURNS SETOF public.my_concepts STRICT STABLE +SET search_path = '' +LANGUAGE sql +AS $$ + SELECT * from public.my_concepts WHERE id = any(relation.refs); +$$; +COMMENT ON FUNCTION public.concepts_of_relation(public.my_concepts) +IS 'Computed one-to-many: returns all Concept instances are referred to in the current concept.'; + +CREATE OR REPLACE FUNCTION public.document_of_content(content public.my_contents) +RETURNS SETOF public.my_documents STRICT STABLE +ROWS 1 +SET search_path = '' +LANGUAGE sql +AS $$ + SELECT * from public.my_documents WHERE id=content.document_id; +$$; +COMMENT ON FUNCTION public.document_of_content(public.my_contents) +IS 'Computed one-to-one: returns the containing Document for a given Content.'; + +CREATE OR REPLACE FUNCTION public.content_of_concept(concept public.my_concepts) +RETURNS SETOF public.my_contents STRICT STABLE +ROWS 1 +SET search_path = '' +LANGUAGE sql +AS $$ + SELECT * from public.my_contents WHERE id=concept.represented_by_id; +$$; +COMMENT ON FUNCTION public.content_of_concept(public.my_concepts) +IS 'Computed one-to-one: returns the representing Content for a given Concept.'; + +CREATE OR REPLACE FUNCTION public.author_of_content(content public.my_contents) +RETURNS SETOF public.my_accounts STRICT STABLE +ROWS 1 +SET search_path = '' +LANGUAGE sql +AS $$ + SELECT * from public.my_accounts WHERE id=content.author_id; +$$; +COMMENT ON FUNCTION public.author_of_content(public.my_contents) +IS 'Computed one-to-one: returns the PlatformAccount which authored a given Content.'; + +CREATE OR REPLACE FUNCTION public.author_of_concept(concept public.my_concepts) +RETURNS SETOF public.my_accounts STRICT STABLE +ROWS 1 +SET search_path = '' +LANGUAGE sql +AS $$ + SELECT * from public.my_accounts WHERE id=concept.author_id; +$$; +COMMENT ON FUNCTION public.author_of_concept(public.my_concepts) +IS 'Computed one-to-one: returns the PlatformAccount which authored a given Concept.'; + +CREATE OR REPLACE VIEW public.my_contents_with_embedding_openai_text_embedding_3_small_1536 AS +SELECT + ct.id, + ct.document_id, + ct.source_local_id, + ct.variant, + ct.author_id, + ct.creator_id, + ct.created, + ct.text, + ct.metadata, + ct.scale, + ct.space_id, + ct.last_modified, + ct.part_of_id, + emb.model, + emb.vector +FROM public."Content" AS ct +JOIN public."ContentEmbedding_openai_text_embedding_3_small_1536" AS emb ON (ct.id=emb.target_id) +WHERE ct.space_id = any(public.my_space_ids()) AND NOT emb.obsolete; + + +CREATE OR REPLACE FUNCTION public.match_content_embeddings ( +query_embedding extensions.vector, +match_threshold double precision, +match_count integer, +current_document_id integer DEFAULT NULL::integer) +RETURNS TABLE ( +content_id bigint, +roam_uid Text, +text_content Text, +similarity double precision) +SET search_path = 'extensions' +LANGUAGE sql STABLE +AS $$ +SELECT + c.id AS content_id, + c.source_local_id AS roam_uid, + c.text AS text_content, + 1 - (c.vector <=> query_embedding) AS similarity +FROM public.my_contents_with_embedding_openai_text_embedding_3_small_1536 AS c +WHERE 1 - (c.vector <=> query_embedding) > match_threshold + AND (current_document_id IS NULL OR c.document_id = current_document_id) +ORDER BY + c.vector <=> query_embedding ASC +LIMIT match_count; +$$ ; + +CREATE OR REPLACE FUNCTION public.match_embeddings_for_subset_nodes ( +"p_query_embedding" extensions.vector, +"p_subset_roam_uids" Text []) +RETURNS TABLE (content_id bigint, +roam_uid Text, +text_content Text, +similarity double precision) +LANGUAGE sql STABLE +SET search_path = 'extensions' +AS $$ +WITH subset_content_with_embeddings AS ( + -- Step 1: Identify content and fetch embeddings ONLY for the nodes in the provided Roam UID subset + SELECT + c.id AS content_id, + c.source_local_id AS roam_uid, + c.text AS text_content, + c.vector AS embedding_vector + FROM public.my_contents_with_embedding_openai_text_embedding_3_small_1536 AS c + WHERE + c.source_local_id = ANY(p_subset_roam_uids) -- Filter Content by the provided Roam UIDs +) +SELECT + ss_ce.content_id, + ss_ce.roam_uid, + ss_ce.text_content, + 1 - (ss_ce.embedding_vector <=> p_query_embedding) AS similarity +FROM subset_content_with_embeddings AS ss_ce +ORDER BY similarity DESC; -- Order by calculated similarity, highest first +$$ ; diff --git a/packages/database/supabase/schemas/access_token.sql b/packages/database/supabase/schemas/access_token.sql index a1b7b2c9c..19f5395fd 100644 --- a/packages/database/supabase/schemas/access_token.sql +++ b/packages/database/supabase/schemas/access_token.sql @@ -40,4 +40,4 @@ GRANT INSERT ON TABLE "public"."access_token" TO "anon"; ALTER TABLE public.access_token ENABLE ROW LEVEL SECURITY; DROP POLICY IF EXISTS access_token_policy ON public.access_token; -CREATE POLICY access_token_policy ON public.access_token FOR ALL USING (public.my_account(platform_account_id)); +CREATE POLICY access_token_policy ON public.access_token FOR ALL USING (platform_account_id IS NULL OR public.is_my_account(platform_account_id)); diff --git a/packages/database/supabase/schemas/account.sql b/packages/database/supabase/schemas/account.sql index d1b7ccaef..80944dc83 100644 --- a/packages/database/supabase/schemas/account.sql +++ b/packages/database/supabase/schemas/account.sql @@ -181,24 +181,54 @@ AS $$ $$; -CREATE OR REPLACE FUNCTION public.my_account(account_id BIGINT) RETURNS boolean +CREATE OR REPLACE FUNCTION public.is_my_account(account_id BIGINT) RETURNS boolean STABLE SECURITY DEFINER SET search_path = '' LANGUAGE sql AS $$ - SELECT dg_account = auth.uid() FROM public."PlatformAccount" WHERE id=account_id; + WITH u AS (SELECT auth.uid() LIMIT 1) + SELECT true FROM public."PlatformAccount" pa + JOIN u ON pa.dg_account = u.uid + WHERE pa.id = account_id; $$; -COMMENT ON FUNCTION public.my_account IS 'security utility: is this my own account?'; +COMMENT ON FUNCTION public.is_my_account IS 'security utility: is this my own account?'; + +CREATE OR REPLACE FUNCTION public.my_account() RETURNS BIGINT +STABLE SECURITY DEFINER +SET search_path = '' +LANGUAGE sql +AS $$ + WITH u AS (SELECT auth.uid() LIMIT 1) + SELECT id FROM public."PlatformAccount" pa + JOIN u ON pa.dg_account = u.uid LIMIT 1; +$$; + +COMMENT ON FUNCTION public.my_account IS 'security utility: id of my account'; + +CREATE OR REPLACE FUNCTION public.my_space_ids() RETURNS BIGINT [] +STABLE SECURITY DEFINER +SET search_path = '' +LANGUAGE sql +AS $$ + WITH u AS (SELECT auth.uid() LIMIT 1) + SELECT COALESCE(array_agg(distinct sa.space_id), '{}') AS ids FROM public."SpaceAccess" AS sa + JOIN public."PlatformAccount" AS pa ON pa.id=sa.account_id + JOIN u ON pa.dg_account = u.uid; +$$; +COMMENT ON FUNCTION public.my_space_ids IS 'security utility: all spaces the user has access to'; + CREATE OR REPLACE FUNCTION public.in_space(space_id BIGINT) RETURNS boolean STABLE SECURITY DEFINER SET search_path = '' LANGUAGE sql AS $$ - SELECT COUNT(*) > 0 FROM public."SpaceAccess" AS sa - JOIN public."PlatformAccount" AS pa ON (pa.id=sa.account_id) - WHERE sa.space_id = $1 AND pa.dg_account = auth.uid(); + WITH u AS (SELECT auth.uid() LIMIT 1), + pa AS (SELECT sa.space_id AS id FROM public."SpaceAccess" AS sa + JOIN public."PlatformAccount" AS pa ON pa.id=sa.account_id + JOIN u ON pa.dg_account = u.uid) + SELECT EXISTS (SELECT id FROM pa WHERE id = space_id ); $$; COMMENT ON FUNCTION public.in_space IS 'security utility: does current user have access to this space?'; @@ -208,25 +238,31 @@ CREATE OR REPLACE FUNCTION public.account_in_shared_space(p_account_id BIGINT) R STABLE SECURITY DEFINER SET search_path = '' LANGUAGE sql AS $$ - SELECT COUNT(*) > 0 FROM public."PlatformAccount" AS my_account - JOIN public."SpaceAccess" AS my_access ON (my_account.id=my_access.account_id) - JOIN public."SpaceAccess" AS their_access ON (their_access.space_id = my_access.space_id AND their_access.account_id=p_account_id) - WHERE my_account.dg_account = auth.uid(); + SELECT EXISTS ( + SELECT 1 + FROM public."SpaceAccess" AS sa + WHERE sa.account_id = p_account_id + AND sa.space_id = ANY(public.my_space_ids()) + ); $$; +COMMENT ON FUNCTION public.account_in_shared_space IS 'security utility: does current user share a space with this account?'; + CREATE OR REPLACE FUNCTION public.unowned_account_in_shared_space(p_account_id BIGINT) RETURNS boolean STABLE SECURITY DEFINER SET search_path = '' LANGUAGE sql AS $$ - SELECT COUNT(*) > 0 FROM public."PlatformAccount" AS my_account - JOIN public."SpaceAccess" AS my_access ON (my_account.id=my_access.account_id) - JOIN public."SpaceAccess" AS their_access ON (their_access.space_id = my_access.space_id AND their_access.account_id=p_account_id) - JOIN public."PlatformAccount" AS their_account ON (their_access.account_id = their_account.id AND their_account.id=p_account_id) - WHERE my_account.dg_account = auth.uid() AND COALESCE(their_account.dg_account, auth.uid()) = auth.uid(); + SELECT EXISTS ( + SELECT 1 + FROM public."SpaceAccess" AS sa + JOIN public."PlatformAccount" AS pa ON (pa.id = sa.account_id) + WHERE sa.account_id = p_account_id + AND sa.space_id = ANY(public.my_space_ids()) + AND pa.dg_account IS NULL + ); $$; -COMMENT ON FUNCTION public.unowned_account_in_shared_space IS 'security utility: does current user share a space with this account? And is this an un-owned account (other than mine)?'; - +COMMENT ON FUNCTION public.unowned_account_in_shared_space IS 'security utility: does current user share a space with this unowned account?'; -- Space: Allow anyone to insert, but only users who are members of the space can update or select @@ -238,17 +274,53 @@ CREATE POLICY space_policy ON public."Space" FOR ALL USING (public.in_space(id)) DROP POLICY IF EXISTS space_insert_policy ON public."Space"; CREATE POLICY space_insert_policy ON public."Space" FOR INSERT WITH CHECK (true); +CREATE OR REPLACE VIEW public.my_spaces AS +SELECT + id, + url, + name, + platform +FROM public."Space" WHERE id = any(public.my_space_ids()); + -- PlatformAccount: Access to anyone sharing a space with you to create an account, to allow editing authors -- Once the account is claimed by a user, only allow this user to modify it. -- Eventually: Allow platform admin to modify? ALTER TABLE public."PlatformAccount" ENABLE ROW LEVEL SECURITY; +CREATE OR REPLACE VIEW public.my_accounts AS +SELECT + pa.id, + pa.name, + pa.platform, + pa.account_local_id, + pa.write_permission, + pa.active, + pa.agent_type, + pa.metadata, + pa.dg_account +FROM public."PlatformAccount" AS pa +WHERE EXISTS ( + SELECT 1 + FROM public."SpaceAccess" AS sa + WHERE + sa.account_id = pa.id + AND sa.space_id = any(public.my_space_ids()) +); + DROP POLICY IF EXISTS platform_account_policy ON public."PlatformAccount"; -CREATE POLICY platform_account_policy ON public."PlatformAccount" FOR ALL USING (dg_account = (SELECT auth.uid()) OR (dg_account IS null AND public.unowned_account_in_shared_space(id))); DROP POLICY IF EXISTS platform_account_select_policy ON public."PlatformAccount"; -CREATE POLICY platform_account_select_policy ON public."PlatformAccount" FOR SELECT USING (dg_account = (SELECT auth.uid()) OR public.account_in_shared_space(id)); +CREATE POLICY platform_account_select_policy ON public."PlatformAccount" FOR SELECT USING (dg_account = (SELECT auth.uid() LIMIT 1) OR public.account_in_shared_space(id)); + +DROP POLICY IF EXISTS platform_account_delete_policy ON public."PlatformAccount"; +CREATE POLICY platform_account_delete_policy ON public."PlatformAccount" FOR DELETE USING (dg_account = (SELECT auth.uid() LIMIT 1) OR (dg_account IS null AND public.unowned_account_in_shared_space(id))); + +DROP POLICY IF EXISTS platform_account_insert_policy ON public."PlatformAccount"; +CREATE POLICY platform_account_insert_policy ON public."PlatformAccount" FOR INSERT WITH CHECK (dg_account = (SELECT auth.uid() LIMIT 1) OR (dg_account IS null AND public.unowned_account_in_shared_space(id))); + +DROP POLICY IF EXISTS platform_account_update_policy ON public."PlatformAccount"; +CREATE POLICY platform_account_update_policy ON public."PlatformAccount" FOR UPDATE WITH CHECK (dg_account = (SELECT auth.uid() LIMIT 1) OR (dg_account IS null AND public.unowned_account_in_shared_space(id))); -- SpaceAccess: Created through the create_account_in_space and the Space create route, both of which bypass RLS. -- Can be updated by a space peer for now, unless claimed by a user. @@ -257,10 +329,18 @@ CREATE POLICY platform_account_select_policy ON public."PlatformAccount" FOR SEL ALTER TABLE public."SpaceAccess" ENABLE ROW LEVEL SECURITY; DROP POLICY IF EXISTS space_access_policy ON public."SpaceAccess"; -CREATE POLICY space_access_policy ON public."SpaceAccess" FOR ALL USING (public.unowned_account_in_shared_space(account_id)); DROP POLICY IF EXISTS space_access_select_policy ON public."SpaceAccess"; -CREATE POLICY space_access_select_policy ON public."SpaceAccess" FOR ALL USING (public.in_space(space_id)); +CREATE POLICY space_access_select_policy ON public."SpaceAccess" FOR SELECT USING (public.in_space(space_id)); + +DROP POLICY IF EXISTS space_access_delete_policy ON public."SpaceAccess"; +CREATE POLICY space_access_delete_policy ON public."SpaceAccess" FOR DELETE USING (public.unowned_account_in_shared_space(account_id) OR account_id = public.my_account()); + +DROP POLICY IF EXISTS space_access_insert_policy ON public."SpaceAccess"; +CREATE POLICY space_access_insert_policy ON public."SpaceAccess" FOR INSERT WITH CHECK (public.unowned_account_in_shared_space(account_id) OR account_id = public.my_account()); + +DROP POLICY IF EXISTS space_access_update_policy ON public."SpaceAccess"; +CREATE POLICY space_access_update_policy ON public."SpaceAccess" FOR UPDATE WITH CHECK (public.unowned_account_in_shared_space(account_id) OR account_id = public.my_account()); -- AgentIdentifier: Allow space members to do anything, to allow editing authors. -- Eventually: Once the account is claimed by a user, only allow this user to modify it. @@ -268,7 +348,15 @@ CREATE POLICY space_access_select_policy ON public."SpaceAccess" FOR ALL USING ( ALTER TABLE public."AgentIdentifier" ENABLE ROW LEVEL SECURITY; DROP POLICY IF EXISTS agent_identifier_policy ON public."AgentIdentifier"; -CREATE POLICY agent_identifier_policy ON public."AgentIdentifier" FOR ALL USING (public.unowned_account_in_shared_space(account_id)); DROP POLICY IF EXISTS agent_identifier_select_policy ON public."AgentIdentifier"; CREATE POLICY agent_identifier_select_policy ON public."AgentIdentifier" FOR SELECT USING (public.account_in_shared_space(account_id)); + +DROP POLICY IF EXISTS agent_identifier_delete_policy ON public."AgentIdentifier"; +CREATE POLICY agent_identifier_delete_policy ON public."AgentIdentifier" FOR DELETE USING (public.unowned_account_in_shared_space(account_id) OR account_id = public.my_account()); + +DROP POLICY IF EXISTS agent_identifier_insert_policy ON public."AgentIdentifier"; +CREATE POLICY agent_identifier_insert_policy ON public."AgentIdentifier" FOR INSERT WITH CHECK (public.unowned_account_in_shared_space(account_id) OR account_id = public.my_account()); + +DROP POLICY IF EXISTS agent_identifier_update_policy ON public."AgentIdentifier"; +CREATE POLICY agent_identifier_update_policy ON public."AgentIdentifier" FOR UPDATE WITH CHECK (public.unowned_account_in_shared_space(account_id) OR account_id = public.my_account()); diff --git a/packages/database/supabase/schemas/concept.sql b/packages/database/supabase/schemas/concept.sql index 623b76884..21b73cd2a 100644 --- a/packages/database/supabase/schemas/concept.sql +++ b/packages/database/supabase/schemas/concept.sql @@ -115,28 +115,24 @@ REVOKE ALL ON TABLE public."Concept" FROM anon; GRANT ALL ON TABLE public."Concept" TO authenticated; GRANT ALL ON TABLE public."Concept" TO service_role; - -CREATE TYPE public.concept_local_input AS ( - -- concept columns - epistemic_status public."EpistemicStatus", - name character varying, - description text, - author_id bigint, - created timestamp without time zone, - last_modified timestamp without time zone, - space_id bigint, - schema_id bigint, - literal_content jsonb, - is_schema boolean, - represented_by_id bigint, - reference_content jsonb, - -- local values - author_local_id VARCHAR, - represented_by_local_id VARCHAR, - schema_represented_by_local_id VARCHAR, - space_url VARCHAR, - local_reference_content JSONB -); +CREATE OR REPLACE VIEW public.my_concepts AS +SELECT + id, + epistemic_status, + name, + description, + author_id, + created, + last_modified, + space_id, + arity, + schema_id, + literal_content, + reference_content, + refs, + is_schema, + represented_by_id +FROM public."Concept" WHERE space_id = any(public.my_space_ids()); -- following https://docs.postgrest.org/en/v13/references/api/resource_embedding.html#recursive-relationships CREATE OR REPLACE FUNCTION public.schema_of_concept(concept public."Concept") @@ -150,6 +146,17 @@ $$; COMMENT ON FUNCTION public.schema_of_concept(public."Concept") IS 'Computed one-to-one: returns the schema Concept for a given Concept (by schema_id).'; +CREATE OR REPLACE FUNCTION public.schema_of_concept(concept public.my_concepts) +RETURNS SETOF public.my_concepts STRICT STABLE +ROWS 1 +SET search_path = '' +LANGUAGE sql +AS $$ + SELECT * from public.my_concepts WHERE id=concept.schema_id; +$$; +COMMENT ON FUNCTION public.schema_of_concept(public.my_concepts) +IS 'Computed one-to-one: returns the schema Concept for a given Concept (by schema_id).'; + CREATE OR REPLACE FUNCTION public.instances_of_schema(schema public."Concept") RETURNS SETOF public."Concept" STRICT STABLE SET search_path = '' @@ -160,6 +167,15 @@ $$; COMMENT ON FUNCTION public.instances_of_schema(public."Concept") IS 'Computed one-to-many: returns all Concept instances that are based on the given schema Concept.'; +CREATE OR REPLACE FUNCTION public.instances_of_schema(schema public.my_concepts) +RETURNS SETOF public.my_concepts STRICT STABLE +SET search_path = '' +LANGUAGE sql +AS $$ + SELECT * from public.my_concepts WHERE schema_id=schema.id; +$$; +COMMENT ON FUNCTION public.instances_of_schema(public.my_concepts) +IS 'Computed one-to-many: returns all Concept instances that are based on the given schema Concept.'; CREATE OR REPLACE FUNCTION public.concept_in_relations(concept public."Concept") RETURNS SETOF public."Concept" STRICT STABLE @@ -171,6 +187,16 @@ $$; COMMENT ON FUNCTION public.concept_in_relations(public."Concept") IS 'Computed one-to-many: returns all Concept instances that are relations including the current concept.'; +CREATE OR REPLACE FUNCTION public.concept_in_relations(concept public.my_concepts) +RETURNS SETOF public.my_concepts STRICT STABLE +SET search_path = '' +LANGUAGE sql +AS $$ + SELECT * from public.my_concepts WHERE refs @> ARRAY[concept.id]; +$$; +COMMENT ON FUNCTION public.concept_in_relations(public.my_concepts) +IS 'Computed one-to-many: returns all Concept instances that are relations including the current concept.'; + CREATE OR REPLACE FUNCTION public.concepts_of_relation(relation public."Concept") RETURNS SETOF public."Concept" STRICT STABLE SET search_path = '' @@ -181,6 +207,60 @@ $$; COMMENT ON FUNCTION public.concepts_of_relation(public."Concept") IS 'Computed one-to-many: returns all Concept instances are referred to in the current concept.'; +CREATE OR REPLACE FUNCTION public.concepts_of_relation(relation public.my_concepts) +RETURNS SETOF public.my_concepts STRICT STABLE +SET search_path = '' +LANGUAGE sql +AS $$ + SELECT * from public.my_concepts WHERE id = any(relation.refs); +$$; +COMMENT ON FUNCTION public.concepts_of_relation(public.my_concepts) +IS 'Computed one-to-many: returns all Concept instances are referred to in the current concept.'; + +CREATE OR REPLACE FUNCTION public.content_of_concept(concept public.my_concepts) +RETURNS SETOF public.my_contents STRICT STABLE +ROWS 1 +SET search_path = '' +LANGUAGE sql +AS $$ + SELECT * from public.my_contents WHERE id=concept.represented_by_id; +$$; +COMMENT ON FUNCTION public.content_of_concept(public.my_concepts) +IS 'Computed one-to-one: returns the representing Content for a given Concept.'; + +CREATE OR REPLACE FUNCTION public.author_of_concept(concept public.my_concepts) +RETURNS SETOF public.my_accounts STRICT STABLE +ROWS 1 +SET search_path = '' +LANGUAGE sql +AS $$ + SELECT * from public.my_accounts WHERE id=concept.author_id; +$$; +COMMENT ON FUNCTION public.author_of_concept(public.my_concepts) +IS 'Computed one-to-one: returns the PlatformAccount which authored a given Concept.'; + + +CREATE TYPE public.concept_local_input AS ( + -- concept columns + epistemic_status public."EpistemicStatus", + name character varying, + description text, + author_id bigint, + created timestamp without time zone, + last_modified timestamp without time zone, + space_id bigint, + schema_id bigint, + literal_content jsonb, + is_schema boolean, + represented_by_id bigint, + reference_content jsonb, + -- local values + author_local_id VARCHAR, + represented_by_local_id VARCHAR, + schema_represented_by_local_id VARCHAR, + space_url VARCHAR, + local_reference_content JSONB +); -- private function. Transform concept with local (platform) references to concept with db references CREATE OR REPLACE FUNCTION public._local_concept_to_db_concept(data public.concept_local_input) diff --git a/packages/database/supabase/schemas/content.sql b/packages/database/supabase/schemas/content.sql index 8bc6382c4..b8a4c8694 100644 --- a/packages/database/supabase/schemas/content.sql +++ b/packages/database/supabase/schemas/content.sql @@ -69,6 +69,19 @@ COMMENT ON COLUMN public."Document".author_id IS 'The author of content'; COMMENT ON COLUMN public."Document".contents IS 'A large object OID for the downloaded raw content'; +-- explicit fields require more maintenance, but respects declared table order. +CREATE OR REPLACE VIEW public.my_documents AS +SELECT + id, + space_id, + source_local_id, + url, + "created", + metadata, + last_modified, + author_id, + contents +FROM public."Document" WHERE space_id = any(public.my_space_ids()); CREATE TABLE IF NOT EXISTS public."Content" ( id bigint DEFAULT nextval( @@ -161,6 +174,45 @@ REVOKE ALL ON TABLE public."Content" FROM anon; GRANT ALL ON TABLE public."Content" TO authenticated; GRANT ALL ON TABLE public."Content" TO service_role; +CREATE OR REPLACE VIEW public.my_contents AS +SELECT + id, + document_id, + source_local_id, + variant, + author_id, + creator_id, + created, + text, + metadata, + scale, + space_id, + last_modified, + part_of_id +FROM public."Content" WHERE space_id = any(public.my_space_ids()); + +CREATE OR REPLACE FUNCTION public.document_of_content(content public.my_contents) +RETURNS SETOF public.my_documents STRICT STABLE +ROWS 1 +SET search_path = '' +LANGUAGE sql +AS $$ + SELECT * from public.my_documents WHERE id=content.document_id; +$$; +COMMENT ON FUNCTION public.document_of_content(public.my_contents) +IS 'Computed one-to-one: returns the containing Document for a given Content.'; + +CREATE OR REPLACE FUNCTION public.author_of_content(content public.my_contents) +RETURNS SETOF public.my_accounts STRICT STABLE +ROWS 1 +SET search_path = '' +LANGUAGE sql +AS $$ + SELECT * from public.my_accounts WHERE id=content.author_id; +$$; +COMMENT ON FUNCTION public.author_of_content(public.my_contents) +IS 'Computed one-to-one: returns the PlatformAccount which authored a given Content.'; + CREATE TYPE public.document_local_input AS ( -- document columns space_id bigint, diff --git a/packages/database/supabase/schemas/embedding.sql b/packages/database/supabase/schemas/embedding.sql index 6d72b8c86..5e02fddc1 100644 --- a/packages/database/supabase/schemas/embedding.sql +++ b/packages/database/supabase/schemas/embedding.sql @@ -28,6 +28,27 @@ REVOKE ALL ON TABLE public."ContentEmbedding_openai_text_embedding_3_small_1536" GRANT ALL ON TABLE public."ContentEmbedding_openai_text_embedding_3_small_1536" TO "authenticated" ; GRANT ALL ON TABLE public."ContentEmbedding_openai_text_embedding_3_small_1536" TO "service_role" ; +CREATE OR REPLACE VIEW public.my_contents_with_embedding_openai_text_embedding_3_small_1536 AS +SELECT + ct.id, + ct.document_id, + ct.source_local_id, + ct.variant, + ct.author_id, + ct.creator_id, + ct.created, + ct.text, + ct.metadata, + ct.scale, + ct.space_id, + ct.last_modified, + ct.part_of_id, + emb.model, + emb.vector +FROM public."Content" AS ct +JOIN public."ContentEmbedding_openai_text_embedding_3_small_1536" AS emb ON (ct.id=emb.target_id) +WHERE ct.space_id = any(public.my_space_ids()) AND NOT emb.obsolete; + set search_path to public, extensions ; CREATE OR REPLACE FUNCTION public.match_content_embeddings ( @@ -47,14 +68,12 @@ SELECT c.id AS content_id, c.source_local_id AS roam_uid, c.text AS text_content, - 1 - (ce.vector <=> query_embedding) AS similarity -FROM public."ContentEmbedding_openai_text_embedding_3_small_1536" AS ce -JOIN public."Content" AS c ON ce.target_id = c.id -WHERE 1 - (ce.vector <=> query_embedding) > match_threshold - AND ce.obsolete = FALSE + 1 - (c.vector <=> query_embedding) AS similarity +FROM public.my_contents_with_embedding_openai_text_embedding_3_small_1536 AS c +WHERE 1 - (c.vector <=> query_embedding) > match_threshold AND (current_document_id IS NULL OR c.document_id = current_document_id) ORDER BY - ce.vector <=> query_embedding ASC + c.vector <=> query_embedding ASC LIMIT match_count; $$ ; @@ -80,12 +99,10 @@ WITH subset_content_with_embeddings AS ( c.id AS content_id, c.source_local_id AS roam_uid, c.text AS text_content, - ce.vector AS embedding_vector - FROM public."Content" AS c - JOIN public."ContentEmbedding_openai_text_embedding_3_small_1536" AS ce ON c.id = ce.target_id + c.vector AS embedding_vector + FROM public.my_contents_with_embedding_openai_text_embedding_3_small_1536 AS c WHERE c.source_local_id = ANY(p_subset_roam_uids) -- Filter Content by the provided Roam UIDs - AND ce.obsolete = FALSE ) SELECT ss_ce.content_id,