package services import ( "context" "net/http" "time" "wm-backend/global" "wm-backend/internal/models/responses" "wm-backend/internal/repositories" redisRepo "wm-backend/internal/repositories/redis" "wm-backend/response" "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" ) // GetProfile returns the authenticated user's profile including roles and permissions. // It first attempts to read from Redis cache; on miss it queries the database. // // @Summary Get current user profile // @Description Returns user info with roles and permissions (requires auth) // @Tags auth // @Security BearerAuth // @Produce json // @Success 200 {object} response.SuccessResponse{data=responses.BodyProfileResponse} // @Failure 401 {object} response.ErrorResponse // @Router /profile [get] func GetProfile(c *gin.Context) error { userID := c.GetString("user_id") ctx := c.Request.Context() var roles []responses.RoleItem var permissions []string // 1. Try Redis cache first (graceful fallback on error) cachedData, found := redisRepo.GetCachedUserPermissions(ctx, userID) if found { // Cache hit - refresh TTL redisRepo.RefreshTTL(ctx, userID, 60*time.Minute) roles = make([]responses.RoleItem, 0, len(cachedData.Roles)) for _, r := range cachedData.Roles { roles = append(roles, responses.RoleItem{ ID: r.ID, Name: r.Name, Description: r.Description, }) } permissions = cachedData.Permissions } else { // 2. Cache miss - fetch from DB dbRoles, err := repositories.GetUserRolesByUserID(ctx, global.Queries, userID) if err != nil { log.Error().Err(err).Str("userID", userID).Msg("Failed to fetch roles from DB") response.InternalServerError(c, http.StatusInternalServerError) return nil } roles = make([]responses.RoleItem, 0, len(dbRoles)) for _, r := range dbRoles { roles = append(roles, responses.RoleItem{ ID: r.ID, Name: r.Name, Description: r.Description, }) } dbPerms, err := repositories.GetPermissionsByUserID(ctx, global.Queries, userID) if err != nil { log.Error().Err(err).Str("userID", userID).Msg("Failed to fetch permissions from DB") response.InternalServerError(c, http.StatusInternalServerError) return nil } permissions = make([]string, 0, len(dbPerms)) for _, p := range dbPerms { permissions = append(permissions, p.Name) } // 3. Save to Redis cache for future requests (use background context) cacheRoles := make([]redisRepo.RoleCacheItem, 0, len(dbRoles)) for _, r := range dbRoles { cacheRoles = append(cacheRoles, redisRepo.RoleCacheItem{ ID: r.ID, Name: r.Name, Description: r.Description, }) } go func() { bgCtx := context.Background() if err := redisRepo.CacheUserPermissions(bgCtx, userID, redisRepo.RBACCachedData{ Roles: cacheRoles, Permissions: permissions, }, 60*time.Minute); err != nil { log.Error().Err(err).Str("userID", userID).Msg("Failed to cache RBAC data in profile") } }() } // 4. Fetch user info from DB user, err := repositories.GetUserByID(ctx, global.Queries, userID) if err != nil { log.Error().Err(err).Str("userID", userID).Msg("Failed to fetch user from DB") response.InternalServerError(c, http.StatusInternalServerError) return nil } if user == nil { response.NotFoundError(c, http.StatusNotFound, "User not found") return nil } // 5. Return response response.Ok(c, "Profile fetched", responses.BodyProfileResponse{ Info: responses.UserInfoResponse{ ID: user.ID, Username: user.Username, Email: user.Email, FullName: user.FullName, IsActive: user.IsActive, }, Roles: roles, Permissions: permissions, }) return nil }