375 lines
10 KiB
Go
375 lines
10 KiB
Go
package controllers
|
||
|
||
import (
|
||
"net/http"
|
||
"strconv"
|
||
"time"
|
||
|
||
database "goGin/app/database/config"
|
||
"goGin/app/database/models"
|
||
"goGin/app/middlewares"
|
||
utils "goGin/pkg/utis"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// UserResponse, kullanıcı verilerini güvenli bir şekilde döndürmek için
|
||
type UserResponse struct {
|
||
ID uint `json:"id"`
|
||
UserName string `json:"username"`
|
||
Email string `json:"email"`
|
||
EmailVerified bool `json:"email_verified"`
|
||
IsAdmin bool `json:"is_admin"`
|
||
}
|
||
|
||
// AdminUserListItem, admin listesinde deleted_at ile ayırt etmek için
|
||
type AdminUserListItem struct {
|
||
UserResponse
|
||
DeletedAt *time.Time `json:"deleted_at,omitempty"`
|
||
}
|
||
|
||
// UserPayload, kullanıcı güncelleme payload'u
|
||
type UserPayload struct {
|
||
UserName string `json:"username"`
|
||
Email string `json:"email"`
|
||
Password string `json:"password,omitempty"` // Opsiyonel şifre güncellemesi
|
||
}
|
||
|
||
// AdminUserUpdatePayload, admin tarafından kullanıcı güncelleme
|
||
type AdminUserUpdatePayload struct {
|
||
UserName string `json:"username"`
|
||
Email string `json:"email"`
|
||
IsAdmin *bool `json:"is_admin"` // Pointer allows checking if field is present
|
||
}
|
||
|
||
// Helper to convert model to response
|
||
func toUserResponse(u models.User) UserResponse {
|
||
isAdmin := false
|
||
if u.IsAdmin != nil {
|
||
isAdmin = *u.IsAdmin
|
||
}
|
||
isVerified := false
|
||
if u.EmailVerified != nil {
|
||
isVerified = *u.EmailVerified
|
||
}
|
||
|
||
return UserResponse{
|
||
ID: u.ID,
|
||
UserName: u.UserName,
|
||
Email: u.Email,
|
||
EmailVerified: isVerified,
|
||
IsAdmin: isAdmin,
|
||
}
|
||
}
|
||
|
||
// toAdminUserListItem, admin listesinde deleted_at döndürmek için
|
||
func toAdminUserListItem(u models.User) AdminUserListItem {
|
||
item := AdminUserListItem{UserResponse: toUserResponse(u)}
|
||
if u.DeletedAt.Valid {
|
||
item.DeletedAt = &u.DeletedAt.Time
|
||
}
|
||
return item
|
||
}
|
||
|
||
// GetProfile godoc
|
||
// @Summary Get current user profile
|
||
// @Description Get profile of the logged-in user
|
||
// @Tags users
|
||
// @Security BearerAuth
|
||
// @Produce json
|
||
// @Success 200 {object} controllers.UserResponse
|
||
// @Failure 401 {object} map[string]string
|
||
// @Failure 500 {object} map[string]string
|
||
// @Router /api/v1/users/profile [get]
|
||
func GetProfile(c *gin.Context) {
|
||
claims, ok := middlewares.GetAuthClaims(c)
|
||
if !ok {
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||
return
|
||
}
|
||
|
||
var user models.User
|
||
if err := database.DB.Preload("SocialAccounts").Preload("Profile").First(&user, claims.UserID).Error; err != nil {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{"data": toUserResponse(user)})
|
||
}
|
||
|
||
// UpdateProfile godoc
|
||
// @Summary Update current user profile
|
||
// @Description Update profile of the logged-in user
|
||
// @Tags users
|
||
// @Security BearerAuth
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param user body UserPayload true "User update payload"
|
||
// @Success 200 {object} controllers.UserResponse
|
||
// @Failure 400 {object} map[string]string
|
||
// @Failure 500 {object} map[string]string
|
||
// @Router /api/v1/users/profile [put]
|
||
func UpdateProfile(c *gin.Context) {
|
||
claims, ok := middlewares.GetAuthClaims(c)
|
||
if !ok {
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||
return
|
||
}
|
||
|
||
var payload UserPayload
|
||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
var user models.User
|
||
if err := database.DB.First(&user, claims.UserID).Error; err != nil {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
||
return
|
||
}
|
||
|
||
if payload.UserName != "" {
|
||
user.UserName = payload.UserName
|
||
}
|
||
if payload.Email != "" {
|
||
user.Email = payload.Email
|
||
// Email değişirse doğrulama sıfırlanabilir
|
||
f := false
|
||
user.EmailVerified = &f
|
||
}
|
||
if payload.Password != "" {
|
||
hashed, err := utils.HashPassword(payload.Password)
|
||
if err == nil {
|
||
user.Password = hashed
|
||
}
|
||
}
|
||
|
||
if err := database.DB.Save(&user).Error; err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{"data": toUserResponse(user)})
|
||
}
|
||
|
||
// AdminListUsers godoc
|
||
// @Summary Admin: List users
|
||
// @Description Admin listing of users with pagination and search
|
||
// @Tags users_admin
|
||
// @Security BearerAuth
|
||
// @Produce json
|
||
// @Param page query int false "Page number"
|
||
// @Param per_page query int false "Items per page"
|
||
// @Param q query string false "Search query (username or email)"
|
||
// @Param soft query string false "Soft delete filter: only|with"
|
||
// @Success 200 {object} map[string]interface{}
|
||
// @Failure 500 {object} map[string]string
|
||
// @Router /api/v1/admin/users [get]
|
||
func AdminListUsers(c *gin.Context) {
|
||
if database.DB == nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "database not configured"})
|
||
return
|
||
}
|
||
pageStr := c.DefaultQuery("page", "1")
|
||
perPageStr := c.DefaultQuery("per_page", "20")
|
||
page, _ := strconv.Atoi(pageStr)
|
||
perPage, _ := strconv.Atoi(perPageStr)
|
||
if page < 1 {
|
||
page = 1
|
||
}
|
||
if perPage < 1 {
|
||
perPage = 20
|
||
}
|
||
if perPage > 100 {
|
||
perPage = 100
|
||
}
|
||
offset := (page - 1) * perPage
|
||
|
||
soft := c.Query("soft")
|
||
var query *gorm.DB
|
||
if soft == "only" {
|
||
query = database.DB.Unscoped().Model(&models.User{}).Where("deleted_at IS NOT NULL")
|
||
} else if soft == "with" {
|
||
query = database.DB.Unscoped().Model(&models.User{})
|
||
} else {
|
||
query = database.DB.Model(&models.User{})
|
||
}
|
||
|
||
if q := c.Query("q"); q != "" {
|
||
like := "%" + q + "%"
|
||
query = query.Where("user_name LIKE ? OR email LIKE ?", like, like)
|
||
}
|
||
|
||
var total int64
|
||
if err := query.Count(&total).Error; err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
var users []models.User
|
||
if err := query.Order("created_at desc").Limit(perPage).Offset(offset).Find(&users).Error; err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
var data []AdminUserListItem
|
||
for _, u := range users {
|
||
data = append(data, toAdminUserListItem(u))
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{"items": data, "total": total, "page": page, "per_page": perPage})
|
||
}
|
||
|
||
// AdminGetUser godoc
|
||
// @Summary Admin: Get user
|
||
// @Description Get user details by ID
|
||
// @Tags users_admin
|
||
// @Security BearerAuth
|
||
// @Produce json
|
||
// @Param id path int true "User ID"
|
||
// @Success 200 {object} controllers.UserResponse
|
||
// @Failure 404 {object} map[string]string
|
||
// @Router /api/v1/admin/users/{id} [get]
|
||
func AdminGetUser(c *gin.Context) {
|
||
idStr := c.Param("id")
|
||
id, err := strconv.Atoi(idStr)
|
||
if err != nil || id < 1 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||
return
|
||
}
|
||
|
||
var user models.User
|
||
// Admin deleted kullanıcıyı da görebilmeli mi? Genelde evet, soft=with ile listede görüyorsa detayda da görmeli.
|
||
// Varsayılan olarak normal get soft-deleted getirmez. Unscoped kullanalım veya id ile direk bakalım.
|
||
if err := database.DB.Unscoped().First(&user, id).Error; err != nil {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{"data": toUserResponse(user)})
|
||
}
|
||
|
||
// AdminUpdateUser godoc
|
||
// @Summary Admin: Update user
|
||
// @Description Update user details (admin)
|
||
// @Tags users_admin
|
||
// @Security BearerAuth
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param id path int true "User ID"
|
||
// @Param user body AdminUserUpdatePayload true "User update payload"
|
||
// @Success 200 {object} controllers.UserResponse
|
||
// @Failure 400 {object} map[string]string
|
||
// @Failure 500 {object} map[string]string
|
||
// @Router /api/v1/admin/users/{id} [put]
|
||
func AdminUpdateUser(c *gin.Context) {
|
||
idStr := c.Param("id")
|
||
id, err := strconv.Atoi(idStr)
|
||
if err != nil || id < 1 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||
return
|
||
}
|
||
|
||
var payload AdminUserUpdatePayload
|
||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
var user models.User
|
||
if err := database.DB.Unscoped().First(&user, id).Error; err != nil {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
||
return
|
||
}
|
||
|
||
if payload.UserName != "" {
|
||
user.UserName = payload.UserName
|
||
}
|
||
if payload.Email != "" {
|
||
user.Email = payload.Email
|
||
}
|
||
if payload.IsAdmin != nil {
|
||
user.IsAdmin = payload.IsAdmin
|
||
}
|
||
|
||
if err := database.DB.Save(&user).Error; err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{"data": toUserResponse(user)})
|
||
}
|
||
|
||
// AdminDeleteUser godoc
|
||
// @Summary Admin: Delete user
|
||
// @Description Soft delete user
|
||
// @Tags users_admin
|
||
// @Security BearerAuth
|
||
// @Produce json
|
||
// @Param id path int true "User ID"
|
||
// @Success 200 {object} map[string]interface{}
|
||
// @Failure 404 {object} map[string]string
|
||
// @Failure 500 {object} map[string]string
|
||
// @Router /api/v1/admin/users/{id} [delete]
|
||
func AdminDeleteUser(c *gin.Context) {
|
||
idStr := c.Param("id")
|
||
id, err := strconv.Atoi(idStr)
|
||
if err != nil || id < 1 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||
return
|
||
}
|
||
|
||
var user models.User
|
||
if err := database.DB.First(&user, id).Error; err != nil {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
||
return
|
||
}
|
||
|
||
if err := database.DB.Delete(&user).Error; err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"message": "user deleted successfully",
|
||
"id": user.ID,
|
||
})
|
||
}
|
||
|
||
// AdminRestoreUser godoc
|
||
// @Summary Admin: Restore user
|
||
// @Description Restore soft-deleted user
|
||
// @Tags users_admin
|
||
// @Security BearerAuth
|
||
// @Produce json
|
||
// @Param id path int true "User ID"
|
||
// @Success 200 {object} controllers.UserResponse
|
||
// @Failure 404 {object} map[string]string
|
||
// @Failure 500 {object} map[string]string
|
||
// @Router /api/v1/admin/users/{id}/restore [post]
|
||
func AdminRestoreUser(c *gin.Context) {
|
||
idStr := c.Param("id")
|
||
id, err := strconv.Atoi(idStr)
|
||
if err != nil || id < 1 {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||
return
|
||
}
|
||
|
||
var user models.User
|
||
if err := database.DB.Unscoped().First(&user, id).Error; err != nil {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
||
return
|
||
}
|
||
|
||
if user.DeletedAt.Valid {
|
||
// Restore
|
||
if err := database.DB.Unscoped().Model(&user).Update("deleted_at", nil).Error; err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{"data": toUserResponse(user)})
|
||
}
|