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:
@@ -18,6 +18,33 @@ import (
|
||||
"gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/database"
|
||||
)
|
||||
|
||||
// mimeByExtension maps common file extensions to MIME types for CAS serving.
|
||||
var mimeByExtension = map[string]string{
|
||||
".jar": "application/java-archive",
|
||||
".json": "application/json",
|
||||
".png": "image/png",
|
||||
".zip": "application/zip",
|
||||
".toml": "application/toml",
|
||||
".cfg": "text/plain",
|
||||
".conf": "text/plain",
|
||||
".txt": "text/plain",
|
||||
".log": "text/plain",
|
||||
".xml": "application/xml",
|
||||
".yml": "application/x-yaml",
|
||||
".yaml": "application/x-yaml",
|
||||
".properties": "text/plain",
|
||||
}
|
||||
|
||||
// detectContentType returns a MIME type based on the file's extension.
|
||||
// Falls back to application/octet-stream for unknown types.
|
||||
func detectContentType(fileName string) string {
|
||||
ext := strings.ToLower(filepath.Ext(fileName))
|
||||
if mime, ok := mimeByExtension[ext]; ok {
|
||||
return mime
|
||||
}
|
||||
return "application/octet-stream"
|
||||
}
|
||||
|
||||
// Handler serves CAS endpoints.
|
||||
type Handler struct {
|
||||
db *database.DB
|
||||
@@ -34,12 +61,13 @@ func (h *Handler) RegisterRoutes(mux *http.ServeMux) {
|
||||
// Public file serving — immutable, long cache.
|
||||
mux.HandleFunc("GET /files/{hash}", h.serveFile)
|
||||
|
||||
// Launcher binary downloads — also served from CAS-like paths.
|
||||
// Launcher binary downloads — served from /files/launcher/{version}/{os}/{arch}/{filename}.
|
||||
mux.HandleFunc("GET /files/launcher/{version}/{os}/{arch}/{filename}", h.serveLauncherAsset)
|
||||
}
|
||||
|
||||
// serveFile serves a file from CAS by its SHA-1 hash.
|
||||
// Files are immutable, so we set Cache-Control: public, max-age=31536000 (1 year).
|
||||
// Content-Type is detected from the original file name stored in global_files.
|
||||
func (h *Handler) serveFile(w http.ResponseWriter, r *http.Request) {
|
||||
hash := r.PathValue("hash")
|
||||
if !isValidHash(hash) {
|
||||
@@ -54,6 +82,16 @@ func (h *Handler) serveFile(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Look up the original file name for Content-Type detection.
|
||||
var fileName string
|
||||
err = h.db.Pool().QueryRow(r.Context(),
|
||||
`SELECT file_name FROM global_files WHERE sha1 = $1`, hash,
|
||||
).Scan(&fileName)
|
||||
if err != nil {
|
||||
fileName = hash // fallback: no extension info
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", detectContentType(fileName))
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
|
||||
w.Write(data)
|
||||
}
|
||||
@@ -89,6 +127,7 @@ func (h *Handler) serveLauncherAsset(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", detectContentType(filename))
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user