package service import ( "context" "errors" "slices" "time" "gitrepo.ru/neonxp/nquest/pkg/models" "gorm.io/gorm" "gorm.io/gorm/clause" ) var ( ErrTeamNotFound = errors.New("team not found") ) type Team struct { DB *gorm.DB User *User } // NewTeam returns new Team. func NewTeam(db *gorm.DB, user *User) *Team { return &Team{ DB: db, User: user, } } func (ts *Team) GetByID(ctx context.Context, id uint) (*models.Team, error) { t := new(models.Team) err := ts.DB. WithContext(ctx). Preload("Members"). Preload("Members.User"). Preload("Requests"). Preload("Requests.User"). First(t, id). Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrTeamNotFound } return nil, err } return t, nil } func (ts *Team) List(ctx context.Context) ([]*models.Team, error) { teams := []*models.Team{} return teams, ts.DB.WithContext(ctx).Preload("Members").Find(&teams).Error } func (ts *Team) Create(ctx context.Context, name string, user *models.User) (*models.Team, error) { t := &models.Team{ Name: name, Members: []*models.TeamMember{{ User: user, Role: models.Captain, }}, } db := ts.DB.WithContext(ctx) if err := db.Delete(&models.TeamRequest{}, `user_id = ?`, user.ID).Error; err != nil { return t, err } return t, db.Create(t).Error } func (ts *Team) Delete(ctx context.Context, id uint) error { if err := ts.DB.WithContext(ctx).Delete(&models.Team{}, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrTeamNotFound } return err } return nil } func (ts *Team) Request(ctx context.Context, teamID uint, user *models.User) error { team, err := ts.GetByID(ctx, teamID) if err != nil { return err } return ts.DB. WithContext(ctx). Clauses(clause.OnConflict{DoNothing: true}). Create(&models.TeamRequest{ Team: team, User: user, }). Error } func (ts *Team) UpdateMembers( ctx context.Context, teamID uint, newMembers []int, ) error { team, err := ts.GetByID(ctx, teamID) if err != nil { return err } newMembersList := make([]*models.TeamMember, 0, len(newMembers)) for _, tm := range team.Members { idx, ok := slices.BinarySearch(newMembers, int(tm.UserID)) if ok { newMembers = slices.Delete(newMembers, idx, idx) newMembersList = append(newMembersList, tm) continue } if err := ts.DB.WithContext(ctx).Delete(tm).Error; err != nil { return err } } for _, userID := range newMembers { _, found := slices.BinarySearchFunc(team.Members, userID, func(tm *models.TeamMember, i int) int { return int(tm.UserID) - i }) if found { continue } user, err := ts.User.GetUserByID(ctx, uint(userID)) if err != nil { return err } newMembersList = append(newMembersList, &models.TeamMember{ TeamID: teamID, Team: team, UserID: user.ID, User: user, Role: models.Member, CreatedAt: time.Now(), }) } team.Members = newMembersList return ts.DB. WithContext(ctx). Session(&gorm.Session{FullSaveAssociations: true}). Updates(team). Error } func (ts *Team) ApproveRequest(ctx context.Context, teamID int, userID uint, approve bool) error { team, err := ts.GetByID(ctx, uint(teamID)) if err != nil { return err } idx, found := slices.BinarySearchFunc(team.Requests, userID, func(tr *models.TeamRequest, i uint) int { return int(tr.UserID - i) }) if !found { return nil } request := team.Requests[idx] team.Requests = slices.DeleteFunc(team.Requests, func(tr *models.TeamRequest) bool { return tr.UserID == uint(userID) }) if err := ts.DB.WithContext(ctx).Delete(request).Error; err != nil { return err } if approve { team.Members = append(team.Members, &models.TeamMember{ Team: team, TeamID: uint(teamID), UserID: uint(userID), Role: models.Member, CreatedAt: time.Now(), }) return ts.DB.WithContext(ctx).Save(team).Error } return nil } func (ts *Team) DeleteMember(ctx context.Context, teamID int, userID uint) error { team, err := ts.GetByID(ctx, uint(teamID)) if err != nil { return err } idx, found := slices.BinarySearchFunc(team.Members, userID, func(tm *models.TeamMember, u uint) int { return int(tm.UserID - u) }) if !found { return nil } member := team.Members[idx] return ts.DB.WithContext(ctx).Delete(member).Error }