Skip to content

Project structure

GitPulse source code organization overview.

High level view

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/"]

Main structure

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 layer

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

An example API endpoint

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)

Models (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

An example of a model

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")

Schematics (Pydantic)

Text Only
1
2
3
4
5
6
7
8
9
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

Example schemes

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
1
2
3
4
5
6
7
8
9
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
1
2
3
4
5
6
7
8
9
src/app/compliance/
+-- __init__.py
+-- engine.py               # Main 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      # Example rubrics

Role Hierarchy (roles.py)

This module defines GitLab access levels (Guest=10, Reporter=20, Developer=30, Maintainer=40, Owner=50) and helper functions access_role_name(), is_contributing_role(), role_priority().

Scoring gate: Members with a role below Developer (Guest/Reporter) and zero activity are excluded from the team aggregate. Inherited group members with no activity are also excluded, regardless of their access level.

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
1
2
3
4
5
6
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 example

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)

Tests

Text Only
tests/
+-- __init__.py
+-- conftest.py             # Pytest fixtures
+-- unit/                   # Unit tests (no external deps)
|   +-- test_compliance_engine.py
|   +-- test_gaming_detection.py
|   +-- test_roles.py               # 36 tests for role hierarchy
|   +-- test_rubric_engine.py
+-- integration/            # Integračné testy
    +-- test_api_courses.py
    +-- test_api_teams.py
    +-- test_gitlab_sync.py

Frontend architecture

Dashboard uses server-side rendering (Jinja2) enriched with htmx for partial updates and Alpine.js for client reactivity. All frontend code is organized according to the component architecture.

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

Using macros

Templates use Jinja2 {% from %} imports to compose pages:

Text Only
1
2
3
4
5
6
{% 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") }}

CDN libraries

Library Version Purpose
htmx 1.9.12 Partial updates
Alpine.js 3. x Client reactivity
Flatpicker latest Date selection
Tom Select 2.3.1 Combo box
Tippy.js 6. x Tooltips

Conventions

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

Type Convention Example
Modules snake_case team_service.py
Classes PascalCase TeamService
Features snake_case get_team_metrics
Constants UPPER_SNAKE MAX_TEAM_SIZE
Endpoints kebab-case /api/v1/team-metrics

Further reading