Skip to content

Commit

Permalink
Rename files (#231)
Browse files Browse the repository at this point in the history
  • Loading branch information
csansoon authored Sep 25, 2024
1 parent 1f39308 commit 1d462b8
Show file tree
Hide file tree
Showing 17 changed files with 378 additions and 47 deletions.
32 changes: 32 additions & 0 deletions apps/web/src/actions/documents/renamePathsAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use server'

import { CommitsRepository } from '@latitude-data/core/repositories'
import { renameDocumentPaths } from '@latitude-data/core/services/documents/renameDocumentPaths'
import { z } from 'zod'

import { withProject } from '../procedures'

export const renameDocumentPathsAction = withProject
.createServerAction()
.input(
z.object({
commitUuid: z.string(),
oldPath: z.string(),
newPath: z.string(),
}),
{ type: 'json' },
)
.handler(async ({ input, ctx }) => {
const commitsScope = new CommitsRepository(ctx.project.workspaceId)
const commit = await commitsScope
.getCommitByUuid({ uuid: input.commitUuid, projectId: ctx.project.id })
.then((r) => r.unwrap())

const result = await renameDocumentPaths({
commit,
oldPath: input.oldPath,
newPath: input.newPath,
})

return result.unwrap()
})
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default function ClientFilesTree({
const { commit, isHead } = useCurrentCommit()
const isMerged = !!commit.mergedAt
const { project } = useCurrentProject()
const documentPath = currentDocument?.path
const documentUuid = currentDocument?.documentUuid
const navigateToDocument = useCallback(
(documentUuid: string) => {
const documentDetails = ROUTES.projects
Expand All @@ -42,16 +42,22 @@ export default function ClientFilesTree({
[project.id, commit.uuid, isHead],
)

const { createFile, destroyFile, destroyFolder, isDestroying, data } =
useDocumentVersions(
{ commitUuid: commit.uuid, projectId: project.id },
{
fallbackData: serverDocuments,
onSuccessCreate: (document) => {
navigateToDocument(document.documentUuid)
},
const {
createFile,
destroyFile,
destroyFolder,
renamePaths,
isDestroying,
data,
} = useDocumentVersions(
{ commitUuid: commit.uuid, projectId: project.id },
{
fallbackData: serverDocuments,
onSuccessCreate: (document) => {
navigateToDocument(document.documentUuid)
},
)
},
)
const onMergeCommitClick = useCallback(() => {
setWarningOpen(true)
}, [setWarningOpen])
Expand All @@ -61,10 +67,11 @@ export default function ClientFilesTree({
<FilesTree
isMerged={isMerged}
documents={data}
currentPath={documentPath}
currentUuid={documentUuid}
navigateToDocument={navigateToDocument}
onMergeCommitClick={onMergeCommitClick}
createFile={createFile}
renamePaths={renamePaths}
destroyFile={destroyFile}
destroyFolder={destroyFolder}
isDestroying={isDestroying}
Expand Down
38 changes: 38 additions & 0 deletions apps/web/src/stores/documentVersions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useToast } from '@latitude-data/web-ui'
import { createDocumentVersionAction } from '$/actions/documents/create'
import { destroyDocumentAction } from '$/actions/documents/destroyDocumentAction'
import { destroyFolderAction } from '$/actions/documents/destroyFolderAction'
import { renameDocumentPathsAction } from '$/actions/documents/renamePathsAction'
import { updateDocumentContentAction } from '$/actions/documents/updateContent'
import useLatitudeAction from '$/hooks/useLatitudeAction'
import { ROUTES } from '$/services/routes'
Expand Down Expand Up @@ -34,6 +35,9 @@ export default function useDocumentVersions(
},
},
)
const { execute: executeRenamePaths } = useServerAction(
renameDocumentPathsAction,
)
const { execute: executeDestroyDocument, isPending: isDestroyingFile } =
useServerAction(destroyDocumentAction)
const { execute: executeDestroyFolder, isPending: isDestroyingFolder } =
Expand Down Expand Up @@ -103,6 +107,39 @@ export default function useDocumentVersions(
[executeCreateDocument, mutate, data, commitUuid],
)

const renamePaths = useCallback(
async ({ oldPath, newPath }: { oldPath: string; newPath: string }) => {
if (!projectId) return

const [updatedDocuments, error] = await executeRenamePaths({
oldPath,
newPath,
projectId,
commitUuid,
})

if (updatedDocuments) {
mutate(
data.map((d) => {
const updatedDocument = updatedDocuments.find(
(ud) => ud.documentUuid === d.documentUuid,
)
return updatedDocument ? updatedDocument : d
}),
)
}

if (error) {
toast({
title: 'Error renaming paths',
description: error.formErrors?.[0] || error.message,
variant: 'destructive',
})
}
},
[executeRenamePaths, mutate, data, commitUuid],
)

const destroyFile = useCallback(
async (documentUuid: string) => {
if (!projectId) return
Expand Down Expand Up @@ -188,6 +225,7 @@ export default function useDocumentVersions(
isLoading: isLoading,
error: swrError,
createFile,
renamePaths,
destroyFile,
destroyFolder,
updateContent,
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"tc": "turbo tc",
"prettier": "prettier --write \"**/*.{ts,tsx,md}\"",
"prettier:check": "prettier --check \"**/*.{ts,tsx,md}\"",
"test": "turbo test"
"test": "turbo test",
"catchup": "pnpm i && pnpm build --filter=\"./packages/**/*\" && pnpm --filter \"@latitude-data/core\" db:migrate"
},
"devDependencies": {
"@babel/parser": "^7.25.4",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/services/documents/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './destroyDocument'
export * from './destroyFolder'
export * from './recomputeChanges'
export * from './getResolvedContent'
export * from './renameDocumentPaths'
80 changes: 80 additions & 0 deletions packages/core/src/services/documents/renameDocumentPaths.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { describe, expect, it } from 'vitest'

import { Providers } from '../../constants'
import { BadRequestError } from '../../lib'
import { DocumentVersionsRepository } from '../../repositories'
import * as factories from '../../tests/factories'
import { renameDocumentPaths } from './renameDocumentPaths'

describe('renameDocumentPaths', () => {
it('renames a single document path', async () => {
const { project, user } = await factories.createProject({
providers: [{ type: Providers.OpenAI, name: 'openai' }],
documents: {
'a/b': factories.helpers.createPrompt({ provider: 'openai' }),
'a/b/c': factories.helpers.createPrompt({ provider: 'openai' }),
},
})
const { commit: draft } = await factories.createDraft({ project, user })

const result = await renameDocumentPaths({
commit: draft,
oldPath: 'a/b', // a/b/c should not be affected, since I'm only renaming a file
newPath: 'new/path',
})

expect(result.ok).toBeTruthy()

const docsScope = new DocumentVersionsRepository(project.workspaceId)
const docs = await docsScope
.getDocumentsAtCommit(draft)
.then((r) => r.unwrap())
const paths = docs.map((d) => d.path).sort()
expect(paths).toEqual(['a/b/c', 'new/path'])
})

it('renames a folder path', async () => {
const { project, user } = await factories.createProject({
providers: [{ type: Providers.OpenAI, name: 'openai' }],
documents: {
'a/b/c': factories.helpers.createPrompt({ provider: 'openai' }),
'a/b/c/d': factories.helpers.createPrompt({ provider: 'openai' }),
'not/affected': factories.helpers.createPrompt({ provider: 'openai' }),
},
})
const { commit: draft } = await factories.createDraft({ project, user })

const result = await renameDocumentPaths({
commit: draft,
oldPath: 'a/b/',
newPath: 'newpath/',
})

expect(result.ok).toBeTruthy()

const docsScope = new DocumentVersionsRepository(project.workspaceId)
const docs = await docsScope
.getDocumentsAtCommit(draft)
.then((r) => r.unwrap())
const paths = docs.map((d) => d.path).sort()
expect(paths).toEqual(['newpath/c', 'newpath/c/d', 'not/affected'])
})

it('fails when trying to rename a folder as a document', async () => {
const { project, user } = await factories.createProject({
providers: [{ type: Providers.OpenAI, name: 'openai' }],
documents: {
'a/b': factories.helpers.createPrompt({ provider: 'openai' }),
},
})
const { commit: draft } = await factories.createDraft({ project, user })

const result = await renameDocumentPaths({
commit: draft,
oldPath: 'a/',
newPath: 'new/path',
})

expect(result.error).toBeInstanceOf(BadRequestError)
})
})
64 changes: 64 additions & 0 deletions packages/core/src/services/documents/renameDocumentPaths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type { Commit, DocumentVersion } from '../../browser'
import { database } from '../../client'
import { findWorkspaceFromCommit } from '../../data-access'
import { Result, Transaction, TypedResult } from '../../lib'
import { BadRequestError } from '../../lib/errors'
import { DocumentVersionsRepository } from '../../repositories'
import { updateDocument } from './update'

export async function renameDocumentPaths(
{
commit,
oldPath,
newPath,
}: {
commit: Commit
oldPath: string
newPath: string
},
db = database,
): Promise<TypedResult<DocumentVersion[], Error>> {
return await Transaction.call(async (tx) => {
if (commit.mergedAt !== null) {
return Result.error(new BadRequestError('Cannot modify a merged commit'))
}

if (oldPath.endsWith('/') !== newPath.endsWith('/')) {
return Result.error(
new BadRequestError(
'Trying to rename a folder as a document or vice versa',
),
)
}

const workspace = await findWorkspaceFromCommit(commit, tx)
const docsScope = new DocumentVersionsRepository(workspace!.id, tx)

const currentDocs = await docsScope
.getDocumentsAtCommit(commit)
.then((r) => r.unwrap())

const docsToUpdate = currentDocs.filter((d) =>
oldPath.endsWith('/') ? d.path.startsWith(oldPath) : d.path === oldPath,
)

const updatedDocs = await Promise.all(
docsToUpdate.map(async (document) => {
const updatedPath = newPath + document.path.slice(oldPath.length)
// A simple replace would also replace other instances of oldPath in the path
// For example, relpacing "a" to "b" in "a/name" would result in "b/nbme"
const updatedDoc = await updateDocument(
{
commit,
document,
path: updatedPath,
},
tx,
)
return updatedDoc.unwrap()
}),
)

return Result.ok(updatedDocs)
}, db)
}
4 changes: 4 additions & 0 deletions packages/env/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ if (environment !== 'production') {
FILES_STORAGE_PATH,
DEFAULT_PROJECT_ID: '1',
DEFAULT_PROVIDER_API_KEY: 'd32da7c2-94fd-49c3-8dca-b57a5c3bbe27',
NEXT_PUBLIC_POSTHOG_KEY: '',
NEXT_PUBLIC_POSTHOG_HOST: '',
},
{ path: pathToEnv },
)
Expand Down Expand Up @@ -80,6 +82,8 @@ export const env = createEnv({
SENTRY_DSN: z.string().optional(),
SENTRY_ORG: z.string().optional(),
SENTRY_PROJECT: z.string().optional(),
NEXT_PUBLIC_POSTHOG_KEY: z.string(),
NEXT_PUBLIC_POSTHOG_HOST: z.string(),
},
runtimeEnv: {
...process.env,
Expand Down
2 changes: 2 additions & 0 deletions packages/web-ui/src/ds/atoms/Icons/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
LoaderCircle,
Lock,
Moon,
Pencil,
RefreshCcw,
SquareDot,
SquareMinus,
Expand Down Expand Up @@ -63,6 +64,7 @@ const Icons = {
sun: Sun,
eye: Eye,
externalLink: ExternalLink,
pencil: Pencil,
refresh: RefreshCcw,
}

Expand Down
Loading

0 comments on commit 1d462b8

Please sign in to comment.