Files
2026-04-10 18:25:21 +03:30

316 lines
8.4 KiB
Go

package profile
import (
"context"
"errors"
"github.com/google/uuid"
"go.uber.org/fx"
"gorm.io/gorm"
domainProfile "base/internal/domain/profile"
)
type profileRepository struct {
db *gorm.DB
}
func NewProfileRepository(lc fx.Lifecycle, db *gorm.DB) domainProfile.Repository {
lc.Append(
fx.Hook{
OnStart: func(ctx context.Context) error {
return nil
},
OnStop: func(ctx context.Context) error {
return nil
},
})
return &profileRepository{db: db}
}
func (r *profileRepository) Create(ctx context.Context, profile *domainProfile.Profile) error {
model, err := toProfileModel(profile)
if err != nil {
return err
}
// Start a transaction
tx := r.db.WithContext(ctx).Begin()
if tx.Error != nil {
return tx.Error
}
defer tx.Rollback()
// Create profile
if err := tx.Create(model).Error; err != nil {
return err
}
// Create skills if any
if len(profile.Skills) > 0 {
skillModels := toSkillModels(model.ID, profile.Skills)
if err := tx.Create(&skillModels).Error; err != nil {
return err
}
}
// Create social links if any
if len(profile.Contact.SocialLinks) > 0 {
socialLinkModels := toSocialLinkModels(model.ID, profile.Contact.SocialLinks)
if err := tx.Create(&socialLinkModels).Error; err != nil {
return err
}
}
// Create achievements if any
if len(profile.About.Achievements) > 0 {
achievementModels := toAchievementModels(model.ID, profile.About.Achievements)
if err := tx.Create(&achievementModels).Error; err != nil {
return err
}
}
if err := tx.Commit().Error; err != nil {
return err
}
return copyProfileFromModel(profile, model)
}
func (r *profileRepository) loadRelatedData(ctx context.Context, profileID uuid.UUID) ([]domainProfile.Skill, []domainProfile.SocialLink, []domainProfile.Achievement, error) {
// Load skills
var skillModels []SkillModel
if err := r.db.WithContext(ctx).Where("profile_id = ?", profileID).Find(&skillModels).Error; err != nil {
return nil, nil, nil, err
}
skills := toSkillDomains(skillModels)
// Load social links
var socialLinkModels []SocialLinkModel
if err := r.db.WithContext(ctx).Where("profile_id = ?", profileID).Find(&socialLinkModels).Error; err != nil {
return nil, nil, nil, err
}
socialLinks := toSocialLinkDomains(socialLinkModels)
// Load achievements
var achievementModels []AchievementModel
if err := r.db.WithContext(ctx).Where("profile_id = ?", profileID).Find(&achievementModels).Error; err != nil {
return nil, nil, nil, err
}
achievements := toAchievementDomains(achievementModels)
return skills, socialLinks, achievements, nil
}
func (r *profileRepository) FindByID(ctx context.Context, id uuid.UUID) (*domainProfile.Profile, error) {
var model Model
if err := r.db.WithContext(ctx).Preload("Role").Where("id = ?", id).First(&model).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("profile not found")
}
return nil, err
}
skills, socialLinks, achievements, err := r.loadRelatedData(ctx, model.ID)
if err != nil {
return nil, err
}
return toProfileDomain(&model, skills, socialLinks, achievements)
}
func (r *profileRepository) FindByHandle(ctx context.Context, handle string) (*domainProfile.Profile, error) {
var model Model
if err := r.db.WithContext(ctx).Preload("Role").Where("handle = ?", handle).First(&model).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("profile not found")
}
return nil, err
}
skills, socialLinks, achievements, err := r.loadRelatedData(ctx, model.ID)
if err != nil {
return nil, err
}
return toProfileDomain(&model, skills, socialLinks, achievements)
}
func (r *profileRepository) FindByUserID(ctx context.Context, userID uuid.UUID) (*domainProfile.Profile, error) {
var model Model
if err := r.db.WithContext(ctx).Preload("Role").Where("user_id = ? AND user_id IS NOT NULL", userID).First(&model).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("profile not found")
}
return nil, err
}
skills, socialLinks, achievements, err := r.loadRelatedData(ctx, model.ID)
if err != nil {
return nil, err
}
return toProfileDomain(&model, skills, socialLinks, achievements)
}
func (r *profileRepository) Update(ctx context.Context, profile *domainProfile.Profile) error {
model, err := toProfileModel(profile)
if err != nil {
return err
}
// Start a transaction
tx := r.db.WithContext(ctx).Begin()
if tx.Error != nil {
return tx.Error
}
defer tx.Rollback()
// Update profile
if err := tx.Model(&Model{}).Where("id = ?", profile.ID).Updates(model).Error; err != nil {
return err
}
// Delete existing related data
if err := tx.Where("profile_id = ?", profile.ID).Delete(&SkillModel{}).Error; err != nil {
return err
}
if err := tx.Where("profile_id = ?", profile.ID).Delete(&SocialLinkModel{}).Error; err != nil {
return err
}
if err := tx.Where("profile_id = ?", profile.ID).Delete(&AchievementModel{}).Error; err != nil {
return err
}
// Create new skills
if len(profile.Skills) > 0 {
skillModels := toSkillModels(profile.ID, profile.Skills)
if err := tx.Create(&skillModels).Error; err != nil {
return err
}
}
// Create new social links
if len(profile.Contact.SocialLinks) > 0 {
socialLinkModels := toSocialLinkModels(profile.ID, profile.Contact.SocialLinks)
if err := tx.Create(&socialLinkModels).Error; err != nil {
return err
}
}
// Create new achievements
if len(profile.About.Achievements) > 0 {
achievementModels := toAchievementModels(profile.ID, profile.About.Achievements)
if err := tx.Create(&achievementModels).Error; err != nil {
return err
}
}
return tx.Commit().Error
}
func (r *profileRepository) Delete(ctx context.Context, profile *domainProfile.Profile) error {
// Start a transaction
tx := r.db.WithContext(ctx).Begin()
if tx.Error != nil {
return tx.Error
}
defer tx.Rollback()
// Delete related data first
if err := tx.Where("profile_id = ?", profile.ID).Delete(&SkillModel{}).Error; err != nil {
return err
}
if err := tx.Where("profile_id = ?", profile.ID).Delete(&SocialLinkModel{}).Error; err != nil {
return err
}
if err := tx.Where("profile_id = ?", profile.ID).Delete(&AchievementModel{}).Error; err != nil {
return err
}
// Delete profile
if err := tx.Delete(&Model{}, "id = ?", profile.ID).Error; err != nil {
return err
}
return tx.Commit().Error
}
// buildBaseQuery applies common filters to a query
func (r *profileRepository) buildBaseQuery(ctx context.Context, filter domainProfile.Filter) *gorm.DB {
query := r.db.WithContext(ctx).Model(&Model{})
if filter.RoleID != uuid.Nil {
query = query.Where("role_id = ?", filter.RoleID)
}
if filter.FirstName != "" {
query = query.Where("LOWER(first_name) LIKE ?", "%"+filter.FirstName+"%")
}
if filter.LastName != "" {
query = query.Where("LOWER(last_name) LIKE ?", "%"+filter.LastName+"%")
}
if filter.Company != "" {
query = query.Where("LOWER(company) LIKE ?", "%"+filter.Company+"%")
}
if filter.SkillName != "" {
subQuery := r.db.WithContext(ctx).Model(&SkillModel{}).
Select("DISTINCT profile_id").
Where("LOWER(skill_name) LIKE ? AND deleted_at IS NULL", "%"+filter.SkillName+"%")
query = query.Where("profiles.id IN (?)", subQuery)
}
return query
}
func (r *profileRepository) FindAll(ctx context.Context, filter domainProfile.Filter) ([]*domainProfile.Profile, int, error) {
baseQuery := r.buildBaseQuery(ctx, filter)
var total int64
if err := baseQuery.Count(&total).Error; err != nil {
return nil, 0, err
}
query := baseQuery
offset := int((filter.Page - 1) * filter.PageSize)
limit := int(filter.PageSize)
if limit > 0 {
query = query.Limit(limit).Offset(offset)
}
if filter.SortedBy != "" {
order := "ASC"
if !filter.Ascending {
order = "DESC"
}
query = query.Order("profiles." + filter.SortedBy + " " + order)
} else {
query = query.Order("profiles.created_at DESC")
}
var models []Model
if err := query.Preload("Role").Find(&models).Error; err != nil {
return nil, 0, err
}
if len(models) == 0 {
return nil, int(total), nil
}
profiles := make([]*domainProfile.Profile, len(models))
for i, model := range models {
skills, socialLinks, achievements, err := r.loadRelatedData(ctx, model.ID)
if err != nil {
return nil, 0, err
}
profile, err := toProfileDomain(&model, skills, socialLinks, achievements)
if err != nil {
return nil, 0, err
}
profiles[i] = profile
}
return profiles, int(total), nil
}