feat: add Docker infrastructure, migrations, CI/CD client, session cleanup, tests

Docker & Deployment:
- Add Dockerfile (multi-stage, alpine, non-root)
- Add docker-compose.yml (caddy, backend, postgres, watchtower)
- Add Caddyfile (TLS, file_server, reverse proxy)
- Add .env.example

Database:
- Add migrations/001_init.sql (all tables + indexes)

CI/CD:
- Add cmd/ci-release/main.go (launcher binary upload tool)

Session management:
- Add internal/session/cleanup.go (background expired session cleanup)
- Integrate cleanup worker into main.go

Bug fixes:
- Fix launcherLatest download URL to include version segment
- Fix serveLauncherAsset path to match route pattern
- Add Content-Type detection from file extension in CAS serveFile
- Add empty-field validation in webLogin
- Format string fix in ci-release (%d → %s for resp.Status)

Tests:
- Add internal/auth/auth_test.go (8 tests)
- Add internal/cas/cas_test.go (7 tests)
- Add internal/session/cleanup_test.go (1 test)
- Add internal/api/api_test.go (5 tests)
- All tests passing, go vet clean

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-29 20:09:00 +03:00
parent 81c42e1a9a
commit 5fba2e78d5
14 changed files with 986 additions and 4 deletions

View File

@@ -64,7 +64,7 @@ func (h *Handler) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("GET /api/web/profile/{uuid}", h.getProfile)
// Skin serving.
mux.HandleFunc("GET /skins/{hash}.png", h.serveSkin)
mux.HandleFunc("GET /skins/{hash}", h.serveSkin)
}
// ── Request / Response types ──────────────────────────────────
@@ -211,6 +211,11 @@ func (h *Handler) webLogin(w http.ResponseWriter, r *http.Request) {
return
}
if req.Username == "" || req.Password == "" {
writeError(w, http.StatusBadRequest, "Username and password are required")
return
}
var user database.User
err = h.db.Pool().QueryRow(r.Context(),
`SELECT id, username, password_hash, uuid FROM users
@@ -442,8 +447,8 @@ func (h *Handler) launcherLatest(w http.ResponseWriter, r *http.Request) {
return
}
downloadURL := fmt.Sprintf("%s/files/launcher/%s/%s/%s",
h.cfg.BaseURL, osParam, archParam, filepath.Base(release.FilePath))
downloadURL := fmt.Sprintf("%s/files/launcher/%s/%s/%s/%s",
h.cfg.BaseURL, release.Version, osParam, archParam, filepath.Base(release.FilePath))
writeJSON(w, http.StatusOK, launcherLatestResponse{
Version: release.Version,