From 502da3b9c5d70277bb12e0419e394516b114ee4e Mon Sep 17 00:00:00 2001 From: Alexis Rico Date: Wed, 16 Apr 2025 10:36:30 +0200 Subject: [PATCH 1/6] Add basic visibility --- apps/dbagent/migrations/0008_public-chats.sql | 23 + .../migrations/meta/0008_snapshot.json | 2028 +++++++++++++++++ apps/dbagent/migrations/meta/_journal.json | 7 + .../src/app/(main)/share/[chat]/page.tsx | 56 + apps/dbagent/src/app/api/chat/route.ts | 15 +- apps/dbagent/src/components/chat/chat.tsx | 43 +- apps/dbagent/src/components/chat/header.tsx | 56 +- apps/dbagent/src/components/ui/side-nav.tsx | 24 + apps/dbagent/src/lib/db/schema.ts | 13 +- apps/dbagent/src/middleware.ts | 2 +- 10 files changed, 2256 insertions(+), 11 deletions(-) create mode 100644 apps/dbagent/migrations/0008_public-chats.sql create mode 100644 apps/dbagent/migrations/meta/0008_snapshot.json create mode 100644 apps/dbagent/src/app/(main)/share/[chat]/page.tsx diff --git a/apps/dbagent/migrations/0008_public-chats.sql b/apps/dbagent/migrations/0008_public-chats.sql new file mode 100644 index 00000000..e1afb445 --- /dev/null +++ b/apps/dbagent/migrations/0008_public-chats.sql @@ -0,0 +1,23 @@ +CREATE TYPE "public"."visibility" AS ENUM('public', 'private'); + +--> statement-breakpoint +ALTER TABLE + "chats" +ADD + COLUMN "visibility" "visibility" DEFAULT 'private' NOT NULL; + +--> statement-breakpoint +CREATE POLICY "chats_view_policy" ON "chats" AS PERMISSIVE FOR +SELECT + TO public USING ( + EXISTS ( + SELECT + 1 + FROM + project_members + WHERE + project_id = chats.project_id + AND user_id = current_setting('app.current_user', true) :: TEXT + ) + OR visibility = 'public' + ); \ No newline at end of file diff --git a/apps/dbagent/migrations/meta/0008_snapshot.json b/apps/dbagent/migrations/meta/0008_snapshot.json new file mode 100644 index 00000000..25f09c5e --- /dev/null +++ b/apps/dbagent/migrations/meta/0008_snapshot.json @@ -0,0 +1,2028 @@ +{ + "id": "83b9c1f3-9c7d-4def-aa8b-399ef563eaed", + "prevId": "35bc8b93-8a60-408c-8428-3bff3063184b", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.artifact_documents": { + "name": "artifact_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'text'" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_artifact_documents_project_id": { + "name": "idx_artifact_documents_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_artifact_documents_user_id": { + "name": "idx_artifact_documents_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_artifact_documents_project": { + "name": "fk_artifact_documents_project", + "tableFrom": "artifact_documents", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "artifact_documents_id_pk": { + "name": "artifact_documents_id_pk", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "policies": { + "artifact_documents_policy": { + "name": "artifact_documents_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = artifact_documents.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.artifact_suggestions": { + "name": "artifact_suggestions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_created_at": { + "name": "document_created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "original_text": { + "name": "original_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "suggested_text": { + "name": "suggested_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_resolved": { + "name": "is_resolved", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_artifact_suggestions_document_id": { + "name": "idx_artifact_suggestions_document_id", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_artifact_suggestions_project_id": { + "name": "idx_artifact_suggestions_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_artifact_suggestions_user_id": { + "name": "idx_artifact_suggestions_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_artifact_suggestions_project": { + "name": "fk_artifact_suggestions_project", + "tableFrom": "artifact_suggestions", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_artifact_suggestions_document": { + "name": "fk_artifact_suggestions_document", + "tableFrom": "artifact_suggestions", + "tableTo": "artifact_documents", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "artifact_suggestions_id_pk": { + "name": "artifact_suggestions_id_pk", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "policies": { + "artifact_suggestions_policy": { + "name": "artifact_suggestions_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = artifact_suggestions.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.aws_cluster_connections": { + "name": "aws_cluster_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cluster_id": { + "name": "cluster_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_aws_cluster_connections_cluster_id": { + "name": "idx_aws_cluster_connections_cluster_id", + "columns": [ + { + "expression": "cluster_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_aws_cluster_connections_connection_id": { + "name": "idx_aws_cluster_connections_connection_id", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_aws_cluster_connections_project_id": { + "name": "idx_aws_cluster_connections_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_aws_cluster_connections_project": { + "name": "fk_aws_cluster_connections_project", + "tableFrom": "aws_cluster_connections", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_aws_cluster_connections_cluster": { + "name": "fk_aws_cluster_connections_cluster", + "tableFrom": "aws_cluster_connections", + "tableTo": "aws_clusters", + "columnsFrom": ["cluster_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_aws_cluster_connections_connection": { + "name": "fk_aws_cluster_connections_connection", + "tableFrom": "aws_cluster_connections", + "tableTo": "connections", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "aws_cluster_connections_policy": { + "name": "aws_cluster_connections_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = aws_cluster_connections.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.aws_clusters": { + "name": "aws_clusters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cluster_identifier": { + "name": "cluster_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'us-east-1'" + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_aws_clusters_project_id": { + "name": "idx_aws_clusters_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_aws_clusters_project": { + "name": "fk_aws_clusters_project", + "tableFrom": "aws_clusters", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_aws_clusters_integration_identifier": { + "name": "uq_aws_clusters_integration_identifier", + "nullsNotDistinct": false, + "columns": ["cluster_identifier"] + } + }, + "policies": { + "aws_clusters_policy": { + "name": "aws_clusters_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = aws_clusters.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chats": { + "name": "chats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visibility": { + "name": "visibility", + "type": "visibility", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'private'" + } + }, + "indexes": { + "idx_chats_project_id": { + "name": "idx_chats_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_chats_user_id": { + "name": "idx_chats_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_chats_project": { + "name": "fk_chats_project", + "tableFrom": "chats", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "chats_policy": { + "name": "chats_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = chats.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + }, + "chats_view_policy": { + "name": "chats_view_policy", + "as": "PERMISSIVE", + "for": "SELECT", + "to": ["public"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = chats.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n ) OR visibility = 'public'" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_info": { + "name": "connection_info", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_connection_info_connection_id": { + "name": "idx_connection_info_connection_id", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_connection_info_project_id": { + "name": "idx_connection_info_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_connection_info_project": { + "name": "fk_connection_info_project", + "tableFrom": "connection_info", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_connection_info_connection": { + "name": "fk_connection_info_connection", + "tableFrom": "connection_info", + "tableTo": "connections", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_connection_info": { + "name": "uq_connection_info", + "nullsNotDistinct": false, + "columns": ["connection_id", "type"] + } + }, + "policies": { + "connection_info_policy": { + "name": "connection_info_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = connection_info.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connections": { + "name": "connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "connection_string": { + "name": "connection_string", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_connections_project_id": { + "name": "idx_connections_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_connections_project": { + "name": "fk_connections_project", + "tableFrom": "connections", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_connections_name": { + "name": "uq_connections_name", + "nullsNotDistinct": false, + "columns": ["project_id", "name"] + }, + "uq_connections_connection_string": { + "name": "uq_connections_connection_string", + "nullsNotDistinct": false, + "columns": ["project_id", "connection_string"] + } + }, + "policies": { + "connections_policy": { + "name": "connections_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = connections.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gcp_instance_connections": { + "name": "gcp_instance_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_gcp_instance_connections_instance_id": { + "name": "idx_gcp_instance_connections_instance_id", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_gcp_instance_connections_connection_id": { + "name": "idx_gcp_instance_connections_connection_id", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_gcp_instance_connections_project_id": { + "name": "idx_gcp_instance_connections_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_gcp_instance_connections_project": { + "name": "fk_gcp_instance_connections_project", + "tableFrom": "gcp_instance_connections", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_gcp_instance_connections_instance": { + "name": "fk_gcp_instance_connections_instance", + "tableFrom": "gcp_instance_connections", + "tableTo": "gcp_instances", + "columnsFrom": ["instance_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_gcp_instance_connections_connection": { + "name": "fk_gcp_instance_connections_connection", + "tableFrom": "gcp_instance_connections", + "tableTo": "connections", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "gcp_instance_connections_policy": { + "name": "gcp_instance_connections_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = gcp_instance_connections.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gcp_instances": { + "name": "gcp_instances", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "instance_name": { + "name": "instance_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "gcp_project_id": { + "name": "gcp_project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_gcp_instances_project_id": { + "name": "idx_gcp_instances_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_gcp_instances_project": { + "name": "fk_gcp_instances_project", + "tableFrom": "gcp_instances", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_gcp_instances_instance_name": { + "name": "uq_gcp_instances_instance_name", + "nullsNotDistinct": false, + "columns": ["project_id", "gcp_project_id", "instance_name"] + } + }, + "policies": { + "gcp_instances_policy": { + "name": "gcp_instances_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = gcp_instances.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integrations": { + "name": "integrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_integrations_project_id": { + "name": "idx_integrations_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_integrations_project": { + "name": "fk_integrations_project", + "tableFrom": "integrations", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_integrations_name": { + "name": "uq_integrations_name", + "nullsNotDistinct": false, + "columns": ["project_id", "name"] + } + }, + "policies": { + "integrations_policy": { + "name": "integrations_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = integrations.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_votes": { + "name": "message_votes", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_upvoted": { + "name": "is_upvoted", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_message_votes_chat_id": { + "name": "idx_message_votes_chat_id", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_votes_message_id": { + "name": "idx_message_votes_message_id", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_votes_project_id": { + "name": "idx_message_votes_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_votes_user_id": { + "name": "idx_message_votes_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_votes_chat": { + "name": "fk_votes_chat", + "tableFrom": "message_votes", + "tableTo": "chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_message_votes_message": { + "name": "fk_message_votes_message", + "tableFrom": "message_votes", + "tableTo": "messages", + "columnsFrom": ["message_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_message_votes_project": { + "name": "fk_message_votes_project", + "tableFrom": "message_votes", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "message_votes_chat_id_message_id_user_id_pk": { + "name": "message_votes_chat_id_message_id_user_id_pk", + "columns": ["chat_id", "message_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": { + "message_votes_policy": { + "name": "message_votes_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = message_votes.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages": { + "name": "messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "parts": { + "name": "parts", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_messages_project_id": { + "name": "idx_messages_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_messages_chat_id": { + "name": "idx_messages_chat_id", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_messages_project": { + "name": "fk_messages_project", + "tableFrom": "messages", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_messages_chat": { + "name": "fk_messages_chat", + "tableFrom": "messages", + "tableTo": "chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "messages_policy": { + "name": "messages_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = messages.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.playbooks": { + "name": "playbooks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_playbooks_project_id": { + "name": "idx_playbooks_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_playbooks_project": { + "name": "fk_playbooks_project", + "tableFrom": "playbooks", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_playbooks_name": { + "name": "uq_playbooks_name", + "nullsNotDistinct": false, + "columns": ["project_id", "name"] + } + }, + "policies": { + "playbooks_policy": { + "name": "playbooks_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = playbooks.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_members": { + "name": "project_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "added_at": { + "name": "added_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_project_members_project_id": { + "name": "idx_project_members_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_project_members_project": { + "name": "fk_project_members_project", + "tableFrom": "project_members", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_project_members_user_project": { + "name": "uq_project_members_user_project", + "nullsNotDistinct": false, + "columns": ["project_id", "user_id"] + } + }, + "policies": { + "project_members_policy": { + "name": "project_members_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "true" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cloud_provider": { + "name": "cloud_provider", + "type": "cloud_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "projects_view_policy": { + "name": "projects_view_policy", + "as": "PERMISSIVE", + "for": "SELECT", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = projects.id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + }, + "projects_create_policy": { + "name": "projects_create_policy", + "as": "PERMISSIVE", + "for": "INSERT", + "to": ["authenticated_user"], + "withCheck": "true" + }, + "projects_update_policy": { + "name": "projects_update_policy", + "as": "PERMISSIVE", + "for": "UPDATE", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = projects.id AND user_id = current_setting('app.current_user', true)::TEXT AND role = 'owner'\n )" + }, + "projects_delete_policy": { + "name": "projects_delete_policy", + "as": "PERMISSIVE", + "for": "DELETE", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = projects.id AND user_id = current_setting('app.current_user', true)::TEXT AND role = 'owner'\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.schedule_runs": { + "name": "schedule_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "result": { + "name": "result", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notification_level": { + "name": "notification_level", + "type": "notification_level", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "messages": { + "name": "messages", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_schedule_runs_created_at": { + "name": "idx_schedule_runs_created_at", + "columns": [ + { + "expression": "schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_schedule_runs_schedule_id": { + "name": "idx_schedule_runs_schedule_id", + "columns": [ + { + "expression": "schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_schedule_runs_project_id": { + "name": "idx_schedule_runs_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_schedule_runs_notification_level": { + "name": "idx_schedule_runs_notification_level", + "columns": [ + { + "expression": "notification_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_schedule_runs_project": { + "name": "fk_schedule_runs_project", + "tableFrom": "schedule_runs", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_schedule_runs_schedule": { + "name": "fk_schedule_runs_schedule", + "tableFrom": "schedule_runs", + "tableTo": "schedules", + "columnsFrom": ["schedule_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "schedule_runs_policy": { + "name": "schedule_runs_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = schedule_runs.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.schedules": { + "name": "schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "playbook": { + "name": "playbook", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "schedule_type": { + "name": "schedule_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "additional_instructions": { + "name": "additional_instructions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "min_interval": { + "name": "min_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_interval": { + "name": "max_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_run": { + "name": "last_run", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_run": { + "name": "next_run", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "schedule_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'disabled'" + }, + "failures": { + "name": "failures", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "keep_history": { + "name": "keep_history", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 300 + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "max_steps": { + "name": "max_steps", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "notify_level": { + "name": "notify_level", + "type": "notification_level", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'alert'" + }, + "extra_notification_text": { + "name": "extra_notification_text", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_schedules_project_id": { + "name": "idx_schedules_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_schedules_connection_id": { + "name": "idx_schedules_connection_id", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_schedules_status": { + "name": "idx_schedules_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_schedules_next_run": { + "name": "idx_schedules_next_run", + "columns": [ + { + "expression": "next_run", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_schedules_enabled": { + "name": "idx_schedules_enabled", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_schedules_project": { + "name": "fk_schedules_project", + "tableFrom": "schedules", + "tableTo": "projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_schedules_connection": { + "name": "fk_schedules_connection", + "tableFrom": "schedules", + "tableTo": "connections", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "schedules_policy": { + "name": "schedules_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["authenticated_user"], + "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = schedules.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.cloud_provider": { + "name": "cloud_provider", + "schema": "public", + "values": ["aws", "gcp", "other"] + }, + "public.member_role": { + "name": "member_role", + "schema": "public", + "values": ["owner", "member"] + }, + "public.notification_level": { + "name": "notification_level", + "schema": "public", + "values": ["info", "warning", "alert"] + }, + "public.schedule_status": { + "name": "schedule_status", + "schema": "public", + "values": ["disabled", "scheduled", "running"] + }, + "public.visibility": { + "name": "visibility", + "schema": "public", + "values": ["public", "private"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": { + "authenticated_user": { + "name": "authenticated_user", + "createDb": false, + "createRole": false, + "inherit": true + } + }, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/apps/dbagent/migrations/meta/_journal.json b/apps/dbagent/migrations/meta/_journal.json index 9c0ed88e..da79efe6 100644 --- a/apps/dbagent/migrations/meta/_journal.json +++ b/apps/dbagent/migrations/meta/_journal.json @@ -57,6 +57,13 @@ "when": 1744368189657, "tag": "0007_chat_model", "breakpoints": true + }, + { + "idx": 8, + "version": "7", + "when": 1744788121795, + "tag": "0008_public-chats", + "breakpoints": true } ] } diff --git a/apps/dbagent/src/app/(main)/share/[chat]/page.tsx b/apps/dbagent/src/app/(main)/share/[chat]/page.tsx new file mode 100644 index 00000000..2c8ab9a3 --- /dev/null +++ b/apps/dbagent/src/app/(main)/share/[chat]/page.tsx @@ -0,0 +1,56 @@ +import { UIMessage } from 'ai'; +import { notFound } from 'next/navigation'; +import { ReadOnlyChat } from '~/components/chat/chat'; +import { getChatById, getMessagesByChatId } from '~/lib/db/chats'; +import { getUserSessionDBAccess } from '~/lib/db/db'; +import { Message } from '~/lib/db/schema'; + +type PageParams = { + chat: string; +}; + +export default async function SharedChatPage({ params }: { params: Promise }) { + const { chat: chatId } = await params; + + // Get DB access for reading the chat + const dbAccess = await getUserSessionDBAccess(); + + // Fetch the chat and check if it's public + const chatData = await getChatById(dbAccess, { id: chatId }); + + // If the chat doesn't exist or is not public, return 404 + if (!chatData || chatData.visibility !== 'public') { + notFound(); + } + + // Fetch the messages for the chat + const messagesFromDb = await getMessagesByChatId(dbAccess, { id: chatId }); + + function convertToUIMessages(messages: Array): Array { + return messages.map((message) => ({ + id: message.id, + parts: message.parts ?? [], + role: message.role, + content: + message.parts + ?.filter((part) => part.type === 'text') + .map((part) => part.text) + .join('\n') + .trim() ?? '', + createdAt: message.createdAt + })); + } + + return ( +
+
+

{chatData.title}

+

+ Shared chat (read-only) • Created on {new Date(chatData.createdAt).toLocaleDateString()} +

+
+ + +
+ ); +} diff --git a/apps/dbagent/src/app/api/chat/route.ts b/apps/dbagent/src/app/api/chat/route.ts index a8a697b5..eba7d946 100644 --- a/apps/dbagent/src/app/api/chat/route.ts +++ b/apps/dbagent/src/app/api/chat/route.ts @@ -9,6 +9,7 @@ import { deleteChatById, getChatById, getChats, saveMessages, updateChat } from import { getConnection } from '~/lib/db/connections'; import { getUserSessionDBAccess } from '~/lib/db/db'; import { getProjectById } from '~/lib/db/projects'; +import { ChatInsert } from '~/lib/db/schema'; import { getTargetDbPool } from '~/lib/targetdb/db'; import { requireUserSession } from '~/utils/route'; @@ -168,9 +169,11 @@ export async function PATCH(request: Request) { return new Response('Not Found', { status: 404 }); } - const { title } = await request.json(); - if (!title) { - return new Response('Title is required', { status: 400 }); + const data = await request.json(); + const { title, visibility } = data; + + if (!title && !visibility) { + return new Response('Either title or visibility is required', { status: 400 }); } const dbAccess = await getUserSessionDBAccess(); @@ -179,7 +182,11 @@ export async function PATCH(request: Request) { const chat = await getChatById(dbAccess, { id }); if (!chat) notFound(); - await updateChat(dbAccess, id, { title }); + const updateData: Partial = {}; + if (title) updateData.title = title; + if (visibility) updateData.visibility = visibility; + + await updateChat(dbAccess, id, updateData); return new Response('Chat updated', { status: 200 }); } catch (error) { diff --git a/apps/dbagent/src/components/chat/chat.tsx b/apps/dbagent/src/components/chat/chat.tsx index 9e95b56e..12c9ffad 100644 --- a/apps/dbagent/src/components/chat/chat.tsx +++ b/apps/dbagent/src/components/chat/chat.tsx @@ -3,7 +3,7 @@ import { useChat } from '@ai-sdk/react'; import { toast } from '@internal/components'; import { useQuery, useQueryClient } from '@tanstack/react-query'; -import type { UIMessage } from 'ai'; +import type { ChatRequestOptions, Message, UIMessage } from 'ai'; import { memo, useEffect, useRef, useState } from 'react'; import { Connection, MessageVote } from '~/lib/db/schema'; import { Artifact } from './artifacts/artifact'; @@ -68,6 +68,21 @@ function PureChat({ const layoutTopPadding = 'calc(var(--spacing)* 24)'; const heightScreen = `calc(100vh - ${layoutTopPadding})`; + // State for tracking chat visibility + const [visibility, setVisibility] = useState<'public' | 'private'>('private'); + + // Fetch the chat to get its visibility + useQuery({ + queryKey: ['chat', id], + queryFn: async () => { + const response = await fetcher(`/api/chat?id=${id}`); + if (response?.visibility) { + setVisibility(response.visibility); + } + return response; + } + }); + // Using useRef to avoid re-initializing the chat on every render // This is a workaround to avoid re-initializing the chat when the component is re-mounted // and the messages are already loaded @@ -88,11 +103,14 @@ function PureChat({
@@ -153,3 +171,26 @@ export const Chat = memo(PureChat, (prevProps, nextProps) => { prevProps.initialInput === nextProps.initialInput ); }); + +export function ReadOnlyChat({ messages }: { messages: Array }) { + return ( +
+
+ Message[])): void { + throw new Error('Function not implemented.'); + }} + reload={function (chatRequestOptions?: ChatRequestOptions): Promise { + throw new Error('Function not implemented.'); + }} + isArtifactVisible={false} + /> +
+
+ ); +} diff --git a/apps/dbagent/src/components/chat/header.tsx b/apps/dbagent/src/components/chat/header.tsx index 6252da00..01b0a52f 100644 --- a/apps/dbagent/src/components/chat/header.tsx +++ b/apps/dbagent/src/components/chat/header.tsx @@ -1,30 +1,64 @@ 'use client'; -import { Button, cn } from '@internal/components'; -import { PlusIcon } from 'lucide-react'; +import { Button, cn, Switch, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@internal/components'; +import { Globe2Icon, PlusIcon } from 'lucide-react'; import { useParams, useRouter } from 'next/navigation'; -import { memo } from 'react'; -import { Connection } from '~/lib/db/schema'; +import { memo, useState } from 'react'; +import { Connection, Visibility } from '~/lib/db/schema'; import { ConnectionSelector } from './connection-selector'; import { ModelSelector } from './model-selector'; function PureChatHeader({ + chatId, connections, model, setModel, connectionId, setConnectionId, + visibility = 'private', + onVisibilityChange, className }: { + chatId: string; model: string; setModel: (model: string) => void; connections: Connection[]; connectionId: string; setConnectionId: (connectionId: string) => void; + visibility?: Visibility; + onVisibilityChange?: (visibility: Visibility) => void; className?: string; }) { const router = useRouter(); const { project } = useParams<{ project: string }>(); + const [isPublic, setIsPublic] = useState(visibility === 'public'); + + const handleVisibilityToggle = async () => { + const newVisibility = isPublic ? 'private' : 'public'; + setIsPublic(!isPublic); + + try { + const response = await fetch(`/api/chat?id=${chatId}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ visibility: newVisibility }) + }); + + if (!response.ok) { + throw new Error('Failed to update chat visibility'); + } + + if (onVisibilityChange) { + onVisibilityChange(newVisibility); + } + } catch (error) { + console.error('Error updating chat visibility:', error); + // Revert the toggle if update fails + setIsPublic(visibility === 'public'); + } + }; return (
@@ -42,6 +76,20 @@ function PureChatHeader({ + + + + +
+ + +
+
+ + {isPublic ? 'Chat is public - anyone with the link can view' : 'Chat is private'} + +
+
); } diff --git a/apps/dbagent/src/components/ui/side-nav.tsx b/apps/dbagent/src/components/ui/side-nav.tsx index 31e38df8..00508e7d 100644 --- a/apps/dbagent/src/components/ui/side-nav.tsx +++ b/apps/dbagent/src/components/ui/side-nav.tsx @@ -28,6 +28,7 @@ import { SidebarMenuSubButton, SidebarMenuSubItem, SidebarRail, + toast, useSidebar } from '@internal/components'; import { useQuery, useQueryClient } from '@tanstack/react-query'; @@ -41,7 +42,10 @@ import { MoreVertical, NotebookPen, PanelLeft, + Pencil as PencilIcon, Server, + Share2 as ShareIcon, + Trash2 as TrashIcon, ZapIcon } from 'lucide-react'; import Link from 'next/link'; @@ -280,8 +284,27 @@ export function SideNav({ className, project, onboardingComplete }: SideNavProps setIsRenameDialogOpen(true); }} > + Rename + { + // Toggle share dialog or directly copy link if chat is public + if (chat.visibility === 'public') { + const shareUrl = `${window.location.origin}/share/${chat.id}`; + navigator.clipboard.writeText(shareUrl); + toast.success('Share link copied to clipboard!'); + } else { + // Show a toast explaining that the chat needs to be made public + toast.info( + 'To share this chat, open it and toggle the visibility to public in the header.' + ); + } + }} + > + + Share + { setSelectedChat(chat); @@ -289,6 +312,7 @@ export function SideNav({ className, project, onboardingComplete }: SideNavProps }} className="text-destructive focus:text-destructive" > + Delete diff --git a/apps/dbagent/src/lib/db/schema.ts b/apps/dbagent/src/lib/db/schema.ts index 8fc9c179..26068600 100644 --- a/apps/dbagent/src/lib/db/schema.ts +++ b/apps/dbagent/src/lib/db/schema.ts @@ -37,6 +37,9 @@ export type MemberRole = InferEnumType; export const cloudProvider = pgEnum('cloud_provider', ['aws', 'gcp', 'other']); export type CloudProvider = InferEnumType; +export const visibility = pgEnum('visibility', ['public', 'private']); +export type Visibility = InferEnumType; + export const awsClusters = pgTable( 'aws_clusters', { @@ -456,7 +459,8 @@ export const chats = pgTable( createdAt: timestamp('created_at', { mode: 'date' }).defaultNow().notNull(), title: text('title').notNull(), model: text('model').notNull(), - userId: text('user_id').notNull() + userId: text('user_id').notNull(), + visibility: visibility('visibility').default('private').notNull() }, (table) => [ foreignKey({ @@ -473,6 +477,13 @@ export const chats = pgTable( SELECT 1 FROM project_members WHERE project_id = chats.project_id AND user_id = current_setting('app.current_user', true)::TEXT )` + }), + pgPolicy('chats_view_policy', { + for: 'select', + using: sql`EXISTS ( + SELECT 1 FROM project_members + WHERE project_id = chats.project_id AND user_id = current_setting('app.current_user', true)::TEXT + ) OR visibility = 'public'` }) ] ); diff --git a/apps/dbagent/src/middleware.ts b/apps/dbagent/src/middleware.ts index c03352ae..00de926d 100644 --- a/apps/dbagent/src/middleware.ts +++ b/apps/dbagent/src/middleware.ts @@ -2,5 +2,5 @@ export { auth as middleware } from '~/auth'; export const config = { // Don't run middleware on paths that don't require authentication - matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'] + matcher: ['/((?!api|share|_next/static|_next/image|favicon.ico).*)'] }; From 493a9857ed02c817d7f2ea93c77489a567c838fa Mon Sep 17 00:00:00 2001 From: Alexis Rico Date: Wed, 16 Apr 2025 11:07:45 +0200 Subject: [PATCH 2/6] Misc updates Signed-off-by: Alexis Rico --- apps/dbagent/migrations/0008_public-chats.sql | 28 +++++++++---------- .../migrations/meta/0008_snapshot.json | 16 +++++++---- apps/dbagent/migrations/meta/_journal.json | 2 +- .../src/app/(main)/share/[chat]/page.tsx | 4 +-- apps/dbagent/src/lib/db/db.ts | 25 +++++++++++++++-- apps/dbagent/src/lib/db/schema.ts | 9 +++--- apps/dbagent/src/lib/monitoring/scheduler.ts | 2 +- 7 files changed, 55 insertions(+), 31 deletions(-) diff --git a/apps/dbagent/migrations/0008_public-chats.sql b/apps/dbagent/migrations/0008_public-chats.sql index e1afb445..653d3ff1 100644 --- a/apps/dbagent/migrations/0008_public-chats.sql +++ b/apps/dbagent/migrations/0008_public-chats.sql @@ -1,23 +1,21 @@ CREATE TYPE "public"."visibility" AS ENUM('public', 'private'); +--> statement-breakpoint +CREATE ROLE "anonymous_user"; + --> statement-breakpoint ALTER TABLE - "chats" + "chats" ADD - COLUMN "visibility" "visibility" DEFAULT 'private' NOT NULL; + COLUMN "visibility" "visibility" DEFAULT 'private' NOT NULL; --> statement-breakpoint -CREATE POLICY "chats_view_policy" ON "chats" AS PERMISSIVE FOR +CREATE POLICY "chats_anonymous_policy" ON "chats" AS PERMISSIVE FOR SELECT - TO public USING ( - EXISTS ( - SELECT - 1 - FROM - project_members - WHERE - project_id = chats.project_id - AND user_id = current_setting('app.current_user', true) :: TEXT - ) - OR visibility = 'public' - ); \ No newline at end of file + TO "anonymous_user" USING (visibility = 'public'); + +--> statement-breakpoint +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "anonymous_user"; + +--> statement-breakpoint +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON TABLES TO "anonymous_user"; \ No newline at end of file diff --git a/apps/dbagent/migrations/meta/0008_snapshot.json b/apps/dbagent/migrations/meta/0008_snapshot.json index 25f09c5e..5492f1cf 100644 --- a/apps/dbagent/migrations/meta/0008_snapshot.json +++ b/apps/dbagent/migrations/meta/0008_snapshot.json @@ -1,5 +1,5 @@ { - "id": "83b9c1f3-9c7d-4def-aa8b-399ef563eaed", + "id": "918a6ac9-45a2-4201-932e-ce23f1591ec7", "prevId": "35bc8b93-8a60-408c-8428-3bff3063184b", "version": "7", "dialect": "postgresql", @@ -579,12 +579,12 @@ "to": ["authenticated_user"], "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = chats.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n )" }, - "chats_view_policy": { - "name": "chats_view_policy", + "chats_anonymous_policy": { + "name": "chats_anonymous_policy", "as": "PERMISSIVE", "for": "SELECT", - "to": ["public"], - "using": "EXISTS (\n SELECT 1 FROM project_members\n WHERE project_id = chats.project_id AND user_id = current_setting('app.current_user', true)::TEXT\n ) OR visibility = 'public'" + "to": ["anonymous_user"], + "using": "visibility = 'public'" } }, "checkConstraints": {}, @@ -2011,6 +2011,12 @@ "schemas": {}, "sequences": {}, "roles": { + "anonymous_user": { + "name": "anonymous_user", + "createDb": false, + "createRole": false, + "inherit": true + }, "authenticated_user": { "name": "authenticated_user", "createDb": false, diff --git a/apps/dbagent/migrations/meta/_journal.json b/apps/dbagent/migrations/meta/_journal.json index da79efe6..a56a55cc 100644 --- a/apps/dbagent/migrations/meta/_journal.json +++ b/apps/dbagent/migrations/meta/_journal.json @@ -61,7 +61,7 @@ { "idx": 8, "version": "7", - "when": 1744788121795, + "when": 1744794193911, "tag": "0008_public-chats", "breakpoints": true } diff --git a/apps/dbagent/src/app/(main)/share/[chat]/page.tsx b/apps/dbagent/src/app/(main)/share/[chat]/page.tsx index 2c8ab9a3..a1695a07 100644 --- a/apps/dbagent/src/app/(main)/share/[chat]/page.tsx +++ b/apps/dbagent/src/app/(main)/share/[chat]/page.tsx @@ -2,7 +2,7 @@ import { UIMessage } from 'ai'; import { notFound } from 'next/navigation'; import { ReadOnlyChat } from '~/components/chat/chat'; import { getChatById, getMessagesByChatId } from '~/lib/db/chats'; -import { getUserSessionDBAccess } from '~/lib/db/db'; +import { getAnonymousAccess } from '~/lib/db/db'; import { Message } from '~/lib/db/schema'; type PageParams = { @@ -13,7 +13,7 @@ export default async function SharedChatPage({ params }: { params: Promise(callback: (params: { db: ReturnType; userId: string }) => Promise): Promise { + const client = await pool.connect(); + try { + const db = drizzle(client); + await db.execute(sql.raw(`SET ROLE "${anonymousUser.name}"`)); + return await callback({ db, userId: 'anonymous' }); + } finally { + client.release(true); + } + } +} + export async function getUserSessionDBAccess(): Promise { const userId = await requireUserSession(); return new DBUserAccess(userId); @@ -77,6 +94,10 @@ export async function getUserDBAccess(userId: string | undefined | null): Promis return new DBUserAccess(userId); } -export function getAdminAccess(): DBAccess { +export async function getAdminAccess(): Promise { return new DBAdminAccess(); } + +export async function getAnonymousAccess(): Promise { + return new DBAnonymousAccess(); +} diff --git a/apps/dbagent/src/lib/db/schema.ts b/apps/dbagent/src/lib/db/schema.ts index 26068600..0f6cba06 100644 --- a/apps/dbagent/src/lib/db/schema.ts +++ b/apps/dbagent/src/lib/db/schema.ts @@ -22,6 +22,7 @@ import { RDSClusterDetailedInfo } from '../aws/rds'; import { CloudSQLInstanceInfo } from '../gcp/cloudsql'; export const authenticatedUser = pgRole('authenticated_user', { inherit: true }); +export const anonymousUser = pgRole('anonymous_user', { inherit: true }); type InferEnumType> = T extends PgEnum ? U[number] : never; @@ -478,12 +479,10 @@ export const chats = pgTable( WHERE project_id = chats.project_id AND user_id = current_setting('app.current_user', true)::TEXT )` }), - pgPolicy('chats_view_policy', { + pgPolicy('chats_anonymous_policy', { + to: anonymousUser, for: 'select', - using: sql`EXISTS ( - SELECT 1 FROM project_members - WHERE project_id = chats.project_id AND user_id = current_setting('app.current_user', true)::TEXT - ) OR visibility = 'public'` + using: sql`visibility = 'public'` }) ] ); diff --git a/apps/dbagent/src/lib/monitoring/scheduler.ts b/apps/dbagent/src/lib/monitoring/scheduler.ts index e427bb7d..08dc375d 100644 --- a/apps/dbagent/src/lib/monitoring/scheduler.ts +++ b/apps/dbagent/src/lib/monitoring/scheduler.ts @@ -48,7 +48,7 @@ export async function checkAndRunJobsAsAdmin() { console.log('Checking and running jobs as admin'); try { // Use DBAdminAccess to fetch all schedules - const adminAccess = getAdminAccess(); + const adminAccess = await getAdminAccess(); const schedules = await adminAccess.query(async ({ db }) => { return await db.select().from(schedulesSchema); }); From ed06d42249d3b6673965a1effa0622db52819ecb Mon Sep 17 00:00:00 2001 From: Alexis Rico Date: Wed, 16 Apr 2025 11:29:07 +0200 Subject: [PATCH 3/6] Update UI Signed-off-by: Alexis Rico --- apps/dbagent/src/components/chat/chat.tsx | 18 +--- apps/dbagent/src/components/chat/header.tsx | 64 +++--------- .../components/chat/visibility-selector.tsx | 98 +++++++++++++++++++ 3 files changed, 113 insertions(+), 67 deletions(-) create mode 100644 apps/dbagent/src/components/chat/visibility-selector.tsx diff --git a/apps/dbagent/src/components/chat/chat.tsx b/apps/dbagent/src/components/chat/chat.tsx index 12c9ffad..59a1cb50 100644 --- a/apps/dbagent/src/components/chat/chat.tsx +++ b/apps/dbagent/src/components/chat/chat.tsx @@ -68,21 +68,6 @@ function PureChat({ const layoutTopPadding = 'calc(var(--spacing)* 24)'; const heightScreen = `calc(100vh - ${layoutTopPadding})`; - // State for tracking chat visibility - const [visibility, setVisibility] = useState<'public' | 'private'>('private'); - - // Fetch the chat to get its visibility - useQuery({ - queryKey: ['chat', id], - queryFn: async () => { - const response = await fetcher(`/api/chat?id=${id}`); - if (response?.visibility) { - setVisibility(response.visibility); - } - return response; - } - }); - // Using useRef to avoid re-initializing the chat on every render // This is a workaround to avoid re-initializing the chat when the component is re-mounted // and the messages are already loaded @@ -103,14 +88,13 @@ function PureChat({
diff --git a/apps/dbagent/src/components/chat/header.tsx b/apps/dbagent/src/components/chat/header.tsx index 01b0a52f..5c5e8a94 100644 --- a/apps/dbagent/src/components/chat/header.tsx +++ b/apps/dbagent/src/components/chat/header.tsx @@ -1,25 +1,26 @@ 'use client'; -import { Button, cn, Switch, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@internal/components'; -import { Globe2Icon, PlusIcon } from 'lucide-react'; -import { useParams, useRouter } from 'next/navigation'; -import { memo, useState } from 'react'; +import { Button, cn } from '@internal/components'; +import { PlusIcon } from 'lucide-react'; +import { useRouter } from 'next/navigation'; +import { memo } from 'react'; import { Connection, Visibility } from '~/lib/db/schema'; import { ConnectionSelector } from './connection-selector'; import { ModelSelector } from './model-selector'; +import { VisibilitySelector } from './visibility-selector'; function PureChatHeader({ chatId, + projectId, connections, model, setModel, connectionId, setConnectionId, - visibility = 'private', - onVisibilityChange, className }: { chatId: string; + projectId: string; model: string; setModel: (model: string) => void; connections: Connection[]; @@ -30,35 +31,6 @@ function PureChatHeader({ className?: string; }) { const router = useRouter(); - const { project } = useParams<{ project: string }>(); - const [isPublic, setIsPublic] = useState(visibility === 'public'); - - const handleVisibilityToggle = async () => { - const newVisibility = isPublic ? 'private' : 'public'; - setIsPublic(!isPublic); - - try { - const response = await fetch(`/api/chat?id=${chatId}`, { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ visibility: newVisibility }) - }); - - if (!response.ok) { - throw new Error('Failed to update chat visibility'); - } - - if (onVisibilityChange) { - onVisibilityChange(newVisibility); - } - } catch (error) { - console.error('Error updating chat visibility:', error); - // Revert the toggle if update fails - setIsPublic(visibility === 'public'); - } - }; return (
@@ -66,36 +38,28 @@ function PureChatHeader({ variant="outline" className="ml-auto px-2 md:ml-0 md:h-fit md:px-2" onClick={() => { - router.push(`/projects/${project}/chats/new`); + router.push(`/projects/${projectId}/chats/new`); }} > New Chat + + +
+ - - - - -
- - -
-
- - {isPublic ? 'Chat is public - anyone with the link can view' : 'Chat is private'} - -
-
); } export const ChatHeader = memo(PureChatHeader, (prevProps, nextProps) => { return ( + prevProps.projectId === nextProps.projectId && + prevProps.chatId === nextProps.chatId && prevProps.connections === nextProps.connections && prevProps.model === nextProps.model && prevProps.connectionId === nextProps.connectionId diff --git a/apps/dbagent/src/components/chat/visibility-selector.tsx b/apps/dbagent/src/components/chat/visibility-selector.tsx new file mode 100644 index 00000000..8efe0b8e --- /dev/null +++ b/apps/dbagent/src/components/chat/visibility-selector.tsx @@ -0,0 +1,98 @@ +'use client'; + +import { + Button, + cn, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger +} from '@internal/components'; +import { useQuery } from '@tanstack/react-query'; +import { CheckCircleIcon, ChevronDownIcon, GlobeIcon, LockIcon } from 'lucide-react'; +import { ReactNode, useMemo, useState } from 'react'; +import { Visibility } from '~/lib/db/schema'; +import { fetcher } from './utils'; + +const visibilities: Array<{ + id: Visibility; + label: string; + description: string; + icon: ReactNode; +}> = [ + { + id: 'private', + label: 'Private', + description: 'Only you can access this chat', + icon: + }, + { + id: 'public', + label: 'Public', + description: 'Anyone with the link can access this chat', + icon: + } +]; + +export function VisibilitySelector({ + chatId, + className +}: { + chatId: string; +} & React.ComponentProps) { + const [open, setOpen] = useState(false); + + // State for tracking chat visibility + const [visibility, setVisibility] = useState('private'); + + // Fetch the chat to get its visibility + useQuery({ + queryKey: ['chat', chatId], + queryFn: async () => { + const response = await fetcher(`/api/chat?id=${chatId}`); + if (response?.visibility) { + setVisibility(response.visibility); + } + return response; + } + }); + + const selectedVisibility = useMemo(() => visibilities.find(({ id }) => id === visibility), [visibility]); + + return ( + + + + + + + {visibilities.map(({ id, label, description }) => ( + { + setVisibility(id); + setOpen(false); + }} + className="group/item flex flex-row items-center justify-between gap-4" + data-active={visibility === id} + > +
+ {label} + {description &&
{description}
} +
+
+ +
+
+ ))} +
+
+ ); +} From 06890df7d41774db35b5b73c40626aa4e6fc771f Mon Sep 17 00:00:00 2001 From: Alexis Rico Date: Wed, 16 Apr 2025 15:48:40 +0200 Subject: [PATCH 4/6] Stop using crypto.randomUUID Signed-off-by: Alexis Rico --- .../dbagent/src/components/playbooks/custom-playbook-form.tsx | 3 ++- apps/dbagent/src/evals/lib/chat-runner.ts | 4 ++-- apps/dbagent/src/lib/db/projects.ts | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/dbagent/src/components/playbooks/custom-playbook-form.tsx b/apps/dbagent/src/components/playbooks/custom-playbook-form.tsx index e88e220a..38c5b082 100644 --- a/apps/dbagent/src/components/playbooks/custom-playbook-form.tsx +++ b/apps/dbagent/src/components/playbooks/custom-playbook-form.tsx @@ -6,6 +6,7 @@ import Link from 'next/link'; import { useParams, useRouter } from 'next/navigation'; import { useState } from 'react'; import { Playbook } from '~/lib/tools/playbooks'; +import { generateUUID } from '../chat/utils'; import { actionCreatePlaybook, actionDeletePlaybook, @@ -48,7 +49,7 @@ export function CustomPlaybookForm({ initialData, isEditing = false }: CustomPla description, content, projectId: project, - id: crypto.randomUUID(), + id: generateUUID(), isBuiltIn: false, createdBy: '' }); diff --git a/apps/dbagent/src/evals/lib/chat-runner.ts b/apps/dbagent/src/evals/lib/chat-runner.ts index 1668d58d..ccd04ee6 100644 --- a/apps/dbagent/src/evals/lib/chat-runner.ts +++ b/apps/dbagent/src/evals/lib/chat-runner.ts @@ -1,6 +1,6 @@ import { CoreMessage, generateText, Message as SDKMessage } from 'ai'; -import { randomUUID } from 'crypto'; import { ExpectStatic } from 'vitest'; +import { generateUUID } from '~/components/chat/utils'; import { getChatSystemPrompt, getModelInstance } from '~/lib/ai/agent'; import { getTools } from '~/lib/ai/tools'; import { Connection, Project } from '~/lib/db/schema'; @@ -24,7 +24,7 @@ export const evalChat = async ({ }; const connection: Connection = { - id: randomUUID(), + id: generateUUID(), name: 'evaldb', connectionString: dbConnection, projectId: project.id, diff --git a/apps/dbagent/src/lib/db/projects.ts b/apps/dbagent/src/lib/db/projects.ts index 34e92575..a30353ba 100644 --- a/apps/dbagent/src/lib/db/projects.ts +++ b/apps/dbagent/src/lib/db/projects.ts @@ -1,11 +1,12 @@ 'use server'; import { eq } from 'drizzle-orm'; +import { generateUUID } from '~/components/chat/utils'; import { DBAccess } from './db'; import { Project, ProjectInsert, projectMembers, projects } from './schema'; export async function generateProjectId(): Promise { - return crypto.randomUUID(); + return generateUUID(); } export async function createProject(dbAccess: DBAccess, project: ProjectInsert): Promise { From e293bd1dd6243e630aea49e8018324aa2a988999 Mon Sep 17 00:00:00 2001 From: Alexis Rico Date: Tue, 22 Apr 2025 08:58:27 +0200 Subject: [PATCH 5/6] Fix build Signed-off-by: Alexis Rico --- apps/dbagent/src/app/(main)/share/[chat]/page.tsx | 2 +- apps/dbagent/src/lib/db/chats.ts | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/dbagent/src/app/(main)/share/[chat]/page.tsx b/apps/dbagent/src/app/(main)/share/[chat]/page.tsx index a1695a07..7873503c 100644 --- a/apps/dbagent/src/app/(main)/share/[chat]/page.tsx +++ b/apps/dbagent/src/app/(main)/share/[chat]/page.tsx @@ -24,7 +24,7 @@ export default async function SharedChatPage({ params }: { params: Promise): Array { return messages.map((message) => ({ diff --git a/apps/dbagent/src/lib/db/chats.ts b/apps/dbagent/src/lib/db/chats.ts index 8cffdda7..30c90f65 100644 --- a/apps/dbagent/src/lib/db/chats.ts +++ b/apps/dbagent/src/lib/db/chats.ts @@ -7,6 +7,7 @@ import { DBAccess } from './db'; import { artifactDocuments, artifactSuggestions, + Chat, ChatInsert, chats, Message, @@ -16,7 +17,11 @@ import { type ArtifactSuggestion } from './schema'; -export async function saveChat(dbAccess: DBAccess, chat: ChatInsert, chatMessages: Array = []) { +export async function saveChat( + dbAccess: DBAccess, + chat: ChatInsert & Partial, + chatMessages: Array = [] +) { return dbAccess.query(async ({ db }) => { return await db.transaction(async (tx) => { const result = await tx From d7b8b5fa854693cd7f5ae5b87a5248d7751f76c5 Mon Sep 17 00:00:00 2001 From: Alexis Rico Date: Tue, 22 Apr 2025 09:01:34 +0200 Subject: [PATCH 6/6] Update apps/dbagent/src/components/chat/visibility-selector.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- apps/dbagent/src/components/chat/visibility-selector.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/dbagent/src/components/chat/visibility-selector.tsx b/apps/dbagent/src/components/chat/visibility-selector.tsx index 8efe0b8e..f3e19b7f 100644 --- a/apps/dbagent/src/components/chat/visibility-selector.tsx +++ b/apps/dbagent/src/components/chat/visibility-selector.tsx @@ -49,12 +49,12 @@ export function VisibilitySelector({ useQuery({ queryKey: ['chat', chatId], queryFn: async () => { - const response = await fetcher(`/api/chat?id=${chatId}`); + return await fetcher(`/api/chat?id=${chatId}`); + }, + onSuccess: (response) => { if (response?.visibility) { setVisibility(response.visibility); } - return response; - } }); const selectedVisibility = useMemo(() => visibilities.find(({ id }) => id === visibility), [visibility]);