diff --git a/desktop_client/auth.py b/desktop_client/auth.py index 3872442..167cdb9 100644 --- a/desktop_client/auth.py +++ b/desktop_client/auth.py @@ -83,7 +83,7 @@ class RegisterWidget(QWidget): class LoginWidget(QWidget): - def __init__(self, switcher: State): + def __init__(self, switcher: state.State): super().__init__() self.switcher = switcher @@ -116,7 +116,9 @@ class LoginWidget(QWidget): password = self.password_input.text() if not username or not password: - QMessageBox.warning(self, "Input Error", "Email and Password are required") + QMessageBox.warning( + self, "Input Error", "Email and Password are required" + ) return try: @@ -129,8 +131,12 @@ class LoginWidget(QWidget): if access_token: self.switcher.login(access_token) else: - QMessageBox.warning(self, "Error", "No access token received") + QMessageBox.warning( + self, "Error", "No access token received" + ) else: - QMessageBox.warning(self, "Error", f"Login failed: {response.text}") + QMessageBox.warning( + self, "Error", f"Login failed: {response.text}" + ) except httpx.HTTPError as e: QMessageBox.critical(self, "HTTP Error", str(e)) diff --git a/desktop_client/file_widgets.py b/desktop_client/file_widgets.py index c9f7b32..94003c9 100644 --- a/desktop_client/file_widgets.py +++ b/desktop_client/file_widgets.py @@ -7,12 +7,19 @@ import uuid from typing import Protocol, Self import create_folder_widget +import folder_info import httpx import pydantic import state import user from PyQt6.QtCore import QPoint, Qt -from PyQt6.QtGui import QAction, QDragEnterEvent, QDragMoveEvent, QDropEvent, QIcon +from PyQt6.QtGui import ( + QAction, + QDragEnterEvent, + QDragMoveEvent, + QDropEvent, + QIcon, +) from PyQt6.QtWidgets import ( QFileDialog, QHBoxLayout, @@ -57,7 +64,9 @@ class File(pydantic.BaseModel): return self.file_name def delete(self) -> None: - RequestClient().client.delete("/files", params={"file_id": self.file_id}) + RequestClient().client.delete( + "/files", params={"file_id": self.file_id} + ) def details(self, list: FileListWidget) -> QWidget: del list @@ -114,15 +123,14 @@ class Folder(pydantic.BaseModel): return self.folder_name def delete(self) -> None: - print( - RequestClient() - .client.delete("/folders", params={"folder_id": self.folder_id}) - .text + response = RequestClient().client.delete( + "/folders", params={"folder_id": self.folder_id} ) + if not response: + QMessageBox.warning(None, "Error deleting folder", response.text) def details(self, list: FileListWidget) -> QWidget: - # TODO - raise NotImplementedError + return folder_info.FolderInfoWidget(self) def icon(self) -> QIcon: return QIcon("assets/folder.png") @@ -164,7 +172,9 @@ class ListResponse(pydantic.BaseModel): return self.get(self.folder_id) def create_folder(self, file_list: FileListWidget): - return create_folder_widget.CreateFolderWidget(self.folder_id, file_list) + return create_folder_widget.CreateFolderWidget( + self.folder_id, file_list + ) @dataclasses.dataclass(slots=True) @@ -186,7 +196,7 @@ class TlpResponse: def update(self) -> ResponseProtocol: return self.get() - def create_folder(self): + def create_folder(self, _: FileListWidget): return # Not much to do @@ -228,7 +238,6 @@ class FileListWidget(QListWidget): def dropEvent(self, event: QDropEvent): event.accept() - print("hi") for url in event.mimeData().urls(): file_path = url.toLocalFile() self.upload_file(file_path) @@ -241,7 +250,9 @@ class FileListWidget(QListWidget): response = RequestClient().client.post( "http://localhost:3000/files", files=files, - params={"parent_folder": self.current_response().folder_id}, + params={ + "parent_folder": self.current_response().folder_id + }, ) if response.is_success: QMessageBox.information( @@ -283,7 +294,6 @@ class FileListWidget(QListWidget): item = self.current_response().items()[row] item.delete() self.update_response() - QMessageBox.information(self, "Delete", f"{item.name()} deleted") def update(self) -> None: self.clear() diff --git a/desktop_client/folder_info.py b/desktop_client/folder_info.py new file mode 100644 index 0000000..f8e3da3 --- /dev/null +++ b/desktop_client/folder_info.py @@ -0,0 +1,152 @@ +from __future__ import annotations + +import uuid +from dataclasses import dataclass +from request_client import RequestClient +import enum +import file_widgets +from PyQt6.QtWidgets import ( + QWidget, + QMessageBox, + QLabel, + QPushButton, + QComboBox, + QHBoxLayout, + QVBoxLayout, + QLineEdit, +) +import pydantic +from functools import cache, partial +import user + + +class Permission(enum.StrEnum): + read = enum.auto() + write = enum.auto() + manage = enum.auto() + + def set(self, folder_id: uuid.UUID, user_id: int): + response = RequestClient().client.post( + "/permissions", + json={ + "folder_id": str(folder_id), + "permission_type": str(self), + "user_id": int(user_id), + }, + ) + if not response.is_success: + QMessageBox.warning( + None, "Error setting permissions", response.text + ) + + @staticmethod + def delete(folder_id: uuid.UUID, user_id: int, widget: FolderInfoWidget): + response = RequestClient().client.delete( + "/permissions", + params={ + "folder_id": folder_id, + "user_id": user_id, + }, + ) + if not response.is_success: + QMessageBox.warning( + None, "Error deleting permissions", response.text + ) + else: + widget.redraw() + + +@dataclass(slots=True) +class Permissions: + mapping: dict[int, Permission] + + @staticmethod + def get(folder_id: uuid.UUID) -> Permissions: + mapping = pydantic.TypeAdapter(dict[str, Permission]).validate_json( + RequestClient() + .client.get("/permissions", params={"folder_id": folder_id}) + .text + ) + return Permissions(mapping) + + +class FolderInfoWidget(QWidget): + def __init__(self, folder: file_widgets.Folder): + super().__init__() + self.setWindowTitle("Folder info") + self.folder = folder + self.user_cache = cache(user.User.get) + self.user_mapping: dict[str, int] = {} + self.redraw() + + def redraw(self): + self.permissions = Permissions.get(self.folder.folder_id) + main_layout = QVBoxLayout() + + owner_name = self.user_cache(self.folder.owner_id) + main_layout.addWidget( + QLabel( + f"Owner: {owner_name}\n" + + f"Folder name: {self.folder.folder_name}\n" + + f"Created at: {self.folder.created_at}" + ) + ) + for user_id, permissions in self.permissions.mapping.items(): + layout = QHBoxLayout() + name = QLabel(self.user_cache(user_id).username) + combo = QComboBox() + combo.addItems(map(str, Permission)) + combo.setCurrentText(str(permissions)) + combo.currentTextChanged.connect( + partial(self.change, user_id=user_id) + ) + delete = QPushButton("Delete") + delete.clicked.connect( + partial( + Permission.delete, self.folder.folder_id, user_id, self + ) + ) + layout.addWidget(name) + layout.addWidget(combo) + layout.addWidget(delete) + main_layout.addLayout(layout) + + layout = QHBoxLayout() + self.search_str = QLineEdit() + layout.addWidget(self.search_str) + search = QPushButton("Search") + search.clicked.connect(self.search) + layout.addWidget(search) + main_layout.addLayout(layout) + + layout = QHBoxLayout() + self.search_combo = QComboBox() + self.perm_combo = QComboBox() + self.perm_combo.addItems(map(str, Permission)) + button = QPushButton("+") + button.clicked.connect(self.search_save) + layout.addWidget(self.search_combo) + layout.addWidget(self.perm_combo) + layout.addWidget(button) + main_layout.addLayout(layout) + + if self.layout(): + QWidget().setLayout(self.layout()) + self.setLayout(main_layout) + + def change(self, text: str, *, user_id: int): + Permission(text).set(self.folder.folder_id, user_id) + + def search(self): + result = user.UserSearch.search(self.search_str.text()) + self.search_combo.clear() + self.user_mapping.clear() + for item in result: + self.user_mapping[item.username] = item.user_id + self.search_combo.addItem(item.username) + + def search_save(self): + permission = Permission(self.perm_combo.currentText()) + user_id = self.user_mapping[self.search_combo.currentText()] + permission.set(self.folder.folder_id, user_id) + self.redraw() diff --git a/desktop_client/user.py b/desktop_client/user.py index bfea5c1..317fe7e 100644 --- a/desktop_client/user.py +++ b/desktop_client/user.py @@ -27,9 +27,13 @@ class User(pydantic.BaseModel): params["user_id"] = user_id else: url = "/users/current" - return User.model_validate_json( - RequestClient().client.get(url, params=params).text - ) + response = RequestClient().client.get(url, params=params) + if not response.is_success: + QMessageBox.warning( + None, "Error getting permissions", response.text + ) + return + return User.model_validate_json(response.text) @staticmethod def delete():