diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d56a79..664e376 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -225,6 +225,8 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max platforms: linux/amd64,linux/arm64 + build-args: | + BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} # ── Phase 5: GitOps – update preprod k8s manifest ───────────────────────── update-k8s-manifest: diff --git a/backend/Dockerfile b/backend/Dockerfile index 7d1ef31..2eb5cfe 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -18,6 +18,10 @@ RUN pip install --no-cache-dir -r requirements.txt # Copy application code COPY . . +# Inject build metadata +ARG BUILD_DATE="" +ENV BUILD_DATE=${BUILD_DATE} + # Create non-root user RUN useradd -m -u 1000 appuser && \ chown -R appuser:appuser /app diff --git a/backend/app/api/v1/api.py b/backend/app/api/v1/api.py index fe5eafa..8f06400 100644 --- a/backend/app/api/v1/api.py +++ b/backend/app/api/v1/api.py @@ -14,6 +14,7 @@ from app.api.v1.endpoints import ( providers, app_settings, logs, + version, ) api_router = APIRouter() @@ -38,3 +39,4 @@ api_router.include_router(app_settings.router, prefix="/settings", tags=["Settin api_router.include_router( logs.router, prefix="/processing-runs", tags=["Processing Logs"] ) +api_router.include_router(version.router, prefix="/version", tags=["Version"]) diff --git a/backend/app/api/v1/endpoints/version.py b/backend/app/api/v1/endpoints/version.py new file mode 100644 index 0000000..8dbe348 --- /dev/null +++ b/backend/app/api/v1/endpoints/version.py @@ -0,0 +1,18 @@ +""" +Version endpoint – returns the application version and build date. +""" + +from fastapi import APIRouter + +from app.core.config import settings + +router = APIRouter() + + +@router.get("") +async def get_version(): + """Return application version and build metadata.""" + return { + "version": settings.APP_VERSION, + "build_date": settings.BUILD_DATE or None, + } diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 129df41..44b2cea 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -29,6 +29,7 @@ class Settings(BaseSettings): # Application APP_NAME: str = "InboxConverge" APP_VERSION: str = "2.0.0" + BUILD_DATE: str = "" APP_URL: str = "https://inboxconverge.com" CONTACT_EMAIL: str = "christian@inboxconverge.com" DEBUG: bool = False diff --git a/frontend/src/components/DashboardLayout.tsx b/frontend/src/components/DashboardLayout.tsx index 99b2360..e619004 100644 --- a/frontend/src/components/DashboardLayout.tsx +++ b/frontend/src/components/DashboardLayout.tsx @@ -3,7 +3,9 @@ import { useState } from 'react'; import Link from 'next/link'; import { usePathname, useRouter } from 'next/navigation'; +import { useQuery } from '@tanstack/react-query'; import { useAuthStore } from '@/store/authStore'; +import { versionApi } from '@/lib/api'; import { LayoutDashboard, Mail, @@ -30,6 +32,12 @@ export function DashboardLayout({ children }: DashboardLayoutProps) { const router = useRouter(); const { user, logout } = useAuthStore(); + const { data: versionInfo } = useQuery({ + queryKey: ['version'], + queryFn: () => versionApi.get(), + staleTime: Infinity, + }); + const handleLogout = () => { logout(); router.push('/login'); @@ -235,13 +243,30 @@ export function DashboardLayout({ children }: DashboardLayoutProps) { {/* Footer */} diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index b007342..aae2a13 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -755,4 +755,18 @@ export const adminNotificationsApi = { }, }; +// ── Version API ───────────────────────────────────────────────────────── + +export interface VersionInfo { + version: string; + build_date: string | null; +} + +export const versionApi = { + async get(): Promise { + const response = await api.get('/version'); + return response.data; + }, +}; + export default api;