Skip to content

Commit

Permalink
add saved_locations endpoints #264
Browse files Browse the repository at this point in the history
  • Loading branch information
pleary committed Jul 29, 2024
1 parent b14ddc8 commit 45488c4
Show file tree
Hide file tree
Showing 10 changed files with 289 additions and 2 deletions.
18 changes: 18 additions & 0 deletions lib/controllers/v1/saved_locations_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const { saved_locations: savedLocations } = require( "inaturalistjs" );
const InaturalistAPI = require( "../../inaturalist_api" );

const SavedLocationsController = class SavedLocationsController {
static async search( req ) {
return InaturalistAPI.iNatJSWrap( savedLocations.search, req );
}

static async create( req ) {
return InaturalistAPI.iNatJSWrap( savedLocations.create, req );
}

static async delete( req ) {
return InaturalistAPI.iNatJSWrap( savedLocations.delete, req );
}
};

module.exports = SavedLocationsController;
29 changes: 29 additions & 0 deletions lib/controllers/v2/saved_locations_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const _ = require( "lodash" );
const ctrlv1 = require( "../v1/saved_locations_controller" );

const search = async req => {
const response = await ctrlv1.search( req );
response.results = _.map( response.results, r => {
if ( _.isEmpty( r.geoprivacy ) ) {
r.geoprivacy = null;
}
return r;
} );
return response;
};

const create = async req => {
const savedLocation = await ctrlv1.create( req );
return {
page: 1,
per_page: 1,
total_results: 1,
results: [savedLocation]
};
};

module.exports = {
search,
create,
delete: ctrlv1.delete
};
87 changes: 87 additions & 0 deletions openapi/paths/v2/saved_locations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
const _ = require( "lodash" );
const savedLocationsSearchSchema = require( "../../schema/request/saved_locations_search" );
const transform = require( "../../joi_to_openapi_parameter" );
const SavedLocationsController = require( "../../../lib/controllers/v2/saved_locations_controller" );

module.exports = sendWrapper => {
async function GET( req, res ) {
const response = await SavedLocationsController.search( req );
sendWrapper( req, res, null, response );
}

GET.apiDoc = {
tags: ["SavedLocations"],
summary: "Retrieve saved locations for the authenticated user",
security: [{
userJwtRequired: []
}],
parameters: [
{
in: "header",
name: "X-HTTP-Method-Override",
schema: {
type: "string"
}
}
].concat( _.map( savedLocationsSearchSchema.$_terms.keys, child => (
transform( child.schema.label( child.key ) )
) ) ),
responses: {
200: {
description: "An array of saved locations",
content: {
"application/json": {
schema: {
$ref: "#/components/schemas/ResultsSavedLocations"
}
}
}
},
default: {
$ref: "#/components/responses/Error"
}
}
};

async function POST( req, res ) {
const results = await SavedLocationsController.create( req );
sendWrapper( req, res, null, results );
}

POST.apiDoc = {
tags: ["SavedLocations"],
summary: "Create a saved location",
security: [{
userJwtRequired: []
}],
requestBody: {
content: {
"application/json": {
schema: {
$ref: "#/components/schemas/SavedLocationsCreate"
}
}
}
},
responses: {
200: {
description: "An array of saved locations",
content: {
"application/json": {
schema: {
$ref: "#/components/schemas/ResultsSavedLocations"
}
}
}
},
default: {
$ref: "#/components/responses/Error"
}
}
};

return {
GET,
POST
};
};
36 changes: 36 additions & 0 deletions openapi/paths/v2/saved_locations/{id}.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const Joi = require( "joi" );
const transform = require( "../../../joi_to_openapi_parameter" );
const SavedLocationsController = require( "../../../../lib/controllers/v2/saved_locations_controller" );

module.exports = sendWrapper => {
async function DELETE( req, res ) {
await SavedLocationsController.delete( req );
sendWrapper( req, res, null, null );
}

DELETE.apiDoc = {
tags: ["SavedLocations"],
summary: "Delete a saved location",
security: [{
userJwtRequired: []
}],
parameters: [
transform(
Joi.number( ).integer( )
.label( "id" )
.meta( { in: "path" } )
.required( )
.description( "A single ID" )
)
],
responses: {
200: {
description: "No response body; success implies deletion"
}
}
};

return {
DELETE
};
};
12 changes: 12 additions & 0 deletions openapi/schema/request/saved_locations_create.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const Joi = require( "joi" );

module.exports = Joi.object( ).keys( {
fields: Joi.any( ),
saved_location: Joi.object( ).keys( {
latitude: Joi.number( ),
longitude: Joi.number( ),
title: Joi.string( ),
positional_accuracy: Joi.number( ).integer( ),
geoprivacy: Joi.string( ).valid( "open", "obscured", "private" )
} ).required( )
} );
7 changes: 7 additions & 0 deletions openapi/schema/request/saved_locations_search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const Joi = require( "joi" );

module.exports = Joi.object( ).keys( {
q: Joi.string( ),
page: Joi.number( ).integer( ),
fields: Joi.any( )
} ).unknown( false );
9 changes: 9 additions & 0 deletions openapi/schema/response/results_saved_locations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const Joi = require( "joi" );
const savedLocation = require( "./saved_location" );

module.exports = Joi.object( ).keys( {
total_results: Joi.number( ).integer( ).required( ),
page: Joi.number( ).integer( ).required( ),
per_page: Joi.number( ).integer( ).required( ),
results: Joi.array( ).items( savedLocation ).required( )
} ).unknown( false );
14 changes: 14 additions & 0 deletions openapi/schema/response/saved_location.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const Joi = require( "joi" );

module.exports = Joi.object( ).keys( {
id: Joi.number( ).integer( )
.description( "Unique auto-increment integer identifier." ).required( ),
user_id: Joi.number( ).integer( ),
latitude: Joi.number( ),
longitude: Joi.number( ),
title: Joi.string( ),
positional_accuracy: Joi.number( ).integer( ),
created_at: Joi.date( ),
updated_at: Joi.date( ),
geoprivacy: Joi.string( ).valid( "open", "obscured", "private" ).valid( null )
} ).unknown( false ).meta( { className: "SavedLocation" } );
3 changes: 1 addition & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

76 changes: 76 additions & 0 deletions test/integration/v2/saved_locations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
const { expect } = require( "chai" );
const request = require( "supertest" );
const nock = require( "nock" );
const jwt = require( "jsonwebtoken" );
const config = require( "../../../config" );

describe( "SavedLocations", ( ) => {
const token = jwt.sign(
{ user_id: 333 },
config.jwtSecret || "secret",
{ algorithm: "HS512" }
);

describe( "search", ( ) => {
it( "returns JSON", function ( done ) {
nock( "http://localhost:3000" )
.get( "/saved_locations" )
.reply( 200, {
total_results: 1,
page: 1,
per_page: 1,
results: [{
id: 1
}]
} );
request( this.app ).get( "/v2/saved_locations" )
.set( "Authorization", token )
.expect( "Content-Type", /json/ )
.expect( 200, done );
} );
} );

describe( "create", ( ) => {
it( "returns JSON", function ( done ) {
nock( "http://localhost:3000" )
.post( "/saved_locations" )
.reply( 200, { id: 1 } );
request( this.app ).post( "/v2/saved_locations" )
.set( "Authorization", token )
.set( "Content-Type", "application/json" )
// Actual values of what we send don't matter since we're mocking the
// Rails response, but we need it to pass request schema validation
.send( {
saved_location: {
latitude: 1.1,
longitude: 2.2,
title: "NewSavedLocation",
positional_accuracy: 33,
geoprivacy: "open"
}
} )
.expect( 200 )
.expect( res => {
expect( res.body.results[0].id ).to.eq( 1 );
} )
.expect( "Content-Type", /json/ )
.expect( 200, done );
} );
} );

describe( "delete", ( ) => {
it( "should not return anything if successful", function ( done ) {
nock( "http://localhost:3000" )
.delete( "/saved_locations/1" )
.reply( 200 );
request( this.app ).delete( "/v2/saved_locations/1" )
.set( "Authorization", token )
.set( "Content-Type", "application/json" )
.expect( 200 )
.expect( res => {
expect( res.body ).to.eq( "" );
} )
.expect( 200, done );
} );
} );
} );

0 comments on commit 45488c4

Please sign in to comment.