diff --git a/userapi/storage/interface.go b/userapi/storage/interface.go index 0f68c8b9f..0f4a5bb3a 100644 --- a/userapi/storage/interface.go +++ b/userapi/storage/interface.go @@ -31,6 +31,7 @@ type Profile interface { SearchProfiles(ctx context.Context, searchString string, limit int) ([]authtypes.Profile, error) SetAvatarURL(ctx context.Context, localpart string, serverName gomatrixserverlib.ServerName, avatarURL string) error SetDisplayName(ctx context.Context, localpart string, serverName gomatrixserverlib.ServerName, displayName string) error + DeleteProfile(ctx context.Context, localpart string, serverName gomatrixserverlib.ServerName) error } type Account interface { diff --git a/userapi/storage/postgres/profile_table.go b/userapi/storage/postgres/profile_table.go index 46a36ce94..51f375487 100644 --- a/userapi/storage/postgres/profile_table.go +++ b/userapi/storage/postgres/profile_table.go @@ -56,6 +56,9 @@ const setDisplayNameSQL = "" + const selectProfilesBySearchSQL = "" + "SELECT localpart, display_name, avatar_url, server_name FROM account_profiles WHERE localpart LIKE $1 OR display_name LIKE $1 LIMIT $2" +const deleteProfileSQL = "" + + "DELETE FROM account_profiles WHERE localpart = $1 AND server_name = $2" + type profilesStatements struct { serverNoticesLocalpart string insertProfileStmt *sql.Stmt @@ -63,6 +66,7 @@ type profilesStatements struct { setAvatarURLStmt *sql.Stmt setDisplayNameStmt *sql.Stmt selectProfilesBySearchStmt *sql.Stmt + deleteProfileStmt *sql.Stmt } func NewPostgresProfilesTable(db *sql.DB, serverNoticesLocalpart string) (tables.ProfileTable, error) { @@ -79,6 +83,7 @@ func NewPostgresProfilesTable(db *sql.DB, serverNoticesLocalpart string) (tables {&s.setAvatarURLStmt, setAvatarURLSQL}, {&s.setDisplayNameStmt, setDisplayNameSQL}, {&s.selectProfilesBySearchStmt, selectProfilesBySearchSQL}, + {&s.deleteProfileStmt, deleteProfileSQL}, }.Prepare(db) } @@ -139,3 +144,10 @@ func (s *profilesStatements) SelectProfilesBySearch( } return profiles, nil } + +func (s *profilesStatements) DeleteProfile( + ctx context.Context, txn *sql.Tx, localpart string, serverName gomatrixserverlib.ServerName, +) error { + _, err := sqlutil.TxStmtContext(ctx, txn, s.deleteProfileStmt).ExecContext(ctx, localpart, serverName) + return err +} diff --git a/userapi/storage/shared/storage.go b/userapi/storage/shared/storage.go index 310cf826f..3ebdfb409 100644 --- a/userapi/storage/shared/storage.go +++ b/userapi/storage/shared/storage.go @@ -763,3 +763,10 @@ func (d *Database) RemovePushers( func (d *Database) UserStatistics(ctx context.Context) (*types.UserStatistics, *types.DatabaseEngine, error) { return d.Stats.UserStatistics(ctx, nil) } + +// DeleteProfile deletes a user profile from the database +func (d *Database) DeleteProfile(ctx context.Context, localpart string, serverName gomatrixserverlib.ServerName) error { + return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { + return d.Profiles.DeleteProfile(ctx, txn, localpart, serverName) + }) +} diff --git a/userapi/storage/sqlite3/profile_table.go b/userapi/storage/sqlite3/profile_table.go index 4a1b9e775..0d78f49ec 100644 --- a/userapi/storage/sqlite3/profile_table.go +++ b/userapi/storage/sqlite3/profile_table.go @@ -56,6 +56,9 @@ const setDisplayNameSQL = "" + const selectProfilesBySearchSQL = "" + "SELECT localpart, display_name, avatar_url, server_name FROM account_profiles WHERE localpart LIKE $1 OR display_name LIKE $1 LIMIT $2" +const deleteProfileSQL = "" + + "DELETE FROM account_profiles WHERE localpart = $1 AND server_name = $2" + type profilesStatements struct { db *sql.DB serverNoticesLocalpart string @@ -64,6 +67,7 @@ type profilesStatements struct { setAvatarURLStmt *sql.Stmt setDisplayNameStmt *sql.Stmt selectProfilesBySearchStmt *sql.Stmt + deleteProfileStmt *sql.Stmt } func NewSQLiteProfilesTable(db *sql.DB, serverNoticesLocalpart string) (tables.ProfileTable, error) { @@ -81,6 +85,7 @@ func NewSQLiteProfilesTable(db *sql.DB, serverNoticesLocalpart string) (tables.P {&s.setAvatarURLStmt, setAvatarURLSQL}, {&s.setDisplayNameStmt, setDisplayNameSQL}, {&s.selectProfilesBySearchStmt, selectProfilesBySearchSQL}, + {&s.deleteProfileStmt, deleteProfileSQL}, }.Prepare(db) } @@ -143,3 +148,10 @@ func (s *profilesStatements) SelectProfilesBySearch( } return profiles, nil } + +func (s *profilesStatements) DeleteProfile( + ctx context.Context, txn *sql.Tx, localpart string, serverName gomatrixserverlib.ServerName, +) error { + _, err := sqlutil.TxStmtContext(ctx, txn, s.deleteProfileStmt).ExecContext(ctx, localpart, serverName) + return err +} diff --git a/userapi/storage/tables/interface.go b/userapi/storage/tables/interface.go index 092540ae1..061717557 100644 --- a/userapi/storage/tables/interface.go +++ b/userapi/storage/tables/interface.go @@ -88,6 +88,7 @@ type ProfileTable interface { SetAvatarURL(ctx context.Context, txn *sql.Tx, localpart string, serverName gomatrixserverlib.ServerName, avatarURL string) (err error) SetDisplayName(ctx context.Context, txn *sql.Tx, localpart string, serverName gomatrixserverlib.ServerName, displayName string) (err error) SelectProfilesBySearch(ctx context.Context, searchString string, limit int) ([]authtypes.Profile, error) + DeleteProfile(ctx context.Context, txn *sql.Tx, localpart string, serverName gomatrixserverlib.ServerName) error } type ThreePIDTable interface { diff --git a/userapi/storage/tables/profile_table_test.go b/userapi/storage/tables/profile_table_test.go index 316d34165..5da99d698 100644 --- a/userapi/storage/tables/profile_table_test.go +++ b/userapi/storage/tables/profile_table_test.go @@ -113,5 +113,22 @@ func TestProfileTable(t *testing.T) { } } + // Delete dummy1 on serverName1... + if err = tab.DeleteProfile(ctx, nil, "dummy1", serverName1); err != nil { + t.Fatalf("failed to delete profile for user: %s", err) + } + searchRes, err = tab.SelectProfilesBySearch(ctx, "dummy", 10) + if err != nil { + t.Fatalf("unable to search profiles: %v", err) + } + // ... verify that we only get one result back. + if count := len(searchRes); count != 1 { + t.Fatalf("expected 1 results, got %d", count) + } + + // Deleting a non-existent profile should not give an error + if err = tab.DeleteProfile(ctx, nil, "doesnotexist", serverName1); err != nil { + t.Fatalf("failed to delete profile for user: %s", err) + } }) }