130 lines
4.2 KiB
Python
130 lines
4.2 KiB
Python
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:
|
|
raw_path = g.path
|
|
path = "/" + "/".join(raw_path) if raw_path else "/"
|
|
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()
|