package sqlite import ( "context" "database/sql" "encoding/json" "fmt" "time" "git.gobha.me/xcaliber/chat-switchboard/models" "git.gobha.me/xcaliber/chat-switchboard/store" ) type TeamStore struct{} func NewTeamStore() *TeamStore { return &TeamStore{} } func (s *TeamStore) Create(ctx context.Context, t *models.Team) error { settingsJSON := ToJSON(t.Settings) t.ID = store.NewID() now := time.Now().UTC() t.CreatedAt = now t.UpdatedAt = now _, err := DB.ExecContext(ctx, ` INSERT INTO teams (id, name, description, created_by, is_active, settings, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, t.ID, t.Name, t.Description, t.CreatedBy, t.IsActive, settingsJSON, now.Format(timeFmt), now.Format(timeFmt), ) return err } func (s *TeamStore) GetByID(ctx context.Context, id string) (*models.Team, error) { var t models.Team var settingsJSON []byte err := DB.QueryRowContext(ctx, ` SELECT id, name, description, created_by, is_active, settings, created_at, updated_at, (SELECT COUNT(*) FROM team_members WHERE team_id = teams.id) as member_count FROM teams WHERE id = ?`, id).Scan( &t.ID, &t.Name, &t.Description, &t.CreatedBy, &t.IsActive, &settingsJSON, st(&t.CreatedAt), st(&t.UpdatedAt), &t.MemberCount, ) if err != nil { return nil, err } json.Unmarshal(settingsJSON, &t.Settings) return &t, nil } func (s *TeamStore) Update(ctx context.Context, id string, fields map[string]interface{}) error { b := NewUpdate("teams") for k, v := range fields { if k == "settings" { b.SetJSON(k, v) } else { b.Set(k, v) } } if !b.HasSets() { return nil } b.Where("id", id) _, err := b.Exec(DB) return err } func (s *TeamStore) Delete(ctx context.Context, id string) error { _, err := DB.ExecContext(ctx, "DELETE FROM teams WHERE id = ?", id) return err } func (s *TeamStore) List(ctx context.Context) ([]models.Team, error) { rows, err := DB.QueryContext(ctx, ` SELECT id, name, description, created_by, is_active, settings, created_at, updated_at, (SELECT COUNT(*) FROM team_members WHERE team_id = teams.id) as member_count FROM teams ORDER BY name`) if err != nil { return nil, err } defer rows.Close() var result []models.Team for rows.Next() { var t models.Team var settingsJSON []byte err := rows.Scan(&t.ID, &t.Name, &t.Description, &t.CreatedBy, &t.IsActive, &settingsJSON, st(&t.CreatedAt), st(&t.UpdatedAt), &t.MemberCount) if err != nil { return nil, err } json.Unmarshal(settingsJSON, &t.Settings) result = append(result, t) } return result, rows.Err() } // ── Members ───────────────────────────────── func (s *TeamStore) AddMember(ctx context.Context, teamID, userID, role string) error { _, err := DB.ExecContext(ctx, ` INSERT INTO team_members (id, team_id, user_id, role) VALUES (?, ?, ?, ?) ON CONFLICT (team_id, user_id) DO UPDATE SET role = excluded.role`, store.NewID(), teamID, userID, role) return err } func (s *TeamStore) RemoveMember(ctx context.Context, teamID, userID string) error { _, err := DB.ExecContext(ctx, "DELETE FROM team_members WHERE team_id = ? AND user_id = ?", teamID, userID) return err } func (s *TeamStore) UpdateMemberRole(ctx context.Context, teamID, userID, role string) error { _, err := DB.ExecContext(ctx, "UPDATE team_members SET role = ? WHERE team_id = ? AND user_id = ?", role, teamID, userID) return err } func (s *TeamStore) ListMembers(ctx context.Context, teamID string) ([]models.TeamMember, error) { rows, err := DB.QueryContext(ctx, ` SELECT tm.id, tm.team_id, tm.user_id, tm.role, tm.joined_at, u.email, COALESCE(u.display_name, ''), u.username, u.role as user_role FROM team_members tm JOIN users u ON u.id = tm.user_id WHERE tm.team_id = ? ORDER BY tm.role DESC, u.username`, teamID) if err != nil { return nil, err } defer rows.Close() var result []models.TeamMember for rows.Next() { var m models.TeamMember err := rows.Scan(&m.ID, &m.TeamID, &m.UserID, &m.Role, &m.JoinedAt, &m.Email, &m.DisplayName, &m.Username, &m.UserRole) if err != nil { return nil, err } result = append(result, m) } return result, rows.Err() } func (s *TeamStore) GetMember(ctx context.Context, teamID, userID string) (*models.TeamMember, error) { var m models.TeamMember err := DB.QueryRowContext(ctx, ` SELECT tm.id, tm.team_id, tm.user_id, tm.role, tm.joined_at, u.email, COALESCE(u.display_name, ''), u.username, u.role as user_role FROM team_members tm JOIN users u ON u.id = tm.user_id WHERE tm.team_id = ? AND tm.user_id = ?`, teamID, userID).Scan( &m.ID, &m.TeamID, &m.UserID, &m.Role, &m.JoinedAt, &m.Email, &m.DisplayName, &m.Username, &m.UserRole) if err != nil { return nil, err } return &m, nil } // GetUserTeamIDs returns all team IDs a user belongs to. func (s *TeamStore) GetUserTeamIDs(ctx context.Context, userID string) ([]string, error) { rows, err := DB.QueryContext(ctx, "SELECT team_id FROM team_members WHERE user_id = ?", userID) if err != nil { return nil, err } defer rows.Close() var ids []string for rows.Next() { var id string if err := rows.Scan(&id); err != nil { return nil, err } ids = append(ids, id) } return ids, rows.Err() } func (s *TeamStore) IsTeamAdmin(ctx context.Context, teamID, userID string) (bool, error) { var role string err := DB.QueryRowContext(ctx, "SELECT role FROM team_members WHERE team_id = ? AND user_id = ?", teamID, userID).Scan(&role) if err == sql.ErrNoRows { return false, nil } if err != nil { return false, err } return role == models.TeamRoleAdmin, nil } func (s *TeamStore) IsMember(ctx context.Context, teamID, userID string) (bool, error) { var exists bool err := DB.QueryRowContext(ctx, "SELECT EXISTS(SELECT 1 FROM team_members WHERE team_id = ? AND user_id = ?)", teamID, userID).Scan(&exists) return exists, err } // unused but keeping for reference var _ = fmt.Sprintf