Štruktúra projektu
Prehľad organizácie zdrojového kódu GitPulse.
Vysokoúrovňový pohľad
graph TB
subgraph "Repository Root"
SRC["src/app/"]
TESTS["tests/"]
DOCS["docs-site/"]
ALEMBIC["alembic/"]
SCRIPTS["scripts/"]
MONITORING["monitoring/"]
end
SRC --> API["api/"]
SRC --> MODELS["models/"]
SRC --> SCHEMAS["schemas/"]
SRC --> SERVICES["services/"]
SRC --> DB["db/"]
SRC --> COMPLIANCE["compliance/"]
SRC --> WORKERS["workers/"]
Hlavná štruktúra
| Text Only |
|---|
| gitpulse/
+-- src/app/ # Hlavný aplikačný kód
| +-- __init__.py
| +-- main.py # FastAPI aplikácia
| +-- config.py # Konfigurácia (pydantic-settings)
| +-- dependencies.py # Dependency injection
| +-- api/ # HTTP endpointy
| +-- models/ # SQLAlchemy ORM modely
| +-- schemas/ # Pydantic schémy
| +-- services/ # Business logika
| +-- db/ # Database utilities
| +-- compliance/ # Hodnotenie a gaming detection
| +-- workers/ # Background tasks (RQ)
| +-- utils/ # Pomocné funkcie
+-- tests/ # Testy
| +-- unit/ # Unit testy
| +-- integration/ # Integračné testy
+-- alembic/ # Database migrácie
| +-- versions/ # Migračné skripty
+-- docs-site/ # Dokumentácia (MkDocs)
+-- scripts/ # Admin a maintenance skripty
+-- monitoring/ # Prometheus, Grafana config
+-- docker-compose.yml # Produkčný compose
+-- Dockerfile # API image
+-- pyproject.toml # Python dependencies
|
API vrstva
| Text Only |
|---|
| src/app/api/
+-- __init__.py
+-- router.py # Hlavný router (agreguje všetky)
+-- health.py # /health, /ready, /live
+-- courses.py # CRUD pre kurzy
+-- teams.py # CRUD pre tímy
+-- projects.py # CRUD pre projekty
+-- users.py # Správa používateľov
+-- events.py # GitLab events API
+-- webhooks.py # GitLab webhook handler
+-- sync.py # Manuálna synchronizácia
+-- reports.py # Generovanie reportov
+-- dashboard.py # Dashboard data
+-- rubrics.py # Rubric management
+-- docker_verify.py # Docker verification
|
Príklad API endpointu
| Python |
|---|
| # src/app/api/courses.py
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.session import get_db
from app.schemas.course import CourseCreate, CourseResponse
from app.services.course_service import CourseService
from app.security.rbac import require_role
router = APIRouter(prefix="/courses", tags=["courses"])
@router.post("/", response_model=CourseResponse, status_code=status.HTTP_201_CREATED)
async def create_course(
course_data: CourseCreate,
db: AsyncSession = Depends(get_db),
_: None = Depends(require_role("admin")),
):
"""Vytvorí nový kurz."""
service = CourseService(db)
return await service.create(course_data)
|
Modely (ORM)
| Text Only |
|---|
| src/app/models/
+-- __init__.py
+-- base.py # Základná trieda s timestamps
+-- course.py # Course model
+-- team.py # Team model
+-- user.py # User model (students, teachers)
+-- project.py # GitLab project mapping
+-- events.py # GitLab events (commit, MR, issue)
+-- metrics.py # Computed metrics
+-- rubric.py # Rubric definitions
|
Príklad modelu
| Python |
|---|
| # src/app/models/team.py
from sqlalchemy import String, ForeignKey, Integer
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.models.base import BaseModel
class Team(BaseModel):
__tablename__ = "teams"
name: Mapped[str] = mapped_column(String(100))
course_id: Mapped[int] = mapped_column(ForeignKey("courses.id"))
gitlab_group_id: Mapped[int | None] = mapped_column(Integer)
# Relationships
course: Mapped["Course"] = relationship(back_populates="teams")
members: Mapped[list["TeamMembership"]] = relationship(back_populates="team")
projects: Mapped[list["Project"]] = relationship(back_populates="team")
|
Schémy (Pydantic)
| Text Only |
|---|
| src/app/schemas/
+-- __init__.py
+-- common.py # Zdieľané schémy (pagination, etc.)
+-- course.py # CourseCreate, CourseResponse
+-- team.py # TeamCreate, TeamResponse
+-- user.py # UserCreate, UserResponse
+-- event.py # Event schémy
+-- metrics.py # Metric response schémy
+-- report.py # Report configuration
|
Príklad schém
| Python |
|---|
| # src/app/schemas/course.py
from datetime import date
from pydantic import BaseModel, ConfigDict
class CourseBase(BaseModel):
code: str
name: str
academic_year: str
semester: str
class CourseCreate(CourseBase):
start_date: date | None = None
end_date: date | None = None
class CourseResponse(CourseBase):
model_config = ConfigDict(from_attributes=True)
id: int
start_date: date | None
end_date: date | None
team_count: int = 0
|
Services (Business logic)
| Text Only |
|---|
| src/app/services/
+-- __init__.py
+-- course_service.py # Course CRUD + business logic
+-- team_service.py # Team management
+-- gitlab_service.py # GitLab API wrapper
+-- sync_service.py # Data synchronization
+-- metrics_service.py # Metrics computation
+-- report_service.py # Report generation
+-- cache_service.py # Redis caching
|
Service pattern
| Python |
|---|
| # src/app/services/team_service.py
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.models.team import Team
from app.schemas.team import TeamCreate
class TeamService:
def __init__(self, db: AsyncSession):
self.db = db
async def get_by_id(self, team_id: int) -> Team | None:
result = await self.db.execute(
select(Team).where(Team.id == team_id)
)
return result.scalar_one_or_none()
async def create(self, data: TeamCreate, course_id: int) -> Team:
team = Team(**data.model_dump(), course_id=course_id)
self.db.add(team)
await self.db.commit()
await self.db.refresh(team)
return team
|
Compliance engine
| Text Only |
|---|
| src/app/compliance/
+-- __init__.py
+-- engine.py # Hlavný compliance engine
+-- roles.py # GitLab role hierarchy (access levels, scoring gate helpers)
+-- rubric_engine.py # Rubric evaluation
+-- gaming_detection.py # Gaming pattern detection
+-- gaming_types.py # Gaming indicator types
+-- types.py # Shared types
+-- example_rubrics.py # Ukážkové rubrics
|
Hierarchia rolí (roles.py)
Modul definuje GitLab access levels (Guest=10, Reporter=20, Developer=30, Maintainer=40, Owner=50) a pomocné funkcie access_role_name(), is_contributing_role(), role_priority().
Scoring gate: Členovia s rolou nižšou ako Developer (Guest/Reporter) a nulovú aktivitou nie sú zahrnutí do tímového agregátu. Rovnako sú vylúčení dedení členovia (inherited group members) bez aktivity, bez ohľadu na úroveň prístupu.
graph LR
Events["GitLab Events"] --> Engine["Compliance Engine"]
Engine --> Roles["Role Hierarchy"]
Roles --> Engine
Engine --> Rubric["Rubric Engine"]
Engine --> Gaming["Gaming Detection"]
Rubric --> Score["Team Score"]
Gaming --> Flags["Warning Flags"]
Score --> Report["Report"]
Flags --> Report
Workers (Background tasks)
| Text Only |
|---|
| src/app/workers/
+-- __init__.py
+-- sync_worker.py # GitLab sync tasks
+-- metrics_worker.py # Metrics computation
+-- report_worker.py # Async report generation
+-- cleanup_worker.py # Data cleanup tasks
|
Worker príklad
| Python |
|---|
| # src/app/workers/sync_worker.py
import redis
from rq import Queue
redis_conn = redis.from_url("redis://localhost:6379/0")
queue = Queue("sync", connection=redis_conn)
def sync_project_events(project_id: int) -> dict:
"""Background task pre synchronizáciu GitLab events."""
from app.services.sync_service import SyncService
service = SyncService()
result = service.sync_project(project_id)
return {"synced_events": result.event_count}
# Enqueue task
job = queue.enqueue(sync_project_events, project_id=123)
|
Testy
| Text Only |
|---|
| tests/
+-- __init__.py
+-- conftest.py # Pytest fixtures
+-- unit/ # Unit testy (bez external deps)
| +-- test_compliance_engine.py
| +-- test_gaming_detection.py
| +-- test_roles.py # 36 testov pre role hierarchy
| +-- test_rubric_engine.py
| +-- test_docker_lint.py # Docker linting testy
+-- integration/ # Integračné testy
+-- test_api_courses.py
+-- test_api_teams.py
+-- test_gitlab_sync.py
|
Frontend architektúra
Dashboard používa server-side rendering (Jinja2) obohatený o htmx pre parciálne aktualizácie a Alpine.js pre klientskú reaktivitu. Všetok frontend kód je organizovaný podľa komponentovej architektúry.
Šablóny (Templates)
| Text Only |
|---|
| src/app/templates/
+-- base.html # Minimálny shell (~80 riadkov)
+-- components/ # 15 znovupoužiteľných Jinja2 makier
| +-- icons.html # SVG ikony
| +-- buttons.html # Tlačidlá (primary, danger, icon)
| +-- badges.html # Status badges, traffic light
| +-- modals.html # Modal & drawer dialógy
| +-- forms.html # Formulárové polia
| +-- alerts.html # Alert banery
| +-- cards.html # Karty a metriky
| +-- layout.html # Page header, breadcrumbs
| +-- loaders.html # Spinner, skeleton
| +-- tables.html # Triediteľné tabuľky
| +-- toasts.html # Toast notifikácie
| +-- header.html # Navigačný bar
| +-- import_helpers.html # GitLab import modal
| +-- process_status.html # Sync status komponent
| +-- feature_flags.html # Feature flag makrá
+-- errors/ # Chybové stránky (403, 404, 500)
+-- static/
| +-- styles.css # Entry point - @import 18 CSS súborov
| +-- css/ # 18 komponentových CSS súborov
| | +-- tokens.css # Design tokens (farby, spacing)
| | +-- buttons.css # Štýly tlačidiel
| | +-- badges.css # Štýly badges
| | +-- cards.css # Štýly kariet
| | +-- modals.css # Modal & drawer štýly
| | +-- responsive.css # Mobile (≤768px) + phone (≤480px)
| | +-- ... # (toasts, alerts, forms, tables, ...)
| +-- js/ # 6 JS modulov (poradie záleží)
| +-- core.js # CSRF, apiCall, toast, modal, utils
| +-- components.js # Paginator, TableFilter, forms
| +-- global-job-bar.js # Multi-job progress bar + SSE
| +-- sse-stream.js # SSE streaming s fallback
| +-- gitlab-helpers.js # GitLab URL parsing
| +-- sse-stream.js # SSE streaming s fallback
+-- *.html # 20 stránkových šablón
|
Použitie makier
Šablóny používajú Jinja2 {% from %} importy na skladanie stránok:
| Text Only |
|---|
| {% from "components/buttons.html" import btn, btn_icon %}
{% from "components/modals.html" import modal %}
{% from "components/tables.html" import data_table %}
{{ btn("Uložiť", variant="primary") }}
{{ modal("editModal", "Upraviť tím") }}
|
Knižnice CDN
| Knižnica | Verzia | Účel |
| htmx | 1.9.12 | Parciálne aktualizácie |
| Alpine.js | 3.x | Klientská reaktivita |
| Flatpickr | latest | Výber dátumu |
| Tom Select | 2.3.1 | Combo box |
| Tippy.js | 6.x | Tooltipy |
Konvencie
Import ordering
| Python |
|---|
| # 1. Standard library
from datetime import datetime
from typing import Any
# 2. Third-party
from fastapi import APIRouter
from sqlalchemy.ext.asyncio import AsyncSession
# 3. Local imports
from app.models.team import Team
from app.services.team_service import TeamService
|
Naming conventions
| Typ | Konvencia | Príklad |
| Moduly | snake_case | team_service.py |
| Triedy | PascalCase | TeamService |
| Funkcie | snake_case | get_team_metrics |
| Konštanty | UPPER_SNAKE | MAX_TEAM_SIZE |
| Endpointy | kebab-case | /api/v1/team-metrics |
Ďalšie čítanie