first commit

This commit is contained in:
2026-07-16 10:13:46 +03:30
commit 423c528b2f
42 changed files with 7298 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
# The course this project was built from
This project was built incrementally across 10 lessons, each adding one
concept on top of the last. This file is a map from "concept" to "where it
lives in the code" - useful if you want to revisit how/why something was
built the way it was.
| # | Lesson | New concepts | Where it lives |
|---|---|---|---|
| 1 | Project skeleton & chi routing | Standard Go project layout, `chi.Mux`, middleware basics, graceful shutdown via `http.Server` + `srv.Shutdown()` | `cmd/api/main.go`, `internal/router/router.go`, `internal/handlers/health.go` |
| 2 | Structured JSON logging | `log/slog`, `slog.NewJSONHandler`, log levels, the three-layer middleware-factory pattern | `internal/logging/logger.go`, `internal/middleware/request_logger.go` |
| 3 | Config & MySQL connection | `database/sql`, connection pooling (`SetMaxOpenConns` etc.), DSNs, `context.WithTimeout` for a hard deadline on the initial ping | `internal/config/config.go`, `internal/database/mysql.go` |
| 4 | User model & repository pattern | Pointers (`*`/`&`) in depth, pointer receivers, the repository pattern, sentinel errors + `errors.Is` | `internal/models/user.go`, `internal/models/user_repository.go` |
| 5 | Password login | `bcrypt` hashing/salting, decoding JSON request bodies, struct tags, generic error messages to avoid user enumeration | `internal/handlers/auth.go` (Register/Login), `internal/handlers/respond.go` |
| 6 | Server-side sessions (scs + Redis) | `scs.SessionManager`, swapping storage backends via `.Store`, cookie flags (`HttpOnly`, `SameSite`), `RenewToken` to prevent session fixation | `internal/session/session.go`, `internal/session/keys.go`, `Login`/`Logout`/`Me` in `internal/handlers/auth.go` |
| 7 | Login with Google (OAuth2) | Authorization Code flow, `oauth2.Config`, CSRF `state` parameter, account linking by email | `internal/oauth/google.go`, `internal/handlers/oauth_google.go` |
| 8 | Auth middleware & route protection | `context.Context` (`WithValue`/`Value`), private context-key types, type assertions, chi route groups | `internal/middleware/require_auth.go`, `r.Group(...)` in `internal/router/router.go` |
| 9 | Rate limiting & security hardening | `httprate.LimitByIP`, CORS (`go-chi/cors`), environment-aware `Secure` cookie flag | `internal/router/router.go`, `internal/session/session.go`, `internal/config/config.go` |
| 10 | Docker & wrap-up | Multi-stage Docker builds, `docker-compose`, service-name-as-hostname networking, named volumes | `Dockerfile`, `docker-compose.yml` |
## Core Go ideas that recur throughout the codebase
These aren't tied to a single lesson - once introduced, they show up
repeatedly, and are worth having solid:
- **Pointers (`*` / `&`)** - sharing one instance of something stateful
(`*sql.DB`, `*scs.SessionManager`, `*slog.Logger`) across the whole app
instead of copying it; writing a result back into a caller's variable
(`rows.Scan(&x)`, `u.ID = int(id)` inside `Create(ctx, u *User)`).
- **Interfaces satisfied implicitly** - `*chi.Mux` satisfies `http.Handler`
just by having a `ServeHTTP` method; there's no `implements` keyword in Go.
- **Closures / the three-layer middleware pattern** - seen in both
`RequestLogger(logger)` and `RequireAuth(sessions, userRepo, logger)`:
an outer function captures dependencies, returns a
`func(http.Handler) http.Handler`, which itself returns the actual
per-request handler - three layers, each running at a different time.
- **`context.Context`** - carrying request-scoped values (the current
user, a request ID) and deadlines (timeouts) through a call chain
without adding extra parameters to every function signature.
- **Error wrapping and sentinel errors** - `fmt.Errorf("...: %w", err)` to
add context while preserving the original error; `var ErrUserNotFound =
errors.New(...)` plus `errors.Is(err, ErrUserNotFound)` to let callers
branch on error *kind* without string-matching messages.
- **Dependency injection via structs** - `AuthHandler{userRepo, sessions,
logger}` instead of global variables, so every handler's requirements
are explicit and visible in its constructor.
## Suggested next steps
If you want to keep extending this project as further practice:
1. **Testing** - `httptest.NewRequest`/`NewRecorder` for handler tests,
table-driven test cases, and extracting a `UserStore` interface so
`UserRepository` can be swapped for an in-memory fake in tests.
2. **A real migration tool** (e.g. `golang-migrate/migrate`) instead of
`CREATE TABLE IF NOT EXISTS` on every boot.
3. **CSRF tokens** if you ever add a same-origin HTML form frontend
(the current `SameSite=Lax` cookie already covers the JSON-API case).
4. **Refresh/renewal** so an active user's session doesn't hard-expire
after 24 hours regardless of activity.
5. **Machine-readable error codes** (`{"error_code": "invalid_credentials"}`)
so a frontend can branch on a stable code instead of parsing message text.
6. **Grafana Alloy + Loki** - point Alloy at this container's stdout; the
JSON shape from `internal/logging` and `internal/middleware/request_logger.go`
is already structured for it.