Add traverse access level for folder navigation without file access
When a permission is set on a nested folder, traverse is automatically granted on all ancestor folders so users can navigate to it. Traverse only shows subfolders in listings — files are hidden. This prevents orphaned permissions where a user has access to a nested folder but can't reach it. Hierarchy: traverse < view < edit < admin
This commit is contained in:
@@ -54,7 +54,7 @@ export const StoragePermissionService = {
|
||||
return false
|
||||
},
|
||||
|
||||
async getAccessLevel(db: PostgresJsDatabase<any>, folderId: string, userId: string): Promise<'admin' | 'edit' | 'view' | null> {
|
||||
async getAccessLevel(db: PostgresJsDatabase<any>, folderId: string, userId: string): Promise<'admin' | 'edit' | 'view' | 'traverse' | null> {
|
||||
const [folder] = await db.select({ isPublic: storageFolders.isPublic, createdBy: storageFolders.createdBy }).from(storageFolders).where(eq(storageFolders.id, folderId)).limit(1)
|
||||
if (!folder) return null
|
||||
|
||||
@@ -108,9 +108,49 @@ export const StoragePermissionService = {
|
||||
userId: userId ?? null,
|
||||
accessLevel: accessLevel as any,
|
||||
}).returning()
|
||||
|
||||
// Auto-grant 'traverse' up the ancestor chain so the user can navigate to this folder
|
||||
if (accessLevel !== 'traverse') {
|
||||
await this.ensureTraverseAncestors(db, folderId, roleId, userId)
|
||||
}
|
||||
|
||||
return perm
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensure all ancestor folders have at least 'traverse' access for this role/user.
|
||||
* Never upgrades — only adds traverse where there's no existing permission.
|
||||
*/
|
||||
async ensureTraverseAncestors(db: PostgresJsDatabase<any>, folderId: string, roleId: string | undefined, userId: string | undefined) {
|
||||
const [folder] = await db.select({ parentId: storageFolders.parentId }).from(storageFolders).where(eq(storageFolders.id, folderId)).limit(1)
|
||||
if (!folder?.parentId) return
|
||||
|
||||
let currentParentId: string | null = folder.parentId
|
||||
while (currentParentId) {
|
||||
// Check if this role/user already has any permission on this ancestor
|
||||
const whereClause = roleId
|
||||
? and(eq(storageFolderPermissions.folderId, currentParentId), eq(storageFolderPermissions.roleId, roleId))
|
||||
: and(eq(storageFolderPermissions.folderId, currentParentId), eq(storageFolderPermissions.userId, userId!))
|
||||
|
||||
const [existing] = await db.select({ id: storageFolderPermissions.id }).from(storageFolderPermissions).where(whereClause).limit(1)
|
||||
|
||||
if (!existing) {
|
||||
// No permission — add traverse
|
||||
await db.insert(storageFolderPermissions).values({
|
||||
folderId: currentParentId,
|
||||
roleId: roleId ?? null,
|
||||
userId: userId ?? null,
|
||||
accessLevel: 'traverse' as any,
|
||||
})
|
||||
}
|
||||
// If they already have any permission (even traverse), don't touch it
|
||||
|
||||
// Walk up
|
||||
const [parent] = await db.select({ parentId: storageFolders.parentId }).from(storageFolders).where(eq(storageFolders.id, currentParentId)).limit(1)
|
||||
currentParentId = parent?.parentId ?? null
|
||||
}
|
||||
},
|
||||
|
||||
async removePermission(db: PostgresJsDatabase<any>, permissionId: string) {
|
||||
const [perm] = await db.delete(storageFolderPermissions).where(eq(storageFolderPermissions.id, permissionId)).returning()
|
||||
return perm ?? null
|
||||
|
||||
@@ -66,7 +66,7 @@ export const WebDavService = {
|
||||
/**
|
||||
* List children of a folder (or root) as DAV resources.
|
||||
*/
|
||||
async listChildren(db: PostgresJsDatabase<any>, folderId: string | null, basePath: string): Promise<DavResource[]> {
|
||||
async listChildren(db: PostgresJsDatabase<any>, folderId: string | null, basePath: string, foldersOnly = false): Promise<DavResource[]> {
|
||||
const resources: DavResource[] = []
|
||||
|
||||
// Sub-folders
|
||||
@@ -85,8 +85,8 @@ export const WebDavService = {
|
||||
})
|
||||
}
|
||||
|
||||
// Files (only if we're inside a folder, not root)
|
||||
if (folderId) {
|
||||
// Files (only if we're inside a folder, not root, and not traverse-only)
|
||||
if (folderId && !foldersOnly) {
|
||||
const files = await db.select().from(storageFiles)
|
||||
.where(eq(storageFiles.folderId, folderId))
|
||||
.orderBy(storageFiles.filename)
|
||||
|
||||
Reference in New Issue
Block a user