{"id":50695707,"url":"https://github.com/hngprojects/personal-trainer-be","last_synced_at":"2026-06-09T06:08:14.986Z","repository":{"id":355582729,"uuid":"1227577616","full_name":"hngprojects/personal-trainer-be","owner":"hngprojects","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-01T19:50:39.000Z","size":43808,"stargazers_count":1,"open_issues_count":3,"forks_count":7,"subscribers_count":0,"default_branch":"dev","last_synced_at":"2026-06-01T21:13:27.333Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hngprojects.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-02T21:58:32.000Z","updated_at":"2026-06-01T19:50:53.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/hngprojects/personal-trainer-be","commit_stats":null,"previous_names":["hngprojects/personal-trainer-be"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/hngprojects/personal-trainer-be","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hngprojects%2Fpersonal-trainer-be","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hngprojects%2Fpersonal-trainer-be/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hngprojects%2Fpersonal-trainer-be/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hngprojects%2Fpersonal-trainer-be/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hngprojects","download_url":"https://codeload.github.com/hngprojects/personal-trainer-be/tar.gz/refs/heads/dev","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hngprojects%2Fpersonal-trainer-be/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34093840,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-09T02:00:06.510Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-09T06:08:14.145Z","updated_at":"2026-06-09T06:08:14.976Z","avatar_url":"https://github.com/hngprojects.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Personal Trainer Backend\n\nA modern, scalable backend service for personal training management built with Go. This service provides a RESTful API for managing trainers, clients, training sessions, and fitness programs.\n\n## Features\n\n- **Clean Architecture**: Layered architecture with clear separation of concerns (handlers, services, repositories)\n- **RESTful API**: Standard REST conventions with JSON responses\n- **PostgreSQL Database**: Robust relational database with migration management\n- **Session-Based Authentication**: Secure session management for user authentication\n- **Structured Logging**: Comprehensive logging using Go's `slog` package\n- **Error Recovery**: Middleware to gracefully handle panics\n- **Database Migrations**: Version-controlled schema changes using `golang-migrate`\n- **Health Checks**: Built-in health check endpoints for monitoring\n\n## Tech Stack\n\n- **Language**: Go 1.25.3\n- **Database**: PostgreSQL\n- **HTTP Framework**: `Gin`\n- **Logging**: `log/slog` (stdlib)\n- **Migrations**: `golang-migrate`\n- **Logging Enhancement**: `tint` for colored console output\n\n## Project Structure\n\n```\n.\n├── cmd/\n│   └── server/\n│       └── main.go             # Application entry point\n├── internal/\n│   ├── config/                 # Configuration loading from environment\n│   ├── handlers/               # HTTP request handlers\n│   ├── middleware/             # HTTP middleware (logging, recovery, etc.)\n│   ├── models/                 # Domain models\n│   ├── repository/             # Data access layer\n│   ├── server/                 # HTTP server setup\n│   └── service/                # Business logic layer\n├── pkg/\n│   └── logger/                 # Reusable logger utilities\n├── migrations/                 # Database migration files (SQL)\n├── Makefile                    # Development commands\n├── ARCHITECTURE.md             # Detailed architecture documentation\n└── go.mod                      # Go module definition\n```\n\n### Architecture Layers\n\n- **Handler Layer**: Decodes HTTP requests, validates input, encodes responses\n- **Service Layer**: Contains business logic and orchestrates repository calls\n- **Repository Layer**: Abstracts database interactions and returns domain models\n- **Domain Layer**: Plain Go structs representing core entities\n\n## Prerequisites\n\n- Go 1.22 or higher\n- PostgreSQL 12 or higher\n- Docker \u0026 Docker Compose (for local development)\n- `golang-migrate` CLI\n\n## Getting Started\n\n### 1. Clone the Repository\n\n```bash\ngit clone \u003crepository-url\u003e\ncd personal-trainer-be\n```\n\n### 2. Set Up Environment Variables\n\n```bash\ncp .env.example .env\n```\n\nEdit `.env` with your configuration:\n\n```env\nAPP_ENV=development\nPORT=8080\nLOG_LEVEL=debug\nLOG_FORMAT=json\nDATABASE_URL=postgres://user:password@localhost:5432/trainer_db?sslmode=disable\n```\n\n### 3. Start Dependencies\n\n```bash\ndocker compose up -d\n```\n\n### 4. Install Tools\n\n```bash\nmake install-tools\n```\n\n### 5. Run Database Migrations\n\n```bash\nmake migrate-up\n```\n\n### 6. Start the Server\n\n```bash\nmake run\n```\n\nThe server will start on `http://localhost:8080`\n\n## API Endpoints\n\n### Health Check\n\n- `GET /` — Service status message\n- `GET /health` — Health check endpoint\n\n**Example:**\n\n```bash\ncurl http://localhost:8080/health\n```\n\nResponse:\n\n```json\n{\n  \"status\": \"ok\",\n  \"time\": \"2024-05-03T12:00:00Z\"\n}\n```\n\n### Authentication — Password Recovery (admin-only)\n\n- `POST /api/v1/auth/forgot-password` — request a 6-digit reset code by email\n- `POST /api/v1/auth/reset-password` — set a new password using the emailed code\n\nBoth endpoints are gated server-side to users that hold the `admin` role.\nFailures (unknown email, non-admin, deactivated, wrong code) return a generic\nresponse so the existence of an account or its role cannot be probed.\n\n**Request a reset code**\n\n```bash\ncurl -X POST http://localhost:8080/api/v1/auth/forgot-password \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\":\"admin@example.com\"}'\n```\n\nResponse (always the same, regardless of whether the email exists):\n\n```json\n{\n  \"status\": \"success\",\n  \"message\": \"if the email is registered, a reset code has been sent\",\n  \"code\": \"OK\",\n  \"data\": null\n}\n```\n\nIn development the email is dispatched through the LogMailer. The reset code\nitself is **not** written to logs — see [Email Delivery](#email-delivery)\nbelow for why and how to capture it for local E2E testing.\n\n**Reset the password**\n\n```bash\ncurl -X POST http://localhost:8080/api/v1/auth/reset-password \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\":\"admin@example.com\",\"code\":\"123456\",\"new_password\":\"Str0ngPassw0rd\"}'\n```\n\nOn success the code is consumed (single-use) and all existing refresh sessions\nfor the user are revoked. The new password must be 8–128 characters and contain\nupper case, lower case, and a digit.\n\n### Email Delivery\n\nOutbound email is sent through one of three mailers, picked at startup in this\norder:\n\n1. **Resend** — if both `RESEND_API_KEY` and `RESEND_FROM` are set.\n2. **SMTP** — if `SMTP_HOST` is set.\n3. **LogMailer** — default in development when neither of the above is set.\n   In any other environment a warning is logged and emails are not delivered.\n\n**LogMailer behaviour by email type (development only)**\n\n| Email type | What is logged |\n| --- | --- |\n| Email verification code (signup / login OTP) | Recipient, subject, **and the rendered body containing the code** |\n| Password reset code | Recipient, subject, expiry — **the code itself is redacted** |\n\nVerification codes are written to the log so the local signup/login OTP flow\nworks end-to-end without an inbox. Password reset codes are deliberately *not*\nlogged: a leaked log file is enough to take over an admin account. If you need\nthe live reset code in a local test, use a test-only mailer stub that captures\nit directly, not the LogMailer.\n\nResend takes precedence over SMTP whenever it is configured, including in\ndevelopment — useful for end-to-end testing of the live email pipeline. To\nfall back to console-only delivery in development, leave the Resend env vars\nempty.\n\n## Development\n\n### Available Make Commands\n\n```bash\nmake run           # Start the development server\nmake build         # Build binary to bin/server\nmake test          # Run tests with race detector and coverage\nmake test-cover    # Generate HTML coverage report\nmake lint          # Run go vet\nmake fmt           # Format code with gofmt\nmake tidy          # Tidy go.mod and go.sum\nmake clean         # Remove build and coverage artifacts\n```\n\n### Database Migrations\n\n```bash\nmake migrate-up           # Apply all pending migrations\nmake migrate-down         # Rollback the most recent migration\nmake migrate-create NAME=migration_name  # Create new migration\nmake migrate-version      # Show current migration version\nmake migrate-drop         # Drop all tables (destructive)\n```\n\n### Running Tests\n\n```bash\n# Run all tests\nmake test\n\n# Generate coverage report\nmake test-cover\n```\n\nThe coverage report is generated as `coverage.html`.\n\n## Code Standards\n\n- Follow [Effective Go](https://go.dev/doc/effective_go) guidelines\n- Code formatting enforced with `goimports`\n- Linting via `golangci-lint`\n- Structured logging using `log/slog`\n- Error handling with context wrapping\n- Unit tests alongside source code\n\n### Error Handling\n\nErrors are wrapped with context to maintain error chains:\n\n```go\nif err != nil {\n    return fmt.Errorf(\"doing X: %w\", err)\n}\n```\n\n### Logging\n\nStructured logging with request IDs:\n\n```go\nlog.Info(\"user created\", \"user_id\", userID, \"email\", email)\nlog.Error(\"database error\", \"err\", err)\n```\n\n## Security\n\n- Passwords hashed with bcrypt (cost factor ≥ 12)\n- All secrets stored in environment variables\n- SQL injection prevention via parameterized queries\n- Session-based authentication with 7-day expiration\n- HTTPS enforced in production\n- Request payload size limits\n- Rate limiting middleware support\n- CORS explicitly configured\n\n## API Response Format\n\n### Success Response\n\n```json\n{\n  \"status\": \"success\",\n  \"message\": \"Human-readable message\",\n  \"code\": \"MACHINE_READABLE_CODE\",\n  \"data\": {},\n  \"meta\": {}\n}\n```\n\n### Error Response\n\n```json\n{\n  \"status\": \"error\",\n  \"message\": \"Human-readable error message\",\n  \"code\": \"MACHINE_READABLE_ERROR_CODE\",\n  \"errors\": []\n}\n```\n\n### HTTP Status Codes\n\n| Code | Meaning               |\n| ---- | --------------------- |\n| 200  | OK                    |\n| 201  | Created               |\n| 204  | No Content            |\n| 400  | Bad Request           |\n| 401  | Unauthenticated       |\n| 403  | Forbidden             |\n| 404  | Not Found             |\n| 409  | Conflict              |\n| 422  | Unprocessable Entity  |\n| 500  | Internal Server Error |\n\n## Configuration\n\nAll configuration is loaded from environment variables at startup:\n\n- `APP_ENV`: Application environment (`development`, `production`)\n- `PORT`: Server port (default: `8080`)\n- `LOG_LEVEL`: Logging level (`debug`, `info`, `warn`, `error`)\n- `LOG_FORMAT`: Log format (`json`, `text`)\n- `DATABASE_URL`: PostgreSQL connection string\n\n### Email (Resend)\n\nSet both to enable Resend — leave empty to fall back to SMTP / LogMailer:\n\n- `RESEND_API_KEY`: API key from the Resend dashboard\n- `RESEND_FROM`: verified sender address (e.g. `fitcal@hng14.com`)\n\n### Email (SMTP — fallback)\n\nUsed only when Resend is not configured:\n\n- `SMTP_HOST`, `SMTP_PORT` (default `587`), `SMTP_USER`, `SMTP_PASSWORD`, `SMTP_FROM`\n\nMissing required variables will cause the server to exit with a descriptive error.\n\n## Database\n\n### Schema Management\n\n- All schema changes must be made via migration files\n- Never edit the database manually\n- Migrations run automatically in development\n- Use transactions for multi-table operations\n\n### Conventions\n\n- Timestamps stored as `TIMESTAMPTZ` (UTC)\n- UUIDs used as primary keys (`uuid_generate_v4()`)\n- Connection pooling configured via pgx\n- Parameterized queries to prevent SQL injection\n\n## Deployment\n\n### Building for Production\n\n```bash\nmake build\n```\n\nOutput binary: `bin/server`\n\n### Pre-Deployment Steps\n\n1. Run migrations: `make migrate-up`\n2. Set environment variables for production\n3. Start the application\n\n### Environment Variables\n\nSet these in your production environment:\n\n```env\nAPP_ENV=production\nPORT=8080\nLOG_LEVEL=info\nLOG_FORMAT=json\nDATABASE_URL=postgres://user:password@prod-db:5432/trainer_db?sslmode=require\n```\n\n## Branch Protection\n\nThis repository enforces the following branch protection rules on `prod` and `staging`:\n\n- **Pull Requests are required** before any code can be merged\n- **At least 2 approvals** is required on every PR\n- **Direct pushes are disabled** on both branches\n- All changes to `staging` and `prod` must go through a reviewed PR\n\n### Branch Strategy\n\n| Branch | Environment | Purpose |\n|--------|-------------|---------|\n| `dev` | Development | Active development work |\n| `staging` | Pre-production | Testing before release |\n| `prod` | Production | Live production code |\n\n## Contributing\n\n1. Follow the code standards defined in `ARCHITECTURE.md`\n2. Write tests for new features\n3. Ensure tests pass: `make test`\n4. Run linting: `make lint`\n5. Format code: `make fmt`\n\n## Troubleshooting\n\n### Database Connection Issues\n\n```bash\n# Check if the database is running\ndocker ps\n\n# View logs\ndocker logs \u003ccontainer_id\u003e\n\n# Verify connection string in .env\n```\n\n### Migration Errors\n\n```bash\n# Check current migration version\nmake migrate-version\n\n# Force a version if migrations are dirty\nmake migrate-force VERSION=1\n```\n\n### Port Already in Use\n\nChange the `PORT` in `.env`:\n\n```env\nPORT=3000\n```\n\n## Additional Resources\n\n- [Architecture Documentation](ARCHITECTURE.md)\n- [Go Effective Documentation](https://go.dev/doc/effective_go)\n- [golang-migrate Documentation](https://github.com/golang-migrate/migrate)\n- [PostgreSQL Documentation](https://www.postgresql.org/docs/)\n\n## License\n\nTBD\n\n## Contact\n\nFor issues or questions, please reach out to the development team.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhngprojects%2Fpersonal-trainer-be","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhngprojects%2Fpersonal-trainer-be","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhngprojects%2Fpersonal-trainer-be/lists"}