package storage import ( "errors" "fmt" ) // ErrLastAdmin protects the server from losing its last administrator. var ErrLastAdmin = errors.New("Der letzte Server-Administrator kann nicht gelöscht werden. Bitte ernenne vorher ein anderes Konto zum Administrator.") // PurgeUserData deletes everything the user owns – thoughts and tasks, notes (with blocks, history and // shares), folders, shared folders, task lists and lists – in one transaction. The account, its // settings, themes, API keys and group memberships stay. Other people's tasks in the user's task lists // become their own personal tasks, as when a single list is deleted. func (s *Store) PurgeUserData(userID string) error { tx, err := s.db.Begin() if err != nil { return err } defer tx.Rollback() ownDocs := `(SELECT id FROM documents WHERE user_id = ?)` ownTaskLists := `(SELECT id FROM task_lists WHERE user_id = ?)` ownFolders := `(SELECT id FROM workspace_shared_folders WHERE owner_id = ?)` stmts := []struct { q string args int // how many times userID is passed }{ // Task lists: keep the tasks of others, drop shares and the lists {`UPDATE thoughts SET task_list_id = '' WHERE task_list_id IN ` + ownTaskLists + ` AND user_id != ?`, 2}, {`DELETE FROM task_list_shares WHERE task_list_id IN ` + ownTaskLists, 1}, {`DELETE FROM task_lists WHERE user_id = ?`, 1}, // Tasks of others linked to blocks of the user's notes lose the link {`UPDATE thoughts SET converted_type = '', converted_ref_id = '' WHERE converted_type = 'document_block' AND substr(converted_ref_id, 1, instr(converted_ref_id, ':') - 1) IN ` + ownDocs, 1}, {`DELETE FROM thoughts WHERE user_id = ?`, 1}, // Shared folders owned by the user {`UPDATE documents SET shared_folder_id = '', sub_folder_path = '' WHERE shared_folder_id IN ` + ownFolders, 1}, {`DELETE FROM workspace_folder_mounts WHERE folder_id IN ` + ownFolders, 1}, {`DELETE FROM workspace_folder_shares WHERE folder_id IN ` + ownFolders, 1}, {`DELETE FROM workspace_folders WHERE shared_folder_id IN ` + ownFolders, 1}, {`DELETE FROM workspace_shared_folders WHERE owner_id = ?`, 1}, // Notes (history tables cascade with the document) {`DELETE FROM blocks WHERE document_id IN ` + ownDocs, 1}, {`DELETE FROM block_tags WHERE document_id IN ` + ownDocs, 1}, {`DELETE FROM doc_user_folders WHERE doc_id IN ` + ownDocs, 1}, {`DELETE FROM doc_team_shares WHERE doc_id IN ` + ownDocs, 1}, {`DELETE FROM doc_shares WHERE doc_id IN ` + ownDocs, 1}, {`DELETE FROM documents WHERE user_id = ?`, 1}, // The user's own placement of notes, folders, pins and tag pages {`DELETE FROM doc_user_folders WHERE user_id = ?`, 1}, {`DELETE FROM doc_pins WHERE user_id = ?`, 1}, {`DELETE FROM workspace_folders WHERE user_id = ?`, 1}, {`DELETE FROM tag_pages WHERE user_id = ?`, 1}, // Lists (items cascade) {`DELETE FROM list_shares WHERE list_id IN (SELECT id FROM lists WHERE user_id = ?)`, 1}, {`DELETE FROM lists WHERE user_id = ?`, 1}, } for _, st := range stmts { args := make([]interface{}, st.args) for i := range args { args[i] = userID } if _, err := tx.Exec(st.q, args...); err != nil { return fmt.Errorf("Daten löschen: %w", err) } } if err := tx.Commit(); err != nil { return err } s.PruneTaskAssignees() return nil } // IsLastAdmin reports whether userID is an administrator and no other one exists. func (s *Store) IsLastAdmin(userID string) bool { var role string if err := s.db.QueryRow(`SELECT role FROM users WHERE id = ?`, userID).Scan(&role); err != nil || role != "admin" { return false } var others int _ = s.db.QueryRow(`SELECT COUNT(*) FROM users WHERE role = 'admin' AND id != ?`, userID).Scan(&others) return others == 0 } // DeleteOwnAccount removes the user with all data; refused for the last administrator. func (s *Store) DeleteOwnAccount(userID string) error { if s.IsLastAdmin(userID) { return ErrLastAdmin } return s.DeleteUser(userID) }