API Backend (bme-konza-api)
The backbone of the platform.
Stack
- Go (module declares
go 1.23; CI builds with 1.25), Fiber v2 HTTP framework. - PostgreSQL. Two access paths:
- GORM (
gorm.io/driver/postgres) for application queries. - River (
riverqueue/riveroverriverpgxv5, i.e. apgx/v5pool) for the background job queue — see Background workers. - Schema via goose migrations (db/migrations/,
embedded through
db/embed.go). - Firebase Admin SDK (Auth, FCM push, Cloud Storage).
- Cobra CLI (
konza <command>) + Viper/godotenv config. - Build:
nixpacks.toml→go build -o konza main.go; run:./konza server.
Repo layout
main.go # entrypoint → cmd.Execute(); also holds Swagger annotations
cmd/ # Cobra commands (see below)
pkg/
api/ # HTTP handlers + route registration (routes_v1.go, routes_v2.go)
services/ # business logic (one *_service.go per domain)
models/ # data models / DB structs
middleware/ # auth_middleware, jwt_middleware, role_middleware
config/ # config.go (Config struct)
firebase/ # Firebase Admin SDK init
logging/ # logger
Reports/ # report generation
schema/, validation/, utils/, ProblemDetail/, testutils/
internal/workers/ # background workers (see below)
db/migrations/ # goose SQL migrations (50 files)
docs/ # swagger.yaml + docs.go (generated OpenAPI)
public/ # static assets served by the API
logs/ # logfile.log (LOGGING_FILE)
CLI commands (konza ...)
From cmd/root.go:
| Command | Purpose |
|---|---|
server |
Run the HTTP API |
migrate |
Run DB migrations |
user --email --name --role --password [--deviceToken] |
Create a user (e.g. first admin) |
update_data & one-off scripts |
Data backfills: update question/response counts, sync resource stats, mark notifications read, create/delete reputation data, backfill Firebase UIDs |
These backfill commands are one-shot maintenance scripts tied to specific migrations.
[TODO: note which have already been run in prod so they aren't re-run by mistake.]
Configuration
Config is bound from environment variables (.env) — see
.env.example — or konza.yml
(konza.example.yml). Key groups:
| Group | Keys | Notes |
|---|---|---|
| Server | SERVER_HOST, SERVER_PORT (8000), SERVER_CORS_ALLOW_ORIGINS/HEADERS |
CORS origins must include the web/admin domains |
| Database | DATABASE_HOST/PORT/NAME/USERNAME/PASSWORD/SSLMODE |
see 06 |
| Auth | AUTH_JWT_SECRET_KEY 🔑, AUTH_JWT_EXPIRY_TIMEOUT_SECONDS, AUTH_SESSION_LIFETIME, AUTH_MAX_LOGIN_ATTEMPTS (7), AUTH_REQUIRE_VERIFIED_ACCOUNTS |
JWT-based; secret in vault |
| Firebase | FIREBASE_FILE_NAME (<PROD_FIREBASE_PROJECT>.json), FIREBASE_SERVICE_FILE (remote link to the service file), FIREBASE_PROJECT_ID, FIREBASE_BUCKET_NAME 🔑 |
see 11 |
| Email (SMTP) | SMTP_HOST/PORT/PASSWORD/FROM_EMAIL/TO_EMAIL/REPORTS_EMAIL 🔑 |
reports + notifications |
| Reputation points | NEW_QUESTION_POINTS (5), UPVOTE_ANSWER_POINTS (10), ANSWER_ACCEPTED_POINTS (15), NEW_RESPONSE_POINTS (5) |
tune scoring here |
| Logging | LOGGING_LEVEL (debug), LOGGING_FILE |
see 13 |
| Timeouts | RESPONSE_TIMEOUT, DATABASE_TIMEOUT, SOFT_STOP_TIMEOUT, HARD_STOP_TIMEOUT |
graceful shutdown |
Authentication & authorization
The API has two route groups (pkg/api/api.go mounts both):
- v2 (current) — authenticates via Firebase Auth and applies role-based
authorization. Roles (pkg/models/user.go):
Member,Admin,Superuser. - v1 (legacy) — an older token-based version, being retired in favour of v2.
New work should target v2. The migration links users to Firebase identities
(UpdateUserRecordsWithFirebaseUid backfill).
Detailed auth internals and the v1 retirement plan are tracked in the private operations notes (not published here).
HTTP surface
Base paths api/v1/ and api/v2/ (mounted in pkg/api/api.go).
Static files are served at /static (from ./public) and the full, authoritative API
spec is the Swagger UI at /swagger/*.
🔒 = authentication required. [A] = Admin/Superuser role, [S] = Superuser role (v2).
v1 — /api/v1/* (routes_v1.go)
| Domain | Endpoints |
|---|---|
| Equipment | GET equipments, GET equipments/:id, 🔒POST/PUT/DELETE equipments |
| Brands | GET brands, GET brands/:id, GET brands/:equipmentID/filter-by-equipment, 🔒POST/PUT/DELETE brands |
| Models | GET models, GET models/:brandID/filter-by-brand, GET models/:equipmentID/filter-by-equipment, GET models/:equipmentID/filter-by-equipment-brand/:brandID, 🔒POST/PUT/DELETE models |
| Resources | GET resources, GET resources/:id, GET resources/:id/models, GET resources/filter/:type, GET resources/filter/limited/:type, GET resources/categories/:type, 🔒POST/PUT/DELETE resources |
| Q&A | GET questions, GET questions/:id, GET questions/filter/limit, GET questions/unanswered/get, GET questions/category/:categoryId, 🔒POST/PUT/DELETE questions, 🔒GET questions/user/get, 🔒GET questions/user/interests[/unanswered] |
| Responses | GET responses/:questionID, 🔒POST responses, 🔒GET/PUT/DELETE responses/:id, 🔒POST responses/:id (upvote), 🔒GET responses/upvotes/:questionID, 🔒PUT responses/:id/accept, 🔒PUT responses/:id/unmark-as-solution |
| Users | 🔒GET/DELETE users/:id, 🔒POST users, 🔒GET users/view/:id (public view), 🔒admin-users, 🔒regular-users, 🔒 bookmarks (get/create/:id), 🔒 preferences (new/get/delete/:id), 🔒PATCH users/update-title, 🔒PATCH user/country/update, 🔒PATCH users/volunteer-status/toggle, 🔒GET users/:id/points, 🔒GET peers/interest/filter |
| Experience | 🔒GET/POST/PATCH experiences, 🔒GET experiences/get/:id, 🔒DELETE experiences/:id, 🔒PATCH user/experiences/change-visibility |
| Tiers / Leaderboard | 🔒GET/POST/PUT/DELETE tiers, 🔒GET tiers/:id, 🔒GET leaderboard/:tierID, 🔒GET leaderboard/:userID/user |
| Volunteers | 🔒GET volunteers |
| Notifications | 🔒GET notifications, 🔒GET notifications/unread, 🔒PATCH notifications/:id/mark-read, 🔒PATCH notifications/mark-all-read |
| Announcements | 🔒GET/POST announcements, 🔒DELETE announcements/:id |
| Feedback | POST feedback, 🔒GET feedback, 🔒GET feedback/:id |
| Tags | GET tags, 🔒POST/PATCH/DELETE tags |
| Categories | GET categories, 🔒POST/PUT/DELETE categories |
| Requests | POST requests, 🔒GET requests |
| Device IDs | POST device-id/save, GET device-id |
| Analytics | GET analytics, GET analytics/rank-data, PATCH analytics/add-view-count/:questionID, PATCH analytics/add-resource-view-count/:resourceID, PATCH analytics/update-download-count/:resourceID, POST /pdf/generate |
| Search | GET search, GET search-with-filters, GET filter/:modelID |
v2 — /api/v2/* (routes_v2.go)
v2 redefines the write/admin side with Firebase auth + roles, and adds new features. Public reads (questions, tags, search, active banners) need no auth.
| Domain | Endpoints |
|---|---|
| Equipment/Brands/Models | 🔒[A]POST/PUT/DELETE for each |
| Resources | 🔒[A]POST/PUT resources, 🔒[S]DELETE resources/:id |
| Q&A | GET questions, GET questions/:id, GET questions/filter/limit, GET questions/unanswered/get, GET questions/category/:id, 🔒POST/PUT/DELETE questions, 🔒POST responses, 🔒PUT/DELETE responses/:id, 🔒GET questions/user/get, 🔒GET questions/user/interests[/unanswered] |
| Users | 🔒[S]DELETE users/:id, 🔒[S]POST users, 🔒[S]GET admin-users, 🔒[A]GET regular-users, 🔒 bookmarks (get/create/:id) |
| Tiers / Leaderboard | 🔒[A]GET/POST/PUT/DELETE tiers, 🔒[A]GET leaderboard/... |
| Announcements | 🔒[A]GET/POST/DELETE announcements |
| Feedback | 🔒[A]GET feedback, 🔒[A]GET feedback/:id |
| Tags | GET tags, 🔒[A]POST/PATCH tags, 🔒[S]DELETE tags/:id |
| Categories | 🔒[A]POST/PUT categories, 🔒[S]DELETE categories/:id |
| Banners (new) | GET banners/active, 🔒[A]GET/POST/PUT/DELETE banners |
| Reports (new) | 🔒[S]GET reports, 🔒[S]DELETE reports/:id |
| Requests | 🔒[A]GET requests |
| Search | GET search-with-filters |
Admin group admin/ |
🔒[A]POST admin/remove-question/:id, 🔒[A]POST admin/remove-response/:id, 🔒[A]GET admin/reports, 🔒[A]DELETE admin/reports/:id |
API docs (Swagger / OpenAPI)
Swagger UI is served at /swagger/* (gofiber/swagger). Annotations live in main.go
and handlers; the generated spec is docs/swagger.yaml
(+ docs.go). Regenerate with swag init after changing annotations.
Background workers
Background jobs run on River (riverqueue/river), a
Postgres-backed job queue. The River client and workers are wired up in-process in
cmd/server.go
— so they run inside the konza server process, not as a separate deployment. Recurring
work is registered via River periodic jobs (RiverClient.PeriodicJobs().Add(...)), which
replaces the old "cron" model. Default queue, MaxWorkers: 100.
Worker implementations live in internal/workers/:
| Worker (River) | Purpose |
|---|---|
MonthlyReportWorker |
Generate/email monthly reports |
ProcessReputationPointsWorker |
Award/recompute reputation points |
QaReminderWorker |
Remind about unanswered questions |
QuestionNotificationWorker |
Notify on new questions/answers (FCM) |
SaveSearchWorker |
Persist search queries (search_state_log) |
UserInteractionReportWorker |
User-activity reporting |
River keeps its own tables in Postgres (job state, queues, leadership). The periodic-job schedules are defined in
cmd/server.go.[TODO: list each periodic job's interval.]
Tests
go test ./... (CI runs this on every PR). Helpers in pkg/testutils.
[TODO: note any tests needing a live DB/Firebase and how to run them locally.]