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,38 @@
from pathlib import Path
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
app_name: str = 'RouterOS Backup Manager Next'
app_env: str = 'development'
secret_key: str = 'change-me'
jwt_algorithm: str = 'HS256'
access_token_expire_minutes: int = 1440
database_url: str = 'sqlite:///./storage/routeros_backup_next.db'
data_dir: str = './storage'
allow_registration: bool = True
api_prefix: str = '/api'
timezone: str = 'Europe/Warsaw'
default_admin_username: str = 'admin'
default_admin_password: str = 'admin'
smtp_starttls: bool = True
smtp_timeout_seconds: int = 20
cors_origins: list[str] = Field(default_factory=lambda: ['http://localhost:4200', 'http://127.0.0.1:4200'])
model_config = SettingsConfigDict(
env_file='.env',
env_file_encoding='utf-8',
extra='ignore',
env_nested_delimiter='__',
)
@property
def data_path(self) -> Path:
path = Path(self.data_dir)
path.mkdir(parents=True, exist_ok=True)
return path
settings = Settings()

View File

@@ -0,0 +1,88 @@
from __future__ import annotations
from datetime import datetime
from zoneinfo import ZoneInfo
from apscheduler.triggers.cron import CronTrigger
WEEKDAY_LABELS = {
'0': 'Sunday',
'1': 'Monday',
'2': 'Tuesday',
'3': 'Wednesday',
'4': 'Thursday',
'5': 'Friday',
'6': 'Saturday',
'7': 'Sunday',
'sun': 'Sunday',
'mon': 'Monday',
'tue': 'Tuesday',
'wed': 'Wednesday',
'thu': 'Thursday',
'fri': 'Friday',
'sat': 'Saturday',
}
class CronValidationError(ValueError):
pass
def parse_cron_expression(expr: str, timezone_str: str) -> CronTrigger:
expr = (expr or '').strip()
if not expr:
raise CronValidationError('Cron expression cannot be empty')
parts = expr.split()
if len(parts) != 5:
raise CronValidationError('Cron expression must contain exactly 5 fields')
minute, hour, day, month, day_of_week = parts
try:
return CronTrigger(
minute=minute,
hour=hour,
day=day,
month=month,
day_of_week=day_of_week,
timezone=ZoneInfo(timezone_str),
)
except Exception as exc: # pragma: no cover - APScheduler formats messages
raise CronValidationError(str(exc)) from exc
def validate_cron_expression(expr: str, timezone_str: str) -> None:
parse_cron_expression(expr, timezone_str)
def preview_next_runs(expr: str, timezone_str: str, count: int = 3) -> list[datetime]:
trigger = parse_cron_expression(expr, timezone_str)
now = datetime.now(ZoneInfo(timezone_str))
previous = None
runs: list[datetime] = []
for _ in range(max(count, 0)):
next_run = trigger.get_next_fire_time(previous, now)
if not next_run:
break
runs.append(next_run)
previous = next_run
now = next_run
return runs
def describe_cron_expression(expr: str) -> str:
expr = (expr or '').strip()
if not expr:
return 'Disabled'
parts = expr.split()
if len(parts) != 5:
return 'Custom cron'
minute, hour, day, month, day_of_week = parts
if minute.isdigit() and hour.isdigit() and day == '*' and month == '*' and day_of_week == '*':
return f'Every day at {int(hour):02d}:{int(minute):02d}'
if minute.isdigit() and hour.isdigit() and day == '*' and month == '*' and day_of_week.lower() in WEEKDAY_LABELS:
weekday = WEEKDAY_LABELS[day_of_week.lower()]
return f'Every {weekday} at {int(hour):02d}:{int(minute):02d}'
return 'Custom cron'

View File

@@ -0,0 +1,22 @@
from datetime import datetime, timedelta, timezone
from jose import jwt
from passlib.context import CryptContext
from app.core.config import settings
pwd_context = CryptContext(schemes=["pbkdf2_sha256"], deprecated="auto")
def verify_password(plain_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password: str) -> str:
return pwd_context.hash(password)
def create_access_token(subject: str, expires_delta: timedelta) -> str:
expire = datetime.now(timezone.utc) + expires_delta
to_encode = {"sub": subject, "exp": expire}
return jwt.encode(to_encode, settings.secret_key, algorithm=settings.jwt_algorithm)