Advanced file sharing
This is a WIP for advanced file sharing, but I won't pursue this for now since using symlinks in the file system works for me, and this is adding a ton of complexity I don't want or need right now.
This commit is contained in:
@@ -4,24 +4,24 @@ import { humanFileSize, TRASH_PATH } from '/lib/utils/files.ts';
|
|||||||
interface ListFilesProps {
|
interface ListFilesProps {
|
||||||
directories: Directory[];
|
directories: Directory[];
|
||||||
files: DirectoryFile[];
|
files: DirectoryFile[];
|
||||||
onClickDeleteDirectory: (parentPath: string, name: string) => Promise<void>;
|
|
||||||
onClickDeleteFile: (parentPath: string, name: string) => Promise<void>;
|
|
||||||
onClickOpenRenameDirectory: (parentPath: string, name: string) => void;
|
onClickOpenRenameDirectory: (parentPath: string, name: string) => void;
|
||||||
onClickOpenRenameFile: (parentPath: string, name: string) => void;
|
onClickOpenRenameFile: (parentPath: string, name: string) => void;
|
||||||
onClickOpenMoveDirectory: (parentPath: string, name: string) => void;
|
onClickOpenMoveDirectory: (parentPath: string, name: string) => void;
|
||||||
onClickOpenMoveFile: (parentPath: string, name: string) => void;
|
onClickOpenMoveFile: (parentPath: string, name: string) => void;
|
||||||
|
onClickDeleteDirectory: (parentPath: string, name: string) => Promise<void>;
|
||||||
|
onClickDeleteFile: (parentPath: string, name: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ListFiles(
|
export default function ListFiles(
|
||||||
{
|
{
|
||||||
directories,
|
directories,
|
||||||
files,
|
files,
|
||||||
onClickDeleteDirectory,
|
|
||||||
onClickDeleteFile,
|
|
||||||
onClickOpenRenameDirectory,
|
onClickOpenRenameDirectory,
|
||||||
onClickOpenRenameFile,
|
onClickOpenRenameFile,
|
||||||
onClickOpenMoveDirectory,
|
onClickOpenMoveDirectory,
|
||||||
onClickOpenMoveFile,
|
onClickOpenMoveFile,
|
||||||
|
onClickDeleteDirectory,
|
||||||
|
onClickDeleteFile,
|
||||||
}: ListFilesProps,
|
}: ListFilesProps,
|
||||||
) {
|
) {
|
||||||
const dateFormat = new Intl.DateTimeFormat('en-GB', {
|
const dateFormat = new Intl.DateTimeFormat('en-GB', {
|
||||||
|
|||||||
@@ -452,12 +452,12 @@ export default function MainFiles({ initialDirectories, initialFiles, initialPat
|
|||||||
<ListFiles
|
<ListFiles
|
||||||
directories={directories.value}
|
directories={directories.value}
|
||||||
files={files.value}
|
files={files.value}
|
||||||
onClickDeleteDirectory={onClickDeleteDirectory}
|
|
||||||
onClickDeleteFile={onClickDeleteFile}
|
|
||||||
onClickOpenRenameDirectory={onClickOpenRenameDirectory}
|
onClickOpenRenameDirectory={onClickOpenRenameDirectory}
|
||||||
onClickOpenRenameFile={onClickOpenRenameFile}
|
onClickOpenRenameFile={onClickOpenRenameFile}
|
||||||
onClickOpenMoveDirectory={onClickOpenMoveDirectory}
|
onClickOpenMoveDirectory={onClickOpenMoveDirectory}
|
||||||
onClickOpenMoveFile={onClickOpenMoveFile}
|
onClickOpenMoveFile={onClickOpenMoveFile}
|
||||||
|
onClickDeleteDirectory={onClickDeleteDirectory}
|
||||||
|
onClickDeleteFile={onClickDeleteFile}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
|
|||||||
56
db-migrations/005-files-app.pgsql
Normal file
56
db-migrations/005-files-app.pgsql
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
SET statement_timeout = 0;
|
||||||
|
SET lock_timeout = 0;
|
||||||
|
SET idle_in_transaction_session_timeout = 0;
|
||||||
|
SET client_encoding = 'UTF8';
|
||||||
|
SET standard_conforming_strings = on;
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
SET check_function_bodies = false;
|
||||||
|
SET xmloption = content;
|
||||||
|
SET client_min_messages = warning;
|
||||||
|
SET row_security = off;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: bewcloud_file_shares; Type: TABLE; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.bewcloud_file_shares (
|
||||||
|
id uuid DEFAULT gen_random_uuid(),
|
||||||
|
owner_user_id uuid DEFAULT gen_random_uuid(),
|
||||||
|
owner_parent_path text NOT NULL,
|
||||||
|
parent_path text NOT NULL,
|
||||||
|
name text NOT NULL,
|
||||||
|
type varchar NOT NULL,
|
||||||
|
user_ids_with_read_access uuid[] NOT NULL,
|
||||||
|
user_ids_with_write_access uuid[] NOT NULL,
|
||||||
|
extra jsonb NOT NULL,
|
||||||
|
created_at timestamp with time zone DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE public.bewcloud_file_shares OWNER TO postgres;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX bewcloud_file_shares_unique_index ON public.bewcloud_file_shares ( owner_user_id, owner_parent_path, name, type );
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: bewcloud_file_shares bewcloud_file_shares_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.bewcloud_file_shares
|
||||||
|
ADD CONSTRAINT bewcloud_file_shares_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: bewcloud_file_shares bewcloud_file_shares_owner_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.bewcloud_file_shares
|
||||||
|
ADD CONSTRAINT bewcloud_file_shares_owner_user_id_fkey FOREIGN KEY (owner_user_id) REFERENCES public.bewcloud_users(id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: TABLE bewcloud_file_shares; Type: ACL; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
GRANT ALL ON TABLE public.bewcloud_file_shares TO postgres;
|
||||||
@@ -1,47 +1,29 @@
|
|||||||
import { join } from 'std/path/join.ts';
|
import { join } from 'std/path/join.ts';
|
||||||
|
|
||||||
// import Database, { sql } from '/lib/interfaces/database.ts';
|
import Database, { sql } from '/lib/interfaces/database.ts';
|
||||||
import { getFilesRootPath } from '/lib/config.ts';
|
import { getFilesRootPath } from '/lib/config.ts';
|
||||||
import { Directory, DirectoryFile, FileShare } from '/lib/types.ts';
|
import { Directory, DirectoryFile, FileShare, FileShareLink } from '/lib/types.ts';
|
||||||
import { sortDirectoriesByName, sortEntriesByName, sortFilesByName, TRASH_PATH } from '/lib/utils/files.ts';
|
import { sortDirectoriesByName, sortEntriesByName, sortFilesByName, TRASH_PATH } from '/lib/utils/files.ts';
|
||||||
|
|
||||||
// const db = new Database();
|
const db = new Database();
|
||||||
|
|
||||||
export async function getDirectories(userId: string, path: string): Promise<Directory[]> {
|
export async function getDirectories(userId: string, path: string): Promise<Directory[]> {
|
||||||
const rootPath = join(getFilesRootPath(), userId, path);
|
const rootPath = join(getFilesRootPath(), userId, path);
|
||||||
|
|
||||||
// const directoryShares = await db.query<FileShare>(sql`SELECT * FROM "bewcloud_file_shares"
|
const directoryShares = await db.query<FileShare>(
|
||||||
// WHERE "parent_path" = $2
|
sql`SELECT * FROM "bewcloud_file_shares"
|
||||||
// AND "type" = 'directory'
|
WHERE "parent_path" = $2
|
||||||
// AND (
|
AND "type" = 'directory'
|
||||||
// "owner_user_id" = $1
|
AND (
|
||||||
// OR ANY("user_ids_with_read_access") = $1
|
"owner_user_id" = $1
|
||||||
// OR ANY("user_ids_with_write_access") = $1
|
OR ANY("user_ids_with_read_access") = $1
|
||||||
// )`, [
|
OR ANY("user_ids_with_write_access") = $1
|
||||||
// userId,
|
)`,
|
||||||
// path,
|
[
|
||||||
// ]);
|
userId,
|
||||||
|
path,
|
||||||
const directoryShares: FileShare[] = [];
|
],
|
||||||
|
);
|
||||||
// TODO: Remove this mock test
|
|
||||||
if (path === '/') {
|
|
||||||
directoryShares.push({
|
|
||||||
id: 'test-ing-123',
|
|
||||||
owner_user_id: userId,
|
|
||||||
parent_path: '/',
|
|
||||||
name: 'Testing',
|
|
||||||
type: 'directory',
|
|
||||||
user_ids_with_read_access: [],
|
|
||||||
user_ids_with_write_access: [],
|
|
||||||
extra: {
|
|
||||||
read_share_links: [],
|
|
||||||
write_share_links: [],
|
|
||||||
},
|
|
||||||
updated_at: new Date('2024-04-01'),
|
|
||||||
created_at: new Date('2024-03-31'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const directories: Directory[] = [];
|
const directories: Directory[] = [];
|
||||||
|
|
||||||
@@ -66,7 +48,26 @@ export async function getDirectories(userId: string, path: string): Promise<Dire
|
|||||||
directories.push(directory);
|
directories.push(directory);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add directoryShares that aren't owned by this user
|
// Add directoryShares that aren't owned by this user
|
||||||
|
const foreignDirectoryShares = directoryShares.filter((directoryShare) => directoryShare.owner_user_id !== userId);
|
||||||
|
for (const share of foreignDirectoryShares) {
|
||||||
|
const stat = await Deno.stat(join(getFilesRootPath(), share.owner_user_id, path, share.name));
|
||||||
|
|
||||||
|
const hasWriteAccess = share.user_ids_with_write_access.includes(userId);
|
||||||
|
|
||||||
|
const directory: Directory = {
|
||||||
|
owner_user_id: share.owner_user_id,
|
||||||
|
parent_path: path,
|
||||||
|
directory_name: share.name,
|
||||||
|
has_write_access: hasWriteAccess,
|
||||||
|
file_share: share,
|
||||||
|
size_in_bytes: stat.size,
|
||||||
|
updated_at: stat.mtime || new Date(),
|
||||||
|
created_at: stat.birthtime || new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
directories.push(directory);
|
||||||
|
}
|
||||||
|
|
||||||
directories.sort(sortDirectoriesByName);
|
directories.sort(sortDirectoriesByName);
|
||||||
|
|
||||||
@@ -76,19 +77,20 @@ export async function getDirectories(userId: string, path: string): Promise<Dire
|
|||||||
export async function getFiles(userId: string, path: string): Promise<DirectoryFile[]> {
|
export async function getFiles(userId: string, path: string): Promise<DirectoryFile[]> {
|
||||||
const rootPath = join(getFilesRootPath(), userId, path);
|
const rootPath = join(getFilesRootPath(), userId, path);
|
||||||
|
|
||||||
// const fileShares = await db.query<FileShare>(sql`SELECT * FROM "bewcloud_file_shares"
|
const fileShares = await db.query<FileShare>(
|
||||||
// WHERE "parent_path" = $2
|
sql`SELECT * FROM "bewcloud_file_shares"
|
||||||
// AND "type" = 'file'
|
WHERE "parent_path" = $2
|
||||||
// AND (
|
AND "type" = 'file'
|
||||||
// "owner_user_id" = $1
|
AND (
|
||||||
// OR ANY("user_ids_with_read_access") = $1
|
"owner_user_id" = $1
|
||||||
// OR ANY("user_ids_with_write_access") = $1
|
OR ANY("user_ids_with_read_access") = $1
|
||||||
// )`, [
|
OR ANY("user_ids_with_write_access") = $1
|
||||||
// userId,
|
)`,
|
||||||
// path,
|
[
|
||||||
// ]);
|
userId,
|
||||||
|
path,
|
||||||
const fileShares: FileShare[] = [];
|
],
|
||||||
|
);
|
||||||
|
|
||||||
const files: DirectoryFile[] = [];
|
const files: DirectoryFile[] = [];
|
||||||
|
|
||||||
@@ -113,7 +115,28 @@ export async function getFiles(userId: string, path: string): Promise<DirectoryF
|
|||||||
files.push(file);
|
files.push(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add fileShares that aren't owned by this user
|
// Add fileShares that aren't owned by this user
|
||||||
|
const foreignFileShares = fileShares.filter((fileShare) => fileShare.owner_user_id !== userId);
|
||||||
|
for (const share of foreignFileShares) {
|
||||||
|
const stat = await Deno.stat(join(getFilesRootPath(), share.owner_user_id, path, share.name));
|
||||||
|
|
||||||
|
const hasWriteAccess = share.user_ids_with_write_access.includes(userId);
|
||||||
|
|
||||||
|
const file: DirectoryFile = {
|
||||||
|
owner_user_id: share.owner_user_id,
|
||||||
|
parent_path: path,
|
||||||
|
file_name: share.name,
|
||||||
|
has_write_access: hasWriteAccess,
|
||||||
|
file_share: share,
|
||||||
|
size_in_bytes: stat.size,
|
||||||
|
updated_at: stat.mtime || new Date(),
|
||||||
|
created_at: stat.birthtime || new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
files.push(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Check fileshare directories and list files from there too
|
||||||
|
|
||||||
files.sort(sortFilesByName);
|
files.sort(sortFilesByName);
|
||||||
|
|
||||||
@@ -129,7 +152,7 @@ async function getPathEntries(userId: string, path: string): Promise<Deno.DirEnt
|
|||||||
await Deno.stat(rootPath);
|
await Deno.stat(rootPath);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.toString().includes('NotFound')) {
|
if (error.toString().includes('NotFound')) {
|
||||||
await Deno.mkdir(rootPath, { recursive: true });
|
await Deno.mkdir(join(rootPath, TRASH_PATH), { recursive: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,7 +194,22 @@ export async function renameDirectoryOrFile(
|
|||||||
try {
|
try {
|
||||||
await Deno.rename(join(oldRootPath, oldName), join(newRootPath, newName));
|
await Deno.rename(join(oldRootPath, oldName), join(newRootPath, newName));
|
||||||
|
|
||||||
// TODO: Update any matching file shares
|
// Update any matching file shares
|
||||||
|
await db.query<FileShare>(
|
||||||
|
sql`UPDATE "bewcloud_file_shares" SET
|
||||||
|
"parent_path" = $4,
|
||||||
|
"name" = $5
|
||||||
|
WHERE "parent_path" = $2
|
||||||
|
AND "name" = $3
|
||||||
|
AND "owner_user_id" = $1`,
|
||||||
|
[
|
||||||
|
userId,
|
||||||
|
oldPath,
|
||||||
|
oldName,
|
||||||
|
newPath,
|
||||||
|
newName,
|
||||||
|
],
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return false;
|
return false;
|
||||||
@@ -190,7 +228,18 @@ export async function deleteDirectoryOrFile(userId: string, path: string, name:
|
|||||||
const trashPath = join(getFilesRootPath(), userId, TRASH_PATH);
|
const trashPath = join(getFilesRootPath(), userId, TRASH_PATH);
|
||||||
await Deno.rename(join(rootPath, name), join(trashPath, name));
|
await Deno.rename(join(rootPath, name), join(trashPath, name));
|
||||||
|
|
||||||
// TODO: Delete any matching file shares
|
// Delete any matching file shares
|
||||||
|
await db.query<FileShare>(
|
||||||
|
sql`DELETE FROM "bewcloud_file_shares"
|
||||||
|
WHERE "parent_path" = $2
|
||||||
|
AND "name" = $3
|
||||||
|
AND "owner_user_id" = $1`,
|
||||||
|
[
|
||||||
|
userId,
|
||||||
|
path,
|
||||||
|
name,
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -259,3 +308,220 @@ export async function getFile(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createFileShare(
|
||||||
|
userId: string,
|
||||||
|
path: string,
|
||||||
|
name: string,
|
||||||
|
type: 'directory' | 'file',
|
||||||
|
userIdsWithReadAccess: string[],
|
||||||
|
userIdsWithWriteAccess: string[],
|
||||||
|
readShareLinks: FileShareLink[],
|
||||||
|
writeShareLinks: FileShareLink[],
|
||||||
|
): Promise<FileShare> {
|
||||||
|
const extra: FileShare['extra'] = {
|
||||||
|
read_share_links: readShareLinks,
|
||||||
|
write_share_links: writeShareLinks,
|
||||||
|
};
|
||||||
|
|
||||||
|
const newFileShare = (await db.query<FileShare>(
|
||||||
|
sql`INSERT INTO "bewcloud_file_shares" (
|
||||||
|
"owner_user_id",
|
||||||
|
"owner_parent_path",
|
||||||
|
"parent_path",
|
||||||
|
"name",
|
||||||
|
"type",
|
||||||
|
"user_ids_with_read_access",
|
||||||
|
"user_ids_with_write_access",
|
||||||
|
"extra"
|
||||||
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||||
|
RETURNING *`,
|
||||||
|
[
|
||||||
|
userId,
|
||||||
|
path,
|
||||||
|
'/',
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
userIdsWithReadAccess,
|
||||||
|
userIdsWithWriteAccess,
|
||||||
|
JSON.stringify(extra),
|
||||||
|
],
|
||||||
|
))[0];
|
||||||
|
|
||||||
|
return newFileShare;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateFileShare(fileShare: FileShare) {
|
||||||
|
await db.query(
|
||||||
|
sql`UPDATE "bewcloud_file_shares" SET
|
||||||
|
"owner_parent_path" = $2,
|
||||||
|
"parent_path" = $3,
|
||||||
|
"name" = $4,
|
||||||
|
"user_ids_with_read_access" = $5,
|
||||||
|
"user_ids_with_write_access" = $6,
|
||||||
|
"extra" = $7
|
||||||
|
WHERE "id" = $1`,
|
||||||
|
[
|
||||||
|
fileShare.id,
|
||||||
|
fileShare.owner_parent_path,
|
||||||
|
fileShare.parent_path,
|
||||||
|
fileShare.name,
|
||||||
|
fileShare.user_ids_with_read_access,
|
||||||
|
fileShare.user_ids_with_write_access,
|
||||||
|
JSON.stringify(fileShare.extra),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDirectoryAccess(
|
||||||
|
userId: string,
|
||||||
|
parentPath: string,
|
||||||
|
name?: string,
|
||||||
|
): Promise<{ hasReadAccess: boolean; hasWriteAccess: boolean; ownerUserId: string; ownerParentPath: string }> {
|
||||||
|
const rootPath = join(getFilesRootPath(), userId, parentPath, name || '');
|
||||||
|
|
||||||
|
// If it exists in the correct filesystem path, it's the user's
|
||||||
|
try {
|
||||||
|
await Deno.stat(rootPath);
|
||||||
|
|
||||||
|
return { hasReadAccess: true, hasWriteAccess: true, ownerUserId: userId, ownerParentPath: parentPath };
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise check if it's been shared with them
|
||||||
|
const parentPaths: string[] = [];
|
||||||
|
let nextParentPath: string | null = rootPath;
|
||||||
|
|
||||||
|
while (nextParentPath !== null) {
|
||||||
|
parentPaths.push(nextParentPath);
|
||||||
|
|
||||||
|
nextParentPath = `/${nextParentPath.split('/').filter(Boolean).slice(0, -1).join('/')}`;
|
||||||
|
|
||||||
|
if (nextParentPath === '/') {
|
||||||
|
parentPaths.push(nextParentPath);
|
||||||
|
nextParentPath = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileShare = (await db.query<FileShare>(
|
||||||
|
sql`SELECT * FROM "bewcloud_file_shares"
|
||||||
|
WHERE "parent_path" = ANY($2)
|
||||||
|
AND "name" = $3
|
||||||
|
AND "type" = 'directory'
|
||||||
|
AND (
|
||||||
|
ANY("user_ids_with_read_access") = $1
|
||||||
|
OR ANY("user_ids_with_write_access") = $1
|
||||||
|
)
|
||||||
|
ORDER BY "parent_path" ASC
|
||||||
|
LIMIT 1`,
|
||||||
|
[
|
||||||
|
userId,
|
||||||
|
parentPaths,
|
||||||
|
name,
|
||||||
|
],
|
||||||
|
))[0];
|
||||||
|
|
||||||
|
if (fileShare) {
|
||||||
|
return {
|
||||||
|
hasReadAccess: fileShare.user_ids_with_read_access.includes(userId) ||
|
||||||
|
fileShare.user_ids_with_write_access.includes(userId),
|
||||||
|
hasWriteAccess: fileShare.user_ids_with_write_access.includes(userId),
|
||||||
|
ownerUserId: fileShare.owner_user_id,
|
||||||
|
ownerParentPath: fileShare.owner_parent_path,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { hasReadAccess: false, hasWriteAccess: false, ownerUserId: userId, ownerParentPath: parentPath };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFileAccess(
|
||||||
|
userId: string,
|
||||||
|
parentPath: string,
|
||||||
|
name: string,
|
||||||
|
): Promise<{ hasReadAccess: boolean; hasWriteAccess: boolean; ownerUserId: string; ownerParentPath: string }> {
|
||||||
|
const rootPath = join(getFilesRootPath(), userId, parentPath, name);
|
||||||
|
|
||||||
|
// If it exists in the correct filesystem path, it's the user's
|
||||||
|
try {
|
||||||
|
await Deno.stat(rootPath);
|
||||||
|
|
||||||
|
return { hasReadAccess: true, hasWriteAccess: true, ownerUserId: userId, ownerParentPath: parentPath };
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise check if it's been shared with them
|
||||||
|
let fileShare = (await db.query<FileShare>(
|
||||||
|
sql`SELECT * FROM "bewcloud_file_shares"
|
||||||
|
WHERE "parent_path" = $2
|
||||||
|
AND "name" = $3
|
||||||
|
AND "type" = 'file'
|
||||||
|
AND (
|
||||||
|
ANY("user_ids_with_read_access") = $1
|
||||||
|
OR ANY("user_ids_with_write_access") = $1
|
||||||
|
)
|
||||||
|
ORDER BY "parent_path" ASC
|
||||||
|
LIMIT 1`,
|
||||||
|
[
|
||||||
|
userId,
|
||||||
|
parentPath,
|
||||||
|
name,
|
||||||
|
],
|
||||||
|
))[0];
|
||||||
|
|
||||||
|
if (fileShare) {
|
||||||
|
return {
|
||||||
|
hasReadAccess: fileShare.user_ids_with_read_access.includes(userId) ||
|
||||||
|
fileShare.user_ids_with_write_access.includes(userId),
|
||||||
|
hasWriteAccess: fileShare.user_ids_with_write_access.includes(userId),
|
||||||
|
ownerUserId: fileShare.owner_user_id,
|
||||||
|
ownerParentPath: fileShare.owner_parent_path,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise check if it's a parent directory has been shared with them, which would also give them access
|
||||||
|
const parentPaths: string[] = [];
|
||||||
|
let nextParentPath: string | null = rootPath;
|
||||||
|
|
||||||
|
while (nextParentPath !== null) {
|
||||||
|
parentPaths.push(nextParentPath);
|
||||||
|
|
||||||
|
nextParentPath = `/${nextParentPath.split('/').filter(Boolean).slice(0, -1).join('/')}`;
|
||||||
|
|
||||||
|
if (nextParentPath === '/') {
|
||||||
|
parentPaths.push(nextParentPath);
|
||||||
|
nextParentPath = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileShare = (await db.query<FileShare>(
|
||||||
|
sql`SELECT * FROM "bewcloud_file_shares"
|
||||||
|
WHERE "parent_path" = ANY($2)
|
||||||
|
AND "name" = $3
|
||||||
|
AND "type" = 'directory'
|
||||||
|
AND (
|
||||||
|
ANY("user_ids_with_read_access") = $1
|
||||||
|
OR ANY("user_ids_with_write_access") = $1
|
||||||
|
)
|
||||||
|
ORDER BY "parent_path" ASC
|
||||||
|
LIMIT 1`,
|
||||||
|
[
|
||||||
|
userId,
|
||||||
|
parentPaths,
|
||||||
|
name,
|
||||||
|
],
|
||||||
|
))[0];
|
||||||
|
|
||||||
|
if (fileShare) {
|
||||||
|
return {
|
||||||
|
hasReadAccess: fileShare.user_ids_with_read_access.includes(userId) ||
|
||||||
|
fileShare.user_ids_with_write_access.includes(userId),
|
||||||
|
hasWriteAccess: fileShare.user_ids_with_write_access.includes(userId),
|
||||||
|
ownerUserId: fileShare.owner_user_id,
|
||||||
|
ownerParentPath: fileShare.owner_parent_path,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { hasReadAccess: false, hasWriteAccess: false, ownerUserId: userId, ownerParentPath: parentPath };
|
||||||
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export interface NewsFeedArticle {
|
|||||||
created_at: Date;
|
created_at: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DirectoryOrFileShareLink {
|
export interface FileShareLink {
|
||||||
url: string;
|
url: string;
|
||||||
hashed_password: string;
|
hashed_password: string;
|
||||||
}
|
}
|
||||||
@@ -93,16 +93,16 @@ export interface DirectoryOrFileShareLink {
|
|||||||
export interface FileShare {
|
export interface FileShare {
|
||||||
id: string;
|
id: string;
|
||||||
owner_user_id: string;
|
owner_user_id: string;
|
||||||
|
owner_parent_path: string;
|
||||||
parent_path: string;
|
parent_path: string;
|
||||||
name: string;
|
name: string;
|
||||||
type: 'directory' | 'file';
|
type: 'directory' | 'file';
|
||||||
user_ids_with_read_access: string[];
|
user_ids_with_read_access: string[];
|
||||||
user_ids_with_write_access: string[];
|
user_ids_with_write_access: string[];
|
||||||
extra: {
|
extra: {
|
||||||
read_share_links: DirectoryOrFileShareLink[];
|
read_share_links: FileShareLink[];
|
||||||
write_share_links: DirectoryOrFileShareLink[];
|
write_share_links: FileShareLink[];
|
||||||
};
|
};
|
||||||
updated_at: Date;
|
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Handlers } from 'fresh/server.ts';
|
import { Handlers } from 'fresh/server.ts';
|
||||||
|
|
||||||
import { Directory, FreshContextState } from '/lib/types.ts';
|
import { Directory, FreshContextState } from '/lib/types.ts';
|
||||||
import { createDirectory, getDirectories } from '/lib/data/files.ts';
|
import { createDirectory, getDirectories, getDirectoryAccess } from '/lib/data/files.ts';
|
||||||
|
|
||||||
interface Data {}
|
interface Data {}
|
||||||
|
|
||||||
@@ -30,14 +30,22 @@ export const handler: Handlers<Data, FreshContextState> = {
|
|||||||
return new Response('Bad Request', { status: 400 });
|
return new Response('Bad Request', { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Verify user has write access to path and get the appropriate ownerUserId
|
const { hasWriteAccess, ownerUserId, ownerParentPath } = await getDirectoryAccess(
|
||||||
|
|
||||||
const createdDirectory = await createDirectory(
|
|
||||||
context.state.user.id,
|
context.state.user.id,
|
||||||
requestBody.parentPath,
|
requestBody.parentPath,
|
||||||
requestBody.name.trim(),
|
requestBody.name.trim(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!hasWriteAccess) {
|
||||||
|
return new Response('Forbidden', { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdDirectory = await createDirectory(
|
||||||
|
ownerUserId,
|
||||||
|
ownerParentPath,
|
||||||
|
requestBody.name.trim(),
|
||||||
|
);
|
||||||
|
|
||||||
const newDirectories = await getDirectories(context.state.user.id, requestBody.parentPath);
|
const newDirectories = await getDirectories(context.state.user.id, requestBody.parentPath);
|
||||||
|
|
||||||
const responseBody: ResponseBody = { success: createdDirectory, newDirectories };
|
const responseBody: ResponseBody = { success: createdDirectory, newDirectories };
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Handlers } from 'fresh/server.ts';
|
import { Handlers } from 'fresh/server.ts';
|
||||||
|
|
||||||
import { Directory, FreshContextState } from '/lib/types.ts';
|
import { Directory, FreshContextState } from '/lib/types.ts';
|
||||||
import { deleteDirectoryOrFile, getDirectories } from '/lib/data/files.ts';
|
import { deleteDirectoryOrFile, getDirectories, getDirectoryAccess } from '/lib/data/files.ts';
|
||||||
|
|
||||||
interface Data {}
|
interface Data {}
|
||||||
|
|
||||||
@@ -30,14 +30,22 @@ export const handler: Handlers<Data, FreshContextState> = {
|
|||||||
return new Response('Bad Request', { status: 400 });
|
return new Response('Bad Request', { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Verify user has write access to path and get the appropriate ownerUserId
|
const { hasWriteAccess, ownerUserId, ownerParentPath } = await getDirectoryAccess(
|
||||||
|
|
||||||
const deletedDirectory = await deleteDirectoryOrFile(
|
|
||||||
context.state.user.id,
|
context.state.user.id,
|
||||||
requestBody.parentPath,
|
requestBody.parentPath,
|
||||||
requestBody.name.trim(),
|
requestBody.name.trim(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!hasWriteAccess) {
|
||||||
|
return new Response('Forbidden', { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedDirectory = await deleteDirectoryOrFile(
|
||||||
|
ownerUserId,
|
||||||
|
ownerParentPath,
|
||||||
|
requestBody.name.trim(),
|
||||||
|
);
|
||||||
|
|
||||||
const newDirectories = await getDirectories(context.state.user.id, requestBody.parentPath);
|
const newDirectories = await getDirectories(context.state.user.id, requestBody.parentPath);
|
||||||
|
|
||||||
const responseBody: ResponseBody = { success: deletedDirectory, newDirectories };
|
const responseBody: ResponseBody = { success: deletedDirectory, newDirectories };
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Handlers } from 'fresh/server.ts';
|
import { Handlers } from 'fresh/server.ts';
|
||||||
|
|
||||||
import { DirectoryFile, FreshContextState } from '/lib/types.ts';
|
import { DirectoryFile, FreshContextState } from '/lib/types.ts';
|
||||||
import { deleteDirectoryOrFile, getFiles } from '/lib/data/files.ts';
|
import { deleteDirectoryOrFile, getDirectoryAccess, getFileAccess, getFiles } from '/lib/data/files.ts';
|
||||||
|
|
||||||
interface Data {}
|
interface Data {}
|
||||||
|
|
||||||
@@ -30,14 +30,30 @@ export const handler: Handlers<Data, FreshContextState> = {
|
|||||||
return new Response('Bad Request', { status: 400 });
|
return new Response('Bad Request', { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Verify user has write access to path/file and get the appropriate ownerUserId
|
let { hasWriteAccess, ownerUserId, ownerParentPath } = await getFileAccess(
|
||||||
|
|
||||||
const deletedFile = await deleteDirectoryOrFile(
|
|
||||||
context.state.user.id,
|
context.state.user.id,
|
||||||
requestBody.parentPath,
|
requestBody.parentPath,
|
||||||
requestBody.name.trim(),
|
requestBody.name.trim(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!hasWriteAccess) {
|
||||||
|
const directoryAccessResult = await getDirectoryAccess(context.state.user.id, requestBody.parentPath);
|
||||||
|
|
||||||
|
hasWriteAccess = directoryAccessResult.hasWriteAccess;
|
||||||
|
ownerUserId = directoryAccessResult.ownerUserId;
|
||||||
|
ownerParentPath = directoryAccessResult.ownerParentPath;
|
||||||
|
|
||||||
|
if (!hasWriteAccess) {
|
||||||
|
return new Response('Forbidden', { status: 403 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedFile = await deleteDirectoryOrFile(
|
||||||
|
ownerUserId,
|
||||||
|
ownerParentPath,
|
||||||
|
requestBody.name.trim(),
|
||||||
|
);
|
||||||
|
|
||||||
const newFiles = await getFiles(context.state.user.id, requestBody.parentPath);
|
const newFiles = await getFiles(context.state.user.id, requestBody.parentPath);
|
||||||
|
|
||||||
const responseBody: ResponseBody = { success: deletedFile, newFiles };
|
const responseBody: ResponseBody = { success: deletedFile, newFiles };
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Handlers } from 'fresh/server.ts';
|
import { Handlers } from 'fresh/server.ts';
|
||||||
|
|
||||||
import { Directory, FreshContextState } from '/lib/types.ts';
|
import { Directory, FreshContextState } from '/lib/types.ts';
|
||||||
import { getDirectories, renameDirectoryOrFile } from '/lib/data/files.ts';
|
import { getDirectories, getDirectoryAccess, renameDirectoryOrFile } from '/lib/data/files.ts';
|
||||||
|
|
||||||
interface Data {}
|
interface Data {}
|
||||||
|
|
||||||
@@ -33,12 +33,26 @@ export const handler: Handlers<Data, FreshContextState> = {
|
|||||||
return new Response('Bad Request', { status: 400 });
|
return new Response('Bad Request', { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Verify user has write access to old and new paths and get the appropriate ownerUserIds
|
const { hasWriteAccess: hasOldWriteAccess, ownerUserId, ownerParentPath: oldOwnerParentPath } =
|
||||||
|
await getDirectoryAccess(context.state.user.id, requestBody.oldParentPath, requestBody.name.trim());
|
||||||
|
|
||||||
|
if (!hasOldWriteAccess) {
|
||||||
|
return new Response('Forbidden', { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { hasWriteAccess: hasNewWriteAccess, ownerParentPath: newOwnerParentPath } = await getDirectoryAccess(
|
||||||
|
context.state.user.id,
|
||||||
|
requestBody.newParentPath,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasNewWriteAccess) {
|
||||||
|
return new Response('Forbidden', { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
const movedDirectory = await renameDirectoryOrFile(
|
const movedDirectory = await renameDirectoryOrFile(
|
||||||
context.state.user.id,
|
ownerUserId,
|
||||||
requestBody.oldParentPath,
|
oldOwnerParentPath,
|
||||||
requestBody.newParentPath,
|
newOwnerParentPath,
|
||||||
requestBody.name.trim(),
|
requestBody.name.trim(),
|
||||||
requestBody.name.trim(),
|
requestBody.name.trim(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Handlers } from 'fresh/server.ts';
|
import { Handlers } from 'fresh/server.ts';
|
||||||
|
|
||||||
import { DirectoryFile, FreshContextState } from '/lib/types.ts';
|
import { DirectoryFile, FreshContextState } from '/lib/types.ts';
|
||||||
import { getFiles, renameDirectoryOrFile } from '/lib/data/files.ts';
|
import { getDirectoryAccess, getFileAccess, getFiles, renameDirectoryOrFile } from '/lib/data/files.ts';
|
||||||
|
|
||||||
interface Data {}
|
interface Data {}
|
||||||
|
|
||||||
@@ -33,12 +33,35 @@ export const handler: Handlers<Data, FreshContextState> = {
|
|||||||
return new Response('Bad Request', { status: 400 });
|
return new Response('Bad Request', { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Verify user has write access to old and new paths/files and get the appropriate ownerUserIds
|
let { hasWriteAccess: hasOldWriteAccess, ownerUserId, ownerParentPath: oldOwnerParentPath } = await getFileAccess(
|
||||||
|
|
||||||
const movedFile = await renameDirectoryOrFile(
|
|
||||||
context.state.user.id,
|
context.state.user.id,
|
||||||
requestBody.oldParentPath,
|
requestBody.oldParentPath,
|
||||||
|
requestBody.name.trim(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasOldWriteAccess) {
|
||||||
|
const directoryAccessResult = await getDirectoryAccess(context.state.user.id, requestBody.oldParentPath);
|
||||||
|
|
||||||
|
hasOldWriteAccess = directoryAccessResult.hasWriteAccess;
|
||||||
|
ownerUserId = directoryAccessResult.ownerUserId;
|
||||||
|
oldOwnerParentPath = directoryAccessResult.ownerParentPath;
|
||||||
|
|
||||||
|
return new Response('Forbidden', { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { hasWriteAccess: hasNewWriteAccess, ownerParentPath: newOwnerParentPath } = await getDirectoryAccess(
|
||||||
|
context.state.user.id,
|
||||||
requestBody.newParentPath,
|
requestBody.newParentPath,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasNewWriteAccess) {
|
||||||
|
return new Response('Forbidden', { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const movedFile = await renameDirectoryOrFile(
|
||||||
|
ownerUserId,
|
||||||
|
oldOwnerParentPath,
|
||||||
|
newOwnerParentPath,
|
||||||
requestBody.name.trim(),
|
requestBody.name.trim(),
|
||||||
requestBody.name.trim(),
|
requestBody.name.trim(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Handlers } from 'fresh/server.ts';
|
import { Handlers } from 'fresh/server.ts';
|
||||||
|
|
||||||
import { Directory, FreshContextState } from '/lib/types.ts';
|
import { Directory, FreshContextState } from '/lib/types.ts';
|
||||||
import { getDirectories, renameDirectoryOrFile } from '/lib/data/files.ts';
|
import { getDirectories, getDirectoryAccess, renameDirectoryOrFile } from '/lib/data/files.ts';
|
||||||
|
|
||||||
interface Data {}
|
interface Data {}
|
||||||
|
|
||||||
@@ -32,12 +32,20 @@ export const handler: Handlers<Data, FreshContextState> = {
|
|||||||
return new Response('Bad Request', { status: 400 });
|
return new Response('Bad Request', { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Verify user has write access to path and get the appropriate ownerUserId
|
const { hasWriteAccess, ownerUserId, ownerParentPath } = await getDirectoryAccess(
|
||||||
|
|
||||||
const movedDirectory = await renameDirectoryOrFile(
|
|
||||||
context.state.user.id,
|
context.state.user.id,
|
||||||
requestBody.parentPath,
|
requestBody.parentPath,
|
||||||
requestBody.parentPath,
|
requestBody.oldName.trim(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasWriteAccess) {
|
||||||
|
return new Response('Forbidden', { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const movedDirectory = await renameDirectoryOrFile(
|
||||||
|
ownerUserId,
|
||||||
|
ownerParentPath,
|
||||||
|
ownerParentPath,
|
||||||
requestBody.oldName.trim(),
|
requestBody.oldName.trim(),
|
||||||
requestBody.newName.trim(),
|
requestBody.newName.trim(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Handlers } from 'fresh/server.ts';
|
import { Handlers } from 'fresh/server.ts';
|
||||||
|
|
||||||
import { DirectoryFile, FreshContextState } from '/lib/types.ts';
|
import { DirectoryFile, FreshContextState } from '/lib/types.ts';
|
||||||
import { getFiles, renameDirectoryOrFile } from '/lib/data/files.ts';
|
import { getDirectoryAccess, getFileAccess, getFiles, renameDirectoryOrFile } from '/lib/data/files.ts';
|
||||||
|
|
||||||
interface Data {}
|
interface Data {}
|
||||||
|
|
||||||
@@ -32,12 +32,28 @@ export const handler: Handlers<Data, FreshContextState> = {
|
|||||||
return new Response('Bad Request', { status: 400 });
|
return new Response('Bad Request', { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Verify user has write access to path/file and get the appropriate ownerUserId
|
let { hasWriteAccess, ownerUserId, ownerParentPath } = await getFileAccess(
|
||||||
|
|
||||||
const movedFile = await renameDirectoryOrFile(
|
|
||||||
context.state.user.id,
|
context.state.user.id,
|
||||||
requestBody.parentPath,
|
requestBody.parentPath,
|
||||||
requestBody.parentPath,
|
requestBody.oldName.trim(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasWriteAccess) {
|
||||||
|
const directoryAccessResult = await getDirectoryAccess(context.state.user.id, requestBody.parentPath);
|
||||||
|
|
||||||
|
hasWriteAccess = directoryAccessResult.hasWriteAccess;
|
||||||
|
ownerUserId = directoryAccessResult.ownerUserId;
|
||||||
|
ownerParentPath = directoryAccessResult.ownerParentPath;
|
||||||
|
|
||||||
|
if (!hasWriteAccess) {
|
||||||
|
return new Response('Forbidden', { status: 403 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const movedFile = await renameDirectoryOrFile(
|
||||||
|
ownerUserId,
|
||||||
|
ownerParentPath,
|
||||||
|
ownerParentPath,
|
||||||
requestBody.oldName.trim(),
|
requestBody.oldName.trim(),
|
||||||
requestBody.newName.trim(),
|
requestBody.newName.trim(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Handlers } from 'fresh/server.ts';
|
import { Handlers } from 'fresh/server.ts';
|
||||||
|
|
||||||
import { DirectoryFile, FreshContextState } from '/lib/types.ts';
|
import { DirectoryFile, FreshContextState } from '/lib/types.ts';
|
||||||
import { createFile, getFiles } from '/lib/data/files.ts';
|
import { createFile, getDirectoryAccess, getFileAccess, getFiles } from '/lib/data/files.ts';
|
||||||
|
|
||||||
interface Data {}
|
interface Data {}
|
||||||
|
|
||||||
@@ -29,9 +29,25 @@ export const handler: Handlers<Data, FreshContextState> = {
|
|||||||
return new Response('Bad Request', { status: 400 });
|
return new Response('Bad Request', { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Verify user has write access to path and get the appropriate ownerUserId
|
let { hasWriteAccess, ownerUserId, ownerParentPath } = await getFileAccess(
|
||||||
|
context.state.user.id,
|
||||||
|
parentPath,
|
||||||
|
name.trim(),
|
||||||
|
);
|
||||||
|
|
||||||
const createdFile = await createFile(context.state.user.id, parentPath, name.trim(), await contents.arrayBuffer());
|
if (!hasWriteAccess) {
|
||||||
|
const directoryAccessResult = await getDirectoryAccess(context.state.user.id, parentPath);
|
||||||
|
|
||||||
|
hasWriteAccess = directoryAccessResult.hasWriteAccess;
|
||||||
|
ownerUserId = directoryAccessResult.ownerUserId;
|
||||||
|
ownerParentPath = directoryAccessResult.ownerParentPath;
|
||||||
|
|
||||||
|
if (!hasWriteAccess) {
|
||||||
|
return new Response('Forbidden', { status: 403 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdFile = await createFile(ownerUserId, ownerParentPath, name.trim(), await contents.arrayBuffer());
|
||||||
|
|
||||||
const newFiles = await getFiles(context.state.user.id, parentPath);
|
const newFiles = await getFiles(context.state.user.id, parentPath);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Handlers } from 'fresh/server.ts';
|
import { Handlers } from 'fresh/server.ts';
|
||||||
|
|
||||||
import { FreshContextState } from '/lib/types.ts';
|
import { FreshContextState } from '/lib/types.ts';
|
||||||
import { getFile } from '/lib/data/files.ts';
|
import { getFile, getDirectoryAccess, getFileAccess } from '/lib/data/files.ts';
|
||||||
|
|
||||||
interface Data {}
|
interface Data {}
|
||||||
|
|
||||||
@@ -31,9 +31,25 @@ export const handler: Handlers<Data, FreshContextState> = {
|
|||||||
currentPath = `${currentPath}/`;
|
currentPath = `${currentPath}/`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Verify user has read or write access to path/file and get the appropriate ownerUserId
|
let { hasWriteAccess, ownerUserId, ownerParentPath } = await getFileAccess(
|
||||||
|
context.state.user.id,
|
||||||
|
currentPath,
|
||||||
|
decodeURIComponent(fileName),
|
||||||
|
);
|
||||||
|
|
||||||
const fileResult = await getFile(context.state.user.id, currentPath, decodeURIComponent(fileName));
|
if (!hasWriteAccess) {
|
||||||
|
const directoryAccessResult = await getDirectoryAccess(context.state.user.id, currentPath);
|
||||||
|
|
||||||
|
hasWriteAccess = directoryAccessResult.hasWriteAccess;
|
||||||
|
ownerUserId = directoryAccessResult.ownerUserId;
|
||||||
|
ownerParentPath = directoryAccessResult.ownerParentPath;
|
||||||
|
|
||||||
|
if (!hasWriteAccess) {
|
||||||
|
return new Response('Forbidden', { status: 403 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileResult = await getFile(ownerUserId, ownerParentPath, decodeURIComponent(fileName));
|
||||||
|
|
||||||
if (!fileResult.success) {
|
if (!fileResult.success) {
|
||||||
return new Response('Not Found', { status: 404 });
|
return new Response('Not Found', { status: 404 });
|
||||||
|
|||||||
Reference in New Issue
Block a user