208 lines
6.1 KiB
Go
208 lines
6.1 KiB
Go
package admin
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"github.com/mattnite/forgejo-tickets/internal/models"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
type UserHandler struct {
|
|
deps Dependencies
|
|
}
|
|
|
|
func (h *UserHandler) List(c *gin.Context) {
|
|
var users []models.User
|
|
if err := h.deps.DB.Order("created_at DESC").Limit(100).Find(&users).Error; err != nil {
|
|
log.Error().Err(err).Msg("list users error")
|
|
h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusInternalServerError, "Failed to load users")
|
|
return
|
|
}
|
|
|
|
h.deps.Renderer.Render(c.Writer, c.Request, "admin/users/list", map[string]interface{}{
|
|
"Users": users,
|
|
})
|
|
}
|
|
|
|
func (h *UserHandler) Detail(c *gin.Context) {
|
|
userID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusBadRequest, "Invalid user ID")
|
|
return
|
|
}
|
|
|
|
var user models.User
|
|
if err := h.deps.DB.First(&user, "id = ?", userID).Error; err != nil {
|
|
h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusNotFound, "User not found")
|
|
return
|
|
}
|
|
|
|
var tickets []models.Ticket
|
|
h.deps.DB.Preload("Repo").Where("user_id = ?", user.ID).Order("created_at DESC").Limit(50).Find(&tickets)
|
|
|
|
// Load all repos and user's assigned repo IDs
|
|
var allRepos []models.Repo
|
|
h.deps.DB.Where("active = ?", true).Order("name ASC").Find(&allRepos)
|
|
|
|
var userRepos []models.UserRepo
|
|
h.deps.DB.Where("user_id = ?", user.ID).Find(&userRepos)
|
|
|
|
assignedRepoIDs := make(map[string]bool)
|
|
for _, ur := range userRepos {
|
|
assignedRepoIDs[ur.RepoID.String()] = true
|
|
}
|
|
|
|
h.deps.Renderer.Render(c.Writer, c.Request, "admin/users/detail", map[string]interface{}{
|
|
"User": user,
|
|
"Tickets": tickets,
|
|
"AllRepos": allRepos,
|
|
"AssignedRepoIDs": assignedRepoIDs,
|
|
})
|
|
}
|
|
|
|
func (h *UserHandler) NewForm(c *gin.Context) {
|
|
h.deps.Renderer.Render(c.Writer, c.Request, "admin/users/new", nil)
|
|
}
|
|
|
|
func (h *UserHandler) Create(c *gin.Context) {
|
|
name := strings.TrimSpace(c.PostForm("name"))
|
|
email := strings.TrimSpace(c.PostForm("email"))
|
|
|
|
if name == "" || email == "" {
|
|
h.deps.Renderer.Render(c.Writer, c.Request, "admin/users/new", map[string]interface{}{
|
|
"Error": "Name and email are required",
|
|
"Name": name,
|
|
"Email": email,
|
|
})
|
|
return
|
|
}
|
|
|
|
tempPassBytes := make([]byte, 12)
|
|
rand.Read(tempPassBytes)
|
|
tempPassword := hex.EncodeToString(tempPassBytes)[:16]
|
|
|
|
user, err := h.deps.Auth.CreateUserWithPassword(c.Request.Context(), email, tempPassword, name, true, true)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "duplicate key") || strings.Contains(err.Error(), "unique") {
|
|
h.deps.Renderer.Render(c.Writer, c.Request, "admin/users/new", map[string]interface{}{
|
|
"Error": "A user with this email already exists",
|
|
"Name": name,
|
|
"Email": email,
|
|
})
|
|
} else {
|
|
h.deps.Renderer.Render(c.Writer, c.Request, "admin/users/new", map[string]interface{}{
|
|
"Error": "Failed to create user: " + err.Error(),
|
|
"Name": name,
|
|
"Email": email,
|
|
})
|
|
}
|
|
return
|
|
}
|
|
|
|
if err := h.deps.EmailClient.SendWelcomeEmail(email, name, tempPassword); err != nil {
|
|
log.Error().Err(err).Msg("send welcome email error")
|
|
}
|
|
|
|
c.Redirect(http.StatusSeeOther, "/users/"+user.ID.String())
|
|
}
|
|
|
|
func (h *UserHandler) PendingList(c *gin.Context) {
|
|
var users []models.User
|
|
if err := h.deps.DB.Where("email_verified = ? AND approved = ?", true, false).Order("created_at DESC").Find(&users).Error; err != nil {
|
|
log.Error().Err(err).Msg("list pending users error")
|
|
h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusInternalServerError, "Failed to load pending users")
|
|
return
|
|
}
|
|
|
|
h.deps.Renderer.Render(c.Writer, c.Request, "admin/users/pending", map[string]interface{}{
|
|
"Users": users,
|
|
})
|
|
}
|
|
|
|
func (h *UserHandler) Approve(c *gin.Context) {
|
|
userID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusBadRequest, "Invalid user ID")
|
|
return
|
|
}
|
|
|
|
var user models.User
|
|
if err := h.deps.DB.First(&user, "id = ?", userID).Error; err != nil {
|
|
h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusNotFound, "User not found")
|
|
return
|
|
}
|
|
|
|
h.deps.DB.Model(&user).Update("approved", true)
|
|
|
|
if err := h.deps.EmailClient.SendAccountApprovedEmail(user.Email, user.Name); err != nil {
|
|
log.Error().Err(err).Msg("send approval email error")
|
|
}
|
|
|
|
redirectURL := "/users/pending?" + url.Values{
|
|
"flash": {"User " + user.Email + " has been approved"},
|
|
"flash_type": {"success"},
|
|
}.Encode()
|
|
c.Redirect(http.StatusSeeOther, redirectURL)
|
|
}
|
|
|
|
func (h *UserHandler) Reject(c *gin.Context) {
|
|
userID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusBadRequest, "Invalid user ID")
|
|
return
|
|
}
|
|
|
|
if err := h.deps.DB.Delete(&models.User{}, "id = ?", userID).Error; err != nil {
|
|
log.Error().Err(err).Msg("delete user error")
|
|
h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusInternalServerError, "Failed to reject user")
|
|
return
|
|
}
|
|
|
|
redirectURL := "/users/pending?" + url.Values{
|
|
"flash": {"User request has been rejected"},
|
|
"flash_type": {"success"},
|
|
}.Encode()
|
|
c.Redirect(http.StatusSeeOther, redirectURL)
|
|
}
|
|
|
|
func (h *UserHandler) UpdateRepos(c *gin.Context) {
|
|
userID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusBadRequest, "Invalid user ID")
|
|
return
|
|
}
|
|
|
|
var user models.User
|
|
if err := h.deps.DB.First(&user, "id = ?", userID).Error; err != nil {
|
|
h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusNotFound, "User not found")
|
|
return
|
|
}
|
|
|
|
// Get selected repo IDs from form
|
|
repoIDs := c.PostFormArray("repo_ids")
|
|
|
|
// Delete existing assignments
|
|
h.deps.DB.Where("user_id = ?", userID).Delete(&models.UserRepo{})
|
|
|
|
// Create new assignments
|
|
for _, idStr := range repoIDs {
|
|
repoID, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
h.deps.DB.Create(&models.UserRepo{UserID: userID, RepoID: repoID})
|
|
}
|
|
|
|
redirectURL := "/users/" + userID.String() + "?" + url.Values{
|
|
"flash": {"Project assignments updated"},
|
|
"flash_type": {"success"},
|
|
}.Encode()
|
|
c.Redirect(http.StatusSeeOther, redirectURL)
|
|
}
|