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 }