feat: unify sharing UI, redesign lists header, fix team sharing, and add user autocomplete
MindSift CI & Docker Release / Build, Test in Container & Push (push) Failing after 18s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Failing after 18s
This commit is contained in:
1 parent
6d07352e80
commit
354129b2d4
10 files changed
+1049
-393
No files matched your search
+330
-40
@@ -337,6 +337,14 @@ func (s *Store) migrate() error {
|
||||
"CREATE TABLE IF NOT EXISTS task_list_shares (id TEXT PRIMARY KEY, task_list_id TEXT NOT NULL, target_type TEXT NOT NULL CHECK(target_type IN ('team', 'user')), target_id TEXT NOT NULL, created_at TEXT NOT NULL, created_by TEXT NOT NULL, UNIQUE(task_list_id, target_type, target_id));",
|
||||
"CREATE INDEX IF NOT EXISTS idx_task_list_shares_target ON task_list_shares(target_type, target_id);",
|
||||
"CREATE INDEX IF NOT EXISTS idx_task_list_shares_list ON task_list_shares(task_list_id);",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS doc_team_shares (id TEXT PRIMARY KEY, doc_id TEXT NOT NULL, team_id TEXT NOT NULL, permission TEXT NOT NULL CHECK(permission IN ('read', 'write')), created_at TEXT NOT NULL, created_by TEXT NOT NULL, UNIQUE(doc_id, team_id));",
|
||||
"CREATE INDEX IF NOT EXISTS idx_doc_team_shares_team ON doc_team_shares(team_id);",
|
||||
"CREATE INDEX IF NOT EXISTS idx_doc_team_shares_doc ON doc_team_shares(doc_id);",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS space_team_shares (id TEXT PRIMARY KEY, space_id TEXT NOT NULL, team_id TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'editor', created_at TEXT NOT NULL, created_by TEXT NOT NULL, UNIQUE(space_id, team_id));",
|
||||
"CREATE INDEX IF NOT EXISTS idx_space_team_shares_team ON space_team_shares(team_id);",
|
||||
"CREATE INDEX IF NOT EXISTS idx_space_team_shares_space ON space_team_shares(space_id);",
|
||||
}
|
||||
for _, q := range columnMigrations {
|
||||
_, _ = s.db.Exec(q)
|
||||
@@ -1331,19 +1339,26 @@ func (s *Store) GetDocuments(userID string) ([]domain.Document, error) {
|
||||
}
|
||||
_ = rows.Close()
|
||||
|
||||
// 2. Fetch documents shared with the user (with per-user is_pinned from doc_pins)
|
||||
// 2. Fetch documents shared with the user directly or via teams (with per-user is_pinned from doc_pins)
|
||||
sharedRows, err := s.db.Query(
|
||||
`SELECT d.id, d.user_id, d.space_id, d.title, d.icon, d.content, d.blocks_json, d.tags, d.folder_path,
|
||||
CASE WHEN p.doc_id IS NOT NULL THEN 1 ELSE 0 END AS is_pinned,
|
||||
d.is_archived, d.created_at, d.updated_at,
|
||||
s.permission, u.display_name
|
||||
COALESCE(s.permission, dts.permission, 'read') AS permission, u.display_name
|
||||
FROM documents d
|
||||
JOIN doc_shares s ON d.id = s.doc_id
|
||||
JOIN users u ON d.user_id = u.id
|
||||
LEFT JOIN doc_shares s ON d.id = s.doc_id AND s.user_id = ?
|
||||
LEFT JOIN (
|
||||
SELECT dts2.doc_id, dts2.permission
|
||||
FROM doc_team_shares dts2
|
||||
JOIN team_members tm ON tm.team_id = dts2.team_id
|
||||
WHERE tm.user_id = ?
|
||||
) dts ON d.id = dts.doc_id
|
||||
LEFT JOIN doc_pins p ON p.doc_id = d.id AND p.user_id = ?
|
||||
WHERE s.user_id = ? AND d.is_archived = 0
|
||||
WHERE (s.user_id = ? OR dts.doc_id IS NOT NULL) AND d.is_archived = 0 AND d.user_id != ?
|
||||
GROUP BY d.id
|
||||
ORDER BY is_pinned DESC, d.updated_at DESC`,
|
||||
userID, userID,
|
||||
userID, userID, userID, userID, userID,
|
||||
)
|
||||
if err == nil {
|
||||
for sharedRows.Next() {
|
||||
@@ -1404,18 +1419,25 @@ func (s *Store) GetDocumentByID(userID, docID string) (*domain.Document, error)
|
||||
return doc, nil
|
||||
}
|
||||
|
||||
// Try shared document (with per-user is_pinned)
|
||||
// Try shared document (direct or team share, with per-user is_pinned)
|
||||
sharedRow := s.db.QueryRow(
|
||||
`SELECT d.id, d.user_id, d.space_id, d.title, d.icon, d.content, d.blocks_json, d.tags, d.folder_path,
|
||||
CASE WHEN p.doc_id IS NOT NULL THEN 1 ELSE 0 END AS is_pinned,
|
||||
d.is_archived, d.created_at, d.updated_at,
|
||||
s.permission, u.display_name
|
||||
COALESCE(s.permission, dts.permission, 'read') AS permission, u.display_name
|
||||
FROM documents d
|
||||
JOIN doc_shares s ON d.id = s.doc_id
|
||||
JOIN users u ON d.user_id = u.id
|
||||
LEFT JOIN doc_shares s ON d.id = s.doc_id AND s.user_id = ?
|
||||
LEFT JOIN (
|
||||
SELECT dts2.doc_id, dts2.permission
|
||||
FROM doc_team_shares dts2
|
||||
JOIN team_members tm ON tm.team_id = dts2.team_id
|
||||
WHERE tm.user_id = ?
|
||||
) dts ON d.id = dts.doc_id
|
||||
LEFT JOIN doc_pins p ON p.doc_id = d.id AND p.user_id = ?
|
||||
WHERE d.id = ? AND s.user_id = ?`,
|
||||
userID, docID, userID,
|
||||
WHERE d.id = ? AND (s.user_id = ? OR dts.doc_id IS NOT NULL)
|
||||
LIMIT 1`,
|
||||
userID, userID, userID, docID, userID,
|
||||
)
|
||||
d := &domain.Document{}
|
||||
var blocksStr, tagsStr string
|
||||
@@ -1630,11 +1652,17 @@ func (s *Store) GetWorkspaceTags(userID string) ([]domain.TagSummary, error) {
|
||||
FROM doc_block_tags t
|
||||
JOIN documents d ON t.doc_id = d.id
|
||||
LEFT JOIN doc_shares s ON s.doc_id = d.id AND s.user_id = ?
|
||||
WHERE (d.user_id = ? OR s.user_id = ?)
|
||||
LEFT JOIN (
|
||||
SELECT dts2.doc_id
|
||||
FROM doc_team_shares dts2
|
||||
JOIN team_members tm ON tm.team_id = dts2.team_id
|
||||
WHERE tm.user_id = ?
|
||||
) dts ON d.id = dts.doc_id
|
||||
WHERE (d.user_id = ? OR s.user_id = ? OR dts.doc_id IS NOT NULL)
|
||||
AND d.is_archived = 0
|
||||
GROUP BY t.tag
|
||||
ORDER BY count DESC, t.tag ASC`,
|
||||
userID, userID, userID,
|
||||
userID, userID, userID, userID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1686,16 +1714,22 @@ func (s *Store) GetTagDetails(userID, tag string) (*domain.TagDetails, error) {
|
||||
rows, err := s.db.Query(
|
||||
`SELECT t.block_id, d.id, d.title, d.icon, d.folder_path, d.blocks_json, d.updated_at, d.user_id,
|
||||
COALESCE(u.display_name, '') as owner_name,
|
||||
COALESCE(s.permission, '') as shared_permission
|
||||
COALESCE(s.permission, dts.permission, '') as shared_permission
|
||||
FROM doc_block_tags t
|
||||
JOIN documents d ON t.doc_id = d.id
|
||||
LEFT JOIN users u ON d.user_id = u.id
|
||||
LEFT JOIN doc_shares s ON s.doc_id = d.id AND s.user_id = ?
|
||||
WHERE (d.user_id = ? OR s.user_id = ?)
|
||||
LEFT JOIN (
|
||||
SELECT dts2.doc_id, dts2.permission
|
||||
FROM doc_team_shares dts2
|
||||
JOIN team_members tm ON tm.team_id = dts2.team_id
|
||||
WHERE tm.user_id = ?
|
||||
) dts ON d.id = dts.doc_id
|
||||
WHERE (d.user_id = ? OR s.user_id = ? OR dts.doc_id IS NOT NULL)
|
||||
AND t.tag = ?
|
||||
AND d.is_archived = 0
|
||||
ORDER BY d.updated_at DESC`,
|
||||
userID, userID, userID, cleanTag,
|
||||
userID, userID, userID, userID, cleanTag,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1946,7 +1980,10 @@ func (s *Store) DeleteFolder(userID, folderPath string) ([]string, error) {
|
||||
}
|
||||
|
||||
func (s *Store) GetDocShares(docID string) ([]domain.DocShare, error) {
|
||||
rows, err := s.db.Query(
|
||||
shares := make([]domain.DocShare, 0)
|
||||
|
||||
// 1. Direct user shares
|
||||
uRows, err := s.db.Query(
|
||||
`SELECT s.id, s.doc_id, s.user_id, u.email, u.display_name, s.permission, s.created_by, s.created_at
|
||||
FROM doc_shares s
|
||||
JOIN users u ON s.user_id = u.id
|
||||
@@ -1954,19 +1991,89 @@ func (s *Store) GetDocShares(docID string) ([]domain.DocShare, error) {
|
||||
ORDER BY s.created_at ASC`,
|
||||
docID,
|
||||
)
|
||||
if err == nil {
|
||||
for uRows.Next() {
|
||||
var sh domain.DocShare
|
||||
if err := uRows.Scan(&sh.ID, &sh.DocID, &sh.UserID, &sh.UserEmail, &sh.DisplayName, &sh.Permission, &sh.CreatedBy, &sh.CreatedAt); err == nil {
|
||||
sh.TargetType = "user"
|
||||
sh.TargetID = sh.UserID
|
||||
sh.TargetName = sh.DisplayName
|
||||
shares = append(shares, sh)
|
||||
}
|
||||
}
|
||||
_ = uRows.Close()
|
||||
}
|
||||
|
||||
// 2. Team shares
|
||||
tRows, err := s.db.Query(
|
||||
`SELECT s.id, s.doc_id, s.team_id, t.name, s.permission, s.created_by, s.created_at
|
||||
FROM doc_team_shares s
|
||||
JOIN teams t ON s.team_id = t.id
|
||||
WHERE s.doc_id = ?
|
||||
ORDER BY s.created_at ASC`,
|
||||
docID,
|
||||
)
|
||||
if err == nil {
|
||||
for tRows.Next() {
|
||||
var sh domain.DocShare
|
||||
var teamID, teamName string
|
||||
if err := tRows.Scan(&sh.ID, &sh.DocID, &teamID, &teamName, &sh.Permission, &sh.CreatedBy, &sh.CreatedAt); err == nil {
|
||||
sh.TargetType = "team"
|
||||
sh.TargetID = teamID
|
||||
sh.TargetName = teamName
|
||||
sh.DisplayName = teamName
|
||||
shares = append(shares, sh)
|
||||
}
|
||||
}
|
||||
_ = tRows.Close()
|
||||
}
|
||||
|
||||
return shares, nil
|
||||
}
|
||||
|
||||
func (s *Store) CreateDocTeamShare(currentUserID, docID, teamID, permission string) (*domain.DocShare, error) {
|
||||
doc, err := s.GetDocumentByID(currentUserID, docID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
shares := make([]domain.DocShare, 0)
|
||||
for rows.Next() {
|
||||
var sh domain.DocShare
|
||||
if err := rows.Scan(&sh.ID, &sh.DocID, &sh.UserID, &sh.UserEmail, &sh.DisplayName, &sh.Permission, &sh.CreatedBy, &sh.CreatedAt); err == nil {
|
||||
shares = append(shares, sh)
|
||||
}
|
||||
if !doc.IsOwner && doc.Permission != "write" {
|
||||
return nil, fmt.Errorf("nur der Eigentümer oder Bearbeiter kann Freigaben verwalten")
|
||||
}
|
||||
return shares, nil
|
||||
|
||||
cleanTeamID := strings.TrimSpace(teamID)
|
||||
var teamName string
|
||||
err = s.db.QueryRow(`SELECT name FROM teams WHERE id = ?`, cleanTeamID).Scan(&teamName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("team nicht gefunden")
|
||||
}
|
||||
|
||||
if permission != "read" && permission != "write" {
|
||||
permission = "read"
|
||||
}
|
||||
|
||||
now := domain.NowISO()
|
||||
shareID := domain.NewID("share")
|
||||
_, err = s.db.Exec(
|
||||
`INSERT INTO doc_team_shares (id, doc_id, team_id, permission, created_at, created_by)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(doc_id, team_id) DO UPDATE SET permission = excluded.permission`,
|
||||
shareID, docID, cleanTeamID, permission, now, currentUserID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &domain.DocShare{
|
||||
ID: shareID,
|
||||
DocID: docID,
|
||||
TargetType: "team",
|
||||
TargetID: cleanTeamID,
|
||||
TargetName: teamName,
|
||||
DisplayName: teamName,
|
||||
Permission: permission,
|
||||
CreatedBy: currentUserID,
|
||||
CreatedAt: now,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Store) CreateDocShare(currentUserID, docID, targetUserIdentifier, permission string) (*domain.DocShare, error) {
|
||||
@@ -1978,13 +2085,19 @@ func (s *Store) CreateDocShare(currentUserID, docID, targetUserIdentifier, permi
|
||||
return nil, fmt.Errorf("nur der Eigentümer oder Bearbeiter kann Freigaben verwalten")
|
||||
}
|
||||
|
||||
cleanTarget := strings.TrimSpace(targetUserIdentifier)
|
||||
|
||||
// If identifier looks like a team or is prefixed with team_, route to CreateDocTeamShare
|
||||
if strings.HasPrefix(cleanTarget, "team_") {
|
||||
return s.CreateDocTeamShare(currentUserID, docID, cleanTarget, permission)
|
||||
}
|
||||
|
||||
if permission != "read" && permission != "write" {
|
||||
permission = "read"
|
||||
}
|
||||
|
||||
// Resolve target user by ID, username, or Email
|
||||
var targetUser *domain.User
|
||||
cleanTarget := strings.TrimSpace(targetUserIdentifier)
|
||||
if strings.HasPrefix(cleanTarget, "usr_") {
|
||||
targetUser, _ = s.GetUserByID(cleanTarget)
|
||||
}
|
||||
@@ -1995,6 +2108,11 @@ func (s *Store) CreateDocShare(currentUserID, docID, targetUserIdentifier, permi
|
||||
targetUser, err = s.GetUserByID(cleanTarget)
|
||||
}
|
||||
if err != nil || targetUser == nil {
|
||||
// Check if it's a team ID that doesn't start with team_
|
||||
var tName string
|
||||
if s.db.QueryRow(`SELECT name FROM teams WHERE id = ?`, cleanTarget).Scan(&tName) == nil {
|
||||
return s.CreateDocTeamShare(currentUserID, docID, cleanTarget, permission)
|
||||
}
|
||||
return nil, fmt.Errorf("benutzer nicht gefunden")
|
||||
}
|
||||
|
||||
@@ -2017,6 +2135,9 @@ func (s *Store) CreateDocShare(currentUserID, docID, targetUserIdentifier, permi
|
||||
return &domain.DocShare{
|
||||
ID: shareID,
|
||||
DocID: docID,
|
||||
TargetType: "user",
|
||||
TargetID: targetUser.ID,
|
||||
TargetName: targetUser.DisplayName,
|
||||
UserID: targetUser.ID,
|
||||
UserEmail: targetUser.Email,
|
||||
DisplayName: targetUser.DisplayName,
|
||||
@@ -2026,16 +2147,26 @@ func (s *Store) CreateDocShare(currentUserID, docID, targetUserIdentifier, permi
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Store) DeleteDocShare(currentUserID, docID, targetUserID string) error {
|
||||
func (s *Store) DeleteDocShare(currentUserID, docID, targetID string) error {
|
||||
doc, err := s.GetDocumentByID(currentUserID, docID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !doc.IsOwner && doc.Permission != "write" && currentUserID != targetUserID {
|
||||
if !doc.IsOwner && doc.Permission != "write" && currentUserID != targetID {
|
||||
return fmt.Errorf("keine Berechtigung zum Entfernen der Freigabe")
|
||||
}
|
||||
|
||||
_, err = s.db.Exec(`DELETE FROM doc_shares WHERE doc_id = ? AND user_id = ?`, docID, targetUserID)
|
||||
cleanTarget := strings.TrimSpace(targetID)
|
||||
// Try deleting team share first
|
||||
res, err := s.db.Exec(`DELETE FROM doc_team_shares WHERE doc_id = ? AND (id = ? OR team_id = ?)`, docID, cleanTarget, cleanTarget)
|
||||
if err == nil {
|
||||
if rows, _ := res.RowsAffected(); rows > 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to user share
|
||||
_, err = s.db.Exec(`DELETE FROM doc_shares WHERE doc_id = ? AND (id = ? OR user_id = ?)`, docID, cleanTarget, cleanTarget)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2186,7 +2317,12 @@ func (s *Store) AddSpaceMember(currentUserID, spaceID, targetUserIdentifier, rol
|
||||
return nil, errors.New("benutzer nicht gefunden")
|
||||
}
|
||||
|
||||
if role != "owner" && role != "editor" && role != "viewer" {
|
||||
// Protect existing owner from role downgrade!
|
||||
var existingRole string
|
||||
_ = s.db.QueryRow(`SELECT role FROM space_members WHERE space_id = ? AND user_id = ?`, spaceID, targetUser.ID).Scan(&existingRole)
|
||||
if existingRole == "owner" {
|
||||
role = "owner"
|
||||
} else if role != "owner" && role != "editor" && role != "viewer" {
|
||||
role = "editor"
|
||||
}
|
||||
|
||||
@@ -2210,6 +2346,135 @@ func (s *Store) AddSpaceMember(currentUserID, spaceID, targetUserIdentifier, rol
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Store) CreateSpaceTeamShare(currentUserID, spaceID, teamID, role string) (*domain.SpaceShare, 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 || (myRole != "owner" && myRole != "editor") {
|
||||
return nil, errors.New("keine Berechtigung für diesen Bereich")
|
||||
}
|
||||
|
||||
cleanTeamID := strings.TrimSpace(teamID)
|
||||
var teamName string
|
||||
err = s.db.QueryRow(`SELECT name FROM teams WHERE id = ?`, cleanTeamID).Scan(&teamName)
|
||||
if err != nil {
|
||||
return nil, errors.New("team nicht gefunden")
|
||||
}
|
||||
|
||||
if role != "owner" && role != "editor" && role != "viewer" {
|
||||
role = "editor"
|
||||
}
|
||||
|
||||
now := domain.NowISO()
|
||||
shareID := domain.NewID("spshare")
|
||||
_, err = s.db.Exec(
|
||||
`INSERT INTO space_team_shares (id, space_id, team_id, role, created_at, created_by)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(space_id, team_id) DO UPDATE SET role = excluded.role`,
|
||||
shareID, spaceID, cleanTeamID, role, now, currentUserID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add team members as space members, but preserve owner role!
|
||||
members, _ := s.GetTeamMembers(cleanTeamID)
|
||||
for _, tm := range members {
|
||||
var curRole string
|
||||
_ = s.db.QueryRow(`SELECT role FROM space_members WHERE space_id = ? AND user_id = ?`, spaceID, tm.UserID).Scan(&curRole)
|
||||
if curRole == "owner" {
|
||||
continue
|
||||
}
|
||||
_, _ = 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, tm.UserID, role, now,
|
||||
)
|
||||
}
|
||||
|
||||
return &domain.SpaceShare{
|
||||
ID: shareID,
|
||||
SpaceID: spaceID,
|
||||
TargetType: "team",
|
||||
TargetID: cleanTeamID,
|
||||
TargetName: teamName,
|
||||
Role: role,
|
||||
CreatedBy: currentUserID,
|
||||
CreatedAt: now,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetSpaceShares(spaceID string) ([]domain.SpaceShare, error) {
|
||||
shares := make([]domain.SpaceShare, 0)
|
||||
|
||||
// 1. Team shares
|
||||
tRows, err := s.db.Query(
|
||||
`SELECT s.id, s.space_id, s.team_id, t.name, s.role, s.created_by, s.created_at
|
||||
FROM space_team_shares s
|
||||
JOIN teams t ON s.team_id = t.id
|
||||
WHERE s.space_id = ?
|
||||
ORDER BY s.created_at ASC`,
|
||||
spaceID,
|
||||
)
|
||||
if err == nil {
|
||||
for tRows.Next() {
|
||||
var sh domain.SpaceShare
|
||||
if err := tRows.Scan(&sh.ID, &sh.SpaceID, &sh.TargetID, &sh.TargetName, &sh.Role, &sh.CreatedBy, &sh.CreatedAt); err == nil {
|
||||
sh.TargetType = "team"
|
||||
shares = append(shares, sh)
|
||||
}
|
||||
}
|
||||
_ = tRows.Close()
|
||||
}
|
||||
|
||||
// 2. Individual invited members (non-owner members)
|
||||
var ownerID string
|
||||
_ = s.db.QueryRow(`SELECT owner_id FROM spaces WHERE id = ?`, spaceID).Scan(&ownerID)
|
||||
|
||||
mRows, err := s.db.Query(
|
||||
`SELECT sm.user_id, u.display_name, u.email, sm.role, sm.joined_at
|
||||
FROM space_members sm
|
||||
JOIN users u ON u.id = sm.user_id
|
||||
WHERE sm.space_id = ? AND sm.user_id != ?
|
||||
ORDER BY sm.joined_at ASC`,
|
||||
spaceID, ownerID,
|
||||
)
|
||||
if err == nil {
|
||||
for mRows.Next() {
|
||||
var uid, dName, email, role, joinedAt string
|
||||
if err := mRows.Scan(&uid, &dName, &email, &role, &joinedAt); err == nil {
|
||||
name := dName
|
||||
if name == "" {
|
||||
name = email
|
||||
}
|
||||
shares = append(shares, domain.SpaceShare{
|
||||
ID: "sm_" + uid,
|
||||
SpaceID: spaceID,
|
||||
TargetType: "user",
|
||||
TargetID: uid,
|
||||
TargetName: name,
|
||||
Role: role,
|
||||
CreatedAt: joinedAt,
|
||||
})
|
||||
}
|
||||
}
|
||||
_ = mRows.Close()
|
||||
}
|
||||
|
||||
return shares, nil
|
||||
}
|
||||
|
||||
func (s *Store) RemoveSpaceTeamShare(currentUserID, spaceID, targetID 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 || (myRole != "owner" && myRole != "editor") {
|
||||
return errors.New("keine Berechtigung zum Entfernen der Freigabe")
|
||||
}
|
||||
|
||||
cleanTarget := strings.TrimSpace(targetID)
|
||||
_, err = s.db.Exec(`DELETE FROM space_team_shares WHERE space_id = ? AND (id = ? OR team_id = ?)`, spaceID, cleanTarget, cleanTarget)
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -2217,12 +2482,17 @@ func (s *Store) RemoveSpaceMember(currentUserID, spaceID, targetUserID string) e
|
||||
return errors.New("keine Berechtigung für diesen Bereich")
|
||||
}
|
||||
|
||||
if currentUserID != targetUserID && myRole != "owner" {
|
||||
cleanTarget := strings.TrimSpace(targetUserID)
|
||||
if strings.HasPrefix(cleanTarget, "team_") || strings.HasPrefix(cleanTarget, "spshare_") {
|
||||
return s.RemoveSpaceTeamShare(currentUserID, spaceID, cleanTarget)
|
||||
}
|
||||
|
||||
if currentUserID != cleanTarget && 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)
|
||||
_ = s.db.QueryRow(`SELECT role FROM space_members WHERE space_id = ? AND user_id = ?`, spaceID, cleanTarget).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)
|
||||
@@ -2231,7 +2501,7 @@ func (s *Store) RemoveSpaceMember(currentUserID, spaceID, targetUserID string) e
|
||||
}
|
||||
}
|
||||
|
||||
_, err = s.db.Exec(`DELETE FROM space_members WHERE space_id = ? AND user_id = ?`, spaceID, targetUserID)
|
||||
_, err = s.db.Exec(`DELETE FROM space_members WHERE space_id = ? AND user_id = ?`, spaceID, cleanTarget)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2239,10 +2509,12 @@ 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
|
||||
FROM spaces s
|
||||
JOIN space_members sm ON sm.space_id = s.id
|
||||
WHERE sm.user_id = ?
|
||||
LEFT JOIN space_members sm ON sm.space_id = s.id AND sm.user_id = ?
|
||||
LEFT JOIN space_team_shares sts ON sts.space_id = s.id AND sts.team_id IN (SELECT team_id FROM team_members WHERE user_id = ?)
|
||||
WHERE sm.user_id = ? OR sts.team_id IS NOT NULL
|
||||
GROUP BY s.id
|
||||
ORDER BY s.created_at ASC`,
|
||||
userID,
|
||||
userID, userID, userID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -2261,6 +2533,7 @@ func (s *Store) GetSpacesForUser(userID string) ([]domain.Space, error) {
|
||||
|
||||
for i := range spaces {
|
||||
spaces[i].Members, _ = s.GetSpaceMembers(spaces[i].ID)
|
||||
spaces[i].Shares, _ = s.GetSpaceShares(spaces[i].ID)
|
||||
_ = s.db.QueryRow(`SELECT COUNT(*) FROM lists WHERE space_id = ?`, spaces[i].ID).Scan(&spaces[i].ListsCount)
|
||||
}
|
||||
return spaces, nil
|
||||
@@ -2729,12 +3002,15 @@ func (s *Store) AddTaskListShare(taskListID, targetType, targetID, createdBy str
|
||||
_, err := s.db.Exec(
|
||||
`INSERT INTO task_list_shares (id, task_list_id, target_type, target_id, created_at, created_by)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(task_list_id, target_type, target_id) DO NOTHING`,
|
||||
ON CONFLICT(task_list_id, target_type, target_id) DO UPDATE SET created_at = excluded.created_at`,
|
||||
share.ID, share.TaskListID, share.TargetType, share.TargetID, share.CreatedAt, createdBy,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = s.db.QueryRow(`SELECT id, created_at FROM task_list_shares WHERE task_list_id = ? AND target_type = ? AND target_id = ?`,
|
||||
taskListID, targetType, targetID).Scan(&share.ID, &share.CreatedAt)
|
||||
|
||||
if targetType == "team" {
|
||||
_, _ = s.db.Exec(`UPDATE task_lists SET team_id = ? WHERE id = ? AND (team_id = '' OR team_id IS NULL)`, targetID, taskListID)
|
||||
_ = s.db.QueryRow(`SELECT name FROM teams WHERE id = ?`, targetID).Scan(&share.TargetName)
|
||||
@@ -3181,14 +3457,28 @@ func (s *Store) GetTeamsForOrg(orgID string) ([]domain.Team, error) {
|
||||
return teams, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetTeamByID(teamID string) (*domain.Team, error) {
|
||||
var t domain.Team
|
||||
err := s.db.QueryRow(
|
||||
`SELECT id, org_id, name, description, created_at FROM teams WHERE id = ?`,
|
||||
teamID,
|
||||
).Scan(&t.ID, &t.OrgID, &t.Name, &t.Description, &t.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.Members, _ = s.GetTeamMembers(t.ID)
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
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
|
||||
JOIN team_members tm ON tm.team_id = t.id
|
||||
WHERE tm.user_id = ?
|
||||
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 = ?
|
||||
ORDER BY t.created_at ASC`,
|
||||
userID,
|
||||
userID, userID,
|
||||
)
|
||||
if err != nil {
|
||||
return []domain.Team{}, err
|
||||
|
||||
Reference in new issue
Block a user