first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:46:42 +03:00
commit 2a5b661443
202 changed files with 49770 additions and 0 deletions

View File

@@ -0,0 +1,374 @@
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)})
}