This repository contains a production-leaning scholarship platform MVP built to demonstrate backend and platform engineering skills with a simple, explainable architecture.
Current implementation focus:
- one Go API service using Chi
- PostgreSQL-backed scholarship endpoints
- Redis-backed caching for scholarship read endpoints
- PostgreSQL-backed user registration and login
- PostgreSQL-backed bookmarks and applications
- local PostgreSQL via Docker Compose
- SQL-file migration workflow
- production-leaning API Docker build
- simple GitHub Actions CI
Not implemented yet:
- AI provider behavior
- full auth behavior
services/apicontains the Go API serviceplatform/docker-composecontains the local PostgreSQL setupplatform/db/migrationscontains SQL migrationsplatform/scriptscontains developer helper scriptsdeploy/,infra/, andobservability/contain deployment and platform scaffolding for later phases
- Go 1.24+
- Docker Desktop or Docker Engine with the Docker Compose plugin
- GNU Make
- Bash, which is available by default in WSL Ubuntu and GitHub Actions Linux runners
- Copy the example environment file:
cp .env.example .env- Start PostgreSQL and Redis:
make db-up- Apply migrations:
make migrate-up- Start the API:
make run-api- In another terminal, verify the public scholarship endpoints:
curl http://localhost:8080/healthz
curl http://localhost:8080/scholarships- Register a user and log in:
curl -X POST http://localhost:8080/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"password123","full_name":"Scholarship User"}'
curl -X POST http://localhost:8080/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"password123"}'-
make db-upStarts the local PostgreSQL and Redis containers defined inplatform/docker-compose/docker-compose.yml. -
make db-downStops the local PostgreSQL and Redis containers. -
make migrate-upApplies any unapplied SQL files fromplatform/db/migrationsinto the local database. -
make run-apiRuns the Go API locally fromservices/api. -
make testRunsgo test ./...for the API module.
The GitHub Actions pipeline is intentionally simple but DevSecOps-oriented:
- run
go test ./... - run
go vet ./... - lint the API Dockerfile with Hadolint
- scan Terraform and Kubernetes manifests with Checkov
- build the API Docker image
- scan the built image with Trivy and fail on
HIGHandCRITICAL - on pushes to
main, push the image to a registry - update
deploy/k8s/base/api-deployment.yamlwith the new image tag and commit that change back to Git
Trigger rules:
- pull requests run lint, tests, scans, and image build only
- pushes to
mainrun the full pipeline, including image push and GitOps manifest update
Image tagging strategy:
- every published image gets a commit-based
sha-<full-sha>tag - pushes from
mainalso get a stablemaintag
This keeps deployments reviewable and reproducible without relying on latest alone.
To enable registry push support, configure these repository secrets:
CONTAINER_REGISTRYExample:docker.ioorghcr.ioCONTAINER_REGISTRY_USERNAMEExample: Docker Hub username or GitHub usernameCONTAINER_REGISTRY_TOKENExample: Docker Hub access token or GHCR tokenIMAGE_REPOSITORYExample:your-user/scholarship-platform-apioryour-org/scholarship-platform-api
This keeps the workflow usable with Docker Hub or GHCR without maintaining two separate pipelines.
Base manifests live in deploy/k8s/base and are designed to stay simple and readable:
- one API Deployment and Service
- one PostgreSQL Deployment and Service
- one Redis Deployment and Service
- one ConfigMap
- one example Secret manifest
- one Ingress
Apply the base with:
kubectl apply -k deploy/k8s/baseBefore applying:
- create a real secret from
deploy/k8s/base/secret.example.yaml - set the API image in
deploy/k8s/base/api-deployment.yaml
Image reference guidance:
- for shared environments, use a Docker Hub image such as
docker.io/<your-user>/scholarship-platform-api:mainor a commit-based tag - for local clusters, point the manifest at an image already loaded into the cluster runtime or your local registry
- prefer immutable commit-based tags for repeatable deployments, and use
mainonly as a convenience tag
ArgoCD manifests live in deploy/argocd and stay intentionally simple:
- one
AppProject - one
Application - the Application points directly at
deploy/k8s/base
The ArgoCD Application targets:
- cluster:
https://kubernetes.default.svc - namespace:
scholarship-platform - revision:
main
Before applying the ArgoCD manifests:
- update the GitHub repository URL in
deploy/argocd/project.yaml - update the same repository URL in
deploy/argocd/application.yaml
The intended GitOps flow is:
- GitHub Actions builds and pushes a new API image on
main - the workflow updates the image tag in
deploy/k8s/base/api-deployment.yaml - ArgoCD detects the Git change
- ArgoCD syncs the updated manifests into the cluster
That means ArgoCD still treats Git as the source of truth. The image tag should be changed in Git, not manually in the cluster.
The migration path is intentionally simple:
- write migrations as ordered
.sqlfiles inplatform/db/migrations - start PostgreSQL with
make db-up - apply unapplied migrations with
make migrate-up
The migration runner:
- waits for PostgreSQL to become ready
- creates a
schema_migrationstable if needed - applies SQL files in filename order
- records each applied filename so it is not re-applied
The runner is a bash script at platform/scripts/migrate-up.sh. It loads .env
with standard shell syntax, so keep environment values in KEY=value form.
GET /healthzGET /readyzGET /metricsGET /scholarshipsGET /scholarships/{id}POST /auth/registerPOST /auth/loginGET /mePOST /me/bookmarks/{scholarshipId}GET /me/bookmarksDELETE /me/bookmarks/{scholarshipId}POST /me/applicationsGET /me/applicationsPATCH /me/applications/{id}POST /admin/scholarships
POST /admin/scholarships now requires a JWT with the admin role. The admin role is intentionally simple for now and can be assigned directly in the database for local development.
-
platform/docker-compose/docker-compose.ymlDefines the local PostgreSQL and Redis containers for development. -
platform/db/migrations/0001_create_scholarships.sqlCreates the initialscholarshipstable and its supporting index. -
platform/scripts/migrate-up.shRuns ordered SQL migrations against the local PostgreSQL container and records applied versions. -
services/api/internal/app/app.goInitializes config, logging, PostgreSQL, repositories, services, and the HTTP server. -
services/api/internal/repository/postgres.goContains the concrete PostgreSQL scholarship repository implementation. -
services/api/internal/cache/cache.goContains the Redis-backed scholarship cache implementation used only forGET /scholarshipsandGET /scholarships/{id}. -
services/api/internal/repository/postgres_users.goContains the concrete PostgreSQL user repository implementation for registration, login lookup, and current-user lookup. -
services/api/internal/repository/postgres_bookmarks.goContains the concrete PostgreSQL bookmark repository implementation for create, list, and delete. -
services/api/internal/repository/postgres_applications.goContains the concrete PostgreSQL application repository implementation for create, list, and patch-style update. -
services/api/internal/auth/auth.goContains password hashing, JWT generation, JWT validation, and auth claims handling. -
services/api/DockerfileDefines the production-leaning multi-stage API image build. -
.github/workflows/api-ci.ymlDefines the DevSecOps workflow for linting, testing, IaC scanning, image scanning, image publishing, and GitOps manifest updates. -
.env.exampleShows the environment variables needed by both the API and the local PostgreSQL container. -
MakefileProvides the local developer entrypoints for database startup, migrations, API startup, and tests.