diff --git a/lib/controllers/v2/exemplar_identifications_controller.js b/lib/controllers/v2/exemplar_identifications_controller.js index 4e139eed..9023f8d1 100644 --- a/lib/controllers/v2/exemplar_identifications_controller.js +++ b/lib/controllers/v2/exemplar_identifications_controller.js @@ -88,11 +88,21 @@ const ExemplarIdentificationsController = class ExemplarIdentificationsControlle if ( params.q ) { searchFilters.push( { - match: { - "identification.body": { - query: params.q, - operator: "and" - } + bool: { + should: [{ + match: { + "identification.body": { + query: params.q, + operator: "and" + } + } + }, { + match: { + "identification.user.login_autocomplete": { + query: params.q + } + } + }] } } ); } diff --git a/schema/database.sql b/schema/database.sql index f4c47c25..9cad7d6f 100644 --- a/schema/database.sql +++ b/schema/database.sql @@ -2781,7 +2781,9 @@ CREATE TABLE public.moderator_actions ( resource_user_id integer, resource_parent_id integer, resource_parent_type character varying, - resource_content text + resource_content text, + suspended_until timestamp without time zone, + last_edited_by_user_id integer ); @@ -6118,7 +6120,8 @@ CREATE TABLE public.users ( virtuous_donor_contact_id integer, fundraiseup_plan_frequency character varying, fundraiseup_plan_status character varying, - fundraiseup_plan_started_at date + fundraiseup_plan_started_at date, + suspended_until timestamp without time zone ); @@ -9181,7 +9184,7 @@ CREATE INDEX index_custom_projects_on_project_id ON public.custom_projects USING -- Name: index_delayed_jobs_on_unique_hash; Type: INDEX; Schema: public; Owner: - -- -CREATE INDEX index_delayed_jobs_on_unique_hash ON public.delayed_jobs USING btree (unique_hash); +CREATE UNIQUE INDEX index_delayed_jobs_on_unique_hash ON public.delayed_jobs USING btree (unique_hash); -- @@ -11365,7 +11368,7 @@ CREATE INDEX index_users_on_curator_sponsor_id ON public.users USING btree (cura -- Name: index_users_on_email; Type: INDEX; Schema: public; Owner: - -- -CREATE INDEX index_users_on_email ON public.users USING btree (email); +CREATE UNIQUE INDEX index_users_on_email ON public.users USING btree (email); -- @@ -11473,6 +11476,13 @@ CREATE INDEX index_users_on_spammer ON public.users USING btree (spammer); CREATE INDEX index_users_on_state ON public.users USING btree (state); +-- +-- Name: index_users_on_suspended_until; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_users_on_suspended_until ON public.users USING btree (suspended_until); + + -- -- Name: index_users_on_unconfirmed_email; Type: INDEX; Schema: public; Owner: - -- @@ -12180,6 +12190,13 @@ INSERT INTO "schema_migrations" (version) VALUES ('20251119043443'), ('20251119130558'), ('20251202224705'), -('20260119093529'); +('20260119093529'), +('20260319212735'), +('20260326000001'), +('20260326000002'), +('20260406164708'), +('20260511210120'), +('20260514211433'), +('20260514215925'); diff --git a/schema/fixtures.js b/schema/fixtures.js index 27ebaf0b..09a330e8 100644 --- a/schema/fixtures.js +++ b/schema/fixtures.js @@ -171,6 +171,11 @@ }, "observation": { "id": 1 + }, + "user": { + "id": 121, + "login": "user121", + "login_autocomplete": "user121" } } }, @@ -188,6 +193,11 @@ }, "observation": { "id": 1 + }, + "user": { + "id": 122, + "login": "user122", + "login_autocomplete": "user122" } } }, diff --git a/schema/indices/exemplar_identifications.js b/schema/indices/exemplar_identifications.js index aa4e3961..842ae326 100644 --- a/schema/indices/exemplar_identifications.js +++ b/schema/indices/exemplar_identifications.js @@ -127,6 +127,18 @@ "type": "keyword" } } + }, + "login": { + "type": "text", + "analyzer": "ascii_snowball_analyzer" + }, + "login_autocomplete": { + "type": "text", + "analyzer": "autocomplete_analyzer", + "search_analyzer": "standard_analyzer" + }, + "login_exact": { + "type": "keyword" } } }, diff --git a/test/integration/v2/exemplar_identifications.js b/test/integration/v2/exemplar_identifications.js index c14d9cf3..02bfdd0f 100644 --- a/test/integration/v2/exemplar_identifications.js +++ b/test/integration/v2/exemplar_identifications.js @@ -147,19 +147,6 @@ describe( "ExemplarIdentifications", ( ) => { .expect( 200, done ); } ); - it( "can filter by query", function ( done ) { - request( this.app ).get( "/v2/exemplar_identifications?direct_taxon_id=7&q=unnominated&fields=all" ) - .set( "Authorization", adminToken ) - .set( "Content-Type", "application/json" ) - .expect( "Content-Type", /json/ ) - .expect( res => { - expect( res.body.total_results ).to.eq( 1 ); - expect( res.body.results.length ).to.eq( 1 ); - expect( res.body.results[0].identification.body ).to.eq( "taxon 7 unnominated" ); - } ) - .expect( 200, done ); - } ); - it( "can filter by term_value_id", function ( done ) { request( this.app ).get( "/v2/exemplar_identifications?direct_taxon_id=3&term_value_id=2&fields=all" ) .set( "Authorization", adminToken ) @@ -172,5 +159,61 @@ describe( "ExemplarIdentifications", ( ) => { } ) .expect( 200, done ); } ); + + describe( "q", ( ) => { + it( "can filter by query", function ( done ) { + request( this.app ).get( "/v2/exemplar_identifications?direct_taxon_id=7&q=unnominated&fields=all" ) + .set( "Authorization", adminToken ) + .set( "Content-Type", "application/json" ) + .expect( "Content-Type", /json/ ) + .expect( res => { + expect( res.body.total_results ).to.eq( 1 ); + expect( res.body.results.length ).to.eq( 1 ); + expect( res.body.results[0].identification.body ).to.eq( "taxon 7 unnominated" ); + } ) + .expect( 200, done ); + } ); + + it( "can filter by user login", function ( done ) { + request( this.app ).get( "/v2/exemplar_identifications?direct_taxon_id=7&q=user121&fields=all" ) + .set( "Authorization", adminToken ) + .set( "Content-Type", "application/json" ) + .expect( "Content-Type", /json/ ) + .expect( res => { + expect( res.body.total_results ).to.eq( 1 ); + expect( res.body.results.length ).to.eq( 1 ); + expect( res.body.results[0].identification.user.login ).to.eq( "user121" ); + } ) + .expect( 200, done ); + } ); + + it( "can filter by query and user login", function ( done ) { + request( this.app ).get( "/v2/exemplar_identifications?direct_taxon_id=7&q=unnominated user121&fields=all" ) + .set( "Authorization", adminToken ) + .set( "Content-Type", "application/json" ) + .expect( "Content-Type", /json/ ) + .expect( res => { + expect( res.body.total_results ).to.eq( 1 ); + expect( res.body.results.length ).to.eq( 1 ); + expect( res.body.results[0].identification.user.login ).to.eq( "user121" ); + } ) + .expect( 200, done ); + } ); + + it( "can filter by partial query and user login", function ( done ) { + request( this.app ).get( "/v2/exemplar_identifications?direct_taxon_id=7&q=nom user12&fields=all" ) + .set( "Authorization", adminToken ) + .set( "Content-Type", "application/json" ) + .expect( "Content-Type", /json/ ) + .expect( res => { + expect( res.body.total_results ).to.eq( 2 ); + expect( res.body.results.length ).to.eq( 2 ); + res.body.results.forEach( result => { + expect( result.identification.user.login ).to.be.oneOf( ["user121", "user122"] ); + } ); + } ) + .expect( 200, done ); + } ); + } ); } ); } );