Skip to content

Commit f2435f3

Browse files
committed
Merge branch 'main' into ci-ubuntu22-postgis3
2 parents 542139e + c9e815d commit f2435f3

10 files changed

Lines changed: 167 additions & 34 deletions

File tree

lib/controllers/v1/users_controller.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ const UsersController = class UsersController {
7777
if ( !req.query.q ) {
7878
return InaturalistAPI.basicResponse( req );
7979
}
80-
const filters = [];
80+
const filters = [esClient.termFilter( "suspended", false )];
8181
if ( req.query.project_id ) {
8282
if ( !Number( req.query.project_id ) ) {
8383
// { error: "Invalid project_id", status: 422 }

lib/controllers/v2/users_controller.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const show = async req => {
5656

5757
const index = async req => {
5858
const { page, perPage } = InaturalistAPI.paginationData( req, { default: 10, max: 200 } );
59-
const filters = [];
59+
const filters = [esClient.termFilter( "suspended", false )];
6060
if ( req.query.following ) {
6161
const followingUser = await User.findByLoginOrID( req.query.following );
6262
if ( !followingUser ) {

lib/logstasher.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,26 @@ const Logstasher = class Logstasher {
141141
} else if ( req.inat.requestContext ) {
142142
payload.context = req.inat.requestContext;
143143
}
144+
Logstasher.appendLogExtra( req, payload );
144145
return payload;
145146
}
146147

148+
static appendLogExtra( req, payload ) {
149+
// for log routes, add key/values from the `extra` request body payload if present
150+
if ( !payload?.route?.match( /^\/v[12]\/log$/ )
151+
|| !_.isObject( req?.body?.extra )
152+
) {
153+
return;
154+
}
155+
156+
_.each( req.body.extra, ( v, k ) => {
157+
if ( !_.isObject( v ) ) {
158+
payload.extra ||= { };
159+
payload.extra[k] = v;
160+
}
161+
} );
162+
}
163+
147164
static errorPayload( err ) {
148165
const payload = { };
149166
payload["@timestamp"] = new Date( ).toISOString( );

lib/models/observation.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,22 @@ const Observation = class Observation extends Model {
192192
);
193193
}
194194

195+
static preloadLastUpdaterObservationValue( obs ) {
196+
const updaterOptions = {
197+
foreignKey: "updater_id",
198+
attrName: "updater"
199+
};
200+
return ESModel.fetchBelongsTo(
201+
_.flattenDeep( _.map( obs, "ofvs" ) ),
202+
User,
203+
updaterOptions
204+
);
205+
}
206+
195207
static async preloadAllAssociations( req, obs, localeOpts ) {
196208
await Observation.preloadAnnotationControlledTerms( obs );
197209
await Observation.preloadMinimal( req, obs, localeOpts );
210+
await Observation.preloadLastUpdaterObservationValue( obs );
198211
await Observation.preloadObservationFields( obs );
199212
const withProjects = _.filter(
200213
_.flattenDeep( [

lib/models/project.js

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -256,14 +256,12 @@ const Project = class Project extends Model {
256256
} );
257257
}
258258
} );
259-
await Promise.all( [
260-
ESModel.fetchBelongsTo( projects, Place, { source: { excludes: ["geometry_geojson"] } } ),
261-
ESModel.fetchBelongsTo( taxonRules, Taxon, taxonOpts ),
262-
ESModel.fetchBelongsTo( userRules, User, { foreignKey: "operand_id" } ),
263-
ESModel.fetchBelongsTo( placeRules, Place, placeOpts ),
264-
ESModel.fetchBelongsTo( projectRules, Project, { foreignKey: "operand_id" } ),
265-
ESModel.fetchBelongsTo( controlledTermRules, ControlledTerm, { foreignKey: "value" } )
266-
] );
259+
await ESModel.fetchBelongsTo( projects, Place, { source: { excludes: ["geometry_geojson"] } } );
260+
await ESModel.fetchBelongsTo( taxonRules, Taxon, taxonOpts );
261+
await ESModel.fetchBelongsTo( userRules, User, { foreignKey: "operand_id" } );
262+
await ESModel.fetchBelongsTo( placeRules, Place, placeOpts );
263+
await ESModel.fetchBelongsTo( projectRules, Project, { foreignKey: "operand_id" } );
264+
await ESModel.fetchBelongsTo( controlledTermRules, ControlledTerm, { foreignKey: "value" } );
267265
}
268266
};
269267

lib/util.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ const util = class util {
137137
res.header( "Access-Control-Allow-Origin", "*" );
138138
res.header( "Access-Control-Allow-Headers",
139139
"Origin, X-Requested-With, Content-Type, Accept, Authorization, Access-Control-Allow-Methods, "
140-
+ "X-Installation-ID, X-Via, X-HTTP-Method-Override" );
140+
+ "X-Installation-ID, X-Via, X-HTTP-Method-Override, User-Agent" );
141141
res.header( "Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE" );
142142
next( );
143143
}

openapi/schema/request/log_create.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,15 @@ module.exports = Joi.object( ).keys( {
1111
context: Joi.string( ).description( "Brief description of where the log event came from, e.g. a file or class name" ),
1212
timestamp: Joi.date( ).description( "Datetime the event was logged in the client in case of asynchronous logging" ),
1313
error_type: Joi.string( ).description( "Type of error, e.g. java.lang.NoClassDefFoundError. Only used when level is error" ),
14-
backtrace: Joi.string( ).description( "Only used when level is error" )
14+
backtrace: Joi.string( ).description( "Only used when level is error" ),
15+
extra: Joi.object( ).pattern(
16+
Joi.string( ),
17+
Joi.alternatives( ).try(
18+
Joi.string( ),
19+
Joi.number( ),
20+
Joi.boolean( )
21+
)
22+
).description( "Accepts key/values where the values are not objects, and logs the contents" )
1523
} ).description(
1624
"Log an event that occurred in a client application"
1725
).meta( { unpublished: true } );

schema/fixtures.js

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1851,92 +1851,109 @@
18511851
"login_autocomplete": "userloginautocomplete",
18521852
"name": "username",
18531853
"name_autocomplete": "usernameautocomplete",
1854-
"site_id": 1
1854+
"site_id": 1,
1855+
"suspended": false
18551856
},
18561857
{
18571858
"id": 2,
18581859
"login": "search_test_user",
18591860
"login_autocomplete": "search_test_user",
18601861
"name": "Search Test User",
1861-
"name_autocomplete": "Search Test User"
1862+
"name_autocomplete": "Search Test User",
1863+
"suspended": false
18621864
},
18631865
{
18641866
"id": 5,
18651867
"login": "b-user",
1866-
"name": "B User"
1868+
"name": "B User",
1869+
"suspended": false
18671870
},
18681871
{
18691872
"id": 6,
18701873
"login": "z-user",
1871-
"name": "Z User"
1874+
"name": "Z User",
1875+
"suspended": false
18721876
},
18731877
{
18741878
"id": 121,
18751879
"login": "user121",
1876-
"name": "user121"
1880+
"name": "user121",
1881+
"suspended": false
18771882
},
18781883
{
18791884
"id": 122,
18801885
"login": "user122",
1881-
"name": "user122"
1886+
"name": "user122",
1887+
"suspended": false
18821888
},
18831889
{
18841890
"id": 123,
18851891
"login": "a-user",
18861892
"name": "A User",
1887-
"orcid": "0000-0001-0002-0004"
1893+
"orcid": "0000-0001-0002-0004",
1894+
"suspended": false
18881895
},
18891896
{
18901897
"id": 124,
18911898
"login": "es-user",
18921899
"name": "ES User",
18931900
"locale": "es",
1894-
"place_id": 222
1901+
"place_id": 222,
1902+
"suspended": false
18951903
},
18961904
{
18971905
"id": 125,
1898-
"login": "totally-trustworthy"
1906+
"login": "totally-trustworthy",
1907+
"suspended": false
18991908
},
19001909
{
19011910
"id": 126,
1902-
"login": "totally-trusting"
1911+
"login": "totally-trusting",
1912+
"suspended": false
19031913
},
19041914
{
19051915
"id": 127,
19061916
"login": "user127",
1907-
"name": "Observer that trusts user 123"
1917+
"name": "Observer that trusts user 123",
1918+
"suspended": false
19081919
},
19091920
{
19101921
"id": 128,
19111922
"login": "user125",
1912-
"name": "Observer that does NOT trust user 123"
1923+
"name": "Observer that does NOT trust user 123",
1924+
"suspended": false
19131925
},
19141926
{
19151927
"id": 2,
19161928
"login": "search_test_user",
19171929
"login_autocomplete": "search_test_user",
19181930
"name": "Search Test User",
1919-
"name_autocomplete": "Search Test User"
1931+
"name_autocomplete": "Search Test User",
1932+
"suspended": false
19201933
},
19211934
{
19221935
"id": 129,
19231936
"login": "prefers-no-common-names",
1924-
"name": "Prefers No Common Names"
1937+
"name": "Prefers No Common Names",
1938+
"suspended": false
19251939
},
19261940
{
19271941
"id": 1234,
19281942
"login": "user1234",
1929-
"name": "user1234"
1943+
"name": "user1234",
1944+
"suspended": false
19301945
},
19311946
{
19321947
"id": 2020110501,
19331948
"login": "user2020110501",
1934-
"name": "User that follows user 126 and trusts them"
1949+
"name": "User that follows user 126 and trusts them",
1950+
"suspended": false
19351951
},
19361952
{
19371953
"id": 2020111201,
19381954
"login": "user2020111201",
1939-
"name": "User that follows user 126 and does NOT trusts them"
1955+
"name": "User that follows user 126 and does NOT trusts them",
1956+
"suspended": false
19401957
},
19411958
{
19421959
"id": 2021111401,
@@ -1947,33 +1964,38 @@
19471964
{
19481965
"id": 2021121601,
19491966
"login": "user2021121601",
1950-
"name": "User that will be blocked"
1967+
"name": "User that will be blocked",
1968+
"suspended": false
19511969
},
19521970
{
19531971
"id": 2021121602,
19541972
"login": "user2021121602",
1955-
"name": "User that blocks user2021121601"
1973+
"name": "User that blocks user2021121601",
1974+
"suspended": false
19561975
},
19571976
{
19581977
"id": 2023092501,
19591978
"login": "user2023092501",
19601979
"name": "User2023092501 with email and IP",
19611980
"email": "user2023092501@gmail.com",
1962-
"last_ip": "192.168.0.1"
1981+
"last_ip": "192.168.0.1",
1982+
"suspended": false
19631983
},
19641984
{
19651985
"id": 2023092502,
19661986
"login": "user2023092502",
19671987
"name": "User2023092502 with email and IP",
19681988
"email": "user2023092502@gmail.com",
1969-
"last_ip": "192.168.0.2"
1989+
"last_ip": "192.168.0.2",
1990+
"suspended": false
19701991
},
19711992
{
19721993
"id": 2023092503,
19731994
"login": "user2023092503",
19741995
"name": "User2023092503 with email and IP",
19751996
"email": "user2023092503@gmail.com",
1976-
"last_ip": "192.168.0.3"
1997+
"last_ip": "192.168.0.3",
1998+
"suspended": false
19771999
}
19782000
]
19792001
},

test/controllers/v2/log_controller.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,36 @@ describe( "LogController", ( ) => {
4949
.expect( 204, done );
5050
} );
5151

52+
describe( "extra payload", ( ) => {
53+
it( "accepts extra payloads", function ( done ) {
54+
request( this.app ).post( "/v2/log" )
55+
.set( "Authorization", userToken )
56+
.set( "Content-Type", "application/json" )
57+
.send( {
58+
extra: {
59+
string: "string",
60+
number: 123.4,
61+
boolean: true
62+
}
63+
} )
64+
.expect( 204, done );
65+
} );
66+
67+
it( "does not accept objects in extra payloads", function ( done ) {
68+
request( this.app ).post( "/v2/log" )
69+
.set( "Authorization", userToken )
70+
.set( "Content-Type", "application/json" )
71+
.send( {
72+
extra: {
73+
object: {
74+
string: "string"
75+
}
76+
}
77+
} )
78+
.expect( 422, done );
79+
} );
80+
} );
81+
5282
describe( "Logstasher.afterRequestPayload", ( ) => {
5383
const sandbox = sinon.createSandbox( );
5484

@@ -104,6 +134,28 @@ describe( "LogController", ( ) => {
104134
done( );
105135
} );
106136
} );
137+
138+
it( "logs extra payloads", function ( done ) {
139+
const body = {
140+
extra: {
141+
string: "string",
142+
number: 123.4,
143+
boolean: true
144+
}
145+
};
146+
request( this.app ).post( "/v2/log" )
147+
.set( "Authorization", [userToken, applicationToken].join( ", " ) )
148+
.set( "Content-Type", "application/json" )
149+
.send( body )
150+
.expect( 204, ( ) => {
151+
expect( Logstasher.afterRequestPayload ).to.have.been.called;
152+
const payload = Logstasher.afterRequestPayload.returnValues[0];
153+
expect( payload.extra.string ).to.eq( body.extra.string );
154+
expect( payload.extra.number ).to.eq( body.extra.number );
155+
expect( payload.extra.boolean ).to.eq( body.extra.boolean );
156+
done( );
157+
} );
158+
} );
107159
} );
108160
} );
109161
} );

test/integration/v2/users.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ describe( "Users", ( ) => {
8383
} ).expect( "Content-Type", /json/ )
8484
.expect( 200, done );
8585
} );
86+
it( "never returns suspended users", function ( done ) {
87+
const suspendedUser = fixtures.elasticsearch.users.user.find( u => u.suspended );
88+
expect( suspendedUser ).not.to.be.undefined;
89+
request( this.app ).get( `/v2/users/autocomplete?q=${suspendedUser.login}` )
90+
.expect( res => {
91+
expect( res.body.results.find( u => u.id === suspendedUser.id ) ).to.be.undefined;
92+
} ).expect( "Content-Type", /json/ )
93+
.expect( 200, done );
94+
} );
8695
} );
8796

8897
describe( "update_session", ( ) => {
@@ -256,6 +265,20 @@ describe( "Users", ( ) => {
256265
} ).expect( "Content-Type", /json/ )
257266
.expect( 200, done );
258267
} );
268+
269+
it( "never returns suspended users", function ( done ) {
270+
const suspendedUser = fixtures.elasticsearch.users.user.find( u => u.suspended );
271+
const okUser = fixtures.elasticsearch.users.user.find( u => !u.suspended );
272+
expect( suspendedUser ).not.to.be.undefined;
273+
expect( okUser ).not.to.be.undefined;
274+
request( this.app ).get( "/v2/users?per_page=100" )
275+
.expect( res => {
276+
expect( res.body.results ).not.to.be.empty;
277+
expect( res.body.results.find( u => u.id === suspendedUser.id ) ).not.to.exist;
278+
expect( res.body.results.find( u => u.id === okUser.id ) ).to.exist;
279+
} ).expect( "Content-Type", /json/ )
280+
.expect( 200, done );
281+
} );
259282
} );
260283

261284
describe( "update", ( ) => {

0 commit comments

Comments
 (0)