Frontend basics

This commit is contained in:
Piotr Dec 2025-10-16 23:07:06 +02:00
parent 8565ce19fe
commit e310930d9e
Signed by: stawros
GPG key ID: 74B18A3F0F1E99C0
10 changed files with 607 additions and 58 deletions

3
app/web/__init__.py Normal file
View file

@ -0,0 +1,3 @@
from .passwords import PasswordsController
__all__ = ["PasswordsController"]

138
app/web/passwords.py Normal file
View file

@ -0,0 +1,138 @@
from typing import Optional, List
from pykeepass import Group, Entry
from app.api.models import EntryKind, EntrySimpleDTO, EntryComplexDTO, GroupDTO, NodeDTO, EntryNodeDTO
from app.services import Passwords
class PasswordsController:
def __init__(self, passwords: Passwords):
self._pw = passwords
@staticmethod
def dep() -> "PasswordsController":
# prosta fabryka/DI mostkująca injectable
return PasswordsController(Passwords())
# Helpers
def _group_by_path(self, path: Optional[str]) -> Group:
if not path or path == "/":
return self._pw.kp.root_group
parts = [p for p in path.split("/") if p]
g = self._pw.kp.root_group
for p in parts:
g = next((x for x in g.subgroups if x.name == p), None)
if g is None:
raise ValueError(f"Group not found: {path}")
return g
def _ensure_group(self, parent: Group, name: str) -> Group:
g = next((x for x in parent.subgroups if x.name == name), None)
if g:
return g
return self._pw.kp.add_group(parent, name)
def _entry_by_path(self, path: str) -> Entry:
# path: /Group1/Sub/Title
parts = [p for p in path.split("/") if p]
if not parts:
raise ValueError("Invalid entry path")
title = parts[-1]
group_path = "/" + "/".join(parts[:-1]) if len(parts) > 1 else "/"
g = self._group_by_path(group_path)
e = next((x for x in g.entries if x.title == title), None)
if not e:
raise ValueError(f"Entry not found: {path}")
return e
def _node_of_group(self, g: Group) -> NodeDTO:
def group_path(gr: Group) -> str:
names: List[str] = []
cur = gr
while cur and cur.name:
names.append(cur.name)
cur = cur.parent
names.reverse()
return "/" + "/".join(names) if names else "/"
path = group_path(g)
groups = [self._node_of_group(sg) for sg in g.subgroups]
entries = [EntryNodeDTO(
kind=EntryKind.complex if (e.username or e.url or e.notes) else EntryKind.simple,
title=e.title,
path=path.rstrip("/") + "/" + e.title
) for e in g.entries]
return NodeDTO(name=g.name or "", path=path, groups=groups, entries=entries)
# Tree
def get_tree(self) -> NodeDTO:
return self._node_of_group(self._pw.kp.root_group)
# Groups
def create_group(self, name: str, parent_path: Optional[str]) -> GroupDTO:
parent = self._group_by_path(parent_path)
g = self._ensure_group(parent, name)
return GroupDTO(name=g.name, path=self._node_of_group(g).path)
def rename_group(self, path: str, new_name: str) -> GroupDTO:
g = self._group_by_path(path)
g.name = new_name
return GroupDTO(name=g.name, path=self._node_of_group(g).path)
def delete_group(self, path: str) -> None:
g = self._group_by_path(path)
if g is self._pw.kp.root_group:
raise ValueError("Cannot delete root group")
self._pw.kp.delete_group(g)
# Entries
def create_entry_simple(self, parent_path: str, dto: EntrySimpleDTO) -> NodeDTO:
parent = self._group_by_path(parent_path)
title = dto.key
password = dto.value
self._pw.kp.add_entry(parent, title=title, username="", password=password)
return self._node_of_group(parent)
def create_entry_complex(self, parent_path: str, dto: EntryComplexDTO) -> NodeDTO:
parent = self._group_by_path(parent_path)
self._pw.kp.add_entry(
parent,
title=dto.title,
username=dto.username or "",
password=dto.password or "",
url=dto.url or "",
notes=dto.notes or "",
)
return self._node_of_group(parent)
def update_entry_simple(self, path: str, dto: EntrySimpleDTO) -> NodeDTO:
e = self._entry_by_path(path)
parent = e.group
e.title = dto.key
e.password = dto.value
return self._node_of_group(parent)
def update_entry_complex(self, path: str, dto: EntryComplexDTO) -> NodeDTO:
e = self._entry_by_path(path)
parent = e.group
e.title = dto.title
e.username = dto.username
e.password = dto.password
e.url = dto.url or ""
e.notes = dto.notes or ""
return self._node_of_group(parent)
def move_entry(self, path: str, target_group_path: str) -> NodeDTO:
e = self._entry_by_path(path)
target = self._group_by_path(target_group_path)
self._pw.kp.move_entry(e, target)
return self._node_of_group(target)
def delete_entry(self, path: str) -> None:
e = self._entry_by_path(path)
self._pw.kp.delete_entry(e)
# Persist
def save(self):
self._pw.save()