Thank you for your interest in contributing! This document explains how to work on the project safely and effectively as the portal grows, with a focus on reusability and zero regressions.
Important: All code in this repository is in production. Do not change existing behavior or endpoints unless explicitly agreed.
- Project Overview
- Branching & Pull Requests
- Coding Standards
- Architecture Overview
- Adding a New Feature/Page (Routes)
- Adding Services, Repositories, and Data Sources
- Static Assets (Web Pages, JS, CSS)
- Testing
- PR Checklist
- Kotlin + Ktor backend
- Koin for Dependency Injection
- MongoDB for persistence
- Feature-based, modular route registration via RouteRegistrar
Directory highlights:
- src/main/kotlin/base: Ktor setup (Auth, HTTP, Monitoring, Routing)
- src/main/kotlin/route: Route handlers
- src/main/kotlin/di: Koin modules
- src/main/kotlin/data: Data sources and repositories
- src/main/kotlin/domain: Domain interfaces and services
- src/main/resources: Static assets and config
- docs: Architecture and API documentation
- Create feature branches from main:
feature/<short-description> - Keep PRs small and focused
- Include tests for changes
- Do not change public API or URLs unless approved (and documented)
- Ensure CI build/tests pass before requesting review
Commit messages:
- Use imperative form: "Add Weather cache invalidation"
- Reference issues where applicable
- Kotlin language level: see gradle/libs.versions.toml
- Follow Kotlin style conventions
- Prefer constructor injection and explicit interfaces
- Keep functions small and cohesive
- Log with slf4j; avoid leaking secrets
We use a lightweight feature-based route registration pattern to keep Routing scalable as the portal grows.
Key elements:
- route/common/RouteRegistrar.kt: tiny contract for route registration
- route/common/Registrars.kt: delegates to existing Route extension functions
- di/RouteModule.kt: provides an ordered List
- base/Routing.kt: iterates registrars to register routes, and preserves redirects & 404 handling
This enables contributors to add features without editing a central monolithic file and without changing existing behavior.
- Create a Ktor Route extension function in
src/main/kotlin/route/..., e.g.
fun Route.myFeatureRoute() {
get("/my-feature") { /* handler */ }
}- Add a registrar in
route/common/Registrars.kt:
object MyFeatureRegistrar : RouteRegistrar {
override fun register(root: Route) {
with(root) { myFeatureRoute() }
}
}- Bind it in
di/RouteModule.ktand include it in the ordered list:
single<RouteRegistrar>(named("myFeature")) { MyFeatureRegistrar }
// Then add get(named("myFeature")) to the list order-
If the feature needs auth, apply Ktor authentication within your Route extension without changing URLs.
-
Do not modify existing routes/endpoints unless approved. Preserve existing behavior.
- Define domain interfaces in
src/main/kotlin/domain/... - Implement data-layer repositories in
src/main/kotlin/data/... - Wire bindings in
di/DataModule.kt(repositories/data sources) anddi/DomainModule.kt( services) - Prefer constructor injection; avoid service locators
- Place new assets under
src/main/resources/web/... - Keep shared assets under
src/main/resources/web/cssandweb/js - Reuse components and utilities to avoid duplication
- Put unit tests under
src/test/kotlin - Add tests for services, repositories, and utilities
- For route handlers, prefer testing via Ktor test host where practical
- Run locally:
./gradlew build
- No existing endpoint behavior changed
- New routes registered via RouteRegistrar and bound in di/RouteModule.kt
- DI bindings added for any new services/repositories
- Tests added/updated; build passes
- Docs updated (README or docs/architecture.md) if needed
- No secrets or credentials in code or logs
Thank you for contributing! 馃帀