diff options
author | Neil Alexander <neilalexander@users.noreply.github.com> | 2020-07-28 10:53:17 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-28 10:53:17 +0100 |
commit | acc8e80a51515c953c6710cb24f36fd9d1f7aeb1 (patch) | |
tree | b33ecfd8e4d7ed3ac7aa66226f8fd80c1cdcf649 /userapi | |
parent | c63286713570e1274759db971b15405665fa391a (diff) |
User directory (#1225)
* User directory
* Fix syncapi unit test
* Make user directory only show remote users you know about from your joined rooms
* Update sytest-whitelist
* Review comments
Diffstat (limited to 'userapi')
-rw-r--r-- | userapi/api/api.go | 16 | ||||
-rw-r--r-- | userapi/internal/api.go | 9 | ||||
-rw-r--r-- | userapi/inthttp/client.go | 19 | ||||
-rw-r--r-- | userapi/inthttp/server.go | 13 | ||||
-rw-r--r-- | userapi/storage/accounts/interface.go | 1 | ||||
-rw-r--r-- | userapi/storage/accounts/postgres/profile_table.go | 31 | ||||
-rw-r--r-- | userapi/storage/accounts/postgres/storage.go | 7 | ||||
-rw-r--r-- | userapi/storage/accounts/sqlite3/profile_table.go | 31 | ||||
-rw-r--r-- | userapi/storage/accounts/sqlite3/storage.go | 7 |
9 files changed, 129 insertions, 5 deletions
diff --git a/userapi/api/api.go b/userapi/api/api.go index bd0773f8..5791403f 100644 --- a/userapi/api/api.go +++ b/userapi/api/api.go @@ -18,6 +18,7 @@ import ( "context" "encoding/json" + "github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/gomatrixserverlib" ) @@ -31,6 +32,7 @@ type UserInternalAPI interface { QueryDevices(ctx context.Context, req *QueryDevicesRequest, res *QueryDevicesResponse) error QueryAccountData(ctx context.Context, req *QueryAccountDataRequest, res *QueryAccountDataResponse) error QueryDeviceInfos(ctx context.Context, req *QueryDeviceInfosRequest, res *QueryDeviceInfosResponse) error + QuerySearchProfiles(ctx context.Context, req *QuerySearchProfilesRequest, res *QuerySearchProfilesResponse) error } // InputAccountDataRequest is the request for InputAccountData @@ -112,6 +114,20 @@ type QueryProfileResponse struct { AvatarURL string } +// QuerySearchProfilesRequest is the request for QueryProfile +type QuerySearchProfilesRequest struct { + // The search string to match + SearchString string + // How many results to return + Limit int +} + +// QuerySearchProfilesResponse is the response for QuerySearchProfilesRequest +type QuerySearchProfilesResponse struct { + // Profiles matching the search + Profiles []authtypes.Profile +} + // PerformAccountCreationRequest is the request for PerformAccountCreation type PerformAccountCreationRequest struct { AccountType AccountType // Required: whether this is a guest or user account diff --git a/userapi/internal/api.go b/userapi/internal/api.go index 2de8f960..5b154196 100644 --- a/userapi/internal/api.go +++ b/userapi/internal/api.go @@ -125,6 +125,15 @@ func (a *UserInternalAPI) QueryProfile(ctx context.Context, req *api.QueryProfil return nil } +func (a *UserInternalAPI) QuerySearchProfiles(ctx context.Context, req *api.QuerySearchProfilesRequest, res *api.QuerySearchProfilesResponse) error { + profiles, err := a.AccountDB.SearchProfiles(ctx, req.SearchString, req.Limit) + if err != nil { + return err + } + res.Profiles = profiles + return nil +} + func (a *UserInternalAPI) QueryDeviceInfos(ctx context.Context, req *api.QueryDeviceInfosRequest, res *api.QueryDeviceInfosResponse) error { devices, err := a.DeviceDB.GetDevicesByID(ctx, req.DeviceIDs) if err != nil { diff --git a/userapi/inthttp/client.go b/userapi/inthttp/client.go index b2b42823..3e1ac066 100644 --- a/userapi/inthttp/client.go +++ b/userapi/inthttp/client.go @@ -31,11 +31,12 @@ const ( PerformDeviceCreationPath = "/userapi/performDeviceCreation" PerformAccountCreationPath = "/userapi/performAccountCreation" - QueryProfilePath = "/userapi/queryProfile" - QueryAccessTokenPath = "/userapi/queryAccessToken" - QueryDevicesPath = "/userapi/queryDevices" - QueryAccountDataPath = "/userapi/queryAccountData" - QueryDeviceInfosPath = "/userapi/queryDeviceInfos" + QueryProfilePath = "/userapi/queryProfile" + QueryAccessTokenPath = "/userapi/queryAccessToken" + QueryDevicesPath = "/userapi/queryDevices" + QueryAccountDataPath = "/userapi/queryAccountData" + QueryDeviceInfosPath = "/userapi/queryDeviceInfos" + QuerySearchProfilesPath = "/userapi/querySearchProfiles" ) // NewUserAPIClient creates a UserInternalAPI implemented by talking to a HTTP POST API. @@ -141,3 +142,11 @@ func (h *httpUserInternalAPI) QueryAccountData(ctx context.Context, req *api.Que apiURL := h.apiURL + QueryAccountDataPath return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res) } + +func (h *httpUserInternalAPI) QuerySearchProfiles(ctx context.Context, req *api.QuerySearchProfilesRequest, res *api.QuerySearchProfilesResponse) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "QuerySearchProfiles") + defer span.Finish() + + apiURL := h.apiURL + QuerySearchProfilesPath + return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res) +} diff --git a/userapi/inthttp/server.go b/userapi/inthttp/server.go index d8e151ad..d29f4d44 100644 --- a/userapi/inthttp/server.go +++ b/userapi/inthttp/server.go @@ -117,4 +117,17 @@ func AddRoutes(internalAPIMux *mux.Router, s api.UserInternalAPI) { return util.JSONResponse{Code: http.StatusOK, JSON: &response} }), ) + internalAPIMux.Handle(QueryDeviceInfosPath, + httputil.MakeInternalAPI("querySearchProfiles", func(req *http.Request) util.JSONResponse { + request := api.QuerySearchProfilesRequest{} + response := api.QuerySearchProfilesResponse{} + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.MessageResponse(http.StatusBadRequest, err.Error()) + } + if err := s.QuerySearchProfiles(req.Context(), &request, &response); err != nil { + return util.ErrorResponse(err) + } + return util.JSONResponse{Code: http.StatusOK, JSON: &response} + }), + ) } diff --git a/userapi/storage/accounts/interface.go b/userapi/storage/accounts/interface.go index 6f6caf11..86b91e60 100644 --- a/userapi/storage/accounts/interface.go +++ b/userapi/storage/accounts/interface.go @@ -49,6 +49,7 @@ type Database interface { GetThreePIDsForLocalpart(ctx context.Context, localpart string) (threepids []authtypes.ThreePID, err error) CheckAccountAvailability(ctx context.Context, localpart string) (bool, error) GetAccountByLocalpart(ctx context.Context, localpart string) (*api.Account, error) + SearchProfiles(ctx context.Context, searchString string, limit int) ([]authtypes.Profile, error) } // Err3PIDInUse is the error returned when trying to save an association involving diff --git a/userapi/storage/accounts/postgres/profile_table.go b/userapi/storage/accounts/postgres/profile_table.go index d2cbeb8e..14b12c35 100644 --- a/userapi/storage/accounts/postgres/profile_table.go +++ b/userapi/storage/accounts/postgres/profile_table.go @@ -17,8 +17,10 @@ package postgres import ( "context" "database/sql" + "fmt" "github.com/matrix-org/dendrite/clientapi/auth/authtypes" + "github.com/matrix-org/dendrite/internal" ) const profilesSchema = ` @@ -45,11 +47,15 @@ const setAvatarURLSQL = "" + const setDisplayNameSQL = "" + "UPDATE account_profiles SET display_name = $1 WHERE localpart = $2" +const selectProfilesBySearchSQL = "" + + "SELECT localpart, display_name, avatar_url FROM account_profiles WHERE localpart LIKE $1 OR display_name LIKE $1 LIMIT $2" + type profilesStatements struct { insertProfileStmt *sql.Stmt selectProfileByLocalpartStmt *sql.Stmt setAvatarURLStmt *sql.Stmt setDisplayNameStmt *sql.Stmt + selectProfilesBySearchStmt *sql.Stmt } func (s *profilesStatements) prepare(db *sql.DB) (err error) { @@ -69,6 +75,9 @@ func (s *profilesStatements) prepare(db *sql.DB) (err error) { if s.setDisplayNameStmt, err = db.Prepare(setDisplayNameSQL); err != nil { return } + if s.selectProfilesBySearchStmt, err = db.Prepare(selectProfilesBySearchSQL); err != nil { + return + } return } @@ -105,3 +114,25 @@ func (s *profilesStatements) setDisplayName( _, err = s.setDisplayNameStmt.ExecContext(ctx, displayName, localpart) return } + +func (s *profilesStatements) selectProfilesBySearch( + ctx context.Context, searchString string, limit int, +) ([]authtypes.Profile, error) { + var profiles []authtypes.Profile + // The fmt.Sprintf directive below is building a parameter for the + // "LIKE" condition in the SQL query. %% escapes the % char, so the + // statement in the end will look like "LIKE %searchString%". + rows, err := s.selectProfilesBySearchStmt.QueryContext(ctx, fmt.Sprintf("%%%s%%", searchString), limit) + if err != nil { + return nil, err + } + defer internal.CloseAndLogIfError(ctx, rows, "selectProfilesBySearch: rows.close() failed") + for rows.Next() { + var profile authtypes.Profile + if err := rows.Scan(&profile.Localpart, &profile.DisplayName, &profile.AvatarURL); err != nil { + return nil, err + } + profiles = append(profiles, profile) + } + return profiles, nil +} diff --git a/userapi/storage/accounts/postgres/storage.go b/userapi/storage/accounts/postgres/storage.go index c76b92f1..f56fb6d8 100644 --- a/userapi/storage/accounts/postgres/storage.go +++ b/userapi/storage/accounts/postgres/storage.go @@ -298,3 +298,10 @@ func (d *Database) GetAccountByLocalpart(ctx context.Context, localpart string, ) (*api.Account, error) { return d.accounts.selectAccountByLocalpart(ctx, localpart) } + +// SearchProfiles returns all profiles where the provided localpart or display name +// match any part of the profiles in the database. +func (d *Database) SearchProfiles(ctx context.Context, searchString string, limit int, +) ([]authtypes.Profile, error) { + return d.profiles.selectProfilesBySearch(ctx, searchString, limit) +} diff --git a/userapi/storage/accounts/sqlite3/profile_table.go b/userapi/storage/accounts/sqlite3/profile_table.go index 68cea516..d4c404ca 100644 --- a/userapi/storage/accounts/sqlite3/profile_table.go +++ b/userapi/storage/accounts/sqlite3/profile_table.go @@ -17,8 +17,10 @@ package sqlite3 import ( "context" "database/sql" + "fmt" "github.com/matrix-org/dendrite/clientapi/auth/authtypes" + "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/internal/sqlutil" ) @@ -46,6 +48,9 @@ const setAvatarURLSQL = "" + const setDisplayNameSQL = "" + "UPDATE account_profiles SET display_name = $1 WHERE localpart = $2" +const selectProfilesBySearchSQL = "" + + "SELECT localpart, display_name, avatar_url FROM account_profiles WHERE localpart LIKE $1 OR display_name LIKE $1 LIMIT $2" + type profilesStatements struct { db *sql.DB writer *sqlutil.TransactionWriter @@ -53,6 +58,7 @@ type profilesStatements struct { selectProfileByLocalpartStmt *sql.Stmt setAvatarURLStmt *sql.Stmt setDisplayNameStmt *sql.Stmt + selectProfilesBySearchStmt *sql.Stmt } func (s *profilesStatements) prepare(db *sql.DB) (err error) { @@ -74,6 +80,9 @@ func (s *profilesStatements) prepare(db *sql.DB) (err error) { if s.setDisplayNameStmt, err = db.Prepare(setDisplayNameSQL); err != nil { return } + if s.selectProfilesBySearchStmt, err = db.Prepare(selectProfilesBySearchSQL); err != nil { + return + } return } @@ -112,3 +121,25 @@ func (s *profilesStatements) setDisplayName( _, err = s.setDisplayNameStmt.ExecContext(ctx, displayName, localpart) return } + +func (s *profilesStatements) selectProfilesBySearch( + ctx context.Context, searchString string, limit int, +) ([]authtypes.Profile, error) { + var profiles []authtypes.Profile + // The fmt.Sprintf directive below is building a parameter for the + // "LIKE" condition in the SQL query. %% escapes the % char, so the + // statement in the end will look like "LIKE %searchString%". + rows, err := s.selectProfilesBySearchStmt.QueryContext(ctx, fmt.Sprintf("%%%s%%", searchString), limit) + if err != nil { + return nil, err + } + defer internal.CloseAndLogIfError(ctx, rows, "selectProfilesBySearch: rows.close() failed") + for rows.Next() { + var profile authtypes.Profile + if err := rows.Scan(&profile.Localpart, &profile.DisplayName, &profile.AvatarURL); err != nil { + return nil, err + } + profiles = append(profiles, profile) + } + return profiles, nil +} diff --git a/userapi/storage/accounts/sqlite3/storage.go b/userapi/storage/accounts/sqlite3/storage.go index 2d09090f..72239014 100644 --- a/userapi/storage/accounts/sqlite3/storage.go +++ b/userapi/storage/accounts/sqlite3/storage.go @@ -343,3 +343,10 @@ func (d *Database) GetAccountByLocalpart(ctx context.Context, localpart string, ) (*api.Account, error) { return d.accounts.selectAccountByLocalpart(ctx, localpart) } + +// SearchProfiles returns all profiles where the provided localpart or display name +// match any part of the profiles in the database. +func (d *Database) SearchProfiles(ctx context.Context, searchString string, limit int, +) ([]authtypes.Profile, error) { + return d.profiles.selectProfilesBySearch(ctx, searchString, limit) +} |