first commit

This commit is contained in:
Mateusz Gruszczyński
2026-04-12 21:26:12 +02:00
commit ff7dbcb4e4
123 changed files with 27749 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
from pydantic import BaseModel, Field
class UserResponse(BaseModel):
id: int
username: str
preferred_language: str = 'pl'
preferred_font: str = 'default'
model_config = {"from_attributes": True}
class TokenResponse(BaseModel):
access_token: str
token_type: str = "bearer"
user: UserResponse
class RegisterRequest(BaseModel):
username: str = Field(min_length=3, max_length=120)
password: str = Field(min_length=4, max_length=128)
class ChangePasswordRequest(BaseModel):
current_password: str
new_password: str = Field(min_length=4, max_length=128)
class UpdateUserPreferencesRequest(BaseModel):
preferred_language: str = Field(default='pl', min_length=2, max_length=8)
preferred_font: str = Field(default='default', min_length=2, max_length=32)

View File

@@ -0,0 +1,49 @@
from datetime import datetime
from typing import Literal
from pydantic import BaseModel
class BackupResponse(BaseModel):
id: int
router_id: int
router_name: str | None = None
file_path: str
file_name: str
backup_type: str
checksum: str | None = None
file_size: int | None = None
created_at: datetime
model_config = {'from_attributes': True}
class BackupDiffLine(BaseModel):
type: Literal['context', 'added', 'removed', 'modified']
left_number: int | None = None
right_number: int | None = None
left_text: str = ''
right_text: str = ''
class BackupDiffStats(BaseModel):
added: int = 0
removed: int = 0
modified: int = 0
context: int = 0
class BackupDiffResponse(BaseModel):
left_backup_id: int
right_backup_id: int
left_file_name: str | None = None
right_file_name: str | None = None
diff_text: str
diff_html: str | None = None
stats: BackupDiffStats | None = None
lines: list[BackupDiffLine] = []
class BulkActionRequest(BaseModel):
action: Literal['download', 'delete']
backup_ids: list[int]

View File

@@ -0,0 +1,28 @@
from datetime import datetime
from pydantic import BaseModel
class StorageStats(BaseModel):
total: int
used: int
free: int
folder_used: int
usage_percent: float
class OperationLogResponse(BaseModel):
id: int
message: str
timestamp: datetime
model_config = {"from_attributes": True}
class DashboardResponse(BaseModel):
routers_count: int
export_count: int
binary_count: int
total_backups: int
storage: StorageStats
recent_logs: list[OperationLogResponse]

View File

@@ -0,0 +1,60 @@
import re
from datetime import datetime
from pydantic import BaseModel, Field, field_validator
ALLOWED_NAME_REGEX = re.compile(r"^[A-Za-z0-9_-]+$")
class RouterBase(BaseModel):
name: str = Field(min_length=1, max_length=120)
host: str = Field(min_length=1, max_length=255)
port: int = Field(default=22, ge=1, le=65535)
ssh_user: str = Field(default="admin", min_length=1, max_length=120)
ssh_key: str | None = None
ssh_password: str | None = None
@field_validator("name")
@classmethod
def validate_name(cls, value: str) -> str:
if not ALLOWED_NAME_REGEX.match(value):
raise ValueError("Only letters, digits, dashes and underscores are allowed")
return value
class RouterCreate(RouterBase):
pass
class RouterUpdate(BaseModel):
name: str | None = None
host: str | None = None
port: int | None = Field(default=None, ge=1, le=65535)
ssh_user: str | None = None
ssh_key: str | None = None
ssh_password: str | None = None
class RouterResponse(RouterBase):
id: int
owner_id: int
last_connection_status: bool | None = None
last_connection_tested_at: datetime | None = None
last_connection_error: str | None = None
last_connection_hostname: str | None = None
last_connection_model: str | None = None
last_connection_version: str | None = None
last_connection_uptime: str | None = None
created_at: datetime | None = None
model_config = {"from_attributes": True}
class RouterTestConnection(BaseModel):
success: bool
tested_at: datetime
model: str
uptime: str
hostname: str
version: str | None = None
error: str | None = None

View File

@@ -0,0 +1,85 @@
from datetime import datetime
from pydantic import BaseModel, EmailStr, Field, field_validator
from app.core.config import settings as app_settings
from app.core.cron_utils import CronValidationError, validate_cron_expression
class SettingsBase(BaseModel):
backup_retention_days: int = 7
log_retention_days: int = 7
export_cron: str = ''
binary_cron: str = ''
retention_cron: str = ''
enable_auto_export: bool = False
connection_test_interval_minutes: int = Field(default=0, ge=0, le=1440)
global_ssh_key: str | None = None
pushover_token: str | None = None
pushover_userkey: str | None = None
notify_failures_only: bool = True
smtp_host: str | None = None
smtp_port: int = 587
smtp_login: str | None = None
smtp_password: str | None = None
smtp_notifications_enabled: bool = False
recipient_email: EmailStr | None = None
@field_validator('export_cron', 'binary_cron', 'retention_cron', mode='before')
@classmethod
def normalize_cron(cls, value: str | None) -> str:
return (value or '').strip()
@field_validator('global_ssh_key', mode='before')
@classmethod
def normalize_key(cls, value: str | None) -> str | None:
normalized = (value or '').strip()
return normalized or None
@field_validator('export_cron', 'binary_cron', 'retention_cron')
@classmethod
def validate_cron(cls, value: str) -> str:
if not value:
return value
try:
validate_cron_expression(value, app_settings.timezone)
except CronValidationError as exc:
raise ValueError(f'Invalid cron expression: {exc}') from exc
return value
class SettingsUpdate(SettingsBase):
clear_global_ssh_key: bool = False
class SettingsResponse(SettingsBase):
id: int
has_global_ssh_key: bool = False
model_config = {'from_attributes': True}
class RevealSshKeyRequest(BaseModel):
password: str
class RevealSshKeyResponse(BaseModel):
global_ssh_key: str | None = None
class SchedulerJobStatus(BaseModel):
key: str
label: str
enabled: bool
cron: str | None = None
description: str
description_params: dict[str, str | int] | None = None
valid: bool
next_runs: list[datetime] = []
error: str | None = None
class SchedulerStatusResponse(BaseModel):
timezone: str
running: bool
jobs: list[SchedulerJobStatus]

View File

@@ -0,0 +1,33 @@
from pydantic import BaseModel, Field, field_validator
class SwosBetaCredentials(BaseModel):
host: str = Field(min_length=1, max_length=255)
port: int = Field(default=80, ge=1, le=65535)
username: str = Field(default='admin', min_length=1, max_length=120)
password: str = Field(default='', max_length=255)
label: str | None = Field(default=None, max_length=120)
@field_validator('host', 'username', 'password', mode='before')
@classmethod
def normalize_text(cls, value: str | None) -> str:
return (value or '').strip()
@field_validator('label', mode='before')
@classmethod
def normalize_label(cls, value: str | None) -> str | None:
normalized = (value or '').strip()
return normalized or None
class SwosBetaProbeResponse(BaseModel):
success: bool
base_url: str
status_code: int
auth_mode: str
page_title: str | None = None
content_type: str | None = None
server: str | None = None
save_backup_visible: bool = False
backup_endpoint_ok: bool = False
note: str | None = None