Commit Graph

61 Commits

Author SHA1 Message Date
Paperclip CTO
d2b673cc0f ci: fix migration check order and harden security workflow
All checks were successful
CI Deploy Checks / Build Container (push) Successful in 8s
CI Quality / Ruff Lint (push) Successful in 8s
CI Security / Bandit + pip-audit (push) Successful in 21s
CI Tests / Django Tests (push) Successful in 38s
CI Tests / OpenAPI Schema (push) Successful in 10s
2026-05-19 13:58:21 +02:00
Paperclip CTO
b6e5e2cc14 ci(gitea): remove setup-python cache options for runner compatibility
Some checks failed
CI Deploy Checks / Build Container (push) Successful in 6s
CI Quality / Ruff Lint (push) Successful in 5s
CI Security / Bandit + pip-audit (push) Failing after 9s
CI Tests / Django Tests (push) Failing after 8s
CI Tests / OpenAPI Schema (push) Has been skipped
2026-05-19 13:49:28 +02:00
Paperclip CTO
d868a04032 ci: split workflows into quality, tests, security, deploy checks
Some checks failed
CI Deploy Checks / Build Container (push) Successful in 6s
CI Quality / Ruff Lint (push) Failing after 6s
CI Tests / Django Tests (push) Has been cancelled
CI Tests / OpenAPI Schema (push) Has been cancelled
CI Security / Bandit + pip-audit (push) Has been cancelled
2026-05-19 13:46:33 +02:00
Paperclip CTO
3c2cbb6e39 Apply FRONTEND_API_BASE_URL across compose profiles
All checks were successful
CI / lint (push) Successful in 13s
CI / test (push) Successful in 24s
CI / build-container (push) Successful in 6s
2026-05-19 13:32:22 +02:00
Paperclip CTO
4709794301 Add local docker frontend runtime API env configuration
All checks were successful
CI / lint (push) Successful in 7s
CI / test (push) Successful in 23s
CI / build-container (push) Successful in 12s
2026-05-19 13:26:03 +02:00
Paperclip CTO
679732de7d feat(security): add modsecurity paranoia level 4 ruleset for nginx
All checks were successful
CI / lint (push) Successful in 7s
CI / test (push) Successful in 21s
CI / build-container (push) Successful in 6s
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-19 10:27:48 +02:00
Paperclip CTO
d54aff5db8 TRA-403 use .env env_file across compose stacks
All checks were successful
CI / lint (push) Successful in 7s
CI / test (push) Successful in 22s
CI / build-container (push) Successful in 6s
2026-05-19 10:18:51 +02:00
Paperclip CTO
f15bdb295a TRA-403 use local Docker build context image paths
All checks were successful
CI / lint (push) Successful in 6s
CI / test (push) Successful in 22s
CI / build-container (push) Successful in 6s
2026-05-19 10:11:02 +02:00
Paperclip CTO
6cfeeff0f9 feat(deploy): add compose profile with dedicated frontend container for reverse-proxy setups
All checks were successful
CI / lint (push) Successful in 6s
CI / test (push) Successful in 21s
CI / build-container (push) Successful in 6s
2026-05-19 09:12:04 +02:00
Paperclip CTO
4a46c14857 feat(frontend): add theming, branding sync, and improved frontend error UX
All checks were successful
CI / lint (push) Successful in 10s
CI / test (push) Successful in 23s
CI / build-container (push) Successful in 8s
2026-05-19 09:09:04 +02:00
Paperclip CTO
5ed7cf2225 fix(api): close contract gaps identified in TRA-398 API review
- EmployeeAttendanceSerializer: add meeting_title (source: meeting.title)
  fixes attendance.js rendering r.meeting_title as undefined
- MeetingParticipantSerializer: add user_email and user_display_name
  so meeting detail participant list shows human-readable identity
- OrganizationProfileListCreateView: add GET handler to list org profiles
  (previously POST-only, returning 405 on GET)
- UserProfileSerializer: add org_ids derived from role_bindings
  so frontend can resolve which org profile to load for branding

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-19 08:57:59 +02:00
Paperclip CTO
333c7b8e11 fix(ci): run tests without redis and align permission/upload expectations
All checks were successful
CI / lint (push) Successful in 6s
CI / test (push) Successful in 22s
CI / build-container (push) Successful in 1m21s
2026-05-18 15:09:16 +02:00
Paperclip CTO
80df6d4baf fix(tests): align dwell tracking user creation with email-only account model
Some checks failed
CI / lint (push) Successful in 5s
CI / test (push) Failing after 29s
CI / build-container (push) Has been skipped
2026-05-18 15:07:37 +02:00
Paperclip CTO
7e7e094fb7 fix(tests): unblock sqlite CI failures in mocks, helpers, and legacy cms route
Some checks failed
CI / lint (push) Successful in 6s
CI / test (push) Failing after 26s
CI / build-container (push) Has been skipped
2026-05-18 15:05:47 +02:00
Paperclip CTO
62e14cda87 fix(gitea-ci): remove pre-migrate check on fresh sqlite database
Some checks failed
CI / lint (push) Successful in 5s
CI / test (push) Failing after 27s
CI / build-container (push) Has been skipped
2026-05-18 15:02:20 +02:00
Paperclip CTO
ba83660990 fix(gitea-ci): use sqlite test workflow and explicit django settings
Some checks failed
CI / lint (push) Successful in 6s
CI / test (push) Failing after 8s
CI / build-container (push) Has been skipped
2026-05-18 15:01:04 +02:00
Paperclip CTO
bf96b7b018 ci: inline django/db env in test and schema commands
Some checks failed
CI / lint (push) Successful in 6s
CI / test (push) Failing after 9s
CI / build-container (push) Has been skipped
2026-05-18 15:00:17 +02:00
Paperclip CTO
b07d8e056d ci: switch tests and schema jobs to sqlite-backed workflow
Some checks failed
CI / lint (push) Successful in 6s
CI / test (push) Failing after 9s
CI / build-container (push) Has been skipped
2026-05-18 14:59:10 +02:00
Paperclip CTO
7ed33ffc08 fix(ci): force test settings module in migrate and pytest commands
Some checks failed
CI / lint (push) Successful in 6s
CI / test (push) Failing after 9s
CI / build-container (push) Has been skipped
2026-05-18 14:58:06 +02:00
Paperclip CTO
ec61d2ab99 fix(test-settings): use postgres service as DATABASE_URL fallback
Some checks failed
CI / lint (push) Successful in 6s
CI / test (push) Failing after 9s
CI / build-container (push) Has been skipped
2026-05-18 14:57:23 +02:00
Paperclip CTO
348aecbead fix(test-settings): honor DATABASE_URL in test configuration
Some checks failed
CI / lint (push) Successful in 6s
CI / test (push) Failing after 9s
CI / build-container (push) Has been skipped
2026-05-18 14:56:37 +02:00
Paperclip CTO
8b2ac6b52b fix(ci): use service hostnames for postgres and redis
Some checks failed
CI / lint (push) Successful in 7s
CI / test (push) Failing after 9s
CI / build-container (push) Has been skipped
2026-05-18 14:55:27 +02:00
Paperclip CTO
1e74090e17 fix(ci): add daphne runtime dependency for django app loading
Some checks failed
CI / lint (push) Successful in 6s
CI / test (push) Failing after 19s
CI / build-container (push) Has been skipped
2026-05-18 14:54:23 +02:00
Paperclip CTO
01661bf5a8 fix: restore CI by fixing lint, test collection, and notification syntax
Some checks failed
CI / lint (push) Successful in 6s
CI / test (push) Failing after 7s
CI / build-container (push) Has been skipped
2026-05-18 14:53:29 +02:00
Paperclip CTO
1e886f2022 fix(TRA-379): generate missing migrations and fix daphne INSTALLED_APPS ordering
Some checks failed
CI / lint (push) Failing after 8s
CI / test (push) Failing after 8s
CI / build-container (push) Has been skipped
- Move daphne before django.contrib.staticfiles in INSTALLED_APPS so Daphne
  system check passes and makemigrations --check can run cleanly
- Generate tracking/0002: rename auto-generated AuditEvent index names to
  match Django 5.2 format
- Generate accounts/0005: sync AccountUser managers and ManyToMany field
  help_text to match current Django/guardian defaults

The CallSession / CallParticipant / CallEvent migration (training/0004)
already existed from the groupcall backend commit; this commit ensures
makemigrations --check reports no pending migrations across all apps.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-18 14:44:51 +02:00
Paperclip CTO
1527c649e6 feat(TRA-373): permission management for meeting moderation
- Add meeting:moderate capability to trainer role in ROLE_CAPABILITIES
- IsMeetingModerator permission class already in place from TRA-372 (training:signoff or meeting:moderate)
- Moderation endpoints (mute/unmute/kick, screen-share, session lifecycle) restricted to meeting:moderate
- Add migration 0003 for MeetingParticipant.attendance_session FK
- Add integration tests: 18 cases covering trainer allowed / learner 403 / unauthenticated 401
  for mute, unmute, kick, screen share (start/stop), call session (start/end/view), audit log

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-18 14:17:11 +02:00
Paperclip CTO
dafbfa2bc4 feat(TRA-372): live groupcall backend — WebRTC signaling + session control
- Add Django Channels 4 + channels-redis to requirements and INSTALLED_APPS
- Upgrade ASGI config to ProtocolTypeRouter with JWT-authenticated WebSocket routing
- Add JWTAuthMiddleware for WebSocket token auth via query param
- Add CallSession, CallParticipant, CallEvent models with migration 0004
- MeetingCallConsumer: join/leave, P2P SDP/ICE relay via per-user groups,
  instructor mute/unmute/kick with DB audit and WS broadcast
- REST endpoints: GET/POST/DELETE /meetings/{id}/call/ (session lifecycle),
  POST /moderate/ (mute/unmute/kick), POST /screen-share/, GET /events/ (audit log)
- IsMeetingModerator permission (accepts training:signoff or meeting:moderate)
- Services: get_or_create_call_session, end_call_session, apply_moderation_action,
  toggle_screen_share with full CallEvent audit trail
- Integration tests covering REST lifecycle, moderation, and WebSocket signaling

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-18 14:12:48 +02:00
Paperclip CTO
7225775e12 feat(TRA-370): attendance UI, API endpoints, and migration
Backend:
- Add migration 0002 for Meeting, MeetingParticipant, EmployeeAttendance models
- Add EmployeeAttendanceSerializer with meeting_title helper field
- Add MeetingDetailView (GET /meetings/{id}/ with participants embedded)
- Add EmployeeAttendanceListView (GET /training/attendance/) with
  role-based access: own records always visible; other users require
  progress:view_all / progress:view_team / users:manage capability
- Register meeting-detail and attendance-list routes in urls.py

Frontend:
- attendance.html + attendance.js: JWT login flow, attendance history
  table with status badges, token refresh via sessionStorage only
  (no localStorage for access tokens)
- meetings.html + meetings.js: meeting list, create-meeting form,
  per-meeting participant management, inline attendance recording
  with select + save per row; UUID input validated before submit
- attendance.css: shared stylesheet for both pages

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-18 12:12:05 +02:00
Paperclip CTO
72e319767f feat(TRA-368): add attendance app with check-in/check-out core
Some checks failed
CI / lint (push) Failing after 46s
CI / test (push) Failing after 11s
CI / build-container (push) Has been skipped
- AttendanceSession model with open/closed status, org_id, timestamps
- POST /api/v1/attendance/check-in/ – creates session, rejects duplicate open check-ins (409)
- POST /api/v1/attendance/check-out/ – closes open session, validates checkout > checkin
- GET /api/v1/attendance/status/ – current open session for requesting user
- GET /api/v1/attendance/history/ – paginated session history per user
- GET /api/v1/attendance/admin/sessions/ – admin view for managers/admins with filters
- Initial migration with composite indexes on (user, status) and (user, checked_in_at)
- 18 test cases covering core flows and edge cases

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-18 12:11:16 +02:00
Paperclip CTO
e92ae6c136 feat(TRA-362): add docker-compose.prod.yml with nginx as web server
Introduces a production Docker Compose stack that places nginx in front
of gunicorn. nginx serves the frontend SPA and Django static files
directly, and proxies all backend routes (/api, /admin, /healthz, etc.)
to the Django container. SECURE_SSL_REDIRECT is now env-configurable so
plain-HTTP nginx deployments work without separate settings files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 08:08:52 +02:00
Paperclip CTO
9e3c28b191 feat(TRA-360): enforce license_user_limit when assigning users to an org
Some checks failed
CI / lint (push) Failing after 6s
CI / test (push) Failing after 10s
CI / build-container (push) Has been skipped
Before creating a UserRoleBinding for a specific org_id, check whether
the org's OrganizationProfile.license_user_limit has been reached. New
users attempting to join a full org receive HTTP 403 with a descriptive
message. Re-assigning a user already in the org (role change) is
unaffected, as is assignment to an org with no OrganizationProfile.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-08 07:54:22 +02:00
Paperclip CTO
e711519413 feat(TRA-359): add POST /api/v1/accounts/organizations/ to create OrganizationProfile
Some checks failed
CI / lint (push) Failing after 11s
CI / test (push) Failing after 10s
CI / build-container (push) Has been skipped
- OrganizationProfileListCreateView (admin-only) handles POST
- OrganizationProfileCreateSerializer drops auto UniqueValidator so the
  view can return 409 (instead of 400) for duplicate org_id
- Returns 201 on success, 400 on validation errors, 409 on duplicate
- Tests: create success, defaults, duplicate 409, missing field 400, non-admin 403

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-08 07:52:08 +02:00
Paperclip CTO
22a472b72e fix(TRA-335): make license_user_limit a free-form integer, not fixed tiers
Replace the choices-constrained field (20/50/100/1000 only) with a plain
PositiveIntegerField so any positive integer is accepted. Adds migration
0004 to apply the schema change. Adds a test asserting arbitrary values
(e.g. 75) are accepted. Updates README with a field table and curl example
that documents common values without enforcing them.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-08 07:52:08 +02:00
Paperclip CTO
ca0352fa33 fix(ci): resolve ruff lint violations blocking CI pipeline
- Move `from datetime import timedelta` to top of base.py (E402)
- Add `# noqa: E402` to conditional sentry imports in prod.py (E402)
- Remove unused `import time` and `IsAdminUser` from api/views.py (F401)
- Remove unused `NoReverseMatch` import from tests/test_smoke.py (F401)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-08 07:52:08 +02:00
Paperclip CTO
295ac1c31f feat(TRA-335): license limits, per-customer branding, frontend, Gitea CI, and deployment README
- Added OrganizationProfile model with license_user_limit choices (20/50/100/1000), company_name, brand_logo_url
- Added migration 0003_organization_profile
- Admin-only REST endpoint GET/PATCH /api/v1/accounts/organizations/{org_id}/profile/
- OrganizationProfileSerializer, OrganizationProfileAdmin, factory, and integration tests
- Static Nginx-hostable frontend under frontend/public with configurable API_BASE_URL via config.js
- frontend/nginx.conf reference server config
- .gitea/workflows/ci.yml: ruff lint, pytest with Postgres/Redis services, docker build
- README.md: English deployment guide covering env vars, Docker Compose, migration, org profile API, frontend deploy

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-08 07:52:08 +02:00
403dcb1d05 chore(TRA-306): trigger deploy via push path (add comment)
Co-Authored-By: Paperclip <noreply@paperclip.ai>
2026-05-07 11:57:41 +00:00
fa74f87fbe fix(TRA-306): use docker exec python for health checks, runner can't reach container IPs directly
Co-Authored-By: Paperclip <noreply@paperclip.ai>
2026-05-07 11:55:10 +00:00
d9a24112a3 fix(TRA-306): remove migrate from startup cmd, workflow handles it via docker exec
Some checks failed
Deploy Waitlist / Deploy to VPS (push) Failing after 3m9s
Co-Authored-By: Paperclip <noreply@paperclip.ai>
2026-05-07 11:49:16 +00:00
518bdf0d9f fix(TRA-306): run migrations via docker exec, separate from gunicorn startup
Some checks failed
Deploy Waitlist / Deploy to VPS (push) Has been cancelled
- Release stale Django migration advisory locks before migrating
- Run migrate via docker exec (not in container startup command)
- Update container CMD to start gunicorn directly (no migrate)
- This unblocks the 15-min migrate hang caused by leftover pg advisory lock

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 11:46:06 +00:00
df8fcc7322 fix(TRA-306): improve deploy workflow health polling and diagnostics
Some checks failed
Deploy Waitlist / Deploy to VPS (push) Has been cancelled
- Replace single healthz curl (no timeout) with polling loop (30x10s)
- Add --connect-timeout and --max-time to all curl calls
- Print container logs when gunicorn fails to start
- Print container logs in always() step for visibility

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 11:39:17 +00:00
ba3fa67194 fix(TRA-306): make celery import conditional in config/__init__.py
Waitlist deployment uses requirements/waitlist.txt which does not include celery.
Making the import conditional allows the app to start without celery installed.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 11:31:54 +00:00
Paperclip DevOpsSRE
1687b0e9d8 fix(TRA-306): fix deploy workflow container name and Dockerfile build
Some checks failed
Deploy Waitlist / Deploy to VPS (push) Failing after 2m26s
- Use COMPOSE_PROJECT_NAME=training-software so container names are
  predictable: training-software-waitlist-web-1
- Fix Dockerfile collectstatic: inject dummy env vars so the build
  step does not fail when celery or other settings are not installed
- Fix verify step to use correct container name

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 10:29:20 +02:00
Paperclip DevOpsSRE
4b8622d197 feat(TRA-306): add waitlist app + VPS deploy workflow
Some checks failed
Deploy Waitlist / Deploy to VPS (push) Failing after 2m26s
Adds Django waitlist app to serve the launch-gating endpoints:
- GET  /training/django-cohort/waitlist  → HTML cohort waitlist page
- POST /v1/waitlist/django-cohort        → JSON signup with attribution

Changes:
- waitlist/ app: WaitlistSignup model, views, urls, admin, template
- config/settings/waitlist.py: minimal prod settings for waitlist-only deploy
- config/urls_waitlist.py: slimmed URL conf (waitlist + healthz + admin)
- config/urls.py: registers waitlist routes on full project
- config/settings/base.py: adds waitlist to INSTALLED_APPS
- docker-compose.waitlist.yml: Traefik-labelled deploy for VPS
- requirements/waitlist.txt: minimal dependency set for waitlist build
- .gitea/workflows/deploy-waitlist.yml: CI deploy job using Docker socket
- Dockerfile: parameterise DJANGO_SETTINGS_FOR_COLLECTSTATIC

DNS action required after deploy (board):
  api.usepaperclip.app  → 76.13.129.223 (VPS)
  usepaperclip.app      → 76.13.129.223 (VPS) OR Vercel creds for Next.js fix

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 10:17:25 +02:00
Paperclip CTO
90f7a78197 feat(TRA-248): WCAG 2.1 AA baseline and de/en i18n framework
Some checks failed
CI / Tests (Python 3.12) (push) Failing after 7m3s
CI / OpenAPI Schema (push) Has been skipped
CI / Lint (push) Failing after 4s
A11y:
- Add alt_text field to MediaAsset and ContentBlock (WCAG 2.1 AA 1.1.1)
- Expose alt_text in MediaAssetSerializer and ContentBlockSerializer so
  frontends can render <img alt="..."> and <video aria-label="..."> correctly
- Migration 0002 adds the two alt_text columns

i18n:
- Add LocaleMiddleware to MIDDLEWARE stack (after SessionMiddleware per Django docs)
- Add LANGUAGES = [("de", ...), ("en", ...)] and LOCALE_PATHS = [BASE_DIR/"locale"]
- Add USE_L10N = True
- Seed locale/de/LC_MESSAGES/django.po and locale/en/LC_MESSAGES/django.po with
  translations for all user-facing API strings (upload errors, notification titles)

Tests (tests/a11y/test_a11y_i18n.py):
- alt_text field round-trip on MediaAsset and ContentBlock
- Serializer exposes and accepts alt_text on create, list, PATCH
- LANGUAGES/LOCALE_PATHS/LocaleMiddleware settings assertions
- Accept-Language header switching smoke test against /api/v1/notifications/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 09:36:31 +02:00
Paperclip CTO
6384eac890 feat(TRA-245): Notification service with email and in-app delivery
Some checks failed
CI / Tests (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / OpenAPI Schema (push) Has been cancelled
- Notification model with idempotency_key dedup (unique per event+object+recipient)
- NotificationDelivery audit model (pending/sent/failed/delivered per channel)
- notify() service: creates notification idempotently, enqueues per-channel tasks
- deliver_notification_task Celery task: sends email via send_mail, marks in-app
  as sent without email; marks FAILED with error_detail on exception (autoretry x3)
- Event trigger helpers: notify_course_assigned, notify_attempt_limit_reached,
  notify_certificate_issued, notify_certificate_expiring (daily-reminder safe)
- send_course_due_reminders_task: periodic Celery Beat stub for due-date alerts
- REST API: list notifications (with ?unread=true filter), mark-read, mark-all-read
- Admin registrations with inline delivery audit view
- Initial migration (Notification + NotificationDelivery tables)
- Pytest test suite: idempotency, delivery dispatch, view auth/filtering

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 09:34:24 +02:00
Paperclip CTO
a27663e6fc feat(TRA-244): Reporting and CSV export for completion, progress, and quiz attempts
Some checks failed
CI / Tests (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / OpenAPI Schema (push) Has been cancelled
- CompletionReportView + CSV: enrollment-level completion status, filterable
  by course_id, org_id, date_from, date_to
- ProgressReportView + CSV: page-level dwell-time aggregates, same filters
- AttemptReportView + CSV: quiz attempt scores, pass/fail, timestamps; org
  filter joins through Enrollment.org_id; course filter traverses
  quiz -> page -> lesson -> module -> course
- Streaming CSV responses with _EchoWriter to avoid buffering large exports
- pytest test suite covering filters, aggregation accuracy, and CSV format

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 09:31:31 +02:00
Paperclip CTO
b087a63b56 feat(TRA-237): CMS content blocks, media upload pipeline, and course theme
Some checks failed
CI / Tests (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / OpenAPI Schema (push) Has been cancelled
- MediaAsset model with file metadata and AV scan status tracking
- ContentBlock model (richtext/image/video/embed/download) with ordered
  blocks per page and unique constraint on (page, order)
- CourseTheme one-to-one per course with primary/secondary color and logo
- validate_upload/save_upload helpers with extension and size enforcement
- scan_media_asset_task Celery stub (marks clean; replace with ClamAV)
- REST API: media upload, page content blocks CRUD, block patch/delete
- Admin registrations for all three models
- Factory-boy factories and pytest test suite for views, upload, and task

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 09:28:54 +02:00
Paperclip CTO
4f7232db56 feat(TRA-243): public certificate verification endpoint with hash integrity check
Some checks failed
CI / Tests (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / OpenAPI Schema (push) Has been cancelled
- CertificateVerifyView: GET /certificates/verify/{serial}/ — AllowAny
- Re-computes SHA-256 from stored PDF and compares to archived hash
- Returns {valid, serial_number, course_title, issued_at, hash} — no PII
- Returns 404 for non-completed or non-existent certificates
- 7 integration tests: hash match, tampered hash, missing file, PII exclusion, 404 cases

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 09:24:05 +02:00
Paperclip CTO
b3a7537364 feat(TRA-242): async certificate generation pipeline with hash-based verification
Some checks failed
CI / Tests (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / OpenAPI Schema (push) Has been cancelled
- Certificate model: serial_number (unique), status (pending/rendering/completed/failed),
  pdf_path, pdf_hash (SHA-256), verification_url, render_attempts, training_record FK
- renderer.py: LaTeX template → pdflatex subprocess → PDF; _escape_latex for XSS safety;
  compute_pdf_hash for immutable verification metadata
- services.py: issue_certificate() generates serial, creates record, enqueues Celery task
- tasks.py: render_certificate_task (bind=True, autoretry 3x with 60s backoff);
  sets RENDERING → COMPLETED with hash; FAILED with error on all retries exhausted
- API: /certificates/ (own list), /certificates/{id}/ (own detail)
- 15 unit + integration tests: hash consistency, LaTeX escaping, mocked render, retry behavior

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 09:22:56 +02:00
Paperclip CTO
87cbf23a9d feat(TRA-241): training modes and trainer signoff state machine
Some checks failed
CI / Tests (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / OpenAPI Schema (push) Has been cancelled
- TrainingRecord model with completion_mode (online/offline/blended) and CompletionStatus state machine
- TrainerSignoff model with decision, notes, trainer FK, and audit timestamp
- SignoffEvidence model for uploaded proof files
- services.py state transitions:
  - mark_in_progress: not_started → in_progress
  - mark_online_passed: online → completed; blended → pending_signoff
  - submit_trainer_signoff: offline/blended approved → completed; rejected → in_progress
  - InvalidTransitionError on illegal state moves
- IsTrainer permission class based on training:signoff capability
- API: record detail, start, mark-online-passed, trainer-signoff, pending-signoff list
- 20 unit + integration tests covering all mode paths, invalid transitions, and access control
- Blended completion requires both online pass AND trainer approval

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 09:20:34 +02:00