From 08fcb09f9b6f3cc1629ee0c9499029d74049aec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Sans=C3=B3n?= Date: Tue, 16 Jul 2024 11:35:53 +0200 Subject: [PATCH] Current commit management --- apps/web/src/app/(private)/layout.tsx | 10 +- .../document/[documentId]/page.tsx | 22 + .../commit/[commitUuid]/layout.tsx | 30 + .../commits/[commitUuid]/documents/route.ts | 5 +- .../components/Sidebar/DocumentTree/index.tsx | 24 +- apps/web/src/components/Sidebar/index.tsx | 7 +- apps/web/src/stores/documentVersions.ts | 13 +- packages/core/drizzle/0004_steep_blob.sql | 6 + packages/core/drizzle/meta/0004_snapshot.json | 801 ++++++++++++++++++ packages/core/drizzle/meta/_journal.json | 7 + packages/core/src/data-access/commits.ts | 57 +- .../core/src/data-access/documentVersions.ts | 69 +- packages/core/src/schema/models/commits.ts | 12 +- packages/core/src/schema/models/projects.ts | 2 +- .../documentVersions/materializeAtCommit.ts | 15 +- .../molecules/DocumentTextEditor/editor.tsx | 5 + .../ds/molecules/DocumentTextEditor/index.tsx | 1 + packages/web-ui/src/index.ts | 1 + .../web-ui/src/providers/commitProvider.tsx | 34 + packages/web-ui/src/providers/index.ts | 1 + .../src/sections/DocumentEditor/index.tsx | 7 +- 21 files changed, 1037 insertions(+), 92 deletions(-) create mode 100644 apps/web/src/app/(private)/project/[projectId]/commit/[commitUuid]/document/[documentId]/page.tsx create mode 100644 apps/web/src/app/(private)/project/[projectId]/commit/[commitUuid]/layout.tsx create mode 100644 packages/core/drizzle/0004_steep_blob.sql create mode 100644 packages/core/drizzle/meta/0004_snapshot.json create mode 100644 packages/web-ui/src/providers/commitProvider.tsx create mode 100644 packages/web-ui/src/providers/index.ts diff --git a/apps/web/src/app/(private)/layout.tsx b/apps/web/src/app/(private)/layout.tsx index 709e988ba..63e0f847d 100644 --- a/apps/web/src/app/(private)/layout.tsx +++ b/apps/web/src/app/(private)/layout.tsx @@ -1,6 +1,5 @@ import { ReactNode } from 'react' -import Sidebar from '$/components/Sidebar' import { getSession } from '$/services/auth/getSession' import { ROUTES } from '$/services/routes' import { redirect } from 'next/navigation' @@ -14,12 +13,5 @@ export default async function PrivateLayout({ if (!data.session) { return redirect(ROUTES.auth.login) } - return ( -
-
- -
-
{children}
-
- ) + return children } diff --git a/apps/web/src/app/(private)/project/[projectId]/commit/[commitUuid]/document/[documentId]/page.tsx b/apps/web/src/app/(private)/project/[projectId]/commit/[commitUuid]/document/[documentId]/page.tsx new file mode 100644 index 000000000..29800bcc8 --- /dev/null +++ b/apps/web/src/app/(private)/project/[projectId]/commit/[commitUuid]/document/[documentId]/page.tsx @@ -0,0 +1,22 @@ +import { DocumentEditor } from '@latitude-data/web-ui' +import { getDocument } from '$core/data-access' + +export const dynamic = 'force-dynamic' + +export default async function Editor({ + params, +}: { + params: { commitUuid: string; documentId: number } +}) { + const result = await getDocument({ + commitUuid: params.commitUuid, + documentId: params.documentId, + }) + const { content } = result.unwrap() + + return ( +
+ +
+ ) +} diff --git a/apps/web/src/app/(private)/project/[projectId]/commit/[commitUuid]/layout.tsx b/apps/web/src/app/(private)/project/[projectId]/commit/[commitUuid]/layout.tsx new file mode 100644 index 000000000..168037d97 --- /dev/null +++ b/apps/web/src/app/(private)/project/[projectId]/commit/[commitUuid]/layout.tsx @@ -0,0 +1,30 @@ +import { ReactNode } from 'react' + +import { CommitProvider } from '@latitude-data/web-ui' +import Sidebar from '$/components/Sidebar' +import { getCommitOrder } from '$core/data-access' + +// import { getIsCommitEditableAction } from '$/actions/commits' + +export default async function PrivateLayout({ + children, + params, +}: { + children: ReactNode + params: { commitUuid: string } +}) { + const commitUuid = params.commitUuid + const commitOrder = await getCommitOrder({ uuid: commitUuid }) + const isDraft = commitOrder.unwrap() === null + + return ( + +
+
+ +
+
{children}
+
+
+ ) +} diff --git a/apps/web/src/app/api/commits/[commitUuid]/documents/route.ts b/apps/web/src/app/api/commits/[commitUuid]/documents/route.ts index 41a125cf1..17dded423 100644 --- a/apps/web/src/app/api/commits/[commitUuid]/documents/route.ts +++ b/apps/web/src/app/api/commits/[commitUuid]/documents/route.ts @@ -2,12 +2,11 @@ import { materializeDocumentsAtCommit } from '@latitude-data/core' import { NextRequest, NextResponse } from 'next/server' export async function GET( - req: NextRequest, + _: NextRequest, { commitUuid }: { commitUuid: string }, ) { try { - const staged = Boolean(req.nextUrl.searchParams.get('staged') || false) - const documents = await materializeDocumentsAtCommit({ commitUuid, staged }) + const documents = await materializeDocumentsAtCommit({ commitUuid }) return NextResponse.json(documents) } catch (err: unknown) { diff --git a/apps/web/src/components/Sidebar/DocumentTree/index.tsx b/apps/web/src/components/Sidebar/DocumentTree/index.tsx index 3f2f1d2cf..203d093a9 100644 --- a/apps/web/src/components/Sidebar/DocumentTree/index.tsx +++ b/apps/web/src/components/Sidebar/DocumentTree/index.tsx @@ -2,6 +2,7 @@ import { faker } from '@faker-js/faker' import type { DocumentType, DocumentVersion } from '@latitude-data/core' +import { useCurrentCommit } from '@latitude-data/web-ui' import useDocumentVersions from '$/stores/documentVersions' import { Node, useTree } from '../toTree' @@ -20,16 +21,19 @@ export function CreateNode({ parentId }: { parentId?: number }) { } function CreateFolder({ parentId }: { parentId?: number }) { - const { create } = useDocumentVersions({ staged: true }) + const { commitUuid, isDraft } = useCurrentCommit() + const { create } = useDocumentVersions({ commitUuid: commitUuid }) return ( @@ -37,9 +41,16 @@ function CreateFolder({ parentId }: { parentId?: number }) { } function CreateDocument({ parentId }: { parentId?: number }) { - const { create } = useDocumentVersions({ staged: true }) + const { commitUuid, isDraft } = useCurrentCommit() + const { create } = useDocumentVersions({ commitUuid: commitUuid }) return ( - ) @@ -74,8 +85,9 @@ export default function DocumentTree({ }: { documents: DocumentVersion[] }) { + const { commitUuid } = useCurrentCommit() const { documents } = useDocumentVersions( - { staged: true }, + { commitUuid }, { fallbackData: serverDocuments }, ) const rootNode = useTree({ documents }) diff --git a/apps/web/src/components/Sidebar/index.tsx b/apps/web/src/components/Sidebar/index.tsx index c03362159..7d73e8aec 100644 --- a/apps/web/src/components/Sidebar/index.tsx +++ b/apps/web/src/components/Sidebar/index.tsx @@ -1,11 +1,10 @@ -import { HEAD_COMMIT, materializeDocumentsAtCommit } from '@latitude-data/core' +import { materializeDocumentsAtCommit } from '@latitude-data/core' import DocumentTree, { CreateNode } from './DocumentTree' -export default async function Sidebar() { +export default async function Sidebar({ commitUuid }: { commitUuid: string }) { const documents = await materializeDocumentsAtCommit({ - commitUuid: HEAD_COMMIT, - staged: true, + commitUuid, }) return ( diff --git a/apps/web/src/stores/documentVersions.ts b/apps/web/src/stores/documentVersions.ts index 6afd8e2a7..f739519e4 100644 --- a/apps/web/src/stores/documentVersions.ts +++ b/apps/web/src/stores/documentVersions.ts @@ -11,19 +11,13 @@ import { useServerAction } from 'zsa-react' const FIXME_HARDCODED_PROJECT_ID = 1 export default function useDocumentVersions( { - commitUuid = HEAD_COMMIT, - staged = false, + commitUuid, }: { commitUuid?: string - staged?: boolean }, opts?: SWRConfiguration, ) { - const key = - `/api/commits/${commitUuid}/documents?` + - new URLSearchParams({ - staged: String(staged), - }).toString() + const key = `/api/commits/${commitUuid ?? HEAD_COMMIT}/documents` const { mutate, data, ...rest } = useSWR( key, @@ -34,7 +28,6 @@ export default function useDocumentVersions( const { execute } = useServerAction(createDocumentVersionAction) const create = useCallback( async (payload: { - commitUuid?: string name: string documentType?: DocumentType parentId?: number @@ -43,7 +36,7 @@ export default function useDocumentVersions( ...payload, projectId: FIXME_HARDCODED_PROJECT_ID, name: payload.name!, - commitUuid: payload.commitUuid || HEAD_COMMIT, + commitUuid: commitUuid || HEAD_COMMIT, }) const prev = documents ?? [] diff --git a/packages/core/drizzle/0004_steep_blob.sql b/packages/core/drizzle/0004_steep_blob.sql new file mode 100644 index 000000000..fb3a81d8d --- /dev/null +++ b/packages/core/drizzle/0004_steep_blob.sql @@ -0,0 +1,6 @@ +ALTER TABLE "latitude"."commits" DROP CONSTRAINT "commits_next_commit_id_commits_id_fk"; +--> statement-breakpoint +DROP INDEX IF EXISTS "commit_next_commit_idx";--> statement-breakpoint +ALTER TABLE "latitude"."commits" ADD COLUMN "merged_at" timestamp;--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "project_commit_order_idx" ON "latitude"."commits" USING btree ("merged_at","project_id");--> statement-breakpoint +ALTER TABLE "latitude"."commits" DROP COLUMN IF EXISTS "next_commit_id"; \ No newline at end of file diff --git a/packages/core/drizzle/meta/0004_snapshot.json b/packages/core/drizzle/meta/0004_snapshot.json new file mode 100644 index 000000000..1ddf23446 --- /dev/null +++ b/packages/core/drizzle/meta/0004_snapshot.json @@ -0,0 +1,801 @@ +{ + "id": "08d2df4f-382d-45ce-9c1a-66e53fc45c36", + "prevId": "eedd6d42-c764-44b7-9e9e-2428f529d3dc", + "version": "7", + "dialect": "postgresql", + "tables": { + "latitude.document_hierarchies": { + "name": "document_hierarchies", + "schema": "latitude", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "depth": { + "name": "depth", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "child_id": { + "name": "child_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "latitude.users": { + "name": "users", + "schema": "latitude", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_password": { + "name": "encrypted_password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "latitude.sessions": { + "name": "sessions", + "schema": "latitude", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "schemaTo": "latitude", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "latitude.workspaces": { + "name": "workspaces", + "schema": "latitude", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspaces_creator_id_users_id_fk": { + "name": "workspaces_creator_id_users_id_fk", + "tableFrom": "workspaces", + "tableTo": "users", + "schemaTo": "latitude", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "latitude.memberships": { + "name": "memberships", + "schema": "latitude", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "memberships_workspace_id_workspaces_id_fk": { + "name": "memberships_workspace_id_workspaces_id_fk", + "tableFrom": "memberships", + "tableTo": "workspaces", + "schemaTo": "latitude", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "memberships_user_id_users_id_fk": { + "name": "memberships_user_id_users_id_fk", + "tableFrom": "memberships", + "tableTo": "users", + "schemaTo": "latitude", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "memberships_workspace_id_user_id_pk": { + "name": "memberships_workspace_id_user_id_pk", + "columns": [ + "workspace_id", + "user_id" + ] + } + }, + "uniqueConstraints": {} + }, + "latitude.api_keys": { + "name": "api_keys", + "schema": "latitude", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "uuid": { + "name": "uuid", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workspace_id_idx": { + "name": "workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "api_keys_workspace_id_workspaces_id_fk": { + "name": "api_keys_workspace_id_workspaces_id_fk", + "tableFrom": "api_keys", + "tableTo": "workspaces", + "schemaTo": "latitude", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_keys_uuid_unique": { + "name": "api_keys_uuid_unique", + "nullsNotDistinct": false, + "columns": [ + "uuid" + ] + } + } + }, + "latitude.projects": { + "name": "projects", + "schema": "latitude", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_idx": { + "name": "workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_workspace_id_workspaces_id_fk": { + "name": "projects_workspace_id_workspaces_id_fk", + "tableFrom": "projects", + "tableTo": "workspaces", + "schemaTo": "latitude", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "latitude.commits": { + "name": "commits", + "schema": "latitude", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "uuid": { + "name": "uuid", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "merged_at": { + "name": "merged_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_commit_order_idx": { + "name": "project_commit_order_idx", + "columns": [ + { + "expression": "merged_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "commits_project_id_workspaces_id_fk": { + "name": "commits_project_id_workspaces_id_fk", + "tableFrom": "commits", + "tableTo": "workspaces", + "schemaTo": "latitude", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "commits_uuid_unique": { + "name": "commits_uuid_unique", + "nullsNotDistinct": false, + "columns": [ + "uuid" + ] + } + } + }, + "latitude.document_snapshots": { + "name": "document_snapshots", + "schema": "latitude", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "commit_id": { + "name": "commit_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "document_version_id": { + "name": "document_version_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "prompt_commit_idx": { + "name": "prompt_commit_idx", + "columns": [ + { + "expression": "commit_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_snapshot_document_version_idx": { + "name": "document_snapshot_document_version_idx", + "columns": [ + { + "expression": "document_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_snapshots_commit_id_commits_id_fk": { + "name": "document_snapshots_commit_id_commits_id_fk", + "tableFrom": "document_snapshots", + "tableTo": "commits", + "schemaTo": "latitude", + "columnsFrom": [ + "commit_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "document_snapshots_document_version_id_document_versions_id_fk": { + "name": "document_snapshots_document_version_id_document_versions_id_fk", + "tableFrom": "document_snapshots", + "tableTo": "document_versions", + "schemaTo": "latitude", + "columnsFrom": [ + "document_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "latitude.document_versions": { + "name": "document_versions", + "schema": "latitude", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "document_type": { + "name": "document_type", + "type": "document_type", + "typeSchema": "latitude", + "primaryKey": false, + "notNull": true, + "default": "'document'" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hash": { + "name": "hash", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "document_uuid": { + "name": "document_uuid", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "parent_id": { + "name": "parent_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "commit_id": { + "name": "commit_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "document_versions_parent_id_document_versions_id_fk": { + "name": "document_versions_parent_id_document_versions_id_fk", + "tableFrom": "document_versions", + "tableTo": "document_versions", + "schemaTo": "latitude", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_versions_commit_id_commits_id_fk": { + "name": "document_versions_commit_id_commits_id_fk", + "tableFrom": "document_versions", + "tableTo": "commits", + "schemaTo": "latitude", + "columnsFrom": [ + "commit_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "latitude.document_type": { + "name": "document_type", + "schema": "latitude", + "values": [ + "document", + "folder" + ] + } + }, + "schemas": { + "latitude": "latitude" + }, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/core/drizzle/meta/_journal.json b/packages/core/drizzle/meta/_journal.json index ba3a397a1..a42fe91c2 100644 --- a/packages/core/drizzle/meta/_journal.json +++ b/packages/core/drizzle/meta/_journal.json @@ -29,6 +29,13 @@ "when": 1721124762014, "tag": "0003_cold_spirit", "breakpoints": true + }, + { + "idx": 4, + "version": "7", + "when": 1721144118986, + "tag": "0004_steep_blob", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/core/src/data-access/commits.ts b/packages/core/src/data-access/commits.ts index e605596ad..6fa8794fe 100644 --- a/packages/core/src/data-access/commits.ts +++ b/packages/core/src/data-access/commits.ts @@ -1,26 +1,61 @@ import { database } from '$core/client' import { HEAD_COMMIT } from '$core/constants' +import { Result, TypedResult } from '$core/lib' +import { LatitudeError, NotFoundError } from '$core/lib/errors' import { commits } from '$core/schema' -import { desc, eq, isNull } from 'drizzle-orm' +import { and, desc, eq, isNotNull } from 'drizzle-orm' -const selectCondition = (uuid?: Exclude) => { - if (!uuid) return isNull(commits.nextCommitId) +export async function findHeadCommit( + { projectId }: { projectId: number }, + tx = database, +): Promise> { + const result = await tx + .select({ id: commits.id }) + .from(commits) + .where(and(isNotNull(commits.mergedAt), eq(commits.projectId, projectId))) + .orderBy(desc(commits.mergedAt)) + .limit(1) - return eq(commits.uuid, uuid) + if (result.length < 1) { + return Result.error(new NotFoundError('No head commit found')) + } + + const headCommit = result[0]! + return Result.ok(headCommit.id) } -export async function findCommit({ uuid }: { uuid?: string }, tx = database) { - if (uuid === HEAD_COMMIT) { - return tx.query.commits.findFirst({ orderBy: desc(commits.id) }) - } +export async function findCommit( + { projectId, uuid }: { projectId: number; uuid: string }, + tx = database, +): Promise> { + if (uuid === HEAD_COMMIT) return findHeadCommit({ projectId }, tx) - return tx.query.commits.findFirst({ where: selectCondition(uuid) }) + const res = await tx + .select() + .from(commits) + .where(eq(commits.uuid, uuid)) + .limit(1) + + if (!res.length) return Result.error(new NotFoundError('Commit not found')) + const commit = res[0]! + return Result.ok(commit.id) } export async function listCommits() { return database.select().from(commits) } -export async function listStagedCommits() { - return database.select().from(commits).where(isNull(commits.nextCommitId)) +export async function getCommitMergedAt( + { projectId, uuid }: { projectId: number; uuid: string }, + tx = database, +): Promise> { + const res = await tx + .select({ mergedAt: commits.mergedAt }) + .from(commits) + .where(and(eq(commits.projectId, projectId), eq(commits.uuid, uuid))) + .limit(1) + + if (!res.length) return Result.error(new NotFoundError('Commit not found')) + const commit = res[0]! + return Result.ok(commit.mergedAt) } diff --git a/packages/core/src/data-access/documentVersions.ts b/packages/core/src/data-access/documentVersions.ts index e1ad4f3e3..c97bb4154 100644 --- a/packages/core/src/data-access/documentVersions.ts +++ b/packages/core/src/data-access/documentVersions.ts @@ -1,35 +1,23 @@ -import { commits, database, documentVersions } from '@latitude-data/core' -import { desc, eq, inArray, lte } from 'drizzle-orm' - -import { listStagedCommits } from './commits' - -export async function listStagedDocuments() { - const commits = await listStagedCommits() - if (!commits.length) return [] - - return database - .select() - .from(documentVersions) - .where( - inArray( - documentVersions.commitId, - commits.map((c) => c.id), - ), - ) -} +import { + commits, + database, + documentVersions, + findCommit, + Result, + TypedResult, +} from '@latitude-data/core' +import { LatitudeError, NotFoundError } from '$core/lib/errors' +import { and, desc, eq, inArray, lte } from 'drizzle-orm' export async function getDocumentsAtCommit(commitUuid: string) { - const referenceCommitId = await database - .select({ id: commits.id }) - .from(commits) - .where(eq(commits.uuid, commitUuid)) - - if (referenceCommitId.length === 0) return [] + const commitResult = await findCommit({ uuid: commitUuid }) + if (commitResult.error) return [] + const commitId = commitResult.unwrap() const commitIdsBeforeReferenceCommit = await database .select({ id: commits.id }) .from(commits) - .where(lte(commits.id, referenceCommitId[0]!.id)) + .where(lte(commits.id, commitId)) const docsInCommits = await database .selectDistinct() @@ -41,8 +29,35 @@ export async function getDocumentsAtCommit(commitUuid: string) { commitIdsBeforeReferenceCommit.map((d) => d.id), ), ) - .groupBy(documentVersions.documentUuid) + .groupBy(documentVersions.documentUuid, documentVersions.id, commits.id) .orderBy(desc(documentVersions.commitId)) return docsInCommits.map((doc) => doc.document_versions) } + +export async function getDocument({ + commitUuid, + documentId, +}: { + commitUuid: string + documentId: number +}): Promise> { + const commitResult = await findCommit({ uuid: commitUuid }) + if (commitResult.error) return commitResult + const commitId = commitResult.unwrap() + + const result = await database + .select({ content: documentVersions.content }) + .from(documentVersions) + .where( + and( + eq(documentVersions.id, documentId), + eq(documentVersions.commitId, commitId), + ), + ) + + if (result.length === 0) + return Result.error(new NotFoundError('Document not found')) + const documentVersion = result[0]! + return Result.ok(documentVersion) +} diff --git a/packages/core/src/schema/models/commits.ts b/packages/core/src/schema/models/commits.ts index a40dd5418..973df7bb0 100644 --- a/packages/core/src/schema/models/commits.ts +++ b/packages/core/src/schema/models/commits.ts @@ -1,10 +1,10 @@ import { InferSelectModel, relations, sql } from 'drizzle-orm' import { - AnyPgColumn, bigint, bigserial, index, text, + timestamp, uuid, varchar, } from 'drizzle-orm/pg-core' @@ -20,19 +20,19 @@ export const commits = latitudeSchema.table( .notNull() .unique() .default(sql`gen_random_uuid()`), - nextCommitId: bigint('next_commit_id', { mode: 'number' }).references( - (): AnyPgColumn => commits.id, - { onDelete: 'restrict' }, - ), title: varchar('title', { length: 256 }), description: text('description'), projectId: bigint('project_id', { mode: 'number' }) .notNull() .references(() => workspaces.id, { onDelete: 'cascade' }), + mergedAt: timestamp('merged_at'), ...timestamps(), }, (table) => ({ - nextCommitIdx: index('commit_next_commit_idx').on(table.nextCommitId), + projectCommitOrderIdx: index('project_commit_order_idx').on( + table.mergedAt, + table.projectId, + ), }), ) diff --git a/packages/core/src/schema/models/projects.ts b/packages/core/src/schema/models/projects.ts index 16395fd5d..ade049721 100644 --- a/packages/core/src/schema/models/projects.ts +++ b/packages/core/src/schema/models/projects.ts @@ -15,7 +15,7 @@ export const projects = latitudeSchema.table( ...timestamps(), }, (table) => ({ - nextCommitIdx: index('workspace_idx').on(table.workspaceId), + projectWorkspaceIdx: index('workspace_idx').on(table.workspaceId), }), ) diff --git a/packages/core/src/services/documentVersions/materializeAtCommit.ts b/packages/core/src/services/documentVersions/materializeAtCommit.ts index a7490097e..fb9de1e40 100644 --- a/packages/core/src/services/documentVersions/materializeAtCommit.ts +++ b/packages/core/src/services/documentVersions/materializeAtCommit.ts @@ -1,27 +1,16 @@ -import { uniqBy } from 'lodash-es' - import { HEAD_COMMIT } from '$core/constants' -import { - getDocumentsAtCommit, - listdocumentSnapshots, - listStagedDocuments, -} from '$core/data-access' +import { getDocumentsAtCommit, listdocumentSnapshots } from '$core/data-access' export async function materializeDocumentsAtCommit({ commitUuid = HEAD_COMMIT, - staged = true, }: { commitUuid: string - staged: boolean }) { if (commitUuid === HEAD_COMMIT) { const snapshots = (await listdocumentSnapshots()).map( (snap) => snap.document_versions, ) - if (!staged) return snapshots - - const versions = await listStagedDocuments() - return uniqBy([...versions, ...snapshots], (doc) => doc.documentUuid) + return snapshots } else { return await getDocumentsAtCommit(commitUuid) } diff --git a/packages/web-ui/src/ds/molecules/DocumentTextEditor/editor.tsx b/packages/web-ui/src/ds/molecules/DocumentTextEditor/editor.tsx index 592c06b90..b6fa8c8bc 100644 --- a/packages/web-ui/src/ds/molecules/DocumentTextEditor/editor.tsx +++ b/packages/web-ui/src/ds/molecules/DocumentTextEditor/editor.tsx @@ -21,6 +21,7 @@ export function DocumentTextEditor({ value, metadata, onChange, + disabled, }: DocumentTextEditorProps) { const editorRef = useRef(null) const monacoRef = useRef(null) @@ -94,6 +95,10 @@ export function DocumentTextEditor({ onChange={handleValueChange} options={{ lineNumbers: 'off', + readOnly: disabled, + readOnlyMessage: { + value: 'Create a new draft to edit this document', + }, minimap: { enabled: false, }, diff --git a/packages/web-ui/src/ds/molecules/DocumentTextEditor/index.tsx b/packages/web-ui/src/ds/molecules/DocumentTextEditor/index.tsx index 144165c18..f28604f9a 100644 --- a/packages/web-ui/src/ds/molecules/DocumentTextEditor/index.tsx +++ b/packages/web-ui/src/ds/molecules/DocumentTextEditor/index.tsx @@ -10,6 +10,7 @@ export type DocumentTextEditorProps = { value: string metadata?: ConversationMetadata onChange?: (value: string) => void + disabled?: boolean } const DocumentTextEditor = lazy(() => diff --git a/packages/web-ui/src/index.ts b/packages/web-ui/src/index.ts index 3b623a7cc..ff7a6152a 100644 --- a/packages/web-ui/src/index.ts +++ b/packages/web-ui/src/index.ts @@ -3,3 +3,4 @@ export * from './ds/atoms' export * from './ds/molecules' export * from './layouts' export * from './sections' +export * from './providers' diff --git a/packages/web-ui/src/providers/commitProvider.tsx b/packages/web-ui/src/providers/commitProvider.tsx new file mode 100644 index 000000000..20a87d0e3 --- /dev/null +++ b/packages/web-ui/src/providers/commitProvider.tsx @@ -0,0 +1,34 @@ +'use client' + +import { createContext, ReactNode, useContext } from 'react' + +import { HEAD_COMMIT } from '@latitude-data/core/browser' + +// const HEAD_COMMIT = 'HEAD' + +interface CommitContextType { + commitUuid: string | typeof HEAD_COMMIT + isDraft: boolean +} + +const CommitContext = createContext({ + commitUuid: HEAD_COMMIT, + isDraft: false, +}) + +const CommitProvider = ({ + children, + ...context +}: { + children: ReactNode +} & CommitContextType) => { + return ( + {children} + ) +} + +const useCurrentCommit = () => { + return useContext(CommitContext) +} + +export { CommitProvider, useCurrentCommit } diff --git a/packages/web-ui/src/providers/index.ts b/packages/web-ui/src/providers/index.ts new file mode 100644 index 000000000..9f1634d77 --- /dev/null +++ b/packages/web-ui/src/providers/index.ts @@ -0,0 +1 @@ +export * from './commitProvider' diff --git a/packages/web-ui/src/sections/DocumentEditor/index.tsx b/packages/web-ui/src/sections/DocumentEditor/index.tsx index 87d8a8642..4bcead061 100644 --- a/packages/web-ui/src/sections/DocumentEditor/index.tsx +++ b/packages/web-ui/src/sections/DocumentEditor/index.tsx @@ -8,6 +8,7 @@ import { DocumentTextEditor, DocumentTextEditorFallback, } from '$ui/ds/molecules' +import { useCurrentCommit } from '$ui/providers/commitProvider' function Header({ title, children }: { title: string; children?: ReactNode }) { return ( @@ -18,8 +19,9 @@ function Header({ title, children }: { title: string; children?: ReactNode }) { ) } -export function DocumentEditor({ document }: { document: string }) { - const [value, setValue] = useState(document) +export function DocumentEditor({ content }: { content: string }) { + const { isDraft } = useCurrentCommit() + const [value, setValue] = useState(content) const [metadata, setMetadata] = useState() useEffect(() => { readMetadata({ prompt: value }).then(setMetadata) @@ -38,6 +40,7 @@ export function DocumentEditor({ document }: { document: string }) { value={value} metadata={metadata} onChange={setValue} + disabled={!isDraft} />