mirror of
https://github.com/ghostersk/gowebmail.git
synced 2026-04-17 16:46:01 +01:00
first commit
This commit is contained in:
220
internal/handlers/admin.go
Normal file
220
internal/handlers/admin.go
Normal file
@@ -0,0 +1,220 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/yourusername/gomail/config"
|
||||
"github.com/yourusername/gomail/internal/db"
|
||||
"github.com/yourusername/gomail/internal/middleware"
|
||||
"github.com/yourusername/gomail/internal/models"
|
||||
)
|
||||
|
||||
// AdminHandler handles /admin/* routes (all require admin role).
|
||||
type AdminHandler struct {
|
||||
db *db.DB
|
||||
cfg *config.Config
|
||||
renderer *Renderer
|
||||
}
|
||||
|
||||
func (h *AdminHandler) writeJSON(w http.ResponseWriter, v interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(v)
|
||||
}
|
||||
|
||||
func (h *AdminHandler) writeError(w http.ResponseWriter, status int, msg string) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
json.NewEncoder(w).Encode(map[string]string{"error": msg})
|
||||
}
|
||||
|
||||
// ShowAdmin serves the admin SPA shell for all /admin/* routes.
|
||||
func (h *AdminHandler) ShowAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
h.renderer.Render(w, "admin", nil)
|
||||
}
|
||||
|
||||
// ---- User Management ----
|
||||
|
||||
func (h *AdminHandler) ListUsers(w http.ResponseWriter, r *http.Request) {
|
||||
users, err := h.db.ListUsers()
|
||||
if err != nil {
|
||||
h.writeError(w, http.StatusInternalServerError, "failed to list users")
|
||||
return
|
||||
}
|
||||
// Sanitize: strip password hash
|
||||
type safeUser struct {
|
||||
ID int64 `json:"id"`
|
||||
Email string `json:"email"`
|
||||
Username string `json:"username"`
|
||||
Role models.UserRole `json:"role"`
|
||||
IsActive bool `json:"is_active"`
|
||||
MFAEnabled bool `json:"mfa_enabled"`
|
||||
LastLoginAt interface{} `json:"last_login_at"`
|
||||
CreatedAt interface{} `json:"created_at"`
|
||||
}
|
||||
result := make([]safeUser, 0, len(users))
|
||||
for _, u := range users {
|
||||
result = append(result, safeUser{
|
||||
ID: u.ID, Email: u.Email, Username: u.Username,
|
||||
Role: u.Role, IsActive: u.IsActive, MFAEnabled: u.MFAEnabled,
|
||||
LastLoginAt: u.LastLoginAt, CreatedAt: u.CreatedAt,
|
||||
})
|
||||
}
|
||||
h.writeJSON(w, result)
|
||||
}
|
||||
|
||||
func (h *AdminHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
h.writeError(w, http.StatusBadRequest, "invalid request")
|
||||
return
|
||||
}
|
||||
if req.Username == "" || req.Email == "" || req.Password == "" {
|
||||
h.writeError(w, http.StatusBadRequest, "username, email and password required")
|
||||
return
|
||||
}
|
||||
if len(req.Password) < 8 {
|
||||
h.writeError(w, http.StatusBadRequest, "password must be at least 8 characters")
|
||||
return
|
||||
}
|
||||
role := models.RoleUser
|
||||
if req.Role == "admin" {
|
||||
role = models.RoleAdmin
|
||||
}
|
||||
|
||||
user, err := h.db.CreateUser(req.Username, req.Email, req.Password, role)
|
||||
if err != nil {
|
||||
h.writeError(w, http.StatusConflict, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
adminID := middleware.GetUserID(r)
|
||||
h.db.WriteAudit(&adminID, models.AuditUserCreate,
|
||||
"created user: "+req.Email, middleware.ClientIP(r), r.UserAgent())
|
||||
|
||||
h.writeJSON(w, map[string]interface{}{"id": user.ID, "ok": true})
|
||||
}
|
||||
|
||||
func (h *AdminHandler) UpdateUser(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
targetID, _ := strconv.ParseInt(vars["id"], 10, 64)
|
||||
|
||||
var req struct {
|
||||
IsActive *bool `json:"is_active"`
|
||||
Password string `json:"password"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
h.writeError(w, http.StatusBadRequest, "invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
if req.IsActive != nil {
|
||||
if err := h.db.SetUserActive(targetID, *req.IsActive); err != nil {
|
||||
h.writeError(w, http.StatusInternalServerError, "failed to update user")
|
||||
return
|
||||
}
|
||||
}
|
||||
if req.Password != "" {
|
||||
if len(req.Password) < 8 {
|
||||
h.writeError(w, http.StatusBadRequest, "password must be at least 8 characters")
|
||||
return
|
||||
}
|
||||
if err := h.db.UpdateUserPassword(targetID, req.Password); err != nil {
|
||||
h.writeError(w, http.StatusInternalServerError, "failed to update password")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
adminID := middleware.GetUserID(r)
|
||||
h.db.WriteAudit(&adminID, models.AuditUserUpdate,
|
||||
"updated user id:"+vars["id"], middleware.ClientIP(r), r.UserAgent())
|
||||
|
||||
h.writeJSON(w, map[string]bool{"ok": true})
|
||||
}
|
||||
|
||||
func (h *AdminHandler) DeleteUser(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
targetID, _ := strconv.ParseInt(vars["id"], 10, 64)
|
||||
|
||||
adminID := middleware.GetUserID(r)
|
||||
if targetID == adminID {
|
||||
h.writeError(w, http.StatusBadRequest, "cannot delete yourself")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.DeleteUser(targetID); err != nil {
|
||||
h.writeError(w, http.StatusInternalServerError, "delete failed")
|
||||
return
|
||||
}
|
||||
|
||||
h.db.WriteAudit(&adminID, models.AuditUserDelete,
|
||||
"deleted user id:"+vars["id"], middleware.ClientIP(r), r.UserAgent())
|
||||
h.writeJSON(w, map[string]bool{"ok": true})
|
||||
}
|
||||
|
||||
// ---- Audit Log ----
|
||||
|
||||
func (h *AdminHandler) ListAuditLogs(w http.ResponseWriter, r *http.Request) {
|
||||
page := 1
|
||||
pageSize := 100
|
||||
if p := r.URL.Query().Get("page"); p != "" {
|
||||
if v, err := strconv.Atoi(p); err == nil && v > 0 {
|
||||
page = v
|
||||
}
|
||||
}
|
||||
eventFilter := r.URL.Query().Get("event")
|
||||
|
||||
result, err := h.db.ListAuditLogs(page, pageSize, eventFilter)
|
||||
if err != nil {
|
||||
h.writeError(w, http.StatusInternalServerError, "failed to fetch logs")
|
||||
return
|
||||
}
|
||||
h.writeJSON(w, result)
|
||||
}
|
||||
|
||||
// ---- App Settings ----
|
||||
|
||||
func (h *AdminHandler) GetSettings(w http.ResponseWriter, r *http.Request) {
|
||||
settings, err := config.GetSettings()
|
||||
if err != nil {
|
||||
h.writeError(w, http.StatusInternalServerError, "failed to read settings")
|
||||
return
|
||||
}
|
||||
h.writeJSON(w, settings)
|
||||
}
|
||||
|
||||
func (h *AdminHandler) SetSettings(w http.ResponseWriter, r *http.Request) {
|
||||
adminID := middleware.GetUserID(r)
|
||||
|
||||
var updates map[string]string
|
||||
if err := json.NewDecoder(r.Body).Decode(&updates); err != nil {
|
||||
h.writeError(w, http.StatusBadRequest, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
changed, err := config.SetSettings(updates)
|
||||
if err != nil {
|
||||
h.writeError(w, http.StatusInternalServerError, "failed to save settings")
|
||||
return
|
||||
}
|
||||
|
||||
if len(changed) > 0 {
|
||||
detail := "changed config keys: " + strings.Join(changed, ", ")
|
||||
h.db.WriteAudit(&adminID, models.AuditConfigChange,
|
||||
detail, middleware.ClientIP(r), r.UserAgent())
|
||||
}
|
||||
|
||||
h.writeJSON(w, map[string]interface{}{
|
||||
"ok": true,
|
||||
"changed": changed,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user