Files
MosisService/portal/internal/api/handlers/admin.go

227 lines
5.8 KiB
Go

// Package handlers contains HTTP request handlers
package handlers
import (
"encoding/json"
"net/http"
"strconv"
"github.com/go-chi/chi/v5"
"github.com/omixlab/mosis-portal/internal/database"
"github.com/omixlab/mosis-portal/internal/review"
"github.com/omixlab/mosis-portal/internal/storage"
)
// AdminHandler handles admin operations
type AdminHandler struct {
db *database.DB
store *storage.Storage
review *review.Service
}
// NewAdminHandler creates a new admin handler
func NewAdminHandler(db *database.DB, store *storage.Storage) *AdminHandler {
return &AdminHandler{
db: db,
store: store,
review: review.New(db),
}
}
// Dashboard returns the admin dashboard with stats
func (h *AdminHandler) Dashboard(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
pending, approved, rejected, err := h.db.GetReviewStats(ctx)
if err != nil {
Error(w, http.StatusInternalServerError, "database_error", err.Error())
return
}
JSON(w, http.StatusOK, map[string]interface{}{
"stats": map[string]int{
"pending": pending,
"approved": approved,
"rejected": rejected,
},
})
}
// ReviewQueue lists versions pending review
func (h *AdminHandler) ReviewQueue(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// Parse pagination
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
if page < 1 {
page = 1
}
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
if limit < 1 || limit > 100 {
limit = 20
}
offset := (page - 1) * limit
versions, total, err := h.db.GetVersionsInReview(ctx, limit, offset)
if err != nil {
Error(w, http.StatusInternalServerError, "database_error", err.Error())
return
}
JSON(w, http.StatusOK, map[string]interface{}{
"items": versions,
"pagination": map[string]int{
"page": page,
"limit": limit,
"total": total,
"total_pages": (total + limit - 1) / limit,
},
})
}
// ReviewDetail returns details for a specific version under review
func (h *AdminHandler) ReviewDetail(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
versionID := chi.URLParam(r, "versionID")
versionWithApp, err := h.db.GetVersionWithApp(ctx, versionID)
if err != nil {
Error(w, http.StatusNotFound, "not_found", "Version not found")
return
}
// If package exists, run validation to get flags
var validationResult *review.FullValidationResult
if versionWithApp.Version.PackageURL != "" {
packagePath := h.store.GetPackagePath(versionWithApp.Version.PackageURL)
result, err := h.review.ValidatePackage(packagePath)
if err == nil {
validationResult = result
}
}
JSON(w, http.StatusOK, map[string]interface{}{
"version": versionWithApp.Version,
"app": versionWithApp.App,
"developer": map[string]string{
"name": versionWithApp.DeveloperName,
"email": versionWithApp.DeveloperEmail,
},
"validation": validationResult,
})
}
// ApproveVersion approves a version
func (h *AdminHandler) ApproveVersion(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
versionID := chi.URLParam(r, "versionID")
// Parse request body
var req struct {
Notes string `json:"notes"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
// Notes are optional, continue with empty
}
// Verify version exists and is in review
version, err := h.db.GetVersion(ctx, versionID)
if err != nil {
Error(w, http.StatusNotFound, "not_found", "Version not found")
return
}
if version.Status != "in_review" {
Error(w, http.StatusBadRequest, "invalid_status", "Version is not in review")
return
}
// Approve the version
if err := h.review.ApproveVersion(ctx, versionID, req.Notes); err != nil {
Error(w, http.StatusInternalServerError, "database_error", err.Error())
return
}
JSON(w, http.StatusOK, map[string]interface{}{
"success": true,
"message": "Version approved and published",
})
}
// RejectVersion rejects a version
func (h *AdminHandler) RejectVersion(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
versionID := chi.URLParam(r, "versionID")
// Parse request body
var req struct {
Reason string `json:"reason"`
Message string `json:"message"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
Error(w, http.StatusBadRequest, "invalid_body", "Invalid request body")
return
}
if req.Reason == "" {
Error(w, http.StatusBadRequest, "missing_reason", "Rejection reason is required")
return
}
// Verify version exists and is in review
version, err := h.db.GetVersion(ctx, versionID)
if err != nil {
Error(w, http.StatusNotFound, "not_found", "Version not found")
return
}
if version.Status != "in_review" {
Error(w, http.StatusBadRequest, "invalid_status", "Version is not in review")
return
}
// Reject the version
feedback := &review.RejectionFeedback{
Reason: req.Reason,
Message: req.Message,
CanResubmit: true,
}
if err := h.review.RejectVersion(ctx, versionID, feedback); err != nil {
Error(w, http.StatusInternalServerError, "database_error", err.Error())
return
}
JSON(w, http.StatusOK, map[string]interface{}{
"success": true,
"message": "Version rejected",
})
}
// ValidatePackage runs validation on a package and returns results
func (h *AdminHandler) ValidatePackage(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
versionID := chi.URLParam(r, "versionID")
// Get version with package info
version, err := h.db.GetVersion(ctx, versionID)
if err != nil {
Error(w, http.StatusNotFound, "not_found", "Version not found")
return
}
if version.PackageURL == "" {
Error(w, http.StatusBadRequest, "no_package", "Version has no uploaded package")
return
}
// Run validation
packagePath := h.store.GetPackagePath(version.PackageURL)
result, err := h.review.ValidatePackage(packagePath)
if err != nil {
Error(w, http.StatusInternalServerError, "validation_error", err.Error())
return
}
JSON(w, http.StatusOK, result)
}