diff --git a/apps/web/src/actions/documents/create.ts b/apps/web/src/actions/documents/create.ts
index 30579a416..e5d7dc3f8 100644
--- a/apps/web/src/actions/documents/create.ts
+++ b/apps/web/src/actions/documents/create.ts
@@ -1,7 +1,6 @@
'use server'
-import { createNewDocument } from '@latitude-data/core'
-import { findCommit } from '$/app/(private)/_data-access'
+import { CommitsRepository, createNewDocument } from '@latitude-data/core'
import { z } from 'zod'
import { withProject } from '../procedures'
@@ -15,14 +14,15 @@ export const createDocumentVersionAction = withProject
}),
{ type: 'json' },
)
- .handler(async ({ input }) => {
- const commit = await findCommit({
- projectId: input.projectId,
- uuid: input.commitUuid,
- })
+ .handler(async ({ input, ctx }) => {
+ const commit = await new CommitsRepository(ctx.project.workspaceId)
+ .getCommitByUuid({ uuid: input.commitUuid, projectId: ctx.project.id })
+ .then((r) => r.unwrap())
+
const result = await createNewDocument({
- commitId: commit.id,
+ commit,
path: input.path,
})
+
return result.unwrap()
})
diff --git a/apps/web/src/actions/procedures/index.ts b/apps/web/src/actions/procedures/index.ts
index 70c25bc8e..4a61fff93 100644
--- a/apps/web/src/actions/procedures/index.ts
+++ b/apps/web/src/actions/procedures/index.ts
@@ -1,4 +1,4 @@
-import { findProject } from '@latitude-data/core'
+import { ProjectsRepository } from '@latitude-data/core'
import { getCurrentUser } from '$/services/auth/getCurrentUser'
import { z } from 'zod'
import { createServerActionProcedure } from 'zsa'
@@ -16,11 +16,11 @@ export const authProcedure = createServerActionProcedure().handler(async () => {
export const withProject = createServerActionProcedure(authProcedure)
.input(z.object({ projectId: z.number() }))
.handler(async ({ input, ctx }) => {
+ const { workspace } = ctx
+ const projectScope = new ProjectsRepository(workspace.id)
const project = (
- await findProject({
- projectId: input.projectId,
- workspaceId: ctx.workspace.id,
- })
+ await projectScope.getProjectById(input.projectId)
).unwrap()
+
return { ...ctx, project }
})
diff --git a/apps/web/src/actions/user/setupAction.ts b/apps/web/src/actions/user/setupAction.ts
index 48588a4d5..1d8570d22 100644
--- a/apps/web/src/actions/user/setupAction.ts
+++ b/apps/web/src/actions/user/setupAction.ts
@@ -24,7 +24,6 @@ export const setupAction = createServerAction()
)
.handler(async ({ input }) => {
const itWasAlreadySetup = await isWorkspaceCreated()
-
if (itWasAlreadySetup) {
throw new Error('Workspace already created')
}
diff --git a/apps/web/src/app/(private)/_data-access/index.ts b/apps/web/src/app/(private)/_data-access/index.ts
index a5e0976d2..198c000e9 100644
--- a/apps/web/src/app/(private)/_data-access/index.ts
+++ b/apps/web/src/app/(private)/_data-access/index.ts
@@ -1,16 +1,15 @@
import { cache } from 'react'
import {
- findCommitByUuid as originalfindCommit,
- findProject as originalFindProject,
- getFirstProject as originalGetFirstProject,
- type FindCommitByUuidProps,
- type FindProjectProps,
+ CommitsRepository,
+ Project,
+ ProjectsRepository,
} from '@latitude-data/core'
export const getFirstProject = cache(
async ({ workspaceId }: { workspaceId: number }) => {
- const result = await originalGetFirstProject({ workspaceId })
+ const projectsScope = new ProjectsRepository(workspaceId)
+ const result = await projectsScope.getFirstProject()
const project = result.unwrap()
return project
@@ -18,8 +17,15 @@ export const getFirstProject = cache(
)
export const findProject = cache(
- async ({ projectId, workspaceId }: FindProjectProps) => {
- const result = await originalFindProject({ projectId, workspaceId })
+ async ({
+ projectId,
+ workspaceId,
+ }: {
+ projectId: number
+ workspaceId: number
+ }) => {
+ const projectsScope = new ProjectsRepository(workspaceId)
+ const result = await projectsScope.getProjectById(projectId)
const project = result.unwrap()
return project
@@ -27,8 +33,12 @@ export const findProject = cache(
)
export const findCommit = cache(
- async ({ uuid, projectId }: FindCommitByUuidProps) => {
- const result = await originalfindCommit({ uuid, projectId })
+ async ({ uuid, project }: { uuid: string; project: Project }) => {
+ const commitsScope = new CommitsRepository(project.workspaceId)
+ const result = await commitsScope.getCommitByUuid({
+ projectId: project.id,
+ uuid,
+ })
const commit = result.unwrap()
return commit
diff --git a/apps/web/src/app/(private)/api/v1/projects/[projectId]/commits/[commitUuid]/[...documentPath]/route.test.ts b/apps/web/src/app/(private)/api/v1/projects/[projectId]/commits/[commitUuid]/[...documentPath]/route.test.ts
index 229e7da5d..0449d30a1 100644
--- a/apps/web/src/app/(private)/api/v1/projects/[projectId]/commits/[commitUuid]/[...documentPath]/route.test.ts
+++ b/apps/web/src/app/(private)/api/v1/projects/[projectId]/commits/[commitUuid]/[...documentPath]/route.test.ts
@@ -15,7 +15,7 @@ describe('GET documentVersion', () => {
commit,
})
- await mergeCommit({ commitId: commit.id })
+ await mergeCommit(commit)
const response = await GET(
new NextRequest(
@@ -41,7 +41,7 @@ describe('GET documentVersion', () => {
commit,
})
- await mergeCommit({ commitId: commit.id })
+ await mergeCommit(commit)
const response = await GET(
new NextRequest(
@@ -64,7 +64,7 @@ describe('GET documentVersion', () => {
const { project } = await ctx.factories.createProject()
const { commit } = await ctx.factories.createDraft({ project })
- await mergeCommit({ commitId: commit.id })
+ await mergeCommit(commit)
const response = await GET(
new NextRequest(
diff --git a/apps/web/src/app/(private)/api/v1/projects/[projectId]/commits/[commitUuid]/[...documentPath]/route.ts b/apps/web/src/app/(private)/api/v1/projects/[projectId]/commits/[commitUuid]/[...documentPath]/route.ts
index bcbd259c7..acdfa3a24 100644
--- a/apps/web/src/app/(private)/api/v1/projects/[projectId]/commits/[commitUuid]/[...documentPath]/route.ts
+++ b/apps/web/src/app/(private)/api/v1/projects/[projectId]/commits/[commitUuid]/[...documentPath]/route.ts
@@ -1,9 +1,12 @@
-import { getDocumentByPath } from '@latitude-data/core'
+import {
+ CommitsRepository,
+ DocumentVersionsRepository,
+} from '@latitude-data/core'
import apiRoute from '$/helpers/api/route'
-import { NextRequest } from 'next/server'
+import { LatitudeRequest } from '$/middleware'
export async function GET(
- _: NextRequest,
+ req: LatitudeRequest,
{
params,
}: {
@@ -15,11 +18,15 @@ export async function GET(
},
) {
return apiRoute(async () => {
- const { projectId, commitUuid, documentPath } = params
-
- const result = await getDocumentByPath({
- projectId: Number(projectId),
- commitUuid,
+ const workspaceId = req.workspaceId!
+ const { commitUuid, projectId, documentPath } = params
+ const commitsScope = new CommitsRepository(workspaceId)
+ const commit = await commitsScope
+ .getCommitByUuid({ uuid: commitUuid, projectId })
+ .then((r) => r.unwrap())
+ const documentVersionsScope = new DocumentVersionsRepository(workspaceId)
+ const result = await documentVersionsScope.getDocumentByPath({
+ commit,
path: documentPath.join('/'),
})
const document = result.unwrap()
diff --git a/apps/web/src/app/(private)/page.tsx b/apps/web/src/app/(private)/page.tsx
index 5f57c6971..66b548e30 100644
--- a/apps/web/src/app/(private)/page.tsx
+++ b/apps/web/src/app/(private)/page.tsx
@@ -15,7 +15,9 @@ export default async function AppRoot() {
try {
session = await getCurrentUser()
project = await getFirstProject({ workspaceId: session.workspace.id })
- await findCommit({ uuid: HEAD_COMMIT, projectId: project.id })
+
+ await findCommit({ uuid: HEAD_COMMIT, project })
+
url = PROJECT_ROUTE({ id: project.id }).commits.latest
} catch (error) {
if (error instanceof NotFoundError) {
diff --git a/apps/web/src/app/(private)/projects/[projectId]/page.tsx b/apps/web/src/app/(private)/projects/[projectId]/page.tsx
index 428460402..0cc279af0 100644
--- a/apps/web/src/app/(private)/projects/[projectId]/page.tsx
+++ b/apps/web/src/app/(private)/projects/[projectId]/page.tsx
@@ -18,10 +18,10 @@ export default async function ProjectPage({ params }: ProjectPageParams) {
try {
session = await getCurrentUser()
project = await findProject({
- projectId: params.projectId,
+ projectId: Number(params.projectId),
workspaceId: session.workspace.id,
})
- await findCommit({ uuid: HEAD_COMMIT, projectId: project.id })
+ await findCommit({ uuid: HEAD_COMMIT, project })
url = PROJECT_ROUTE({ id: +project.id }).commits.latest
} catch (error) {
if (error instanceof NotFoundError) {
diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/layout.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/layout.tsx
index 6125c3fd9..4ab0344a7 100644
--- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/layout.tsx
+++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/layout.tsx
@@ -2,13 +2,14 @@ import { ReactNode } from 'react'
import {
Commit,
+ CommitsRepository,
HEAD_COMMIT,
NotFoundError,
Project,
+ ProjectsRepository,
} from '@latitude-data/core'
import { CommitProvider, ProjectProvider } from '@latitude-data/web-ui'
import { AppLayout, BreadcrumpBadge } from '@latitude-data/web-ui/browser'
-import { findCommit, findProject } from '$/app/(private)/_data-access'
import { NAV_LINKS } from '$/app/(private)/_lib/constants'
import { ProjectPageParams } from '$/app/(private)/projects/[projectId]/page'
import { getCurrentUser, SessionData } from '$/services/auth/getCurrentUser'
@@ -29,18 +30,20 @@ export default async function CommitLayout({
let commit: Commit
try {
session = await getCurrentUser()
- project = await findProject({
- projectId: params.projectId,
- workspaceId: session.workspace.id,
- })
- commit = await findCommit({
- uuid: params.commitUuid,
- projectId: project.id,
- })
+ const projectsRepo = new ProjectsRepository(session.workspace.id)
+ const commitsRepo = new CommitsRepository(session.workspace.id)
+ project = (
+ await projectsRepo.getProjectById(Number(params.projectId))
+ ).unwrap()
+ commit = (
+ await commitsRepo.getCommitByUuid({
+ uuid: params.commitUuid,
+ projectId: project.id,
+ })
+ ).unwrap()
} catch (error) {
- if (error instanceof NotFoundError) {
- return notFound()
- }
+ if (error instanceof NotFoundError) return notFound()
+
throw error
}
diff --git a/apps/web/src/app/api/projects/[projectId]/commits/[commitUuid]/documents/route.ts b/apps/web/src/app/api/projects/[projectId]/commits/[commitUuid]/documents/route.ts
new file mode 100644
index 000000000..5c7984845
--- /dev/null
+++ b/apps/web/src/app/api/projects/[projectId]/commits/[commitUuid]/documents/route.ts
@@ -0,0 +1,28 @@
+import {
+ CommitsRepository,
+ DocumentVersionsRepository,
+} from '@latitude-data/core'
+import { LatitudeRequest } from '$/middleware'
+import { NextResponse } from 'next/server'
+
+export async function GET(
+ req: LatitudeRequest,
+ {
+ params: { commitUuid, projectId },
+ }: { params: { commitUuid: string; projectId: number } },
+) {
+ try {
+ const workspaceId = req.workspaceId!
+ const scope = new DocumentVersionsRepository(workspaceId)
+ const commitsScope = new CommitsRepository(workspaceId)
+ const commit = await commitsScope
+ .getCommitByUuid({ uuid: commitUuid, projectId })
+ .then((r) => r.unwrap())
+ const documents = await scope.getDocumentsAtCommit(commit)
+
+ return NextResponse.json(documents.unwrap())
+ } catch (err: unknown) {
+ const error = err as Error
+ return NextResponse.json({ error: error.message }, { status: 500 })
+ }
+}
diff --git a/apps/web/src/components/Sidebar/index.tsx b/apps/web/src/components/Sidebar/index.tsx
index 2116b4275..aeee736b6 100644
--- a/apps/web/src/components/Sidebar/index.tsx
+++ b/apps/web/src/components/Sidebar/index.tsx
@@ -1,20 +1,31 @@
-import { getDocumentsAtCommit } from '@latitude-data/core'
-import { findCommit } from '$/app/(private)/_data-access'
+import {
+ CommitsRepository,
+ DocumentVersionsRepository,
+ Project,
+} from '@latitude-data/core'
import DocumentTree, { CreateNode } from './DocumentTree'
export default async function Sidebar({
commitUuid,
- projectId,
+ project,
}: {
commitUuid: string
- projectId: number
+ project: Project
}) {
- const commit = await findCommit({ projectId, uuid: commitUuid })
- const documentsResult = await getDocumentsAtCommit({
- commitId: commit.id,
- })
- const documents = documentsResult.unwrap()
+ const commitsScope = new CommitsRepository(project.workspaceId)
+ const commit = await commitsScope
+ .getCommitByUuid({
+ uuid: commitUuid,
+ projectId: project.id,
+ })
+ .then((r) => r.unwrap())
+ const documentVersionsScope = new DocumentVersionsRepository(
+ project.workspaceId,
+ )
+ const documents = await documentVersionsScope
+ .getDocumentsAtCommit(commit)
+ .then((r) => r.unwrap())
return (
diff --git a/apps/web/src/data-access/users.ts b/apps/web/src/data-access/users.ts
index a5f474478..d19c5c039 100644
--- a/apps/web/src/data-access/users.ts
+++ b/apps/web/src/data-access/users.ts
@@ -1,6 +1,6 @@
import {
database,
- getUser,
+ unsafelyGetUser,
NotFoundError,
Result,
users,
@@ -31,15 +31,12 @@ export async function getUserFromCredentials({
},
where: eq(users.email, email),
})
-
if (!user) return notFound()
const validPassword = await verifyPassword(password, user.encryptedPassword)
-
if (!validPassword) notFound()
const wpResult = await getWorkspace({ userId: user.id })
-
if (wpResult.error) {
return Result.error(wpResult.error)
}
@@ -60,7 +57,7 @@ export async function getCurrentUserFromDB({
}: {
userId: string | undefined
}): PromisedResult {
- const user = await getUser(userId)
+ const user = await unsafelyGetUser(userId)
if (!user) return notFound()
const wpResult = await getWorkspace({ userId: user.id })
diff --git a/apps/web/src/middleware.ts b/apps/web/src/middleware.ts
index d1a713e89..8bf0ca80f 100644
--- a/apps/web/src/middleware.ts
+++ b/apps/web/src/middleware.ts
@@ -1,4 +1,4 @@
-import { getApiKey } from '@latitude-data/core'
+import { unsafelyGetApiKey } from '@latitude-data/core'
import { NextRequest, NextResponse } from 'next/server'
import env from './env'
@@ -15,7 +15,7 @@ export async function middleware(request: LatitudeRequest) {
return apiUnauthorized()
}
- const result = await getApiKey({ uuid: token })
+ const result = await unsafelyGetApiKey({ uuid: token })
if (result.error) return apiUnauthorized()
request.workspaceId = result.value.workspaceId
diff --git a/packages/core/src/data-access/apiKeys.ts b/packages/core/src/data-access/apiKeys.ts
index 54445e9d8..1c4984123 100644
--- a/packages/core/src/data-access/apiKeys.ts
+++ b/packages/core/src/data-access/apiKeys.ts
@@ -3,8 +3,13 @@ import { NotFoundError, Result } from '$core/lib'
import { apiKeys } from '$core/schema'
import { eq } from 'drizzle-orm'
-export async function getApiKey({ uuid }: { uuid: string }, db = database) {
- const apiKey = await db.query.apiKeys.findFirst({ where: eq(apiKeys, uuid) })
+export async function unsafelyGetApiKey(
+ { uuid }: { uuid: string },
+ db = database,
+) {
+ const apiKey = await db.query.apiKeys.findFirst({
+ where: eq(apiKeys.uuid, uuid),
+ })
if (!apiKey) return Result.error(new NotFoundError('API key not found'))
return Result.ok(apiKey)
diff --git a/packages/core/src/data-access/documentVersions/getDocumentById.ts b/packages/core/src/data-access/documentVersions/getDocumentById.ts
deleted file mode 100644
index 003bb0735..000000000
--- a/packages/core/src/data-access/documentVersions/getDocumentById.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { database } from '$core/client'
-import { LatitudeError, NotFoundError, Result, TypedResult } from '$core/lib'
-import { documentVersions } from '$core/schema'
-import { eq } from 'drizzle-orm'
-
-export async function getDocumentById(
- {
- documentId,
- }: {
- documentId: number
- },
- db = database,
-): Promise> {
- const document = await db.query.documentVersions.findFirst({
- where: eq(documentVersions.id, documentId),
- })
- if (!document) return Result.error(new NotFoundError('Document not found'))
-
- return Result.ok(document)
-}
diff --git a/packages/core/src/data-access/documentVersions/getDocumentByPath.ts b/packages/core/src/data-access/documentVersions/getDocumentByPath.ts
deleted file mode 100644
index 92204da02..000000000
--- a/packages/core/src/data-access/documentVersions/getDocumentByPath.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { database } from '$core/client'
-import { NotFoundError, Result } from '$core/lib'
-
-import { findCommitByUuid } from '../commits'
-import { getDocumentsAtCommit } from './getDocumentsAtCommit'
-
-export async function getDocumentByPath(
- {
- projectId,
- commitUuid,
- path,
- }: {
- projectId: number
- commitUuid: string
- path: string
- },
- db = database,
-) {
- try {
- const commit = await findCommitByUuid({ projectId, uuid: commitUuid }, db)
- if (commit.error) return commit
-
- const result = await getDocumentsAtCommit({ commitId: commit.value.id }, db)
- const documents = result.unwrap()
- const document = documents.find((doc) => doc.path === path)
- if (!document) {
- return Result.error(
- new NotFoundError(
- `No document with path ${path} at commit ${commitUuid}`,
- ),
- )
- }
-
- return Result.ok(document)
- } catch (err) {
- return Result.error(err as Error)
- }
-}
diff --git a/packages/core/src/data-access/documentVersions/getDocumentsAtCommit.ts b/packages/core/src/data-access/documentVersions/getDocumentsAtCommit.ts
deleted file mode 100644
index 1e8730edc..000000000
--- a/packages/core/src/data-access/documentVersions/getDocumentsAtCommit.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-import {
- commits,
- database,
- DocumentVersion,
- documentVersions,
- findCommitById,
- Result,
- TypedResult,
-} from '@latitude-data/core'
-import { LatitudeError } from '$core/lib/errors'
-import { and, eq, getTableColumns, isNotNull, lte, max } from 'drizzle-orm'
-
-async function fetchDocumentsFromMergedCommits(
- {
- projectId,
- maxMergedAt,
- }: {
- projectId: number
- maxMergedAt: Date | null
- },
- tx = database,
-): Promise {
- const filterByMaxMergedAt = () => {
- const mergedAtNotNull = isNotNull(commits.mergedAt)
- if (maxMergedAt === null) return mergedAtNotNull
- return and(mergedAtNotNull, lte(commits.mergedAt, maxMergedAt))
- }
-
- const lastVersionOfEachDocument = tx.$with('lastVersionOfDocuments').as(
- tx
- .select({
- documentUuid: documentVersions.documentUuid,
- mergedAt: max(commits.mergedAt).as('maxMergedAt'),
- })
- .from(documentVersions)
- .innerJoin(commits, eq(commits.id, documentVersions.commitId))
- .where(and(filterByMaxMergedAt(), eq(commits.projectId, projectId)))
- .groupBy(documentVersions.documentUuid),
- )
-
- const documentsFromMergedCommits = await tx
- .with(lastVersionOfEachDocument)
- .select(getTableColumns(documentVersions))
- .from(documentVersions)
- .innerJoin(
- commits,
- and(
- eq(commits.id, documentVersions.commitId),
- isNotNull(commits.mergedAt),
- ),
- )
- .innerJoin(
- lastVersionOfEachDocument,
- and(
- eq(
- documentVersions.documentUuid,
- lastVersionOfEachDocument.documentUuid,
- ),
- eq(commits.mergedAt, lastVersionOfEachDocument.mergedAt),
- ),
- )
-
- return documentsFromMergedCommits
-}
-
-function mergeDocuments(
- ...documentsArr: DocumentVersion[][]
-): DocumentVersion[] {
- return documentsArr.reduce((acc, documents) => {
- return acc
- .filter((d) => {
- return !documents.find((d2) => d2.documentUuid === d.documentUuid)
- })
- .concat(documents)
- }, [])
-}
-
-export async function getDocumentsAtCommit(
- { commitId }: { commitId: number },
- tx = database,
-): Promise> {
- const commitResult = await findCommitById({ id: commitId })
- if (commitResult.error) return commitResult
- const commit = commitResult.value!
-
- const documentsFromMergedCommits = await fetchDocumentsFromMergedCommits({
- projectId: commit.projectId,
- maxMergedAt: commit.mergedAt,
- })
-
- if (commit.mergedAt !== null) {
- // Referenced commit is merged. No additional documents to return.
- return Result.ok(documentsFromMergedCommits)
- }
-
- const documentsFromDraft = await tx
- .select(getTableColumns(documentVersions))
- .from(documentVersions)
- .innerJoin(commits, eq(commits.id, documentVersions.commitId))
- .where(eq(commits.id, commitId))
-
- const totalDocuments = mergeDocuments(
- documentsFromMergedCommits,
- documentsFromDraft,
- )
-
- return Result.ok(totalDocuments)
-}
-
-export async function listCommitChanges(
- { commitId }: { commitId: number },
- tx = database,
-) {
- const changedDocuments = await tx.query.documentVersions.findMany({
- where: eq(documentVersions.commitId, commitId),
- })
-
- return Result.ok(changedDocuments)
-}
diff --git a/packages/core/src/data-access/documentVersions/index.ts b/packages/core/src/data-access/documentVersions/index.ts
deleted file mode 100644
index 84dbd18ac..000000000
--- a/packages/core/src/data-access/documentVersions/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export * from './getDocumentsAtCommit'
-export * from './getDocumentByPath'
-export * from './getDocumentById'
diff --git a/packages/core/src/data-access/index.ts b/packages/core/src/data-access/index.ts
index 952fcc44a..45f6b2153 100644
--- a/packages/core/src/data-access/index.ts
+++ b/packages/core/src/data-access/index.ts
@@ -1,5 +1,3 @@
-export * from './users'
-export * from './projects'
-export * from './commits'
-export * from './documentVersions'
export * from './apiKeys'
+export * from './users'
+export * from './workspaces'
diff --git a/packages/core/src/data-access/projects.ts b/packages/core/src/data-access/projects.ts
deleted file mode 100644
index 8dc9bd692..000000000
--- a/packages/core/src/data-access/projects.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { database } from '$core/client'
-import { Result } from '$core/lib'
-import { NotFoundError } from '$core/lib/errors'
-import { projects } from '$core/schema'
-import { and, eq } from 'drizzle-orm'
-
-const NOT_FOUND_MSG = 'Project not found'
-
-export type FindProjectProps = {
- projectId: number | string
- workspaceId: number
-}
-export async function findProject({
- projectId,
- workspaceId,
-}: FindProjectProps) {
- const id = Number(projectId)
- if (isNaN(id)) {
- return Result.error(new NotFoundError(NOT_FOUND_MSG))
- }
-
- const project = await database.query.projects.findFirst({
- where: and(eq(projects.workspaceId, workspaceId), eq(projects.id, id)),
- })
-
- if (!project) {
- return Result.error(new NotFoundError(NOT_FOUND_MSG))
- }
-
- return Result.ok(project!)
-}
-
-export async function getFirstProject({
- workspaceId,
-}: {
- workspaceId: number
-}) {
- const project = await database.query.projects.findFirst({
- where: eq(projects.workspaceId, workspaceId),
- })
-
- if (!project) {
- return Result.error(new NotFoundError(NOT_FOUND_MSG))
- }
-
- return Result.ok(project!)
-}
diff --git a/packages/core/src/data-access/users.ts b/packages/core/src/data-access/users.ts
index 1b97787a9..da674ca71 100644
--- a/packages/core/src/data-access/users.ts
+++ b/packages/core/src/data-access/users.ts
@@ -6,7 +6,7 @@ export type SessionData = {
workspace: { id: number; name: string }
}
-export function getUser(id?: string) {
+export function unsafelyGetUser(id?: string) {
return database.query.users.findFirst({
where: eq(users.id, id ?? ''),
})
diff --git a/packages/core/src/data-access/workspaces.ts b/packages/core/src/data-access/workspaces.ts
new file mode 100644
index 000000000..fec6cc38a
--- /dev/null
+++ b/packages/core/src/data-access/workspaces.ts
@@ -0,0 +1,15 @@
+import { database } from '$core/client'
+import { Commit, commits, projects, workspaces } from '$core/schema'
+import { eq, getTableColumns } from 'drizzle-orm'
+
+export async function findWorkspaceFromCommit(commit: Commit, db = database) {
+ const results = await db
+ .select(getTableColumns(workspaces))
+ .from(workspaces)
+ .innerJoin(projects, eq(projects.workspaceId, workspaces.id))
+ .innerJoin(commits, eq(commits.projectId, projects.id))
+ .where(eq(commits.id, commit.id))
+ .limit(1)
+
+ return results[0]
+}
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 830fa9395..9e285fb53 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -4,3 +4,4 @@ export * from './data-access'
export * from './lib'
export * from './schema'
export * from './services'
+export * from './repositories'
diff --git a/packages/core/src/repositories/commitsRepository.ts b/packages/core/src/repositories/commitsRepository.ts
new file mode 100644
index 000000000..df68a755b
--- /dev/null
+++ b/packages/core/src/repositories/commitsRepository.ts
@@ -0,0 +1,108 @@
+import { HEAD_COMMIT } from '$core/constants'
+import { NotFoundError, Result } from '$core/lib'
+import { commits, projects } from '$core/schema'
+import { and, desc, eq, isNotNull } from 'drizzle-orm'
+
+import Repository from './repository'
+
+export class CommitsRepository extends Repository {
+ get scope() {
+ return this.db
+ .select()
+ .from(commits)
+ .innerJoin(projects, eq(projects.workspaceId, this.workspaceId))
+ .where(eq(commits.projectId, projects.id))
+ .as('commitsScope')
+ }
+
+ async getHeadCommit({ projectId }: { projectId: number }) {
+ const result = await this.db
+ .select()
+ .from(this.scope)
+ .where(and(isNotNull(commits.mergedAt), eq(commits.projectId, projectId)))
+ .orderBy(desc(commits.mergedAt))
+ .limit(1)
+
+ if (result.length < 1) {
+ return Result.error(new NotFoundError('No head commit found'))
+ }
+
+ return Result.ok(result[0]!.commits)
+ }
+
+ async getCommitByUuid({
+ uuid,
+ projectId,
+ }: {
+ projectId?: number
+ uuid: string
+ }) {
+ if (uuid === HEAD_COMMIT) {
+ if (!projectId) {
+ return Result.error(new NotFoundError('Project ID is required'))
+ }
+
+ return this.getHeadCommit({ projectId })
+ }
+
+ const result = await this.db
+ .select()
+ .from(this.scope)
+ .where(eq(commits.uuid, uuid))
+ .limit(1)
+ const commit = result[0]?.commits
+ if (!commit) return Result.error(new NotFoundError('Commit not found'))
+
+ return Result.ok(commit)
+ }
+
+ async getCommitById(id: number) {
+ const result = await this.db
+ .select()
+ .from(this.scope)
+ .where(eq(commits.id, id))
+ .limit(1)
+ const commit = result[0]?.commits
+ if (!commit) return Result.error(new NotFoundError('Commit not found'))
+
+ return Result.ok(commit)
+ }
+
+ async getCommits() {
+ return this.db.select().from(this.scope)
+ }
+
+ async getCommitMergedAt({
+ projectId,
+ uuid,
+ }: {
+ projectId: number
+ uuid: string
+ }) {
+ if (uuid === HEAD_COMMIT) {
+ const result = await this.db
+ .select({ mergedAt: commits.mergedAt })
+ .from(this.scope)
+ .where(
+ and(eq(commits.projectId, projectId), isNotNull(commits.mergedAt)),
+ )
+ .orderBy(desc(commits.mergedAt))
+ .limit(1)
+
+ if (!result.length) {
+ return Result.error(new NotFoundError('No head commit found'))
+ }
+ const headCommit = result[0]!
+ return Result.ok(headCommit.mergedAt!)
+ }
+
+ const result = await this.db
+ .select()
+ .from(this.scope)
+ .where(eq(commits.uuid, uuid))
+ const commit = result[0]?.commits
+ if (!commit) return Result.error(new NotFoundError('Commit not found'))
+
+ return Result.ok(commit.mergedAt)
+ }
+}
diff --git a/packages/core/src/repositories/documentVersionsRepository.ts b/packages/core/src/repositories/documentVersionsRepository.ts
new file mode 100644
index 000000000..d9e9537b1
--- /dev/null
+++ b/packages/core/src/repositories/documentVersionsRepository.ts
@@ -0,0 +1,155 @@
+import { database } from '$core/client'
+import { NotFoundError, Result } from '$core/lib'
+import {
+ Commit,
+ commits,
+ DocumentVersion,
+ documentVersions,
+ projects,
+} from '$core/schema'
+import { and, eq, getTableColumns, isNotNull, lte, max } from 'drizzle-orm'
+
+import Repository from './repository'
+
+export class DocumentVersionsRepository extends Repository {
+ get scope() {
+ return this.db
+ .select(getTableColumns(documentVersions))
+ .from(documentVersions)
+ .innerJoin(projects, eq(projects.workspaceId, this.workspaceId))
+ .innerJoin(commits, eq(commits.projectId, projects.id))
+ .where(eq(documentVersions.commitId, commits.id))
+ .as('documentVersionsScope')
+ }
+
+ async getDocumentById(documentId: number) {
+ const res = await this.db
+ .select()
+ .from(this.scope)
+ .where(eq(documentVersions.id, documentId))
+
+ // NOTE: I hate this
+ const document = res[0]
+ if (!document) return Result.error(new NotFoundError('Document not found'))
+
+ return Result.ok(document)
+ }
+
+ async getDocumentByPath({ commit, path }: { commit: Commit; path: string }) {
+ try {
+ const result = await this.getDocumentsAtCommit(commit)
+ const documents = result.unwrap()
+ const document = documents.find((doc) => doc.path === path)
+ if (!document) {
+ return Result.error(
+ new NotFoundError(
+ `No document with path ${path} at commit ${commit.uuid}`,
+ ),
+ )
+ }
+
+ return Result.ok(document)
+ } catch (err) {
+ return Result.error(err as Error)
+ }
+ }
+
+ async getDocumentsAtCommit(commit: Commit) {
+ const documentsFromMergedCommits = await fetchDocumentsFromMergedCommits({
+ projectId: commit.projectId,
+ maxMergedAt: commit.mergedAt,
+ })
+
+ if (commit.mergedAt !== null) {
+ // Referenced commit is merged. No additional documents to return.
+ return Result.ok(documentsFromMergedCommits)
+ }
+
+ const documentsFromDraft = await this.db
+ .select(getTableColumns(documentVersions))
+ .from(documentVersions)
+ .innerJoin(commits, eq(commits.id, documentVersions.commitId))
+ .where(eq(commits.id, commit.id))
+
+ const totalDocuments = mergeDocuments(
+ documentsFromMergedCommits,
+ documentsFromDraft,
+ )
+
+ return Result.ok(totalDocuments)
+ }
+
+ async listCommitChanges(commit: Commit) {
+ const changedDocuments = await this.db
+ .select()
+ .from(this.scope)
+ .where(eq(documentVersions.commitId, commit.id))
+
+ return Result.ok(changedDocuments)
+ }
+}
+
+function mergeDocuments(
+ ...documentsArr: DocumentVersion[][]
+): DocumentVersion[] {
+ return documentsArr.reduce((acc, documents) => {
+ return acc
+ .filter((d) => {
+ return !documents.find((d2) => d2.documentUuid === d.documentUuid)
+ })
+ .concat(documents)
+ }, [])
+}
+
+async function fetchDocumentsFromMergedCommits(
+ {
+ projectId,
+ maxMergedAt,
+ }: {
+ projectId: number
+ maxMergedAt: Date | null
+ },
+ tx = database,
+): Promise {
+ const filterByMaxMergedAt = () => {
+ const mergedAtNotNull = isNotNull(commits.mergedAt)
+ if (maxMergedAt === null) return mergedAtNotNull
+ return and(mergedAtNotNull, lte(commits.mergedAt, maxMergedAt))
+ }
+
+ const lastVersionOfEachDocument = tx.$with('lastVersionOfDocuments').as(
+ tx
+ .select({
+ documentUuid: documentVersions.documentUuid,
+ mergedAt: max(commits.mergedAt).as('maxMergedAt'),
+ })
+ .from(documentVersions)
+ .innerJoin(commits, eq(commits.id, documentVersions.commitId))
+ .where(and(filterByMaxMergedAt(), eq(commits.projectId, projectId)))
+ .groupBy(documentVersions.documentUuid),
+ )
+
+ const documentsFromMergedCommits = await tx
+ .with(lastVersionOfEachDocument)
+ .select(getTableColumns(documentVersions))
+ .from(documentVersions)
+ .innerJoin(
+ commits,
+ and(
+ eq(commits.id, documentVersions.commitId),
+ isNotNull(commits.mergedAt),
+ ),
+ )
+ .innerJoin(
+ lastVersionOfEachDocument,
+ and(
+ eq(
+ documentVersions.documentUuid,
+ lastVersionOfEachDocument.documentUuid,
+ ),
+ eq(commits.mergedAt, lastVersionOfEachDocument.mergedAt),
+ ),
+ )
+
+ return documentsFromMergedCommits
+}
diff --git a/packages/core/src/data-access/documentVersions/getDocumentsAtCommit.test.ts b/packages/core/src/repositories/getDocumentsAtCommit.test.ts
similarity index 60%
rename from packages/core/src/data-access/documentVersions/getDocumentsAtCommit.test.ts
rename to packages/core/src/repositories/getDocumentsAtCommit.test.ts
index 2e7e3d91a..62fb93c4d 100644
--- a/packages/core/src/data-access/documentVersions/getDocumentsAtCommit.test.ts
+++ b/packages/core/src/repositories/getDocumentsAtCommit.test.ts
@@ -3,21 +3,21 @@ import { updateDocument } from '$core/services'
import { mergeCommit } from '$core/services/commits/merge'
import { describe, expect, it } from 'vitest'
-import { findCommitByUuid } from '../commits'
-import { getDocumentsAtCommit } from './getDocumentsAtCommit'
+import { CommitsRepository } from './commitsRepository'
+import { DocumentVersionsRepository } from './documentVersionsRepository'
describe('getDocumentsAtCommit', () => {
it('returns the document of the only commit', async (ctx) => {
const { project } = await ctx.factories.createProject()
+ const workspaceId = project.workspaceId
const { commit } = await ctx.factories.createDraft({ project })
const { documentVersion: doc } = await ctx.factories.createDocumentVersion({
commit,
})
- await mergeCommit({ commitId: commit.id })
+ await mergeCommit(commit)
- const result = await getDocumentsAtCommit({
- commitId: commit.id,
- })
+ const scope = new DocumentVersionsRepository(workspaceId)
+ const result = await scope.getDocumentsAtCommit(commit)
const documents = result.unwrap()
expect(documents.length).toBe(1)
@@ -26,113 +26,124 @@ describe('getDocumentsAtCommit', () => {
it('returns the right document version for each commit', async (ctx) => {
const { project } = await ctx.factories.createProject()
+ const workspaceId = project.workspaceId
+ const scope = new DocumentVersionsRepository(workspaceId)
const { commit: commit1 } = await ctx.factories.createDraft({ project })
const { documentVersion: doc } = await ctx.factories.createDocumentVersion({
commit: commit1,
content: 'VERSION 1',
})
- await mergeCommit({ commitId: commit1.id }).then((r) => r.unwrap())
+ await mergeCommit(commit1).then((r) => r.unwrap())
const { commit: commit2 } = await ctx.factories.createDraft({ project })
await updateDocument({
- commitId: commit2.id,
- documentUuid: doc.documentUuid,
+ commit: commit2,
+ document: doc,
content: 'VERSION 2',
}).then((r) => r.unwrap())
const { commit: commit3 } = await ctx.factories.createDraft({ project })
await updateDocument({
- commitId: commit3.id,
- documentUuid: doc.documentUuid,
+ commit: commit3,
+ document: doc,
content: 'VERSION 3 (draft)',
}).then((r) => r.unwrap())
- await mergeCommit({ commitId: commit2.id }).then((r) => r.unwrap())
+ await mergeCommit(commit2).then((r) => r.unwrap())
- const commit1Docs = await getDocumentsAtCommit({
- commitId: commit1.id,
- }).then((r) => r.unwrap())
+ const commit1Docs = await scope
+ .getDocumentsAtCommit(commit1)
+ .then((r) => r.unwrap())
expect(commit1Docs.length).toBe(1)
expect(commit1Docs[0]!.content).toBe('VERSION 1')
- const commit2Docs = await getDocumentsAtCommit({
- commitId: commit2.id,
- }).then((r) => r.unwrap())
+ const commit2Docs = await scope
+ .getDocumentsAtCommit(commit2)
+ .then((r) => r.unwrap())
expect(commit2Docs.length).toBe(1)
expect(commit2Docs[0]!.content).toBe('VERSION 2')
- const commit3Docs = await getDocumentsAtCommit({
- commitId: commit3.id,
- }).then((r) => r.unwrap())
+ const commit3Docs = await scope
+ .getDocumentsAtCommit(commit3)
+ .then((r) => r.unwrap())
expect(commit3Docs.length).toBe(1)
expect(commit3Docs[0]!.content).toBe('VERSION 3 (draft)')
- const headCommit = await findCommitByUuid({
- projectId: project.id,
- uuid: HEAD_COMMIT,
- }).then((r) => r.unwrap())
- const headDocs = await getDocumentsAtCommit({
- commitId: headCommit.id,
- }).then((r) => r.unwrap())
+ const commitsScope = new CommitsRepository(workspaceId)
+ const headCommit = await commitsScope
+ .getCommitByUuid({
+ projectId: project.id,
+ uuid: HEAD_COMMIT,
+ })
+ .then((r) => r.unwrap())
+ const headDocs = await scope
+ .getDocumentsAtCommit(headCommit)
+ .then((r) => r.unwrap())
expect(headDocs.length).toBe(1)
expect(headDocs[0]!.content).toBe('VERSION 2')
})
it('returns documents that were last modified in a previous commit', async (ctx) => {
const { project } = await ctx.factories.createProject()
+ const workspaceId = project.workspaceId
+ const scope = new DocumentVersionsRepository(workspaceId)
const { commit: commit1 } = await ctx.factories.createDraft({ project })
await ctx.factories.createDocumentVersion({
commit: commit1,
content: 'Doc 1 commit 1',
})
- await mergeCommit({ commitId: commit1.id }).then((r) => r.unwrap())
+
+ await mergeCommit(commit1).then((r) => r.unwrap())
const { commit: commit2 } = await ctx.factories.createDraft({ project })
const { documentVersion: doc2 } = await ctx.factories.createDocumentVersion(
{ commit: commit2, content: 'Doc 2 commit 2' },
)
- await mergeCommit({ commitId: commit2.id }).then((r) => r.unwrap())
+ await mergeCommit(commit2).then((r) => r.unwrap())
const { commit: commit3 } = await ctx.factories.createDraft({ project })
await updateDocument({
- commitId: commit3.id,
- documentUuid: doc2.documentUuid,
+ commit: commit3,
+ document: doc2,
content: 'Doc 2 commit 3 (draft)',
}).then((r) => r.unwrap())
- const commit1Docs = await getDocumentsAtCommit({
- commitId: commit1.id,
- }).then((r) => r.unwrap())
+ const commit1Docs = await scope
+ .getDocumentsAtCommit(commit1)
+ .then((r) => r.unwrap())
expect(commit1Docs.length).toBe(1)
const commit1DocContents = commit1Docs.map((d) => d.content)
expect(commit1DocContents).toContain('Doc 1 commit 1')
- const commit2Docs = await getDocumentsAtCommit({
- commitId: commit2.id,
- }).then((r) => r.unwrap())
+ const commit2Docs = await scope
+ .getDocumentsAtCommit(commit2)
+ .then((r) => r.unwrap())
expect(commit2Docs.length).toBe(2)
const commit2DocContents = commit2Docs.map((d) => d.content)
expect(commit2DocContents).toContain('Doc 1 commit 1')
expect(commit2DocContents).toContain('Doc 2 commit 2')
- const commit3Docs = await getDocumentsAtCommit({
- commitId: commit3.id,
- }).then((r) => r.unwrap())
+ const commit3Docs = await scope
+ .getDocumentsAtCommit(commit3)
+ .then((r) => r.unwrap())
expect(commit3Docs.length).toBe(2)
const commit3DocContents = commit3Docs.map((d) => d.content)
expect(commit3DocContents).toContain('Doc 1 commit 1')
expect(commit3DocContents).toContain('Doc 2 commit 3 (draft)')
- const headCommit = await findCommitByUuid({
- projectId: project.id,
- uuid: HEAD_COMMIT,
- })
- const headDocs = await getDocumentsAtCommit({
- commitId: headCommit.unwrap().id,
- }).then((r) => r.unwrap())
+ const commitsScope = new CommitsRepository(workspaceId)
+ const headCommit = await commitsScope
+ .getCommitByUuid({
+ projectId: project.id,
+ uuid: HEAD_COMMIT,
+ })
+ .then((r) => r.unwrap())
+ const headDocs = await scope
+ .getDocumentsAtCommit(headCommit)
+ .then((r) => r.unwrap())
expect(headDocs.length).toBe(2)
const headDocContents = headDocs.map((d) => d.content)
expect(headDocContents).toContain('Doc 1 commit 1')
diff --git a/packages/core/src/repositories/index.ts b/packages/core/src/repositories/index.ts
new file mode 100644
index 000000000..eaa8ae3dc
--- /dev/null
+++ b/packages/core/src/repositories/index.ts
@@ -0,0 +1,3 @@
+export * from './commitsRepository'
+export * from './projectsRepository'
+export * from './documentVersionsRepository'
diff --git a/packages/core/src/repositories/projectsRepository.ts b/packages/core/src/repositories/projectsRepository.ts
new file mode 100644
index 000000000..52196feb9
--- /dev/null
+++ b/packages/core/src/repositories/projectsRepository.ts
@@ -0,0 +1,39 @@
+import { NotFoundError, Result } from '$core/lib'
+import { projects } from '$core/schema'
+import { eq } from 'drizzle-orm'
+
+import Repository from './repository'
+
+const NOT_FOUND_MSG = 'Project not found'
+
+export class ProjectsRepository extends Repository {
+ get scope() {
+ return this.db
+ .select()
+ .from(projects)
+ .where(eq(projects.workspaceId, this.workspaceId))
+ .as('projectsScope')
+ }
+
+ async getProjectById(id: number) {
+ const result = await this.db
+ .select()
+ .from(this.scope)
+ .where(eq(projects.id, id))
+ const project = result[0]
+
+ if (!project) {
+ return Result.error(new NotFoundError(NOT_FOUND_MSG))
+ }
+
+ return Result.ok(project)
+ }
+
+ async getFirstProject() {
+ const result = await this.db.select().from(this.scope).limit(1)
+ const project = result[0]
+ if (!project) return Result.error(new NotFoundError(NOT_FOUND_MSG))
+
+ return Result.ok(project)
+ }
+}
diff --git a/packages/core/src/repositories/repository.ts b/packages/core/src/repositories/repository.ts
new file mode 100644
index 000000000..6c222a5d8
--- /dev/null
+++ b/packages/core/src/repositories/repository.ts
@@ -0,0 +1,14 @@
+import { database } from '$core/client'
+import { Subquery } from 'drizzle-orm'
+
+export default abstract class Repository {
+ protected workspaceId: number
+ protected db = database
+
+ constructor(workspaceId: number, db = database) {
+ this.workspaceId = workspaceId
+ this.db = db
+ }
+
+ abstract get scope(): Subquery
+}
diff --git a/packages/core/src/services/commits/merge.ts b/packages/core/src/services/commits/merge.ts
index f0e049659..6d71cbd7b 100644
--- a/packages/core/src/services/commits/merge.ts
+++ b/packages/core/src/services/commits/merge.ts
@@ -5,30 +5,21 @@ import {
Result,
Transaction,
} from '@latitude-data/core'
-import { LatitudeError, NotFoundError } from '$core/lib/errors'
+import { LatitudeError } from '$core/lib/errors'
import { and, eq } from 'drizzle-orm'
-export async function mergeCommit(
- { commitId }: { commitId: number },
- db = database,
-) {
+export async function mergeCommit(commit: Commit, db = database) {
return Transaction.call(async (tx) => {
const mergedAt = new Date()
-
- const commit = await tx.query.commits.findFirst({
- where: eq(commits.id, commitId),
- })
-
- if (!commit) return Result.error(new NotFoundError('Commit not found'))
-
- // Check that there is no other commit with same mergeAt in the same project
- const otherCommits = await tx.query.commits.findMany({
- where: and(
- eq(commits.projectId, commit.projectId),
- eq(commits.mergedAt, mergedAt),
- ),
- })
-
+ const otherCommits = await tx
+ .select()
+ .from(commits)
+ .where(
+ and(
+ eq(commits.projectId, commit.projectId),
+ eq(commits.mergedAt, mergedAt),
+ ),
+ )
if (otherCommits.length > 0) {
return Result.error(
new LatitudeError('Commit merge time conflict, try again'),
@@ -38,7 +29,7 @@ export async function mergeCommit(
const result = await tx
.update(commits)
.set({ mergedAt })
- .where(eq(commits.id, commitId))
+ .where(eq(commits.id, commit.id))
.returning()
const updatedCommit = result[0]!
diff --git a/packages/core/src/services/documents/create.test.ts b/packages/core/src/services/documents/create.test.ts
index d781532ba..b88cdc822 100644
--- a/packages/core/src/services/documents/create.test.ts
+++ b/packages/core/src/services/documents/create.test.ts
@@ -1,4 +1,4 @@
-import { listCommitChanges } from '$core/data-access'
+import { DocumentVersionsRepository } from '$core/repositories'
import { describe, expect, it } from 'vitest'
import { mergeCommit } from '../commits/merge'
@@ -10,14 +10,15 @@ describe('createNewDocument', () => {
const { commit } = await ctx.factories.createDraft({ project })
const documentResult = await createNewDocument({
- commitId: commit.id,
+ commit,
path: 'foo',
})
const document = documentResult.unwrap()
expect(document.path).toBe('foo')
- const commitChanges = await listCommitChanges({ commitId: commit.id })
+ const scope = new DocumentVersionsRepository(project.workspaceId)
+ const commitChanges = await scope.listCommitChanges(commit)
expect(commitChanges.value.length).toBe(1)
expect(commitChanges.value[0]!.documentUuid).toBe(document.documentUuid)
expect(commitChanges.value[0]!.path).toBe(document.path)
@@ -28,12 +29,12 @@ describe('createNewDocument', () => {
const { commit } = await ctx.factories.createDraft({ project })
await createNewDocument({
- commitId: commit.id,
+ commit,
path: 'foo',
})
const result = await createNewDocument({
- commitId: commit.id,
+ commit,
path: 'foo',
})
@@ -46,10 +47,10 @@ describe('createNewDocument', () => {
it('fails when trying to create a document in a merged commit', async (ctx) => {
const { project } = await ctx.factories.createProject()
const { commit } = await ctx.factories.createDraft({ project })
- await mergeCommit({ commitId: commit.id })
+ await mergeCommit(commit)
const result = await createNewDocument({
- commitId: commit.id,
+ commit,
path: 'foo',
})
diff --git a/packages/core/src/services/documents/create.ts b/packages/core/src/services/documents/create.ts
index 6a7827591..8a53635ae 100644
--- a/packages/core/src/services/documents/create.ts
+++ b/packages/core/src/services/documents/create.ts
@@ -1,7 +1,9 @@
import {
+ Commit,
DocumentVersion,
documentVersions,
- getDocumentsAtCommit,
+ DocumentVersionsRepository,
+ findWorkspaceFromCommit,
Result,
Transaction,
} from '@latitude-data/core'
@@ -13,20 +15,18 @@ import {
} from './utils'
export async function createNewDocument({
- commitId,
+ commit,
path,
}: {
- commitId: number
+ commit: Commit
path: string
}) {
- const commitResult = await assertCommitIsEditable(commitId)
+ const commitResult = await assertCommitIsEditable(commit)
if (commitResult.error) return commitResult
- const currentDocuments = await getDocumentsAtCommit({
- commitId,
- })
- if (currentDocuments.error) return currentDocuments
-
+ const workspace = await findWorkspaceFromCommit(commit)
+ const scope = new DocumentVersionsRepository(workspace!.id)
+ const currentDocuments = await scope.getDocumentsAtCommit(commit)
if (
existsAnotherDocumentWithSamePath({
documents: currentDocuments.value,
@@ -42,7 +42,7 @@ export async function createNewDocument({
const result = await tx
.insert(documentVersions)
.values({
- commitId,
+ commitId: commit.id,
path,
})
.returning()
diff --git a/packages/core/src/services/documents/update.test.ts b/packages/core/src/services/documents/update.test.ts
index fbc92bf46..b98b2e235 100644
--- a/packages/core/src/services/documents/update.test.ts
+++ b/packages/core/src/services/documents/update.test.ts
@@ -1,4 +1,4 @@
-import { listCommitChanges } from '$core/data-access'
+import { DocumentVersionsRepository } from '$core/repositories'
import { describe, expect, it } from 'vitest'
import { mergeCommit } from '../commits/merge'
@@ -13,19 +13,20 @@ describe('updateDocument', () => {
path: 'doc1',
content: 'Doc 1 commit 1',
})
- await mergeCommit({ commitId: commit1.id })
+ await mergeCommit(commit1)
const { commit: commit2 } = await ctx.factories.createDraft({ project })
await updateDocument({
- commitId: commit2.id,
- documentUuid: doc.documentUuid,
+ commit: commit2,
+ document: doc,
content: 'Doc 1 commit 2',
}).then((r) => r.unwrap())
- const changedDocuments = await listCommitChanges({
- commitId: commit2.id,
- }).then((r) => r.unwrap())
+ const scope = new DocumentVersionsRepository(project.workspaceId)
+ const changedDocuments = await scope
+ .listCommitChanges(commit2)
+ .then((r) => r.unwrap())
expect(changedDocuments.length).toBe(1)
expect(changedDocuments[0]!.path).toBe('doc1')
@@ -42,14 +43,15 @@ describe('updateDocument', () => {
})
await updateDocument({
- commitId: commit.id,
- documentUuid: doc.documentUuid,
+ commit,
+ document: doc,
content: 'Doc 1 v2',
}).then((r) => r.unwrap())
- const changedDocuments = await listCommitChanges({
- commitId: commit.id,
- }).then((r) => r.unwrap())
+ const scope = new DocumentVersionsRepository(project.workspaceId)
+ const changedDocuments = await scope
+ .listCommitChanges(commit)
+ .then((r) => r.unwrap())
expect(changedDocuments.length).toBe(1)
expect(changedDocuments[0]!.path).toBe('doc1')
@@ -58,6 +60,7 @@ describe('updateDocument', () => {
it('modifying a document creates a change to all other documents that reference it', async (ctx) => {
const { project } = await ctx.factories.createProject()
+ const scope = new DocumentVersionsRepository(project.workspaceId)
const { commit: commit1 } = await ctx.factories.createDraft({ project })
const { documentVersion: referencedDoc } =
await ctx.factories.createDocumentVersion({
@@ -70,19 +73,19 @@ describe('updateDocument', () => {
path: 'unmodified',
content: '',
})
- await mergeCommit({ commitId: commit1.id })
+ await mergeCommit(commit1)
const { commit: commit2 } = await ctx.factories.createDraft({ project })
await updateDocument({
- commitId: commit2.id,
- documentUuid: referencedDoc.documentUuid,
+ commit: commit2,
+ document: referencedDoc,
content: 'The document that is being referenced v2',
}).then((r) => r.unwrap())
- const changedDocuments = await listCommitChanges({
- commitId: commit2.id,
- }).then((r) => r.unwrap())
+ const changedDocuments = await scope
+ .listCommitChanges(commit2)
+ .then((r) => r.unwrap())
expect(changedDocuments.length).toBe(2)
expect(
@@ -93,6 +96,7 @@ describe('updateDocument', () => {
it('renaming a document creates a change to all other documents that reference it', async (ctx) => {
const { project } = await ctx.factories.createProject()
+ const scope = new DocumentVersionsRepository(project.workspaceId)
const { commit: commit1 } = await ctx.factories.createDraft({ project })
const { documentVersion: referencedDoc } =
await ctx.factories.createDocumentVersion({
@@ -105,19 +109,19 @@ describe('updateDocument', () => {
path: 'unmodified',
content: '',
})
- await mergeCommit({ commitId: commit1.id })
+ await mergeCommit(commit1)
const { commit: commit2 } = await ctx.factories.createDraft({ project })
await updateDocument({
- commitId: commit2.id,
- documentUuid: referencedDoc.documentUuid,
+ commit: commit2,
+ document: referencedDoc,
path: 'referenced/doc2',
}).then((r) => r.unwrap())
- const changedDocuments = await listCommitChanges({
- commitId: commit2.id,
- }).then((r) => r.unwrap())
+ const changedDocuments = await scope
+ .listCommitChanges(commit2)
+ .then((r) => r.unwrap())
expect(changedDocuments.length).toBe(2)
expect(
diff --git a/packages/core/src/services/documents/update.ts b/packages/core/src/services/documents/update.ts
index ddce70672..2eadf856f 100644
--- a/packages/core/src/services/documents/update.ts
+++ b/packages/core/src/services/documents/update.ts
@@ -1,10 +1,11 @@
import { omit } from 'lodash-es'
import { readMetadata } from '@latitude-data/compiler'
-import { getDocumentsAtCommit } from '$core/data-access'
+import { findWorkspaceFromCommit } from '$core/data-access'
import { Result, Transaction, TypedResult } from '$core/lib'
import { BadRequestError, LatitudeError, NotFoundError } from '$core/lib/errors'
-import { DocumentVersion, documentVersions } from '$core/schema'
+import { DocumentVersionsRepository } from '$core/repositories'
+import { Commit, DocumentVersion, documentVersions } from '$core/schema'
import { eq } from 'drizzle-orm'
import { assertCommitIsEditable } from './utils'
@@ -74,37 +75,42 @@ async function getUpdatedDocuments({
return Result.ok(documentsWithUpdatedHash)
}
+// TODO: refactor in smaller chunks
export async function updateDocument({
- commitId,
- documentUuid,
+ commit,
+ document,
path,
content,
deletedAt,
}: {
- commitId: number
- documentUuid: string
+ commit: Commit
+ document: DocumentVersion
path?: string
content?: string | null
deletedAt?: Date | null
}) {
- const commitResult = await assertCommitIsEditable(commitId)
+ const commitResult = await assertCommitIsEditable(commit)
if (commitResult.error) return commitResult
const updateData = Object.fromEntries(
- Object.entries({ documentUuid, path, content, deletedAt }).filter(
- ([_, v]) => v !== undefined,
- ),
+ Object.entries({
+ documentUuid: document.documentUuid,
+ path,
+ content,
+ deletedAt,
+ }).filter(([_, v]) => v !== undefined),
)
- const currentDocuments = await getDocumentsAtCommit({
- commitId,
- })
- if (currentDocuments.error) return currentDocuments
+ const workspace = await findWorkspaceFromCommit(commit)
+ const scope = new DocumentVersionsRepository(workspace!.id)
+ const documents = await scope
+ .getDocumentsAtCommit(commit)
+ .then((r) => r.unwrap())
if (
path &&
- currentDocuments.value.find(
- (d) => d.documentUuid !== documentUuid && d.path === path,
+ documents.find(
+ (d) => d.documentUuid !== document.documentUuid && d.path === path,
)
) {
return Result.error(
@@ -113,7 +119,7 @@ export async function updateDocument({
}
const documentsToUpdateResult = await getUpdatedDocuments({
- currentDocuments: currentDocuments.value,
+ currentDocuments: documents,
updateData,
})
if (documentsToUpdateResult.error) return documentsToUpdateResult
@@ -122,11 +128,11 @@ export async function updateDocument({
return Transaction.call(async (tx) => {
const results = await Promise.all(
documentsToUpdate.map(async (documentData) => {
- const isNewDocumentVersion = documentData.commitId !== commitId
+ const isNewDocumentVersion = documentData.commitId !== commit.id
const newDocumentVersion = {
...omit(documentData, ['id', 'commitId', 'updatedAt', 'createdAt']),
path: documentData.path, // <- This should not be necessary, but Typescript somehow is not sure that path is present.
- commitId,
+ commitId: commit.id,
}
if (isNewDocumentVersion) {
@@ -146,6 +152,8 @@ export async function updateDocument({
}),
)
- return Result.ok(results.find((r) => r.documentUuid === documentUuid)!)
+ return Result.ok(
+ results.find((r) => r.documentUuid === document.documentUuid)!,
+ )
})
}
diff --git a/packages/core/src/services/documents/utils.ts b/packages/core/src/services/documents/utils.ts
index bf7c65339..6ae741da1 100644
--- a/packages/core/src/services/documents/utils.ts
+++ b/packages/core/src/services/documents/utils.ts
@@ -1,17 +1,15 @@
import {
+ Commit,
DocumentVersion,
- findCommitById,
Result,
TypedResult,
} from '@latitude-data/core'
import { ForbiddenError, LatitudeError } from '$core/lib/errors'
export async function assertCommitIsEditable(
- commitId: number,
+ commit: Commit,
): Promise> {
- const commit = await findCommitById({ id: commitId })
-
- if (commit.value?.mergedAt !== null) {
+ if (commit.mergedAt !== null) {
return Result.error(
new ForbiddenError('Cannot create a document version in a merged commit'),
)
diff --git a/packages/core/src/services/projects/create.ts b/packages/core/src/services/projects/create.ts
index 22dda57c0..284b86eab 100644
--- a/packages/core/src/services/projects/create.ts
+++ b/packages/core/src/services/projects/create.ts
@@ -8,6 +8,7 @@ import {
import { createCommit } from '$core/services/commits/create'
import { mergeCommit } from '$core/services/commits/merge'
+// TODO: pass a workspace instead of workspaceId
export async function createProject(
{
workspaceId,
@@ -22,15 +23,13 @@ export async function createProject(
const project = (
await tx.insert(projects).values({ workspaceId, name }).returning()
)[0]!
- const commit = await createCommit({
+ const result = await createCommit({
commit: { projectId: project.id, title: 'Initial version' },
db: tx,
})
+ if (result.error) return result
- if (commit.error) return commit
-
- const resultMerge = await mergeCommit({ commitId: commit.value.id }, tx)
-
+ const resultMerge = await mergeCommit(result.value)
if (resultMerge.error) return resultMerge
return Result.ok(project)
diff --git a/packages/core/src/tests/factories/documents.ts b/packages/core/src/tests/factories/documents.ts
index e957e2808..3eba3dbb7 100644
--- a/packages/core/src/tests/factories/documents.ts
+++ b/packages/core/src/tests/factories/documents.ts
@@ -28,14 +28,14 @@ export async function createDocumentVersion(
}
let result = await createNewDocument({
- commitId: data.commit.id,
+ commit: data.commit,
path: data.path,
})
if (data.content) {
result = await updateDocument({
- commitId: data.commit.id,
- documentUuid: result.unwrap().documentUuid,
+ commit: data.commit,
+ document: result.unwrap(),
content: data.content,
})
}
diff --git a/packages/core/src/tests/factories/projects.ts b/packages/core/src/tests/factories/projects.ts
index 8026229cf..7834b69e3 100644
--- a/packages/core/src/tests/factories/projects.ts
+++ b/packages/core/src/tests/factories/projects.ts
@@ -1,5 +1,5 @@
import { faker } from '@faker-js/faker'
-import { getUser } from '$core/data-access'
+import { unsafelyGetUser } from '$core/data-access'
import { Workspace, type SafeUser } from '$core/schema'
import { createProject as createProjectFn } from '$core/services/projects'
@@ -15,7 +15,7 @@ export async function createProject(projectData: Partial = {}) {
let workspace: Workspace
if ('id' in workspaceData) {
- user = (await getUser(workspaceData.creatorId!)) as SafeUser
+ user = (await unsafelyGetUser(workspaceData.creatorId!)) as SafeUser
workspace = workspaceData as Workspace
} else {
const newWorkspace = await createWorkspace(workspaceData)