Skip to content

Commit 0e94f81

Browse files
authored
Merge pull request #65 from jfmlima/refactor/core-vocabulary
Core owns the vocabulary, edges thin out
2 parents 19a79d6 + eda50fa commit 0e94f81

54 files changed

Lines changed: 1260 additions & 1171 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,6 @@ poetry.toml
173173
pyrightconfig.json
174174

175175
# End of https://www.toptal.com/developers/gitignore/api/python
176+
177+
# Local runtime database
178+
data/

packages/api/src/api/controllers/backup_schedules.py

Lines changed: 16 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
"""Controller for automated backup schedules."""
22

33
from core.domain.entities.backup_schedule import BackupSchedule
4-
from core.use_cases.manage_backup_schedules import (
5-
ManageBackupSchedulesUseCase,
6-
ScheduleAlreadyExistsError,
7-
ScheduleNotFoundError,
8-
)
4+
from core.use_cases.manage_backup_schedules import ManageBackupSchedulesUseCase
95
from core.use_cases.run_due_backups import RunDueBackupsUseCase
106
from litestar import Controller, Router, delete, get, post, put
11-
from litestar.exceptions import HTTPException, NotFoundException
12-
from litestar.status_codes import HTTP_400_BAD_REQUEST, HTTP_409_CONFLICT
137

148
from api.presentation.dto.requests import (
159
CreateBackupScheduleRequest,
@@ -51,13 +45,7 @@ async def create_schedule(
5145
retention_keep_last=data.retention_keep_last,
5246
retention_max_age_days=data.retention_max_age_days,
5347
)
54-
try:
55-
created = await manage_schedules_use_case.create_schedule(schedule)
56-
except ScheduleAlreadyExistsError as err:
57-
raise HTTPException(
58-
status_code=HTTP_409_CONFLICT,
59-
detail=f"Schedule already exists: {data.name}",
60-
) from err
48+
created = await manage_schedules_use_case.create_schedule(schedule)
6149
return _to_response(created)
6250

6351
@get("/{schedule_id:int}")
@@ -67,12 +55,7 @@ async def get_schedule(
6755
manage_schedules_use_case: ManageBackupSchedulesUseCase,
6856
) -> BackupScheduleResponse:
6957
"""Get a backup schedule by ID."""
70-
try:
71-
schedule = await manage_schedules_use_case.get_schedule(schedule_id)
72-
except ScheduleNotFoundError as err:
73-
raise NotFoundException(
74-
detail=f"Schedule not found: {schedule_id}"
75-
) from err
58+
schedule = await manage_schedules_use_case.get_schedule(schedule_id)
7659
return _to_response(schedule)
7760

7861
@put("/{schedule_id:int}")
@@ -83,61 +66,17 @@ async def update_schedule(
8366
manage_schedules_use_case: ManageBackupSchedulesUseCase,
8467
) -> BackupScheduleResponse:
8568
"""Partially update a backup schedule."""
86-
try:
87-
existing = await manage_schedules_use_case.get_schedule(schedule_id)
88-
except ScheduleNotFoundError as err:
89-
raise NotFoundException(
90-
detail=f"Schedule not found: {schedule_id}"
91-
) from err
92-
93-
interval = data.resolved_interval_seconds()
94-
updated = BackupSchedule(
95-
id=schedule_id,
96-
name=data.name if data.name is not None else existing.name,
97-
interval_seconds=(
98-
interval if interval is not None else existing.interval_seconds
99-
),
100-
target_ips=(
101-
data.target_ips if data.target_ips is not None else existing.target_ips
102-
),
103-
target_macs=(
104-
data.target_macs
105-
if data.target_macs is not None
106-
else existing.target_macs
107-
),
108-
all_credentialed=(
109-
data.all_credentialed
110-
if data.all_credentialed is not None
111-
else existing.all_credentialed
112-
),
113-
enabled=data.enabled if data.enabled is not None else existing.enabled,
114-
retention_keep_last=(
115-
data.retention_keep_last
116-
if data.retention_keep_last is not None
117-
else existing.retention_keep_last
118-
),
119-
retention_max_age_days=(
120-
data.retention_max_age_days
121-
if data.retention_max_age_days is not None
122-
else existing.retention_max_age_days
123-
),
124-
next_run_at=existing.next_run_at,
69+
result = await manage_schedules_use_case.apply_schedule_update(
70+
schedule_id,
71+
name=data.name,
72+
interval_seconds=data.resolved_interval_seconds(),
73+
target_ips=data.target_ips,
74+
target_macs=data.target_macs,
75+
all_credentialed=data.all_credentialed,
76+
enabled=data.enabled,
77+
retention_keep_last=data.retention_keep_last,
78+
retention_max_age_days=data.retention_max_age_days,
12579
)
126-
if not (updated.target_ips or updated.target_macs or updated.all_credentialed):
127-
raise HTTPException(
128-
status_code=HTTP_400_BAD_REQUEST,
129-
detail=(
130-
"A schedule needs at least one target "
131-
"(target_ips, target_macs, or all_credentialed)"
132-
),
133-
)
134-
try:
135-
result = await manage_schedules_use_case.update_schedule(updated)
136-
except ScheduleAlreadyExistsError as err:
137-
raise HTTPException(
138-
status_code=HTTP_409_CONFLICT,
139-
detail=f"Schedule name already exists: {data.name}",
140-
) from err
14180
return _to_response(result)
14281

14382
@delete("/{schedule_id:int}")
@@ -147,12 +86,7 @@ async def delete_schedule(
14786
manage_schedules_use_case: ManageBackupSchedulesUseCase,
14887
) -> None:
14988
"""Delete a backup schedule."""
150-
try:
151-
await manage_schedules_use_case.delete_schedule(schedule_id)
152-
except ScheduleNotFoundError as err:
153-
raise NotFoundException(
154-
detail=f"Schedule not found: {schedule_id}"
155-
) from err
89+
await manage_schedules_use_case.delete_schedule(schedule_id)
15690

15791
@post("/{schedule_id:int}/enable")
15892
async def enable_schedule(
@@ -179,12 +113,7 @@ async def run_schedule(
179113
run_due_backups_use_case: RunDueBackupsUseCase,
180114
) -> ScheduleRunResultResponse:
181115
"""Run a backup schedule now, ignoring its next run time."""
182-
try:
183-
result = await run_due_backups_use_case.run_schedule(schedule_id)
184-
except ScheduleNotFoundError as err:
185-
raise NotFoundException(
186-
detail=f"Schedule not found: {schedule_id}"
187-
) from err
116+
result = await run_due_backups_use_case.run_schedule(schedule_id)
188117
return ScheduleRunResultResponse(
189118
schedule_id=result.schedule_id,
190119
schedule_name=result.schedule_name,
@@ -202,12 +131,7 @@ async def _set_enabled(
202131
schedule_id: int,
203132
enabled: bool,
204133
) -> BackupScheduleResponse:
205-
try:
206-
updated = await use_case.set_enabled(schedule_id, enabled)
207-
except ScheduleNotFoundError as err:
208-
raise NotFoundException(
209-
detail=f"Schedule not found: {schedule_id}"
210-
) from err
134+
updated = await use_case.set_enabled(schedule_id, enabled)
211135
return _to_response(updated)
212136

213137

packages/api/src/api/controllers/backups.py

Lines changed: 14 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
11
"""Controller for device configuration backups."""
22

33
from core.domain.entities.device_backup import DeviceBackup, DeviceBackupSummary
4-
from core.domain.entities.exceptions import DeviceNotFoundError
5-
from core.use_cases.backup_device_config import (
6-
BackupDeviceConfig,
7-
BackupError,
8-
BackupNotFoundError,
9-
)
10-
from core.use_cases.restore_device_config import (
11-
DeviceMismatchError,
12-
RestoreDeviceConfig,
13-
)
4+
from core.use_cases.backup_device_config import BackupDeviceConfig
5+
from core.use_cases.restore_device_config import RestoreDeviceConfig
146
from litestar import Controller, Router, delete, get, post
15-
from litestar.exceptions import HTTPException, NotFoundException
167

178
from api.presentation.dto.requests import CreateBackupRequest, RestoreBackupRequest
189
from api.presentation.dto.responses import (
@@ -22,7 +13,6 @@
2213
PaginatedBackupsResponse,
2314
RestoreResultResponse,
2415
)
25-
from api.presentation.exceptions import DeviceNotFoundHTTPException
2616

2717
DEFAULT_PAGE_SIZE = 50
2818
MAX_PAGE_SIZE = 200
@@ -61,14 +51,9 @@ async def create_backup(
6151
backup_use_case: BackupDeviceConfig,
6252
) -> BackupDetailResponse:
6353
"""Capture a full configuration backup of a device."""
64-
try:
65-
backup = await backup_use_case.create_backup(
66-
device_ip=data.device_ip, name=data.name
67-
)
68-
except DeviceNotFoundError as err:
69-
raise DeviceNotFoundHTTPException(data.device_ip) from err
70-
except BackupError as err:
71-
raise HTTPException(status_code=422, detail=str(err)) from err
54+
backup = await backup_use_case.create_backup(
55+
device_ip=data.device_ip, name=data.name
56+
)
7257
return _to_detail(backup)
7358

7459
@get("/{backup_id:int}")
@@ -78,10 +63,7 @@ async def get_backup(
7863
backup_use_case: BackupDeviceConfig,
7964
) -> BackupDetailResponse:
8065
"""Get a backup including its full snapshot."""
81-
try:
82-
backup = await backup_use_case.get_backup(backup_id)
83-
except BackupNotFoundError as err:
84-
raise NotFoundException(detail=f"Backup not found: {backup_id}") from err
66+
backup = await backup_use_case.get_backup(backup_id)
8567
return _to_detail(backup)
8668

8769
@post("/{backup_id:int}/restore")
@@ -92,20 +74,13 @@ async def restore_backup(
9274
restore_use_case: RestoreDeviceConfig,
9375
) -> RestoreResultResponse:
9476
"""Restore selected components from a backup onto a device."""
95-
try:
96-
result = await restore_use_case.restore(
97-
backup_id,
98-
data.device_ip,
99-
component_keys=data.component_keys,
100-
allow_mac_mismatch=data.allow_mac_mismatch,
101-
reboot=data.reboot,
102-
)
103-
except BackupNotFoundError as err:
104-
raise NotFoundException(detail=f"Backup not found: {backup_id}") from err
105-
except DeviceNotFoundError as err:
106-
raise DeviceNotFoundHTTPException(data.device_ip) from err
107-
except DeviceMismatchError as err:
108-
raise HTTPException(status_code=409, detail=str(err)) from err
77+
result = await restore_use_case.restore(
78+
backup_id,
79+
data.device_ip,
80+
component_keys=data.component_keys,
81+
allow_mac_mismatch=data.allow_mac_mismatch,
82+
reboot=data.reboot,
83+
)
10984

11085
return RestoreResultResponse(
11186
success=result.success,
@@ -136,10 +111,7 @@ async def delete_backup(
136111
backup_use_case: BackupDeviceConfig,
137112
) -> None:
138113
"""Delete a backup."""
139-
try:
140-
await backup_use_case.delete_backup(backup_id)
141-
except BackupNotFoundError as err:
142-
raise NotFoundException(detail=f"Backup not found: {backup_id}") from err
114+
await backup_use_case.delete_backup(backup_id)
143115

144116

145117
backups_router = Router(

packages/api/src/api/controllers/credentials.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1-
from core.use_cases.manage_credentials import (
2-
CredentialNotFoundError,
3-
ManageCredentialsUseCase,
4-
)
1+
from core.use_cases.manage_credentials import ManageCredentialsUseCase
52
from litestar import Controller, Router, delete, get, post
6-
from litestar.exceptions import NotFoundException
73

84
from api.presentation.dto.requests import CredentialCreateRequest
95
from api.presentation.dto.responses import CredentialResponse
@@ -50,12 +46,7 @@ async def delete_credential(
5046
credentials_use_case: ManageCredentialsUseCase,
5147
) -> None:
5248
"""Delete credentials for a device."""
53-
try:
54-
await credentials_use_case.delete_credential(mac)
55-
except CredentialNotFoundError as err:
56-
raise NotFoundException(
57-
detail=f"Credential not found for MAC: {mac}"
58-
) from err
49+
await credentials_use_case.delete_credential(mac)
5950

6051

6152
credentials_router = Router(

0 commit comments

Comments
 (0)