710 lines
21 KiB
Go
710 lines
21 KiB
Go
package handlers
|
||
|
||
import (
|
||
"errors"
|
||
"log"
|
||
"net/http"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"ginimageApi/app/accounts/models"
|
||
"ginimageApi/app/middleware"
|
||
"ginimageApi/configs"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"golang.org/x/crypto/bcrypt"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
type adminUserResponse struct {
|
||
ID uint `json:"id"`
|
||
Username string `json:"username"`
|
||
Email string `json:"email"`
|
||
EmailVerified bool `json:"email_verified"`
|
||
IsActive bool `json:"is_active"`
|
||
IsAdmin bool `json:"is_admin"`
|
||
CreatedAt string `json:"created_at"`
|
||
UpdatedAt string `json:"updated_at"`
|
||
}
|
||
|
||
type adminUserListResponse struct {
|
||
Items []adminUserResponse `json:"items"`
|
||
Meta paginationMeta `json:"meta"`
|
||
}
|
||
|
||
type paginationMeta struct {
|
||
Page int `json:"page"`
|
||
Limit int `json:"limit"`
|
||
Total int64 `json:"total"`
|
||
}
|
||
|
||
type adminCreateUserRequest struct {
|
||
Username string `json:"username" binding:"required,min=3"`
|
||
Email string `json:"email" binding:"required,email"`
|
||
Password string `json:"password" binding:"required,min=6"`
|
||
ConfirmPassword string `json:"confirm_password" binding:"required,eqfield=Password"`
|
||
IsAdmin *bool `json:"is_admin"`
|
||
IsActive *bool `json:"is_active"`
|
||
}
|
||
|
||
type adminUpdateUserRequest struct {
|
||
Username string `json:"username" binding:"omitempty,min=3"`
|
||
Email string `json:"email" binding:"omitempty,email"`
|
||
Password string `json:"password" binding:"omitempty,min=6"`
|
||
IsAdmin *bool `json:"is_admin"`
|
||
IsActive *bool `json:"is_active"`
|
||
}
|
||
|
||
type adminUserStatusRequest struct {
|
||
IsActive bool `json:"is_active" binding:"required"`
|
||
}
|
||
|
||
type adminUpdateProfileRequest struct {
|
||
FirstName string `form:"first_name" binding:"omitempty,min=2"`
|
||
LastName string `form:"last_name" binding:"omitempty,min=2"`
|
||
}
|
||
|
||
type adminProfileResponse struct {
|
||
UserID uint64 `json:"user_id"`
|
||
FirstName string `json:"first_name"`
|
||
LastName string `json:"last_name"`
|
||
AvatarURL string `json:"avatar_url"`
|
||
}
|
||
|
||
type adminIssueTokenRequest struct {
|
||
DurationDays int `json:"duration_days" binding:"required,min=1,max=365"`
|
||
}
|
||
|
||
type adminIssueTokenResponse struct {
|
||
AccessToken string `json:"access"`
|
||
ExpiresAt string `json:"expires_at"`
|
||
}
|
||
|
||
type AdminUserResponse = adminUserResponse
|
||
type AdminUserListResponse = adminUserListResponse
|
||
type AdminCreateUserRequest = adminCreateUserRequest
|
||
type AdminUpdateUserRequest = adminUpdateUserRequest
|
||
type AdminUserStatusRequest = adminUserStatusRequest
|
||
type AdminUpdateProfileRequest = adminUpdateProfileRequest
|
||
type AdminProfileResponse = adminProfileResponse
|
||
type AdminIssueTokenRequest = adminIssueTokenRequest
|
||
type AdminIssueTokenResponse = adminIssueTokenResponse
|
||
|
||
func adminActorID(c *gin.Context) any {
|
||
actorID, ok := c.Get("user_id")
|
||
if !ok {
|
||
return "unknown"
|
||
}
|
||
return actorID
|
||
}
|
||
|
||
func maskEmail(email string) string {
|
||
email = strings.TrimSpace(strings.ToLower(email))
|
||
parts := strings.Split(email, "@")
|
||
if len(parts) != 2 || parts[0] == "" {
|
||
return "invalid-email"
|
||
}
|
||
local := parts[0]
|
||
domain := parts[1]
|
||
if len(local) <= 2 {
|
||
return local[:1] + "***@" + domain
|
||
}
|
||
return local[:2] + "***@" + domain
|
||
}
|
||
|
||
func getOrCreateProfileByUserID(userID uint64) (models.Profile, error) {
|
||
var profile models.Profile
|
||
err := configs.DB.Where("user_id = ?", userID).First(&profile).Error
|
||
if err == nil {
|
||
return profile, nil
|
||
}
|
||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return models.Profile{}, err
|
||
}
|
||
|
||
profile = models.Profile{UserID: userID}
|
||
if err := configs.DB.Create(&profile).Error; err != nil {
|
||
return models.Profile{}, err
|
||
}
|
||
|
||
return profile, nil
|
||
}
|
||
|
||
// ListAdminUsers godoc
|
||
// @Summary Admin kullanicilari listeler
|
||
// @Tags admin-users
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param page query int false "Sayfa numarasi" default(1)
|
||
// @Param limit query int false "Sayfa boyutu (max 100)" default(10)
|
||
// @Param search query string false "Kullanici adi/email arama"
|
||
// @Param is_admin query bool false "Admin filtresi"
|
||
// @Param is_active query bool false "Aktiflik filtresi"
|
||
// @Success 200 {object} AdminUserListResponse
|
||
// @Failure 401 {object} ErrorResponse
|
||
// @Failure 403 {object} ErrorResponse
|
||
// @Failure 500 {object} ErrorResponse
|
||
// @Router /api/v1/admin/users [get]
|
||
func ListAdminUsers(c *gin.Context) {
|
||
page := parsePositiveIntOrDefault(c.Query("page"), 1)
|
||
limit := parsePositiveIntOrDefault(c.Query("limit"), 10)
|
||
if limit > 100 {
|
||
limit = 100
|
||
}
|
||
|
||
search := strings.TrimSpace(c.Query("search"))
|
||
isAdminFilter := strings.TrimSpace(c.Query("is_admin"))
|
||
isActiveFilter := strings.TrimSpace(c.Query("is_active"))
|
||
|
||
query := configs.DB.Model(&models.User{})
|
||
|
||
if search != "" {
|
||
like := "%" + strings.ToLower(search) + "%"
|
||
query = query.Where("LOWER(user_name) LIKE ? OR LOWER(email) LIKE ?", like, like)
|
||
}
|
||
|
||
if v, ok := parseOptionalBool(isAdminFilter); ok {
|
||
query = query.Where("is_admin = ?", v)
|
||
}
|
||
if v, ok := parseOptionalBool(isActiveFilter); ok {
|
||
query = query.Where("is_active = ?", v)
|
||
}
|
||
|
||
var total int64
|
||
if err := query.Count(&total).Error; err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "kullanicilar listelenemedi"})
|
||
return
|
||
}
|
||
|
||
var users []models.User
|
||
if err := query.
|
||
Order("id DESC").
|
||
Offset((page - 1) * limit).
|
||
Limit(limit).
|
||
Find(&users).Error; err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "kullanicilar listelenemedi"})
|
||
return
|
||
}
|
||
|
||
items := make([]adminUserResponse, 0, len(users))
|
||
for _, user := range users {
|
||
items = append(items, toAdminUserResponse(user))
|
||
}
|
||
|
||
c.JSON(http.StatusOK, adminUserListResponse{
|
||
Items: items,
|
||
Meta: paginationMeta{
|
||
Page: page,
|
||
Limit: limit,
|
||
Total: total,
|
||
},
|
||
})
|
||
}
|
||
|
||
// GetAdminUser godoc
|
||
// @Summary Admin panel icin kullanici detayi getirir
|
||
// @Tags admin-users
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param id path int true "Kullanici ID"
|
||
// @Success 200 {object} AdminUserResponse
|
||
// @Failure 400 {object} ErrorResponse
|
||
// @Failure 401 {object} ErrorResponse
|
||
// @Failure 403 {object} ErrorResponse
|
||
// @Failure 404 {object} ErrorResponse
|
||
// @Failure 500 {object} ErrorResponse
|
||
// @Router /api/v1/admin/users/{id} [get]
|
||
func GetAdminUser(c *gin.Context) {
|
||
userID, ok := parseUintParam(c, "id")
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
var user models.User
|
||
if err := configs.DB.First(&user, userID).Error; err != nil {
|
||
if err == gorm.ErrRecordNotFound {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "kullanici bulunamadi"})
|
||
return
|
||
}
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "kullanici getirilemedi"})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, toAdminUserResponse(user))
|
||
}
|
||
|
||
// CreateAdminUser godoc
|
||
// @Summary Admin panel icin kullanici olusturur
|
||
// @Tags admin-users
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param request body AdminCreateUserRequest true "Kullanici olusturma verisi"
|
||
// @Success 201 {object} AdminUserResponse
|
||
// @Failure 400 {object} ErrorResponse
|
||
// @Failure 401 {object} ErrorResponse
|
||
// @Failure 403 {object} ErrorResponse
|
||
// @Failure 409 {object} ErrorResponse
|
||
// @Failure 500 {object} ErrorResponse
|
||
// @Router /api/v1/admin/users [post]
|
||
func CreateAdminUser(c *gin.Context) {
|
||
log.Printf("[ADMIN-USER-CREATE] stage=start actor_id=%v ip=%s ua=%q", adminActorID(c), c.ClientIP(), c.Request.UserAgent())
|
||
|
||
var req adminCreateUserRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
log.Printf("[ADMIN-USER-CREATE] stage=bind_failed actor_id=%v ip=%s error=%q", adminActorID(c), c.ClientIP(), err.Error())
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
log.Printf(
|
||
"[ADMIN-USER-CREATE] stage=payload_ok actor_id=%v username=%q email=%q is_admin=%v is_active=%v",
|
||
adminActorID(c),
|
||
req.Username,
|
||
maskEmail(req.Email),
|
||
req.IsAdmin,
|
||
req.IsActive,
|
||
)
|
||
|
||
var exists models.User
|
||
err := configs.DB.Where("email = ?", req.Email).First(&exists).Error
|
||
if err == nil {
|
||
log.Printf(
|
||
"[ADMIN-USER-CREATE] stage=conflict actor_id=%v reason=email_exists incoming_email=%q existing_user_id=%d existing_username=%q existing_active=%v existing_admin=%v",
|
||
adminActorID(c),
|
||
maskEmail(req.Email),
|
||
exists.ID,
|
||
exists.UserName,
|
||
exists.IsActive != nil && *exists.IsActive,
|
||
exists.IsAdmin != nil && *exists.IsAdmin,
|
||
)
|
||
c.JSON(http.StatusConflict, gin.H{
|
||
"error": "email zaten kayitli",
|
||
"code": "EMAIL_ALREADY_EXISTS",
|
||
})
|
||
return
|
||
}
|
||
if err != nil && err != gorm.ErrRecordNotFound {
|
||
log.Printf("[ADMIN-USER-CREATE] stage=check_failed actor_id=%v email=%q error=%q", adminActorID(c), maskEmail(req.Email), err.Error())
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "kullanici kontrol edilemedi"})
|
||
return
|
||
}
|
||
|
||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||
if err != nil {
|
||
log.Printf("[ADMIN-USER-CREATE] stage=hash_failed actor_id=%v email=%q error=%q", adminActorID(c), maskEmail(req.Email), err.Error())
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "sifre islenemedi"})
|
||
return
|
||
}
|
||
|
||
isAdmin := false
|
||
if req.IsAdmin != nil {
|
||
isAdmin = *req.IsAdmin
|
||
}
|
||
isActive := true
|
||
if req.IsActive != nil {
|
||
isActive = *req.IsActive
|
||
}
|
||
|
||
user := models.User{
|
||
UserName: req.Username,
|
||
Email: req.Email,
|
||
Password: string(hashedPassword),
|
||
EmailVerified: boolPtr(false),
|
||
IsActive: boolPtr(isActive),
|
||
IsAdmin: boolPtr(isAdmin),
|
||
}
|
||
|
||
if err := configs.DB.Create(&user).Error; err != nil {
|
||
log.Printf("[ADMIN-USER-CREATE] stage=create_failed actor_id=%v email=%q error=%q", adminActorID(c), maskEmail(req.Email), err.Error())
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "kullanici olusturulamadi"})
|
||
return
|
||
}
|
||
log.Printf("[ADMIN-USER-CREATE] stage=success actor_id=%v created_user_id=%d email=%q", adminActorID(c), user.ID, maskEmail(user.Email))
|
||
|
||
c.JSON(http.StatusCreated, toAdminUserResponse(user))
|
||
}
|
||
|
||
// UpdateAdminUser godoc
|
||
// @Summary Admin panel icin kullaniciyi gunceller
|
||
// @Tags admin-users
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param id path int true "Kullanici ID"
|
||
// @Param request body AdminUpdateUserRequest true "Kullanici guncelleme verisi"
|
||
// @Success 200 {object} AdminUserResponse
|
||
// @Failure 400 {object} ErrorResponse
|
||
// @Failure 401 {object} ErrorResponse
|
||
// @Failure 403 {object} ErrorResponse
|
||
// @Failure 404 {object} ErrorResponse
|
||
// @Failure 409 {object} ErrorResponse
|
||
// @Failure 500 {object} ErrorResponse
|
||
// @Router /api/v1/admin/users/{id} [put]
|
||
func UpdateAdminUser(c *gin.Context) {
|
||
userID, ok := parseUintParam(c, "id")
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
var req adminUpdateUserRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
var user models.User
|
||
if err := configs.DB.First(&user, userID).Error; err != nil {
|
||
if err == gorm.ErrRecordNotFound {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "kullanici bulunamadi"})
|
||
return
|
||
}
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "kullanici getirilemedi"})
|
||
return
|
||
}
|
||
|
||
if req.Email != "" && req.Email != user.Email {
|
||
var exists models.User
|
||
err := configs.DB.Where("email = ? AND id <> ?", req.Email, user.ID).First(&exists).Error
|
||
if err == nil {
|
||
c.JSON(http.StatusConflict, gin.H{"error": "email zaten kayitli"})
|
||
return
|
||
}
|
||
if err != nil && err != gorm.ErrRecordNotFound {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "kullanici kontrol edilemedi"})
|
||
return
|
||
}
|
||
user.Email = req.Email
|
||
}
|
||
|
||
if req.Username != "" {
|
||
user.UserName = req.Username
|
||
}
|
||
if req.IsAdmin != nil {
|
||
user.IsAdmin = boolPtr(*req.IsAdmin)
|
||
}
|
||
if req.IsActive != nil {
|
||
user.IsActive = boolPtr(*req.IsActive)
|
||
}
|
||
if req.Password != "" {
|
||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "sifre islenemedi"})
|
||
return
|
||
}
|
||
user.Password = string(hashedPassword)
|
||
}
|
||
|
||
if err := configs.DB.Save(&user).Error; err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "kullanici guncellenemedi"})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, toAdminUserResponse(user))
|
||
}
|
||
|
||
// UpdateAdminUserStatus godoc
|
||
// @Summary Admin panel icin kullanici aktiflik durumunu gunceller
|
||
// @Tags admin-users
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param id path int true "Kullanici ID"
|
||
// @Param request body AdminUserStatusRequest true "Durum verisi"
|
||
// @Success 200 {object} AdminUserResponse
|
||
// @Failure 400 {object} ErrorResponse
|
||
// @Failure 401 {object} ErrorResponse
|
||
// @Failure 403 {object} ErrorResponse
|
||
// @Failure 404 {object} ErrorResponse
|
||
// @Failure 500 {object} ErrorResponse
|
||
// @Router /api/v1/admin/users/{id}/status [patch]
|
||
func UpdateAdminUserStatus(c *gin.Context) {
|
||
userID, ok := parseUintParam(c, "id")
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
var req adminUserStatusRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
result := configs.DB.Model(&models.User{}).Where("id = ?", userID).Update("is_active", req.IsActive)
|
||
if result.Error != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "kullanici durumu guncellenemedi"})
|
||
return
|
||
}
|
||
if result.RowsAffected == 0 {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "kullanici bulunamadi"})
|
||
return
|
||
}
|
||
|
||
var user models.User
|
||
if err := configs.DB.First(&user, userID).Error; err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "kullanici getirilemedi"})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, toAdminUserResponse(user))
|
||
}
|
||
|
||
// DeleteAdminUser godoc
|
||
// @Summary Admin panel icin kullanici siler
|
||
// @Tags admin-users
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param id path int true "Kullanici ID"
|
||
// @Success 200 {object} MessageResponse
|
||
// @Failure 400 {object} ErrorResponse
|
||
// @Failure 401 {object} ErrorResponse
|
||
// @Failure 403 {object} ErrorResponse
|
||
// @Failure 404 {object} ErrorResponse
|
||
// @Failure 500 {object} ErrorResponse
|
||
// @Router /api/v1/admin/users/{id} [delete]
|
||
func DeleteAdminUser(c *gin.Context) {
|
||
userID, ok := parseUintParam(c, "id")
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
result := configs.DB.Delete(&models.User{}, userID)
|
||
if result.Error != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "kullanici silinemedi"})
|
||
return
|
||
}
|
||
if result.RowsAffected == 0 {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "kullanici bulunamadi"})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{"message": "kullanici silindi"})
|
||
}
|
||
|
||
// IssueAdminScopedToken godoc
|
||
// @Summary Admin için gün bazlı access token üretir
|
||
// @Description Sadece admin rolü için, istekle verilen gün kadar geçerli access token üretir. Refresh token üretilmez.
|
||
// @Tags admin-users
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param request body AdminIssueTokenRequest true "Token süresi (gün)"
|
||
// @Success 200 {object} AdminIssueTokenResponse
|
||
// @Failure 400 {object} ErrorResponse
|
||
// @Failure 401 {object} ErrorResponse
|
||
// @Failure 403 {object} ErrorResponse
|
||
// @Failure 404 {object} ErrorResponse
|
||
// @Failure 500 {object} ErrorResponse
|
||
// @Router /api/v1/admin/tokens/issue [post]
|
||
func IssueAdminScopedToken(c *gin.Context) {
|
||
var req adminIssueTokenRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
userID, err := currentUserID(c)
|
||
if err != nil {
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
var user models.User
|
||
if err := configs.DB.First(&user, userID).Error; err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "kullanici bulunamadi"})
|
||
return
|
||
}
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "kullanici getirilemedi"})
|
||
return
|
||
}
|
||
if user.IsAdmin == nil || !*user.IsAdmin {
|
||
c.JSON(http.StatusForbidden, gin.H{"error": "admin yetkisi gerekli"})
|
||
return
|
||
}
|
||
|
||
tokenTTL := time.Duration(req.DurationDays) * 24 * time.Hour
|
||
accessToken, err := middleware.GenerateAccessToken(user.ID, user.Email, user.UserName, tokenTTL)
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "token olusturulamadi"})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, adminIssueTokenResponse{
|
||
AccessToken: accessToken,
|
||
ExpiresAt: time.Now().Add(tokenTTL).Format(time.RFC3339),
|
||
})
|
||
}
|
||
|
||
// GetAdminUserProfile godoc
|
||
// @Summary Admin panel icin kullanicinin profilini getirir
|
||
// @Tags admin-users
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param id path int true "Kullanici ID"
|
||
// @Success 200 {object} AdminProfileResponse
|
||
// @Failure 400 {object} ErrorResponse
|
||
// @Failure 401 {object} ErrorResponse
|
||
// @Failure 403 {object} ErrorResponse
|
||
// @Failure 404 {object} ErrorResponse
|
||
// @Failure 500 {object} ErrorResponse
|
||
// @Router /api/v1/admin/users/{id}/profile [get]
|
||
func GetAdminUserProfile(c *gin.Context) {
|
||
userID, ok := parseUintParam(c, "id")
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
var user models.User
|
||
if err := configs.DB.First(&user, userID).Error; err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "kullanici bulunamadi"})
|
||
return
|
||
}
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "kullanici getirilemedi"})
|
||
return
|
||
}
|
||
|
||
profile, err := getOrCreateProfileByUserID(uint64(userID))
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "profil getirilemedi"})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, adminProfileResponse{
|
||
UserID: profile.UserID,
|
||
FirstName: profile.FirstName,
|
||
LastName: profile.LastName,
|
||
AvatarURL: profile.AvatarURL,
|
||
})
|
||
}
|
||
|
||
// UpdateAdminUserProfile godoc
|
||
// @Summary Admin panel icin kullanici profilini gunceller
|
||
// @Tags admin-users
|
||
// @Accept multipart/form-data
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param id path int true "Kullanici ID"
|
||
// @Param first_name formData string false "Ad"
|
||
// @Param last_name formData string false "Soyad"
|
||
// @Param avatar formData file false "Avatar dosyasi"
|
||
// @Success 200 {object} AdminProfileResponse
|
||
// @Failure 400 {object} ErrorResponse
|
||
// @Failure 401 {object} ErrorResponse
|
||
// @Failure 403 {object} ErrorResponse
|
||
// @Failure 404 {object} ErrorResponse
|
||
// @Failure 500 {object} ErrorResponse
|
||
// @Router /api/v1/admin/users/{id}/profile [put]
|
||
func UpdateAdminUserProfile(c *gin.Context) {
|
||
userID, ok := parseUintParam(c, "id")
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
var req adminUpdateProfileRequest
|
||
_ = c.ShouldBind(&req)
|
||
|
||
var user models.User
|
||
if err := configs.DB.First(&user, userID).Error; err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "kullanici bulunamadi"})
|
||
return
|
||
}
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "kullanici getirilemedi"})
|
||
return
|
||
}
|
||
|
||
profile, err := getOrCreateProfileByUserID(uint64(userID))
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "profil getirilemedi"})
|
||
return
|
||
}
|
||
|
||
if req.FirstName != "" {
|
||
profile.FirstName = req.FirstName
|
||
}
|
||
if req.LastName != "" {
|
||
profile.LastName = req.LastName
|
||
}
|
||
oldAvatarURL := profile.AvatarURL
|
||
avatarURL, hasAvatar, err := saveAvatarFromMultipart(c, "avatar")
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "avatar dosyasi okunamadi"})
|
||
return
|
||
}
|
||
if hasAvatar {
|
||
profile.AvatarURL = avatarURL
|
||
}
|
||
|
||
if err := configs.DB.Save(&profile).Error; err != nil {
|
||
if hasAvatar {
|
||
_ = deleteLocalAvatarByURL(avatarURL)
|
||
}
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "profil guncellenemedi"})
|
||
return
|
||
}
|
||
if hasAvatar && oldAvatarURL != "" && oldAvatarURL != profile.AvatarURL {
|
||
if err := deleteLocalAvatarByURL(oldAvatarURL); err != nil {
|
||
log.Printf("[ADMIN-PROFILE-UPDATE] user_id=%d result=warn stage=delete_old_avatar error=%v", userID, err)
|
||
}
|
||
}
|
||
|
||
c.JSON(http.StatusOK, adminProfileResponse{
|
||
UserID: profile.UserID,
|
||
FirstName: profile.FirstName,
|
||
LastName: profile.LastName,
|
||
AvatarURL: profile.AvatarURL,
|
||
})
|
||
}
|
||
|
||
func parsePositiveIntOrDefault(raw string, fallback int) int {
|
||
if strings.TrimSpace(raw) == "" {
|
||
return fallback
|
||
}
|
||
v, err := strconv.Atoi(raw)
|
||
if err != nil || v <= 0 {
|
||
return fallback
|
||
}
|
||
return v
|
||
}
|
||
|
||
func parseOptionalBool(raw string) (bool, bool) {
|
||
if strings.TrimSpace(raw) == "" {
|
||
return false, false
|
||
}
|
||
v, err := strconv.ParseBool(raw)
|
||
if err != nil {
|
||
return false, false
|
||
}
|
||
return v, true
|
||
}
|
||
|
||
func parseUintParam(c *gin.Context, key string) (uint, bool) {
|
||
raw := strings.TrimSpace(c.Param(key))
|
||
if raw == "" {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "gecersiz kullanici id"})
|
||
return 0, false
|
||
}
|
||
|
||
id, err := strconv.ParseUint(raw, 10, 64)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "gecersiz kullanici id"})
|
||
return 0, false
|
||
}
|
||
return uint(id), true
|
||
}
|
||
|
||
func toAdminUserResponse(user models.User) adminUserResponse {
|
||
return adminUserResponse{
|
||
ID: user.ID,
|
||
Username: user.UserName,
|
||
Email: user.Email,
|
||
EmailVerified: user.IsEmailVerified(),
|
||
IsActive: user.IsActive != nil && *user.IsActive,
|
||
IsAdmin: user.IsAdmin != nil && *user.IsAdmin,
|
||
CreatedAt: user.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||
UpdatedAt: user.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||
}
|
||
}
|