feat(lists): add direct user invitation to shared lists and streamline live-sync indicator
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 56s

This commit is contained in:
sandrodach committed 2026-10-25 11:05:03 +01:00
1 parent 6e09888a44
commit 7bc5fbe001
7 files changed
+580 -29

No files matched your search

+94 -6
View File
@@ -606,14 +606,25 @@ func (s *Store) GetDiscoverableUsers(viewerID string) ([]domain.User, error) {
return []domain.User{*viewer}, nil
}
// teams_only: users sharing at least one team or organization with viewer
// 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 (
SELECT tm2.user_id FROM team_members tm1
JOIN team_members tm2 ON tm1.team_id = tm2.team_id
WHERE tm1.user_id = ?
UNION
SELECT sm2.user_id FROM space_members sm1
JOIN space_members sm2 ON sm1.space_id = sm2.space_id
WHERE sm1.user_id = ?
)`,
viewerID, viewerID,
viewerID, viewerID, viewerID,
)
if err != nil {
return []domain.User{*viewer}, nil
@@ -1460,12 +1471,16 @@ func (s *Store) CreateDocShare(currentUserID, docID, targetUserIdentifier, permi
permission = "read"
}
// Resolve target user by ID or Email
// Resolve target user by ID, username, or Email
var targetUser *domain.User
cleanTarget := strings.TrimSpace(targetUserIdentifier)
if strings.Contains(cleanTarget, "@") {
targetUser, err = s.GetUserByEmail(cleanTarget)
} else {
if strings.HasPrefix(cleanTarget, "usr_") {
targetUser, _ = s.GetUserByID(cleanTarget)
}
if targetUser == nil {
targetUser, _ = s.GetUserByLogin(cleanTarget)
}
if targetUser == nil {
targetUser, err = s.GetUserByID(cleanTarget)
}
if err != nil || targetUser == nil {
@@ -1636,6 +1651,79 @@ func (s *Store) GetSpaceMembers(spaceID string) ([]domain.SpaceMember, error) {
return members, nil
}
func (s *Store) AddSpaceMember(currentUserID, spaceID, targetUserIdentifier, role string) (*domain.SpaceMember, error) {
// 1. Check if current user is member of space
var myRole string
err := s.db.QueryRow(`SELECT role FROM space_members WHERE space_id = ? AND user_id = ?`, spaceID, currentUserID).Scan(&myRole)
if err != nil {
return nil, errors.New("keine Berechtigung für diesen Bereich")
}
// 2. Resolve target user by ID, username or email
cleanTarget := strings.TrimSpace(targetUserIdentifier)
var targetUser *domain.User
if strings.HasPrefix(cleanTarget, "usr_") {
targetUser, _ = s.GetUserByID(cleanTarget)
}
if targetUser == nil {
targetUser, _ = s.GetUserByLogin(cleanTarget)
}
if targetUser == nil {
targetUser, err = s.GetUserByID(cleanTarget)
}
if err != nil || targetUser == nil {
return nil, errors.New("benutzer nicht gefunden")
}
if role != "owner" && role != "editor" && role != "viewer" {
role = "editor"
}
now := domain.NowISO()
_, err = s.db.Exec(
`INSERT INTO space_members (space_id, user_id, role, joined_at) VALUES (?, ?, ?, ?)
ON CONFLICT(space_id, user_id) DO UPDATE SET role = excluded.role`,
spaceID, targetUser.ID, role, now,
)
if err != nil {
return nil, err
}
return &domain.SpaceMember{
SpaceID: spaceID,
UserID: targetUser.ID,
DisplayName: targetUser.DisplayName,
Email: targetUser.Email,
Role: role,
JoinedAt: now,
}, nil
}
func (s *Store) RemoveSpaceMember(currentUserID, spaceID, targetUserID string) error {
var myRole string
err := s.db.QueryRow(`SELECT role FROM space_members WHERE space_id = ? AND user_id = ?`, spaceID, currentUserID).Scan(&myRole)
if err != nil {
return errors.New("keine Berechtigung für diesen Bereich")
}
if currentUserID != targetUserID && myRole != "owner" {
return errors.New("nur der Eigentümer kann andere Mitglieder entfernen")
}
var targetRole string
_ = s.db.QueryRow(`SELECT role FROM space_members WHERE space_id = ? AND user_id = ?`, spaceID, targetUserID).Scan(&targetRole)
if targetRole == "owner" {
var ownerCount int
_ = s.db.QueryRow(`SELECT COUNT(*) FROM space_members WHERE space_id = ? AND role = 'owner'`, spaceID).Scan(&ownerCount)
if ownerCount <= 1 {
return errors.New("der letzte Eigentümer kann den Bereich nicht verlassen")
}
}
_, err = s.db.Exec(`DELETE FROM space_members WHERE space_id = ? AND user_id = ?`, spaceID, targetUserID)
return err
}
func (s *Store) GetSpacesForUser(userID string) ([]domain.Space, error) {
rows, err := s.db.Query(
`SELECT s.id, s.name, s.icon, s.description, s.invite_code, s.is_personal, s.owner_id, s.created_at