refactor: move convex into packages

This commit is contained in:
2025-09-16 23:17:01 +00:00
parent ff1334aa36
commit ec8ed588f5
22 changed files with 43 additions and 14 deletions

View File

@@ -0,0 +1,154 @@
import type { Doc, Id } from "@fileone/convex/_generated/dataModel"
import type {
AuthenticatedMutationCtx,
AuthenticatedQueryCtx,
} from "../functions"
import * as Err from "./error"
type Directory = {
kind: "directory"
doc: Doc<"directories">
}
type File = {
kind: "file"
doc: Doc<"files">
}
export type DirectoryItem = Directory | File
export type DirectoryItemKind = DirectoryItem["kind"]
export async function fetchContent(
ctx: AuthenticatedQueryCtx,
directoryId?: Id<"directories">,
): Promise<DirectoryItem[]> {
const [files, directories] = await Promise.all([
ctx.db
.query("files")
.withIndex("byDirectoryId", (q) =>
q
.eq("userId", ctx.user._id)
.eq("directoryId", directoryId)
.eq("deletedAt", undefined),
)
.collect(),
ctx.db
.query("directories")
.withIndex("byParentId", (q) =>
q
.eq("userId", ctx.user._id)
.eq("parentId", directoryId)
.eq("deletedAt", undefined),
)
.collect(),
])
const items: DirectoryItem[] = []
for (const directory of directories) {
items.push({ kind: "directory", doc: directory })
}
for (const file of files) {
items.push({ kind: "file", doc: file })
}
return items
}
export async function create(
ctx: AuthenticatedMutationCtx,
{ name, parentId }: { name: string; parentId?: Id<"directories"> },
): Promise<Id<"directories">> {
let parentDir: Doc<"directories"> | null = null
if (parentId) {
parentDir = await ctx.db.get(parentId)
if (!parentDir) {
throw Err.create(
Err.Code.DirectoryNotFound,
`Parent directory ${parentId} not found`,
)
}
}
const existing = await ctx.db
.query("directories")
.withIndex("uniqueDirectoryInDirectory", (q) =>
q
.eq("userId", ctx.user._id)
.eq("parentId", parentId)
.eq("name", name)
.eq("deletedAt", undefined),
)
.first()
if (existing) {
throw Err.create(
Err.Code.DirectoryExists,
`Directory with name ${name} already exists in ${parentId ? `directory ${parentId}` : "root"}`,
)
}
const now = new Date().toISOString()
return await ctx.db.insert("directories", {
name,
parentId,
userId: ctx.user._id,
createdAt: now,
updatedAt: now,
path: parentDir ? `${parentDir.path}/${name}` : "/",
})
}
export async function moveToTrashRecursive(
ctx: AuthenticatedMutationCtx,
directoryId: Id<"directories">,
): Promise<void> {
const now = new Date().toISOString()
const filesToDelete: Id<"files">[] = []
const directoriesToDelete: Id<"directories">[] = []
const directoryQueue: Id<"directories">[] = [directoryId]
while (directoryQueue.length > 0) {
const currentDirectoryId = directoryQueue.shift()!
directoriesToDelete.push(currentDirectoryId)
const files = await ctx.db
.query("files")
.withIndex("byDirectoryId", (q) =>
q
.eq("userId", ctx.user._id)
.eq("directoryId", currentDirectoryId)
.eq("deletedAt", undefined),
)
.collect()
for (const file of files) {
filesToDelete.push(file._id)
}
const subdirectories = await ctx.db
.query("directories")
.withIndex("byParentId", (q) =>
q
.eq("userId", ctx.user._id)
.eq("parentId", currentDirectoryId)
.eq("deletedAt", undefined),
)
.collect()
for (const subdirectory of subdirectories) {
directoryQueue.push(subdirectory._id)
}
}
const filePatches = filesToDelete.map((fileId) =>
ctx.db.patch(fileId, { deletedAt: now }),
)
const directoryPatches = directoriesToDelete.map((dirId) =>
ctx.db.patch(dirId, { deletedAt: now }),
)
await Promise.all([...filePatches, ...directoryPatches])
}

View File

@@ -0,0 +1,22 @@
import { ConvexError } from "convex/values"
export enum Code {
DirectoryExists = "DirectoryExists",
DirectoryNotFound = "DirectoryNotFound",
FileExists = "FileExists",
Internal = "Internal",
Unauthenticated = "Unauthenticated",
}
export type ApplicationError = ConvexError<{ code: Code; message: string }>
export function isApplicationError(error: unknown): error is ApplicationError {
return error instanceof ConvexError && "code" in error.data
}
export function create(code: Code, message?: string) {
return new ConvexError({
code,
message,
})
}

View File

@@ -0,0 +1,46 @@
import type { Id } from "../_generated/dataModel"
import type { MutationCtx, QueryCtx } from "../_generated/server"
import type { AuthenticatedMutationCtx } from "../functions"
import * as Err from "./error"
/**
* Get the current authenticated user identity
* Throws an error if the user is not authenticated */
export async function userIdentityOrThrow(ctx: QueryCtx | MutationCtx) {
const identity = await ctx.auth.getUserIdentity()
if (!identity) {
throw Err.create(Err.Code.Unauthenticated, "Not authenticated")
}
return identity
}
/**
* Get internal user document from JWT authentication
* Throws an error if the user is not authenticated
*/
export async function userOrThrow(ctx: QueryCtx | MutationCtx) {
const identity = await userIdentityOrThrow(ctx)
// Look for existing user by JWT subject
const user = await ctx.db
.query("users")
.withIndex("byJwtSubject", (q) => q.eq("jwtSubject", identity.subject))
.first()
if (!user) {
throw Err.create(
Err.Code.Unauthenticated,
"User not found - please sync user first",
)
}
return user
}
export async function register(ctx: AuthenticatedMutationCtx) {
await ctx.db.insert("users", {
jwtSubject: ctx.identity.subject,
})
}