refactor: deduplicate sha1Hex/writeJSON/writeError into pkg/utils
- admin.go: replace local sha1Hex, sha256Hex, writeJSON, writeError with pkg/utils equivalents - auth.go: replace local writeJSON with utils.WriteJSON; rewrite writeError as thin wrapper - cas.go: remove local sha1Hex and unused writeJSON; use utils.SHA1Bytes - pkg/utils.go: add WriteJSON, WriteError; reorder imports
This commit is contained in:
@@ -5,10 +5,7 @@ import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -18,6 +15,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitea.mrixs.me/Mrixs/MrixsCraft-server/pkg/utils"
|
||||
"gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/auth"
|
||||
"gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/config"
|
||||
"gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/database"
|
||||
@@ -55,7 +53,7 @@ func (h *Handler) auth(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
token := auth.ExtractBearer(r.Header.Get("Authorization"))
|
||||
if token == "" {
|
||||
writeError(w, http.StatusUnauthorized, "Missing authorization token")
|
||||
utils.WriteError(w, http.StatusUnauthorized, "Missing authorization token")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -70,11 +68,11 @@ func (h *Handler) auth(next http.HandlerFunc) http.HandlerFunc {
|
||||
).Scan(&userID, &role)
|
||||
|
||||
if err != nil {
|
||||
writeError(w, http.StatusUnauthorized, "Invalid token")
|
||||
utils.WriteError(w, http.StatusUnauthorized, "Invalid token")
|
||||
return
|
||||
}
|
||||
if role != "admin" {
|
||||
writeError(w, http.StatusForbidden, "Admin access required")
|
||||
utils.WriteError(w, http.StatusForbidden, "Admin access required")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -87,7 +85,7 @@ func (h *Handler) ciToken(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.Header.Get("X-CI-Token")
|
||||
if token == "" || subtle.ConstantTimeCompare([]byte(token), []byte(h.cfg.CIsecret)) != 1 {
|
||||
writeError(w, http.StatusForbidden, "Invalid CI token")
|
||||
utils.WriteError(w, http.StatusForbidden, "Invalid CI token")
|
||||
return
|
||||
}
|
||||
next(w, r)
|
||||
@@ -119,7 +117,7 @@ func (h *Handler) listModpacks(w http.ResponseWriter, r *http.Request) {
|
||||
`SELECT id, slug, name, minecraft_version, java_version, server_ip, is_active
|
||||
FROM modpacks ORDER BY created_at DESC`)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "Database error")
|
||||
utils.WriteError(w, http.StatusInternalServerError, "Database error")
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
@@ -132,24 +130,24 @@ func (h *Handler) listModpacks(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
modpacks = append(modpacks, m)
|
||||
}
|
||||
writeJSON(w, http.StatusOK, modpacks)
|
||||
utils.WriteJSON(w, http.StatusOK, modpacks)
|
||||
}
|
||||
|
||||
func (h *Handler) createModpack(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Cannot read body")
|
||||
utils.WriteError(w, http.StatusBadRequest, "Cannot read body")
|
||||
return
|
||||
}
|
||||
|
||||
var req modpackRequest
|
||||
if err := json.Unmarshal(body, &req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Invalid JSON")
|
||||
utils.WriteError(w, http.StatusBadRequest, "Invalid JSON")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Slug == "" || req.Name == "" || req.MinecraftVersion == "" {
|
||||
writeError(w, http.StatusBadRequest, "slug, name, and minecraft_version are required")
|
||||
utils.WriteError(w, http.StatusBadRequest, "slug, name, and minecraft_version are required")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -159,29 +157,29 @@ func (h *Handler) createModpack(w http.ResponseWriter, r *http.Request) {
|
||||
req.Slug, req.Name, req.MinecraftVersion, req.JavaVersion, req.ServerIP,
|
||||
)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusConflict, "Modpack with this slug already exists")
|
||||
utils.WriteError(w, http.StatusConflict, "Modpack with this slug already exists")
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, map[string]string{"status": "created"})
|
||||
utils.WriteJSON(w, http.StatusCreated, map[string]string{"status": "created"})
|
||||
}
|
||||
|
||||
func (h *Handler) updateModpack(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.Atoi(r.PathValue("id"))
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Invalid modpack ID")
|
||||
utils.WriteError(w, http.StatusBadRequest, "Invalid modpack ID")
|
||||
return
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Cannot read body")
|
||||
utils.WriteError(w, http.StatusBadRequest, "Cannot read body")
|
||||
return
|
||||
}
|
||||
|
||||
var req modpackRequest
|
||||
if err := json.Unmarshal(body, &req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Invalid JSON")
|
||||
utils.WriteError(w, http.StatusBadRequest, "Invalid JSON")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -191,28 +189,28 @@ func (h *Handler) updateModpack(w http.ResponseWriter, r *http.Request) {
|
||||
req.Name, req.Slug, req.MinecraftVersion, req.JavaVersion, req.ServerIP, id,
|
||||
)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "Update failed")
|
||||
utils.WriteError(w, http.StatusInternalServerError, "Update failed")
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]string{"status": "updated"})
|
||||
utils.WriteJSON(w, http.StatusOK, map[string]string{"status": "updated"})
|
||||
}
|
||||
|
||||
func (h *Handler) deleteModpack(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.Atoi(r.PathValue("id"))
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Invalid modpack ID")
|
||||
utils.WriteError(w, http.StatusBadRequest, "Invalid modpack ID")
|
||||
return
|
||||
}
|
||||
|
||||
_, err = h.db.Pool().Exec(r.Context(),
|
||||
`UPDATE modpacks SET is_active = false WHERE id = $1`, id)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "Delete failed")
|
||||
utils.WriteError(w, http.StatusInternalServerError, "Delete failed")
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
|
||||
utils.WriteJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
|
||||
}
|
||||
|
||||
// ── File Upload ───────────────────────────────────────────────
|
||||
@@ -220,7 +218,7 @@ func (h *Handler) deleteModpack(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *Handler) uploadFiles(w http.ResponseWriter, r *http.Request) {
|
||||
slug := r.PathValue("slug")
|
||||
if slug == "" {
|
||||
writeError(w, http.StatusBadRequest, "Modpack slug is required")
|
||||
utils.WriteError(w, http.StatusBadRequest, "Modpack slug is required")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -229,18 +227,18 @@ func (h *Handler) uploadFiles(w http.ResponseWriter, r *http.Request) {
|
||||
`SELECT EXISTS(SELECT 1 FROM modpacks WHERE slug = $1 AND is_active = true)`, slug,
|
||||
).Scan(&exists)
|
||||
if err != nil || !exists {
|
||||
writeError(w, http.StatusNotFound, "Modpack not found")
|
||||
utils.WriteError(w, http.StatusNotFound, "Modpack not found")
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.ParseMultipartForm(500 << 20); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Cannot parse form (max 500 MB)")
|
||||
utils.WriteError(w, http.StatusBadRequest, "Cannot parse form (max 500 MB)")
|
||||
return
|
||||
}
|
||||
|
||||
files := r.MultipartForm.File["files"]
|
||||
if len(files) == 0 {
|
||||
writeError(w, http.StatusBadRequest, "No files uploaded (field name: 'files')")
|
||||
utils.WriteError(w, http.StatusBadRequest, "No files uploaded (field name: 'files')")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -267,7 +265,7 @@ func (h *Handler) uploadFiles(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
hash := sha1Hex(data)
|
||||
hash := utils.SHA1Bytes(data)
|
||||
destDir := filepath.Join(h.cfg.CASDir, hash[:2])
|
||||
os.MkdirAll(destDir, 0o755)
|
||||
os.WriteFile(filepath.Join(destDir, hash), data, 0o644)
|
||||
@@ -285,7 +283,7 @@ func (h *Handler) uploadFiles(w http.ResponseWriter, r *http.Request) {
|
||||
uploaded = append(uploaded, fh.Filename)
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
utils.WriteJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"status": "uploaded",
|
||||
"files": uploaded,
|
||||
"count": len(uploaded),
|
||||
@@ -370,19 +368,19 @@ type manifestLaunch struct {
|
||||
func (h *Handler) generateManifest(w http.ResponseWriter, r *http.Request) {
|
||||
slug := r.PathValue("slug")
|
||||
if slug == "" {
|
||||
writeError(w, http.StatusBadRequest, "Modpack slug is required")
|
||||
utils.WriteError(w, http.StatusBadRequest, "Modpack slug is required")
|
||||
return
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Cannot read body")
|
||||
utils.WriteError(w, http.StatusBadRequest, "Cannot read body")
|
||||
return
|
||||
}
|
||||
|
||||
var req manifestRequest
|
||||
if err := json.Unmarshal(body, &req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Invalid JSON")
|
||||
utils.WriteError(w, http.StatusBadRequest, "Invalid JSON")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -392,7 +390,7 @@ func (h *Handler) generateManifest(w http.ResponseWriter, r *http.Request) {
|
||||
WHERE slug = $1 AND is_active = true`, slug,
|
||||
).Scan(&mp.ID, &mp.Name, &mp.MinecraftVersion, &mp.JavaVersion, &mp.ServerIP)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, "Modpack not found")
|
||||
utils.WriteError(w, http.StatusNotFound, "Modpack not found")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -410,7 +408,7 @@ func (h *Handler) generateManifest(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
hash := sha1Hex(data)
|
||||
hash := utils.SHA1Bytes(data)
|
||||
|
||||
casDir := filepath.Join(h.cfg.CASDir, hash[:2])
|
||||
os.MkdirAll(casDir, 0o755)
|
||||
@@ -452,14 +450,14 @@ func (h *Handler) generateManifest(w http.ResponseWriter, r *http.Request) {
|
||||
data, _ := json.MarshalIndent(manifest, "", " ")
|
||||
os.WriteFile(filepath.Join(manifestDir, "manifest.json"), data, 0o644)
|
||||
|
||||
writeJSON(w, http.StatusOK, manifest)
|
||||
utils.WriteJSON(w, http.StatusOK, manifest)
|
||||
}
|
||||
|
||||
// ── Launcher Release (CI/CD) ──────────────────────────────────
|
||||
|
||||
func (h *Handler) launcherRelease(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseMultipartForm(100 << 20); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Cannot parse form (max 100 MB)")
|
||||
utils.WriteError(w, http.StatusBadRequest, "Cannot parse form (max 100 MB)")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -469,13 +467,13 @@ func (h *Handler) launcherRelease(w http.ResponseWriter, r *http.Request) {
|
||||
sha256 := r.FormValue("sha256")
|
||||
|
||||
if version == "" || osParam == "" || arch == "" || sha256 == "" {
|
||||
writeError(w, http.StatusBadRequest, "version, os, arch, and sha256 are required")
|
||||
utils.WriteError(w, http.StatusBadRequest, "version, os, arch, and sha256 are required")
|
||||
return
|
||||
}
|
||||
|
||||
file, header, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, "No file uploaded (field name: 'file')")
|
||||
utils.WriteError(w, http.StatusBadRequest, "No file uploaded (field name: 'file')")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
@@ -485,18 +483,18 @@ func (h *Handler) launcherRelease(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "Cannot read uploaded file")
|
||||
utils.WriteError(w, http.StatusInternalServerError, "Cannot read uploaded file")
|
||||
return
|
||||
}
|
||||
|
||||
if got := sha256Hex(data); got != sha256 {
|
||||
writeError(w, http.StatusBadRequest, fmt.Sprintf("SHA-256 mismatch: expected %s, got %s", sha256, got))
|
||||
if got := utils.SHA256Bytes(data); got != sha256 {
|
||||
utils.WriteError(w, http.StatusBadRequest, fmt.Sprintf("SHA-256 mismatch: expected %s, got %s", sha256, got))
|
||||
return
|
||||
}
|
||||
|
||||
dest := filepath.Join(destDir, header.Filename)
|
||||
if err := os.WriteFile(dest, data, 0o755); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "Failed to store binary")
|
||||
utils.WriteError(w, http.StatusInternalServerError, "Failed to store binary")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -506,34 +504,12 @@ func (h *Handler) launcherRelease(w http.ResponseWriter, r *http.Request) {
|
||||
version, osParam, arch, sha256, dest,
|
||||
)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "Failed to register release")
|
||||
utils.WriteError(w, http.StatusInternalServerError, "Failed to register release")
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, map[string]string{
|
||||
utils.WriteJSON(w, http.StatusCreated, map[string]string{
|
||||
"status": "released",
|
||||
"version": version,
|
||||
})
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────
|
||||
|
||||
func sha1Hex(data []byte) string {
|
||||
h := sha1.Sum(data)
|
||||
return hex.EncodeToString(h[:])
|
||||
}
|
||||
|
||||
func sha256Hex(data []byte) string {
|
||||
h := sha256.Sum256(data)
|
||||
return hex.EncodeToString(h[:])
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, status int, v any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
_ = json.NewEncoder(w).Encode(v)
|
||||
}
|
||||
|
||||
func writeError(w http.ResponseWriter, status int, msg string) {
|
||||
writeJSON(w, status, map[string]string{"error": msg})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user