feat: overhaul groups management, remove global team_admin and isolate group administration
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m2s

This commit is contained in:
sandrodach committed 2026-10-25 17:14:15 +01:00
1 parent 60c302e23e
commit e37302f73c
11 files changed
+936 -388

No files matched your search

+85 -19
View File
@@ -178,7 +178,7 @@ func (s *Store) migrate() error {
CREATE TABLE IF NOT EXISTS team_members (
team_id TEXT NOT NULL,
user_id TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'editor',
role TEXT NOT NULL DEFAULT 'member',
joined_at TEXT NOT NULL,
PRIMARY KEY (team_id, user_id),
FOREIGN KEY(team_id) REFERENCES teams(id) ON DELETE CASCADE,
@@ -391,6 +391,11 @@ func (s *Store) migrate() error {
}
}
// Clean up legacy roles in team_members and users
_, _ = s.db.Exec(`UPDATE team_members SET role = 'admin' WHERE role IN ('team_admin', 'owner')`)
_, _ = s.db.Exec(`UPDATE team_members SET role = 'member' WHERE role NOT IN ('admin', 'member')`)
_, _ = s.db.Exec(`UPDATE users SET role = 'member' WHERE role = 'team_admin'`)
// Reindex block tags if table is newly added or empty
var tagCount int
_ = s.db.QueryRow(`SELECT COUNT(*) FROM doc_block_tags`).Scan(&tagCount)
@@ -534,7 +539,7 @@ func (s *Store) CreateUser(email, passwordHash, displayName, role string) (*doma
}
func (s *Store) CreateUserWithUsername(email, username, passwordHash, displayName, role string) (*domain.User, error) {
if role != "admin" && role != "team_admin" && role != "member" {
if role != "admin" && role != "member" {
role = "member"
}
cleanEmail := strings.ToLower(strings.TrimSpace(email))
@@ -674,20 +679,13 @@ func (s *Store) GetDiscoverableUsers(viewerID string) ([]domain.User, error) {
mode = sys.SharingMode
}
if viewer.Role == "admin" || viewer.Role == "team_admin" || mode == "open" {
if viewer.Role == "admin" || mode == "open" || mode == "teams_only" {
return s.GetAllUsers()
}
if mode == "strict_private" {
return []domain.User{*viewer}, nil
}
// teams_only: if no teams exist yet, allow discovering users on this instance
var teamCount int
_ = s.db.QueryRow(`SELECT COUNT(*) FROM teams`).Scan(&teamCount)
if teamCount == 0 {
return s.GetAllUsers()
}
// users sharing at least one team, organization or space with viewer
rows, err := s.db.Query(
`SELECT DISTINCT `+userSelectCols+` FROM users WHERE id = ? OR id IN (
@@ -742,7 +740,7 @@ func (s *Store) UpdateUserFull(
if email != nil {
u.Email = strings.ToLower(strings.TrimSpace(*email))
}
if role != nil && (*role == "admin" || *role == "team_admin" || *role == "member") {
if role != nil && (*role == "admin" || *role == "member") {
u.Role = *role
}
if isActive != nil {
@@ -3313,7 +3311,7 @@ func (s *Store) GetOrganizationsForUser(userID string) ([]domain.Organization, e
var rows *sql.Rows
var err error
if viewer != nil && (viewer.Role == "admin" || viewer.Role == "team_admin") {
if viewer != nil && viewer.Role == "admin" {
rows, err = s.db.Query(
`SELECT id, name, owner_id, created_at FROM organizations ORDER BY created_at ASC`,
)
@@ -3474,11 +3472,10 @@ func (s *Store) GetTeamsForUser(userID string) ([]domain.Team, error) {
rows, err := s.db.Query(
`SELECT DISTINCT t.id, t.org_id, t.name, t.description, t.created_at
FROM teams t
LEFT JOIN team_members tm ON tm.team_id = t.id
LEFT JOIN organizations o ON o.id = t.org_id
WHERE tm.user_id = ? OR o.owner_id = ?
JOIN team_members tm ON tm.team_id = t.id
WHERE tm.user_id = ?
ORDER BY t.created_at ASC`,
userID, userID,
userID,
)
if err != nil {
return []domain.Team{}, err
@@ -3501,8 +3498,39 @@ func (s *Store) GetTeamsForUser(userID string) ([]domain.Team, error) {
return teams, nil
}
func (s *Store) UpdateTeam(teamID, name, description string) (*domain.Team, error) {
name = strings.TrimSpace(name)
if name == "" {
return nil, errors.New("Name für die Gruppe ist erforderlich")
}
_, err := s.db.Exec(`UPDATE teams SET name = ?, description = ? WHERE id = ?`, name, strings.TrimSpace(description), teamID)
if err != nil {
return nil, err
}
return s.GetTeamByID(teamID)
}
func (s *Store) GetTeamMemberRole(teamID, userID string) (string, error) {
var role string
err := s.db.QueryRow(`SELECT role FROM team_members WHERE team_id = ? AND user_id = ?`, teamID, userID).Scan(&role)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return "", nil
}
return "", err
}
return role, nil
}
func (s *Store) CountTeamAdmins(teamID string) (int, error) {
var count int
err := s.db.QueryRow(`SELECT COUNT(*) FROM team_members WHERE team_id = ? AND role = 'admin'`, teamID).Scan(&count)
return count, err
}
func (s *Store) AddTeamMember(teamID, userID, role string) error {
if role == "" {
role = strings.ToLower(strings.TrimSpace(role))
if role != "admin" && role != "member" {
role = "member"
}
_, err := s.db.Exec(
@@ -3513,8 +3541,46 @@ func (s *Store) AddTeamMember(teamID, userID, role string) error {
return err
}
func (s *Store) UpdateTeamMemberRole(teamID, userID, role string) error {
role = strings.ToLower(strings.TrimSpace(role))
if role != "admin" && role != "member" {
return errors.New("Ungültige Rolle (erlaubt sind: admin, member)")
}
currentRole, err := s.GetTeamMemberRole(teamID, userID)
if err != nil {
return err
}
if currentRole == "" {
return errors.New("Benutzer ist kein Mitglied dieser Gruppe")
}
if currentRole == "admin" && role != "admin" {
adminCount, err := s.CountTeamAdmins(teamID)
if err != nil {
return err
}
if adminCount <= 1 {
return errors.New("Die Gruppe muss mindestens einen Gruppen-Admin behalten")
}
}
_, err = s.db.Exec(`UPDATE team_members SET role = ? WHERE team_id = ? AND user_id = ?`, role, teamID, userID)
return err
}
func (s *Store) RemoveTeamMember(teamID, userID string) error {
_, err := s.db.Exec(`DELETE FROM team_members WHERE team_id = ? AND user_id = ?`, teamID, userID)
currentRole, err := s.GetTeamMemberRole(teamID, userID)
if err != nil {
return err
}
if currentRole == "admin" {
adminCount, err := s.CountTeamAdmins(teamID)
if err != nil {
return err
}
if adminCount <= 1 {
return errors.New("Du bist der letzte Gruppen-Admin. Bitte ernenne zuerst einen anderen Admin oder lösche die Gruppe.")
}
}
_, err = s.db.Exec(`DELETE FROM team_members WHERE team_id = ? AND user_id = ?`, teamID, userID)
return err
}
@@ -3545,7 +3611,7 @@ func (s *Store) GetTeamMembers(teamID string) ([]domain.TeamMember, error) {
// --- USER INVITES ---
func (s *Store) CreateUserInvite(createdBy, email, role, teamID string, validDays int) (*domain.UserInvite, error) {
if role != "admin" && role != "team_admin" && role != "member" {
if role != "admin" && role != "member" {
role = "member"
}
if validDays <= 0 || validDays > 365 {