Files
NexaPG/backend/app/api/routes/service_info.py
nessi 0445a72764
All checks were successful
PostgreSQL Compatibility Matrix / PG14 smoke (push) Successful in 8s
PostgreSQL Compatibility Matrix / PG15 smoke (push) Successful in 8s
PostgreSQL Compatibility Matrix / PG16 smoke (push) Successful in 8s
PostgreSQL Compatibility Matrix / PG17 smoke (push) Successful in 8s
PostgreSQL Compatibility Matrix / PG18 smoke (push) Successful in 8s
Add service information feature with version checks
This commit introduces a new "Service Information" section displaying runtime details, installed version, and update status for the NexaPG application. It includes backend API endpoints, database schema changes, and a corresponding frontend page that allows users to check for updates against the official repository. The `.env` example now includes an `APP_VERSION` variable, and related documentation has been updated.
2026-02-13 08:54:13 +01:00

106 lines
3.4 KiB
Python

import os
import platform
from datetime import datetime, timezone
from fastapi import APIRouter, Depends
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.config import get_settings
from app.core.db import get_db
from app.core.deps import get_current_user
from app.models.models import ServiceInfoSettings, User
from app.schemas.service_info import ServiceInfoCheckResult, ServiceInfoOut
from app.services.audit import write_audit_log
from app.services.service_info import (
UPSTREAM_REPO_WEB,
fetch_latest_from_upstream,
is_update_available,
utcnow,
)
router = APIRouter()
settings = get_settings()
service_started_at = datetime.now(timezone.utc)
async def _get_or_create_service_settings(db: AsyncSession) -> ServiceInfoSettings:
row = await db.scalar(select(ServiceInfoSettings).limit(1))
if row:
return row
row = ServiceInfoSettings(current_version=settings.app_version)
db.add(row)
await db.commit()
await db.refresh(row)
return row
def _to_out(row: ServiceInfoSettings) -> ServiceInfoOut:
uptime_seconds = int((utcnow() - service_started_at).total_seconds())
return ServiceInfoOut(
app_name=settings.app_name,
environment=settings.environment,
api_prefix=settings.api_v1_prefix,
app_version=settings.app_version,
hostname=platform.node() or os.getenv("HOSTNAME", "unknown"),
python_version=platform.python_version(),
platform=platform.platform(),
service_started_at=service_started_at,
uptime_seconds=max(uptime_seconds, 0),
update_source=UPSTREAM_REPO_WEB,
latest_version=row.latest_version,
latest_ref=(row.release_check_url or None),
update_available=row.update_available,
last_checked_at=row.last_checked_at,
last_check_error=row.last_check_error,
)
@router.get("/info", response_model=ServiceInfoOut)
async def get_service_info(
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
) -> ServiceInfoOut:
_ = user
row = await _get_or_create_service_settings(db)
return _to_out(row)
@router.post("/info/check", response_model=ServiceInfoCheckResult)
async def check_service_version(
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
) -> ServiceInfoCheckResult:
row = await _get_or_create_service_settings(db)
check_time = utcnow()
latest, latest_ref, error = await fetch_latest_from_upstream()
row.last_checked_at = check_time
row.last_check_error = error
if latest:
row.latest_version = latest
row.release_check_url = latest_ref
row.update_available = is_update_available(settings.app_version, latest)
else:
row.update_available = False
await db.commit()
await db.refresh(row)
await write_audit_log(
db,
"service.info.check",
user.id,
{
"latest_version": row.latest_version,
"latest_ref": row.release_check_url,
"update_available": row.update_available,
"last_check_error": row.last_check_error,
},
)
return ServiceInfoCheckResult(
latest_version=row.latest_version,
latest_ref=(row.release_check_url or None),
update_available=row.update_available,
last_checked_at=row.last_checked_at or check_time,
last_check_error=row.last_check_error,
)