Some checks are pending
CI Pipeline / Run Tests (push) Waiting to run
CI Pipeline / Lint Code (push) Waiting to run
CI Pipeline / Security Scan (push) Waiting to run
CI Pipeline / Build Docker Images (push) Blocked by required conditions
CI Pipeline / E2E Tests (push) Blocked by required conditions
674 lines
19 KiB
Go
674 lines
19 KiB
Go
package admin
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"git.sovbit.dev/enki/torrentGateway/internal/profile"
|
|
"git.sovbit.dev/enki/torrentGateway/internal/storage"
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
// GatewayInterface defines the methods needed from the gateway
|
|
type GatewayInterface interface {
|
|
GetDB() *sql.DB
|
|
GetStorage() *storage.Backend
|
|
CleanupOldFiles(olderThan time.Duration) (map[string]interface{}, error)
|
|
CleanupOrphanedChunks() (map[string]interface{}, error)
|
|
CleanupInactiveUsers(days int) (map[string]interface{}, error)
|
|
}
|
|
|
|
// AdminHandlers provides admin-related HTTP handlers
|
|
type AdminHandlers struct {
|
|
adminAuth *AdminAuth
|
|
gateway GatewayInterface
|
|
profileFetcher *profile.ProfileFetcher
|
|
}
|
|
|
|
// NewAdminHandlers creates new admin handlers
|
|
func NewAdminHandlers(adminAuth *AdminAuth, gateway GatewayInterface, defaultRelays []string) *AdminHandlers {
|
|
return &AdminHandlers{
|
|
adminAuth: adminAuth,
|
|
gateway: gateway,
|
|
profileFetcher: profile.NewProfileFetcher(defaultRelays),
|
|
}
|
|
}
|
|
|
|
// AdminStatsResponse represents admin statistics
|
|
type AdminStatsResponse struct {
|
|
TotalFiles int `json:"total_files"`
|
|
TotalUsers int `json:"total_users"`
|
|
TotalStorage int64 `json:"total_storage"`
|
|
BannedUsers int `json:"banned_users"`
|
|
PendingReports int `json:"pending_reports"`
|
|
RecentUploads int `json:"recent_uploads_24h"`
|
|
ErrorRate float64 `json:"error_rate"`
|
|
}
|
|
|
|
// AdminUser represents a user in admin view
|
|
type AdminUser struct {
|
|
Pubkey string `json:"pubkey"`
|
|
DisplayName string `json:"display_name"`
|
|
FileCount int `json:"file_count"`
|
|
StorageUsed int64 `json:"storage_used"`
|
|
LastLogin time.Time `json:"last_login"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
IsBanned bool `json:"is_banned"`
|
|
Profile *profile.ProfileMetadata `json:"profile,omitempty"`
|
|
}
|
|
|
|
// AdminFile represents a file in admin view
|
|
type AdminFile struct {
|
|
Hash string `json:"hash"`
|
|
Name string `json:"name"`
|
|
Size int64 `json:"size"`
|
|
StorageType string `json:"storage_type"`
|
|
AccessLevel string `json:"access_level"`
|
|
OwnerPubkey string `json:"owner_pubkey"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
AccessCount int `json:"access_count"`
|
|
ReportCount int `json:"report_count"`
|
|
OwnerProfile *profile.ProfileMetadata `json:"owner_profile,omitempty"`
|
|
}
|
|
|
|
// AdminStatsHandler returns system statistics for admins
|
|
func (ah *AdminHandlers) AdminStatsHandler(w http.ResponseWriter, r *http.Request) {
|
|
adminPubkey, err := ah.adminAuth.ValidateAdminRequest(r)
|
|
if err != nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"success": false,
|
|
"error": "Unauthorized",
|
|
})
|
|
return
|
|
}
|
|
|
|
// Get total files
|
|
var totalFiles int
|
|
err = ah.gateway.GetDB().QueryRow("SELECT COUNT(*) FROM files").Scan(&totalFiles)
|
|
if err != nil {
|
|
http.Error(w, "Failed to get file count", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Get total users
|
|
var totalUsers int
|
|
err = ah.gateway.GetDB().QueryRow("SELECT COUNT(*) FROM users").Scan(&totalUsers)
|
|
if err != nil {
|
|
http.Error(w, "Failed to get user count", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Get total storage
|
|
var totalStorage int64
|
|
err = ah.gateway.GetDB().QueryRow("SELECT COALESCE(SUM(size), 0) FROM files").Scan(&totalStorage)
|
|
if err != nil {
|
|
http.Error(w, "Failed to get storage total", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Get banned users count
|
|
var bannedUsers int
|
|
err = ah.gateway.GetDB().QueryRow("SELECT COUNT(*) FROM banned_users").Scan(&bannedUsers)
|
|
if err != nil {
|
|
http.Error(w, "Failed to get banned users count", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Get pending reports
|
|
var pendingReports int
|
|
err = ah.gateway.GetDB().QueryRow("SELECT COUNT(*) FROM content_reports WHERE status = 'pending'").Scan(&pendingReports)
|
|
if err != nil {
|
|
http.Error(w, "Failed to get pending reports count", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Get recent uploads (24h)
|
|
var recentUploads int
|
|
err = ah.gateway.GetDB().QueryRow("SELECT COUNT(*) FROM files WHERE created_at > datetime('now', '-1 day')").Scan(&recentUploads)
|
|
if err != nil {
|
|
http.Error(w, "Failed to get recent uploads count", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Log admin action
|
|
ah.adminAuth.LogAdminAction(adminPubkey, "view_stats", "", "Admin viewed system statistics")
|
|
|
|
response := AdminStatsResponse{
|
|
TotalFiles: totalFiles,
|
|
TotalUsers: totalUsers,
|
|
TotalStorage: totalStorage,
|
|
BannedUsers: bannedUsers,
|
|
PendingReports: pendingReports,
|
|
RecentUploads: recentUploads,
|
|
ErrorRate: 0.0, // TODO: Implement error rate tracking
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(response)
|
|
}
|
|
|
|
// AdminUsersHandler returns list of users for admin management
|
|
func (ah *AdminHandlers) AdminUsersHandler(w http.ResponseWriter, r *http.Request) {
|
|
adminPubkey, err := ah.adminAuth.ValidateAdminRequest(r)
|
|
if err != nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"success": false,
|
|
"error": "Unauthorized",
|
|
})
|
|
return
|
|
}
|
|
|
|
// Parse query parameters
|
|
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
|
|
if limit <= 0 || limit > 100 {
|
|
limit = 50
|
|
}
|
|
offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
|
|
|
|
query := `
|
|
SELECT u.pubkey, COALESCE(u.display_name, '') as display_name, u.file_count, u.storage_used, u.last_login, u.created_at,
|
|
EXISTS(SELECT 1 FROM banned_users WHERE pubkey = u.pubkey) as is_banned
|
|
FROM users u
|
|
ORDER BY u.created_at DESC
|
|
LIMIT ? OFFSET ?
|
|
`
|
|
|
|
rows, err := ah.gateway.GetDB().Query(query, limit, offset)
|
|
if err != nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"success": false,
|
|
"error": "Failed to query users",
|
|
})
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
var users []AdminUser
|
|
for rows.Next() {
|
|
var user AdminUser
|
|
err := rows.Scan(&user.Pubkey, &user.DisplayName, &user.FileCount,
|
|
&user.StorageUsed, &user.LastLogin, &user.CreatedAt, &user.IsBanned)
|
|
if err != nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"success": false,
|
|
"error": "Failed to scan user",
|
|
})
|
|
return
|
|
}
|
|
users = append(users, user)
|
|
}
|
|
|
|
// Fetch profile metadata for all users
|
|
pubkeys := make([]string, len(users))
|
|
for i, user := range users {
|
|
pubkeys[i] = user.Pubkey
|
|
}
|
|
|
|
profiles := ah.profileFetcher.GetBatchProfiles(pubkeys)
|
|
for i := range users {
|
|
if profile, exists := profiles[users[i].Pubkey]; exists {
|
|
users[i].Profile = profile
|
|
}
|
|
}
|
|
|
|
// Log admin action
|
|
ah.adminAuth.LogAdminAction(adminPubkey, "view_users", "", "Admin viewed user list")
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(users)
|
|
}
|
|
|
|
// AdminFilesHandler returns list of files for admin management
|
|
func (ah *AdminHandlers) AdminFilesHandler(w http.ResponseWriter, r *http.Request) {
|
|
adminPubkey, err := ah.adminAuth.ValidateAdminRequest(r)
|
|
if err != nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"success": false,
|
|
"error": "Unauthorized",
|
|
})
|
|
return
|
|
}
|
|
|
|
// Parse query parameters
|
|
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
|
|
if limit <= 0 || limit > 100 {
|
|
limit = 50
|
|
}
|
|
offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
|
|
|
|
storageType := r.URL.Query().Get("storage_type")
|
|
accessLevel := r.URL.Query().Get("access_level")
|
|
|
|
// Build query with filters
|
|
query := `
|
|
SELECT f.hash, f.original_name, f.size, f.storage_type, f.access_level,
|
|
COALESCE(f.owner_pubkey, '') as owner_pubkey, f.created_at, f.access_count,
|
|
COALESCE((SELECT COUNT(*) FROM content_reports WHERE file_hash = f.hash), 0) as report_count
|
|
FROM files f
|
|
WHERE 1=1
|
|
`
|
|
args := []interface{}{}
|
|
|
|
if storageType != "" {
|
|
query += " AND f.storage_type = ?"
|
|
args = append(args, storageType)
|
|
}
|
|
if accessLevel != "" {
|
|
query += " AND f.access_level = ?"
|
|
args = append(args, accessLevel)
|
|
}
|
|
|
|
query += " ORDER BY f.created_at DESC LIMIT ? OFFSET ?"
|
|
args = append(args, limit, offset)
|
|
|
|
rows, err := ah.gateway.GetDB().Query(query, args...)
|
|
if err != nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"success": false,
|
|
"error": "Failed to query files",
|
|
})
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
var files []AdminFile
|
|
for rows.Next() {
|
|
var file AdminFile
|
|
err := rows.Scan(&file.Hash, &file.Name, &file.Size, &file.StorageType,
|
|
&file.AccessLevel, &file.OwnerPubkey, &file.CreatedAt,
|
|
&file.AccessCount, &file.ReportCount)
|
|
if err != nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"success": false,
|
|
"error": "Failed to scan file",
|
|
})
|
|
return
|
|
}
|
|
files = append(files, file)
|
|
}
|
|
|
|
// Fetch profile metadata for file owners
|
|
ownerPubkeys := make([]string, 0)
|
|
for _, file := range files {
|
|
if file.OwnerPubkey != "" {
|
|
ownerPubkeys = append(ownerPubkeys, file.OwnerPubkey)
|
|
}
|
|
}
|
|
|
|
if len(ownerPubkeys) > 0 {
|
|
profiles := ah.profileFetcher.GetBatchProfiles(ownerPubkeys)
|
|
for i := range files {
|
|
if files[i].OwnerPubkey != "" {
|
|
if profile, exists := profiles[files[i].OwnerPubkey]; exists {
|
|
files[i].OwnerProfile = profile
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Log admin action
|
|
ah.adminAuth.LogAdminAction(adminPubkey, "view_files", "", "Admin viewed file list")
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(files)
|
|
}
|
|
|
|
// AdminDeleteFileHandler deletes a file with admin privileges
|
|
func (ah *AdminHandlers) AdminDeleteFileHandler(w http.ResponseWriter, r *http.Request) {
|
|
adminPubkey, err := ah.adminAuth.ValidateAdminRequest(r)
|
|
if err != nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"success": false,
|
|
"error": "Unauthorized",
|
|
})
|
|
return
|
|
}
|
|
|
|
vars := mux.Vars(r)
|
|
fileHash := vars["hash"]
|
|
|
|
if fileHash == "" {
|
|
http.Error(w, "Missing file hash", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Get reason from request body
|
|
var reqBody struct {
|
|
Reason string `json:"reason"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil {
|
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Get file info before deletion for logging
|
|
metadata, err := ah.gateway.GetStorage().GetFileMetadata(fileHash)
|
|
if err != nil {
|
|
http.Error(w, "File not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
// Admin can delete any file
|
|
err = ah.gateway.GetStorage().AdminDeleteFile(fileHash)
|
|
if err != nil {
|
|
http.Error(w, "Failed to delete file", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Log admin action
|
|
reason := reqBody.Reason
|
|
if reason == "" {
|
|
reason = "Admin deletion"
|
|
}
|
|
ah.adminAuth.LogAdminAction(adminPubkey, "delete_file", fileHash,
|
|
fmt.Sprintf("Deleted file '%s' (owner: %s) - %s", metadata.OriginalName, metadata.OwnerPubkey, reason))
|
|
|
|
response := map[string]interface{}{
|
|
"success": true,
|
|
"message": "File deleted successfully",
|
|
"hash": fileHash,
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(response)
|
|
}
|
|
|
|
// BanUserRequest represents a user ban request
|
|
type BanUserRequest struct {
|
|
Reason string `json:"reason"`
|
|
}
|
|
|
|
// AdminBanUserHandler bans a user
|
|
func (ah *AdminHandlers) AdminBanUserHandler(w http.ResponseWriter, r *http.Request) {
|
|
adminPubkey, err := ah.adminAuth.ValidateAdminRequest(r)
|
|
if err != nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"success": false,
|
|
"error": "Unauthorized",
|
|
})
|
|
return
|
|
}
|
|
|
|
vars := mux.Vars(r)
|
|
userPubkey := vars["pubkey"]
|
|
|
|
if userPubkey == "" {
|
|
http.Error(w, "Missing user pubkey", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
var req BanUserRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Ban the user
|
|
err = ah.adminAuth.BanUser(userPubkey, adminPubkey, req.Reason)
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("Failed to ban user: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
response := map[string]interface{}{
|
|
"success": true,
|
|
"message": "User banned successfully",
|
|
"pubkey": userPubkey,
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(response)
|
|
}
|
|
|
|
// AdminUnbanUserHandler unbans a user
|
|
func (ah *AdminHandlers) AdminUnbanUserHandler(w http.ResponseWriter, r *http.Request) {
|
|
adminPubkey, err := ah.adminAuth.ValidateAdminRequest(r)
|
|
if err != nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"success": false,
|
|
"error": "Unauthorized",
|
|
})
|
|
return
|
|
}
|
|
|
|
vars := mux.Vars(r)
|
|
userPubkey := vars["pubkey"]
|
|
|
|
if userPubkey == "" {
|
|
http.Error(w, "Missing user pubkey", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
Reason string `json:"reason"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Unban the user
|
|
err = ah.adminAuth.UnbanUser(userPubkey, adminPubkey, req.Reason)
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("Failed to unban user: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
response := map[string]interface{}{
|
|
"success": true,
|
|
"message": "User unbanned successfully",
|
|
"pubkey": userPubkey,
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(response)
|
|
}
|
|
|
|
// AdminReportsHandler returns content reports
|
|
func (ah *AdminHandlers) AdminReportsHandler(w http.ResponseWriter, r *http.Request) {
|
|
adminPubkey, err := ah.adminAuth.ValidateAdminRequest(r)
|
|
if err != nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"success": false,
|
|
"error": "Unauthorized",
|
|
})
|
|
return
|
|
}
|
|
|
|
// Parse query parameters
|
|
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
|
|
if limit <= 0 || limit > 100 {
|
|
limit = 50
|
|
}
|
|
offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
|
|
status := r.URL.Query().Get("status")
|
|
|
|
query := `
|
|
SELECT cr.id, cr.file_hash, cr.reporter_pubkey, cr.reason, cr.status, cr.created_at,
|
|
f.original_name, f.size, f.owner_pubkey
|
|
FROM content_reports cr
|
|
LEFT JOIN files f ON cr.file_hash = f.hash
|
|
WHERE 1=1
|
|
`
|
|
args := []interface{}{}
|
|
|
|
if status != "" {
|
|
query += " AND cr.status = ?"
|
|
args = append(args, status)
|
|
}
|
|
|
|
query += " ORDER BY cr.created_at DESC LIMIT ? OFFSET ?"
|
|
args = append(args, limit, offset)
|
|
|
|
rows, err := ah.gateway.GetDB().Query(query, args...)
|
|
if err != nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"success": false,
|
|
"error": "Failed to query reports",
|
|
})
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
var reports []map[string]interface{}
|
|
for rows.Next() {
|
|
var report ContentReport
|
|
var fileName, ownerPubkey sql.NullString
|
|
var fileSize sql.NullInt64
|
|
|
|
err := rows.Scan(&report.ID, &report.FileHash, &report.ReporterPubkey,
|
|
&report.Reason, &report.Status, &report.CreatedAt,
|
|
&fileName, &fileSize, &ownerPubkey)
|
|
if err != nil {
|
|
http.Error(w, "Failed to scan report", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
reportData := map[string]interface{}{
|
|
"id": report.ID,
|
|
"file_hash": report.FileHash,
|
|
"reporter_pubkey": report.ReporterPubkey,
|
|
"reason": report.Reason,
|
|
"status": report.Status,
|
|
"created_at": report.CreatedAt,
|
|
"file_name": fileName.String,
|
|
"file_size": fileSize.Int64,
|
|
"file_owner": ownerPubkey.String,
|
|
}
|
|
reports = append(reports, reportData)
|
|
}
|
|
|
|
// Log admin action
|
|
ah.adminAuth.LogAdminAction(adminPubkey, "view_reports", "", "Admin viewed content reports")
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(reports)
|
|
}
|
|
|
|
// AdminCleanupHandler triggers cleanup operations
|
|
func (ah *AdminHandlers) AdminCleanupHandler(w http.ResponseWriter, r *http.Request) {
|
|
adminPubkey, err := ah.adminAuth.ValidateAdminRequest(r)
|
|
if err != nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"success": false,
|
|
"error": "Unauthorized",
|
|
})
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
Operation string `json:"operation"`
|
|
MaxAge string `json:"max_age,omitempty"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
var cleanupResult map[string]interface{}
|
|
var cleanupErr error
|
|
|
|
switch req.Operation {
|
|
case "old_files":
|
|
maxAge := "90d"
|
|
if req.MaxAge != "" {
|
|
maxAge = req.MaxAge
|
|
}
|
|
duration, err := time.ParseDuration(maxAge)
|
|
if err != nil {
|
|
http.Error(w, "Invalid max_age format", http.StatusBadRequest)
|
|
return
|
|
}
|
|
cleanupResult, cleanupErr = ah.gateway.CleanupOldFiles(duration)
|
|
|
|
case "orphaned_chunks":
|
|
cleanupResult, cleanupErr = ah.gateway.CleanupOrphanedChunks()
|
|
|
|
case "inactive_users":
|
|
days := 365
|
|
if req.MaxAge != "" {
|
|
if d, err := strconv.Atoi(req.MaxAge); err == nil {
|
|
days = d
|
|
}
|
|
}
|
|
cleanupResult, cleanupErr = ah.gateway.CleanupInactiveUsers(days)
|
|
|
|
default:
|
|
http.Error(w, "Invalid cleanup operation", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if cleanupErr != nil {
|
|
http.Error(w, fmt.Sprintf("Cleanup failed: %v", cleanupErr), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Log admin action
|
|
ah.adminAuth.LogAdminAction(adminPubkey, "cleanup", req.Operation,
|
|
fmt.Sprintf("Executed cleanup operation: %s", req.Operation))
|
|
|
|
response := map[string]interface{}{
|
|
"success": true,
|
|
"operation": req.Operation,
|
|
"result": cleanupResult,
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(response)
|
|
}
|
|
|
|
// AdminLogsHandler returns admin action logs
|
|
func (ah *AdminHandlers) AdminLogsHandler(w http.ResponseWriter, r *http.Request) {
|
|
_, err := ah.adminAuth.ValidateAdminRequest(r)
|
|
if err != nil {
|
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// Parse query parameters
|
|
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
|
|
if limit <= 0 || limit > 100 {
|
|
limit = 50
|
|
}
|
|
offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
|
|
|
|
actions, err := ah.adminAuth.GetAdminActions(limit, offset, "")
|
|
if err != nil {
|
|
http.Error(w, "Failed to get admin actions", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Log admin action (don't log viewing logs to avoid spam)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(actions)
|
|
} |