refactor: migrate to vite and restructure repo

Co-authored-by: Ona <no-reply@ona.com>
This commit is contained in:
2025-10-18 14:02:20 +00:00
parent 83a5f92506
commit 25796ab609
94 changed files with 478 additions and 312 deletions

View File

@@ -1,17 +1,17 @@
import type { Doc, Id } from "@fileone/convex/_generated/dataModel"
import type { Doc, Id } from "../_generated/dataModel"
import type {
AuthenticatedMutationCtx,
AuthenticatedQueryCtx,
} from "../functions"
import { authorizedGet } from "../functions"
import * as Err from "./error"
import * as Err from "../shared/error"
import {
type DirectoryHandle,
type DirectoryPath,
type FileSystemItem,
FileType,
newDirectoryHandle,
} from "./filesystem"
} from "../shared/filesystem"
export type DirectoryInfo = Doc<"directories"> & { path: DirectoryPath }

View File

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

View File

@@ -1,7 +1,7 @@
import type { Doc, Id } from "../_generated/dataModel"
import { type AuthenticatedMutationCtx, authorizedGet } from "../functions"
import * as Err from "./error"
import type { DirectoryHandle, FileHandle } from "./filesystem"
import * as Err from "../shared/error"
import type { DirectoryHandle, FileHandle } from "../shared/filesystem"
export async function renameFile(
ctx: AuthenticatedMutationCtx,

View File

@@ -1,89 +1,24 @@
import { v } from "convex/values"
import type { Doc, Id } from "../_generated/dataModel"
import type {
AuthenticatedMutationCtx,
AuthenticatedQueryCtx,
import {
type AuthenticatedMutationCtx,
type AuthenticatedQueryCtx,
authorizedGet,
} from "../functions"
import { authorizedGet } from "../functions"
import * as Err from "../shared/error"
import type {
DirectoryHandle,
FileHandle,
FileSystemHandle,
} from "../shared/filesystem"
import {
FileType,
newDirectoryHandle,
newFileHandle,
} from "../shared/filesystem"
import * as Directories from "./directories"
import * as Err from "./error"
import * as Files from "./files"
export enum FileType {
File = "File",
Directory = "Directory",
}
export type Directory = {
kind: FileType.Directory
doc: Doc<"directories">
}
export type File = {
kind: FileType.File
doc: Doc<"files">
}
export type FileSystemItem = Directory | File
export type DirectoryPathComponent = {
handle: DirectoryHandle
name: string
}
export type FilePathComponent = {
handle: FileHandle
name: string
}
export type PathComponent = FilePathComponent | DirectoryPathComponent
export type DirectoryPath = [
DirectoryPathComponent,
...DirectoryPathComponent[],
]
export type FilePath = [...DirectoryPathComponent[], PathComponent]
export type ReverseFilePath = [PathComponent, ...DirectoryPathComponent[]]
export type DirectoryHandle = {
kind: FileType.Directory
id: Id<"directories">
}
export type FileHandle = {
kind: FileType.File
id: Id<"files">
}
export type FileSystemHandle = DirectoryHandle | FileHandle
export type DeleteResult = {
deleted: {
files: number
directories: number
}
errors: Err.ApplicationErrorData[]
}
export function newFileSystemHandle(item: FileSystemItem): FileSystemHandle {
console.log("item", item)
switch (item.kind) {
case FileType.File:
return { kind: item.kind, id: item.doc._id }
case FileType.Directory:
return { kind: item.kind, id: item.doc._id }
}
}
export function isSameHandle(
handle1: FileSystemHandle,
handle2: FileSystemHandle,
): boolean {
return handle1.kind === handle2.kind && handle1.id === handle2.id
}
export function newDirectoryHandle(id: Id<"directories">): DirectoryHandle {
return { kind: FileType.Directory, id }
}
export function newFileHandle(id: Id<"files">): FileHandle {
return { kind: FileType.File, id }
}
export const VDirectoryHandle = v.object({
kind: v.literal(FileType.Directory),
id: v.id("directories"),
@@ -95,7 +30,7 @@ export const VFileHandle = v.object({
export const VFileSystemHandle = v.union(VFileHandle, VDirectoryHandle)
export async function queryRootDirectory(
ctx: AuthenticatedQueryCtx,
ctx: AuthenticatedQueryCtx | AuthenticatedMutationCtx,
): Promise<Doc<"directories"> | null> {
return await ctx.db
.query("directories")
@@ -134,24 +69,19 @@ async function collectAllHandlesRecursively(
const fileHandles: FileHandle[] = []
const directoryHandles: DirectoryHandle[] = []
// Process each handle to collect files and directories
for (const handle of handles) {
// Use a queue to process items iteratively instead of recursively
const queue: FileSystemHandle[] = [handle]
while (queue.length > 0) {
const currentHandle = queue.shift()!
// Add current item to appropriate collection
if (currentHandle.kind === FileType.File) {
fileHandles.push(currentHandle)
} else {
directoryHandles.push(currentHandle)
}
// If it's a directory, collect all children and add them to the queue
if (currentHandle.kind === FileType.Directory) {
// Get all child directories that are in trash (deletedAt >= 0)
const childDirectories = await ctx.db
.query("directories")
.withIndex("byParentId", (q) =>
@@ -162,7 +92,6 @@ async function collectAllHandlesRecursively(
)
.collect()
// Get all child files that are in trash (deletedAt >= 0)
const childFiles = await ctx.db
.query("files")
.withIndex("byDirectoryId", (q) =>
@@ -173,16 +102,12 @@ async function collectAllHandlesRecursively(
)
.collect()
// Add child directories to queue for processing
for (const childDir of childDirectories) {
const childHandle = newDirectoryHandle(childDir._id)
queue.push(childHandle)
queue.push(newDirectoryHandle(childDir._id))
}
// Add child files to file handles collection
for (const childFile of childFiles) {
const childFileHandle = newFileHandle(childFile._id)
fileHandles.push(childFileHandle)
fileHandles.push(newFileHandle(childFile._id))
}
}
}
@@ -199,17 +124,14 @@ export async function restoreItems(
ctx: AuthenticatedMutationCtx,
{ handles }: { handles: FileSystemHandle[] },
) {
// Collect all items to restore (including nested items)
const { fileHandles, directoryHandles } =
await collectAllHandlesRecursively(ctx, { handles })
// Restore files and directories by unsetting deletedAt
const [filesResult, directoriesResult] = await Promise.all([
Files.restore(ctx, { items: fileHandles }),
Directories.restore(ctx, { items: directoryHandles }),
])
// Combine results, handling null responses
return {
restored: {
files: filesResult?.restored || 0,
@@ -225,20 +147,15 @@ export async function restoreItems(
export async function deleteItemsPermanently(
ctx: AuthenticatedMutationCtx,
{ handles }: { handles: FileSystemHandle[] },
): Promise<DeleteResult> {
// Collect all items to delete (including nested items)
const {
fileHandles: fileHandlesToDelete,
directoryHandles: directoryHandlesToDelete,
} = await collectAllHandlesRecursively(ctx, { handles })
) {
const { fileHandles, directoryHandles } =
await collectAllHandlesRecursively(ctx, { handles })
// Delete files and directories using their respective models
const [filesResult, directoriesResult] = await Promise.all([
Files.deletePermanently(ctx, { items: fileHandlesToDelete }),
Directories.deletePermanently(ctx, { items: directoryHandlesToDelete }),
Files.deletePermanently(ctx, { items: fileHandles }),
Directories.deletePermanently(ctx, { items: directoryHandles }),
])
// Combine results, handling null responses
return {
deleted: {
files: filesResult?.deleted || 0,
@@ -251,9 +168,7 @@ export async function deleteItemsPermanently(
}
}
export async function emptyTrash(
ctx: AuthenticatedMutationCtx,
): Promise<DeleteResult> {
export async function emptyTrash(ctx: AuthenticatedMutationCtx) {
const rootDir = await queryRootDirectory(ctx)
if (!rootDir) {
throw Err.create(Err.Code.NotFound, "user root directory not found")

View File

@@ -1,6 +1,6 @@
import type { MutationCtx, QueryCtx } from "../_generated/server"
import { authComponent } from "../auth"
import * as Err from "./error"
import * as Err from "../shared/error"
export type AuthUser = Awaited<ReturnType<typeof authComponent.getAuthUser>>