mirror of
https://github.com/get-drexa/drive.git
synced 2025-12-01 14:01:40 +00:00
Compare commits
2 Commits
49b76934b2
...
83a5f92506
| Author | SHA1 | Date | |
|---|---|---|---|
|
83a5f92506
|
|||
|
b802cb5aec
|
@@ -1,26 +1,16 @@
|
|||||||
import { v } from "convex/values"
|
import { v } from "convex/values"
|
||||||
import type { Id } from "./_generated/dataModel"
|
import type { Id } from "./_generated/dataModel"
|
||||||
import { authenticatedMutation, authenticatedQuery } from "./functions"
|
import { authenticatedMutation, authenticatedQuery, authorizedGet } from "./functions"
|
||||||
import * as Directories from "./model/directories"
|
import * as Directories from "./model/directories"
|
||||||
import * as Files from "./model/files"
|
import * as Files from "./model/files"
|
||||||
import type { FileSystemItem } from "./model/filesystem"
|
import type { FileSystemItem } from "./model/filesystem"
|
||||||
|
|
||||||
export const generateUploadUrl = authenticatedMutation({
|
export const generateUploadUrl = authenticatedMutation({
|
||||||
handler: async (ctx) => {
|
handler: async (ctx) => {
|
||||||
// ctx.user and ctx.identity are automatically available
|
|
||||||
return await ctx.storage.generateUploadUrl()
|
return await ctx.storage.generateUploadUrl()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const generateFileUrl = authenticatedQuery({
|
|
||||||
args: {
|
|
||||||
storageId: v.id("_storage"),
|
|
||||||
},
|
|
||||||
handler: async (ctx, { storageId }) => {
|
|
||||||
return await ctx.storage.getUrl(storageId)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const fetchFiles = authenticatedQuery({
|
export const fetchFiles = authenticatedQuery({
|
||||||
args: {
|
args: {
|
||||||
directoryId: v.optional(v.id("directories")),
|
directoryId: v.optional(v.id("directories")),
|
||||||
@@ -46,6 +36,10 @@ export const fetchDirectory = authenticatedQuery({
|
|||||||
directoryId: v.id("directories"),
|
directoryId: v.id("directories"),
|
||||||
},
|
},
|
||||||
handler: async (ctx, { directoryId }) => {
|
handler: async (ctx, { directoryId }) => {
|
||||||
|
const directory = await authorizedGet(ctx, directoryId)
|
||||||
|
if (!directory) {
|
||||||
|
throw new Error("Directory not found")
|
||||||
|
}
|
||||||
return await Directories.fetch(ctx, { directoryId })
|
return await Directories.fetch(ctx, { directoryId })
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -56,6 +50,11 @@ export const createDirectory = authenticatedMutation({
|
|||||||
directoryId: v.id("directories"),
|
directoryId: v.id("directories"),
|
||||||
},
|
},
|
||||||
handler: async (ctx, { name, directoryId }): Promise<Id<"directories">> => {
|
handler: async (ctx, { name, directoryId }): Promise<Id<"directories">> => {
|
||||||
|
const parentDirectory = await authorizedGet(ctx, directoryId)
|
||||||
|
if (!parentDirectory) {
|
||||||
|
throw new Error("Parent directory not found")
|
||||||
|
}
|
||||||
|
|
||||||
return await Directories.create(ctx, {
|
return await Directories.create(ctx, {
|
||||||
name,
|
name,
|
||||||
parentId: directoryId,
|
parentId: directoryId,
|
||||||
@@ -72,6 +71,11 @@ export const saveFile = authenticatedMutation({
|
|||||||
mimeType: v.optional(v.string()),
|
mimeType: v.optional(v.string()),
|
||||||
},
|
},
|
||||||
handler: async (ctx, { name, storageId, directoryId, size, mimeType }) => {
|
handler: async (ctx, { name, storageId, directoryId, size, mimeType }) => {
|
||||||
|
const directory = await authorizedGet(ctx, directoryId)
|
||||||
|
if (!directory) {
|
||||||
|
throw new Error("Directory not found")
|
||||||
|
}
|
||||||
|
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
|
|
||||||
await ctx.db.insert("files", {
|
await ctx.db.insert("files", {
|
||||||
@@ -94,6 +98,11 @@ export const renameFile = authenticatedMutation({
|
|||||||
newName: v.string(),
|
newName: v.string(),
|
||||||
},
|
},
|
||||||
handler: async (ctx, { directoryId, itemId, newName }) => {
|
handler: async (ctx, { directoryId, itemId, newName }) => {
|
||||||
|
const file = await authorizedGet(ctx, itemId)
|
||||||
|
if (!file) {
|
||||||
|
throw new Error("File not found")
|
||||||
|
}
|
||||||
|
|
||||||
await Files.renameFile(ctx, { directoryId, itemId, newName })
|
await Files.renameFile(ctx, { directoryId, itemId, newName })
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { v } from "convex/values"
|
import { v } from "convex/values"
|
||||||
import { authenticatedMutation, authenticatedQuery } from "./functions"
|
import { authenticatedMutation, authenticatedQuery, authorizedGet } from "./functions"
|
||||||
import * as Directories from "./model/directories"
|
import * as Directories from "./model/directories"
|
||||||
import * as Err from "./model/error"
|
import * as Err from "./model/error"
|
||||||
import * as Files from "./model/files"
|
import * as Files from "./model/files"
|
||||||
@@ -22,10 +22,7 @@ export const moveItems = authenticatedMutation({
|
|||||||
items: v.array(VFileSystemHandle),
|
items: v.array(VFileSystemHandle),
|
||||||
},
|
},
|
||||||
handler: async (ctx, { targetDirectory: targetDirectoryHandle, items }) => {
|
handler: async (ctx, { targetDirectory: targetDirectoryHandle, items }) => {
|
||||||
const targetDirectory = await Directories.fetchHandle(
|
const targetDirectory = await authorizedGet(ctx, targetDirectoryHandle.id)
|
||||||
ctx,
|
|
||||||
targetDirectoryHandle,
|
|
||||||
)
|
|
||||||
if (!targetDirectory) {
|
if (!targetDirectory) {
|
||||||
throw Err.create(
|
throw Err.create(
|
||||||
Err.Code.DirectoryNotFound,
|
Err.Code.DirectoryNotFound,
|
||||||
@@ -69,6 +66,16 @@ export const moveToTrash = authenticatedMutation({
|
|||||||
handles: v.array(VFileSystemHandle),
|
handles: v.array(VFileSystemHandle),
|
||||||
},
|
},
|
||||||
handler: async (ctx, { handles }) => {
|
handler: async (ctx, { handles }) => {
|
||||||
|
for (const handle of handles) {
|
||||||
|
const item = await authorizedGet(ctx, handle.id)
|
||||||
|
if (!item) {
|
||||||
|
throw Err.create(
|
||||||
|
Err.Code.NotFound,
|
||||||
|
`Item ${handle.id} not found`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// biome-ignore lint/suspicious/useIterableCallbackReturn: switch statement is exhaustive
|
// biome-ignore lint/suspicious/useIterableCallbackReturn: switch statement is exhaustive
|
||||||
const promises = handles.map((handle) => {
|
const promises = handles.map((handle) => {
|
||||||
switch (handle.kind) {
|
switch (handle.kind) {
|
||||||
@@ -142,3 +149,17 @@ export const restoreItems = authenticatedMutation({
|
|||||||
return await FileSystem.restoreItems(ctx, { handles })
|
return await FileSystem.restoreItems(ctx, { handles })
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const fetchFileUrl = authenticatedQuery({
|
||||||
|
args: {
|
||||||
|
fileId: v.id("files"),
|
||||||
|
},
|
||||||
|
handler: async (ctx, { fileId }) => {
|
||||||
|
const file = await authorizedGet(ctx, fileId)
|
||||||
|
if (!file) {
|
||||||
|
throw Err.create(Err.Code.NotFound, "File not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return await FileSystem.fetchFileUrl(ctx, { fileId })
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import type { UserIdentity } from "convex/server"
|
import type {
|
||||||
|
DocumentByName,
|
||||||
|
TableNamesInDataModel,
|
||||||
|
UserIdentity,
|
||||||
|
} from "convex/server"
|
||||||
|
import type { GenericId } from "convex/values"
|
||||||
import {
|
import {
|
||||||
customCtx,
|
customCtx,
|
||||||
customMutation,
|
customMutation,
|
||||||
customQuery,
|
customQuery,
|
||||||
} from "convex-helpers/server/customFunctions"
|
} from "convex-helpers/server/customFunctions"
|
||||||
|
import type { DataModel } from "./_generated/dataModel"
|
||||||
import type { MutationCtx, QueryCtx } from "./_generated/server"
|
import type { MutationCtx, QueryCtx } from "./_generated/server"
|
||||||
import { mutation, query } from "./_generated/server"
|
import { mutation, query } from "./_generated/server"
|
||||||
import { type AuthUser, userIdentityOrThrow, userOrThrow } from "./model/user"
|
import { type AuthUser, userIdentityOrThrow, userOrThrow } from "./model/user"
|
||||||
@@ -43,3 +49,19 @@ export const authenticatedMutation = customMutation(
|
|||||||
return { user, identity }
|
return { user, identity }
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a document by its id and checks if the user is authorized to access it
|
||||||
|
*
|
||||||
|
* @returns The document associated with the id or null if the document is not found.
|
||||||
|
*/
|
||||||
|
export async function authorizedGet<T extends TableNamesInDataModel<DataModel>>(
|
||||||
|
ctx: AuthenticatedQueryCtx | AuthenticatedMutationCtx,
|
||||||
|
id: GenericId<T>,
|
||||||
|
): Promise<DocumentByName<DataModel, T> | null> {
|
||||||
|
const item = await ctx.db.get(id)
|
||||||
|
if (item && item.userId !== ctx.user._id) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type {
|
|||||||
AuthenticatedMutationCtx,
|
AuthenticatedMutationCtx,
|
||||||
AuthenticatedQueryCtx,
|
AuthenticatedQueryCtx,
|
||||||
} from "../functions"
|
} from "../functions"
|
||||||
|
import { authorizedGet } from "../functions"
|
||||||
import * as Err from "./error"
|
import * as Err from "./error"
|
||||||
import {
|
import {
|
||||||
type DirectoryHandle,
|
type DirectoryHandle,
|
||||||
@@ -27,8 +28,8 @@ export async function fetchHandle(
|
|||||||
ctx: AuthenticatedQueryCtx,
|
ctx: AuthenticatedQueryCtx,
|
||||||
handle: DirectoryHandle,
|
handle: DirectoryHandle,
|
||||||
): Promise<Doc<"directories">> {
|
): Promise<Doc<"directories">> {
|
||||||
const directory = await ctx.db.get(handle.id)
|
const directory = await authorizedGet(ctx, handle.id)
|
||||||
if (!directory || directory.userId !== ctx.user._id) {
|
if (!directory) {
|
||||||
throw Err.create(
|
throw Err.create(
|
||||||
Err.Code.DirectoryNotFound,
|
Err.Code.DirectoryNotFound,
|
||||||
`Directory ${handle.id} not found`,
|
`Directory ${handle.id} not found`,
|
||||||
@@ -41,7 +42,7 @@ export async function fetch(
|
|||||||
ctx: AuthenticatedQueryCtx,
|
ctx: AuthenticatedQueryCtx,
|
||||||
{ directoryId }: { directoryId: Id<"directories"> },
|
{ directoryId }: { directoryId: Id<"directories"> },
|
||||||
): Promise<DirectoryInfo> {
|
): Promise<DirectoryInfo> {
|
||||||
const directory = await ctx.db.get(directoryId)
|
const directory = await authorizedGet(ctx, directoryId)
|
||||||
if (!directory) {
|
if (!directory) {
|
||||||
throw Err.create(
|
throw Err.create(
|
||||||
Err.Code.DirectoryNotFound,
|
Err.Code.DirectoryNotFound,
|
||||||
@@ -57,7 +58,7 @@ export async function fetch(
|
|||||||
]
|
]
|
||||||
let parentDirId = directory.parentId
|
let parentDirId = directory.parentId
|
||||||
while (parentDirId) {
|
while (parentDirId) {
|
||||||
const parentDir = await ctx.db.get(parentDirId)
|
const parentDir = await authorizedGet(ctx, parentDirId)
|
||||||
if (parentDir) {
|
if (parentDir) {
|
||||||
path.push({
|
path.push({
|
||||||
handle: newDirectoryHandle(parentDir._id),
|
handle: newDirectoryHandle(parentDir._id),
|
||||||
@@ -65,7 +66,7 @@ export async function fetch(
|
|||||||
})
|
})
|
||||||
parentDirId = parentDir.parentId
|
parentDirId = parentDir.parentId
|
||||||
} else {
|
} else {
|
||||||
throw Err.create(Err.Code.Internal)
|
throw Err.create(Err.Code.DirectoryNotFound, "Parent directory not found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +133,7 @@ export async function create(
|
|||||||
ctx: AuthenticatedMutationCtx,
|
ctx: AuthenticatedMutationCtx,
|
||||||
{ name, parentId }: { name: string; parentId: Id<"directories"> },
|
{ name, parentId }: { name: string; parentId: Id<"directories"> },
|
||||||
): Promise<Id<"directories">> {
|
): Promise<Id<"directories">> {
|
||||||
const parentDir = await ctx.db.get(parentId)
|
const parentDir = await authorizedGet(ctx, parentId)
|
||||||
if (!parentDir) {
|
if (!parentDir) {
|
||||||
throw Err.create(
|
throw Err.create(
|
||||||
Err.Code.DirectoryNotFound,
|
Err.Code.DirectoryNotFound,
|
||||||
@@ -180,7 +181,7 @@ export async function move(
|
|||||||
) {
|
) {
|
||||||
const conflictCheckResults = await Promise.allSettled(
|
const conflictCheckResults = await Promise.allSettled(
|
||||||
sourceDirectories.map((directory) =>
|
sourceDirectories.map((directory) =>
|
||||||
ctx.db.get(directory.id).then((d) => {
|
authorizedGet(ctx, directory.id).then((d) => {
|
||||||
if (!d) {
|
if (!d) {
|
||||||
throw Err.create(
|
throw Err.create(
|
||||||
Err.Code.DirectoryNotFound,
|
Err.Code.DirectoryNotFound,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Doc, Id } from "../_generated/dataModel"
|
import type { Doc, Id } from "../_generated/dataModel"
|
||||||
import type { AuthenticatedMutationCtx } from "../functions"
|
import { type AuthenticatedMutationCtx, authorizedGet } from "../functions"
|
||||||
import * as Err from "./error"
|
import * as Err from "./error"
|
||||||
import type { DirectoryHandle, FileHandle } from "./filesystem"
|
import type { DirectoryHandle, FileHandle } from "./filesystem"
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ export async function move(
|
|||||||
) {
|
) {
|
||||||
const conflictCheckResults = await Promise.allSettled(
|
const conflictCheckResults = await Promise.allSettled(
|
||||||
items.map((fileHandle) =>
|
items.map((fileHandle) =>
|
||||||
ctx.db.get(fileHandle.id).then((f) => {
|
authorizedGet(ctx, fileHandle.id).then((f) => {
|
||||||
if (!f) {
|
if (!f) {
|
||||||
throw Err.create(
|
throw Err.create(
|
||||||
Err.Code.FileNotFound,
|
Err.Code.FileNotFound,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type {
|
|||||||
AuthenticatedMutationCtx,
|
AuthenticatedMutationCtx,
|
||||||
AuthenticatedQueryCtx,
|
AuthenticatedQueryCtx,
|
||||||
} from "../functions"
|
} from "../functions"
|
||||||
|
import { authorizedGet } from "../functions"
|
||||||
import * as Directories from "./directories"
|
import * as Directories from "./directories"
|
||||||
import * as Err from "./error"
|
import * as Err from "./error"
|
||||||
import * as Files from "./files"
|
import * as Files from "./files"
|
||||||
@@ -295,3 +296,20 @@ export async function emptyTrash(
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchFileUrl(
|
||||||
|
ctx: AuthenticatedQueryCtx,
|
||||||
|
{ fileId }: { fileId: Id<"files"> },
|
||||||
|
): Promise<string> {
|
||||||
|
const file = await authorizedGet(ctx, fileId)
|
||||||
|
if (!file) {
|
||||||
|
throw Err.create(Err.Code.NotFound, "file not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = await ctx.storage.getUrl(file.storageId)
|
||||||
|
if (!url) {
|
||||||
|
throw Err.create(Err.Code.NotFound, "file not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,10 +12,12 @@ const schema = defineSchema({
|
|||||||
createdAt: v.number(),
|
createdAt: v.number(),
|
||||||
updatedAt: v.number(),
|
updatedAt: v.number(),
|
||||||
deletedAt: v.optional(v.number()),
|
deletedAt: v.optional(v.number()),
|
||||||
|
lastAccessedAt: v.optional(v.number()),
|
||||||
})
|
})
|
||||||
.index("byDirectoryId", ["userId", "directoryId", "deletedAt"])
|
.index("byDirectoryId", ["userId", "directoryId", "deletedAt"])
|
||||||
.index("byUserId", ["userId", "deletedAt"])
|
.index("byUserId", ["userId", "deletedAt"])
|
||||||
.index("byDeletedAt", ["deletedAt"])
|
.index("byDeletedAt", ["deletedAt"])
|
||||||
|
.index("byLastAccessedAt", ["userId", "lastAccessedAt"])
|
||||||
.index("uniqueFileInDirectory", [
|
.index("uniqueFileInDirectory", [
|
||||||
"userId",
|
"userId",
|
||||||
"directoryId",
|
"directoryId",
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ export function ImagePreviewDialog({
|
|||||||
file: Doc<"files">
|
file: Doc<"files">
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
}) {
|
}) {
|
||||||
const fileUrl = useConvexQuery(api.files.generateFileUrl, {
|
const fileUrl = useConvexQuery(api.filesystem.fetchFileUrl, {
|
||||||
storageId: file.storageId,
|
fileId: file._id,
|
||||||
})
|
})
|
||||||
const setZoomLevel = useSetAtom(zoomLevelAtom)
|
const setZoomLevel = useSetAtom(zoomLevelAtom)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user