Skip to content

Commit ef7bff3

Browse files
bewestCopilot
andcommitted
fix(food): add array support to food API POST
Add array normalization to food API POST endpoint: - API layer: normalize single object to array (like activity/profile) - Storage layer: use replaceOne loop with upsert (same as activity pattern) - Storage layer: accept both single object and array for backward compat Previously POST /api/food/ with array input would crash: insertOne([{...}]) → MongoDB error Now supports both single object and array input consistently. Response format is now array (matching treatments pattern). Fixes #8447 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 808b923 commit ef7bff3

File tree

5 files changed

+57
-16
lines changed

5 files changed

+57
-16
lines changed

lib/api/food/index.js

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
22

3+
var _isArray = require('lodash/isArray');
34
var consts = require('../../constants');
45

56
/**
@@ -13,6 +14,19 @@ function isValidObjectId(id) {
1314
return /^[a-fA-F0-9]{24}$/.test(id);
1415
}
1516

17+
/**
18+
* Validate _id field for each document in an array.
19+
* @returns {Object|null} - null if all valid, or {index, id} of first invalid
20+
*/
21+
function findInvalidId(docs) {
22+
for (var i = 0; i < docs.length; i++) {
23+
if (!isValidObjectId(docs[i]._id)) {
24+
return { index: i, id: docs[i]._id };
25+
}
26+
}
27+
return null;
28+
}
29+
1630
function configure (app, wares, ctx) {
1731
var express = require('express'),
1832
api = express.Router( );
@@ -51,14 +65,20 @@ function configure (app, wares, ctx) {
5165

5266
function config_authed (app, api, wares, ctx) {
5367

54-
// create new record
68+
// create new record(s) - supports both single object and array input
5569
api.post('/food/', ctx.authorization.isPermitted('api:food:create'), function(req, res) {
5670
var data = req.body;
5771

58-
// Validate _id if provided
59-
if (!isValidObjectId(data._id)) {
72+
// Normalize to array for consistent handling
73+
if (!_isArray(data)) {
74+
data = [data];
75+
}
76+
77+
// Validate _id fields before storage (return 400 on invalid)
78+
var invalid = findInvalidId(data);
79+
if (invalid) {
6080
return res.sendJSONStatus(res, consts.HTTP_BAD_REQUEST,
61-
'Invalid _id format', 'Must be 24-character hex string or omit for auto-generation. Got: ' + String(data._id));
81+
'Invalid _id format', 'Must be 24-character hex string or omit for auto-generation. Got: ' + String(invalid.id));
6282
}
6383

6484
ctx.food.create(data, function (err, created) {
@@ -68,7 +88,7 @@ function configure (app, wares, ctx) {
6888
console.log(err);
6989
} else {
7090
res.json(created);
71-
console.log('food created',created);
91+
console.log('food created', created);
7292
}
7393
});
7494
});

lib/server/food.js

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,26 @@
33
function storage (env, ctx) {
44
var ObjectID = require('mongodb-legacy').ObjectId;
55

6-
function create (obj, fn) {
7-
obj.created_at = (new Date( )).toISOString( );
8-
api().insertOne(obj, function (err, doc) {
9-
if (err) {
10-
console.log('Data insertion error', err, err.message);
6+
function create (docs, fn) {
7+
// Normalize to array for consistent handling (allows direct storage calls with single objects)
8+
if (!Array.isArray(docs)) {
9+
docs = [docs];
10+
}
1111

12-
}
12+
var firstErr = null
13+
, numDocs = docs.length
14+
, totalCreated = 0;
1315

14-
fn(err, obj);
16+
docs.forEach(function(doc) {
17+
doc.created_at = (new Date( )).toISOString( );
18+
var query = (doc.created_at && doc._id) ? { _id: doc._id, created_at: doc.created_at } : doc;
19+
api().replaceOne(query, doc, { upsert: true }, function(err, updateResults) {
20+
firstErr = firstErr || err;
21+
22+
if (++totalCreated === numDocs) {
23+
fn(firstErr, docs);
24+
}
25+
});
1526
});
1627
}
1728

tests/api.id-validation.test.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,10 @@ describe('_id Validation API Tests', function() {
107107
.send({ "name": "Banana", "type": "food", "carbs": 27 })
108108
.expect(200)
109109
.expect(function(response) {
110-
response.body.should.have.property('_id');
110+
// Food API returns array (consistent with treatments pattern)
111+
response.body.should.be.an.Array();
112+
response.body.length.should.equal(1);
113+
response.body[0].should.have.property('name', 'Banana');
111114
})
112115
.end(done);
113116
});

tests/fixtures/profiles-examples.js

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/storage.shape-handling.test.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -324,10 +324,12 @@ describe('Storage Layer Shape Handling - Direct Storage Tests', function () {
324324
fat: 5
325325
};
326326

327-
self.ctx.food.create(food, function (err, doc) {
327+
self.ctx.food.create(food, function (err, docs) {
328328
should.not.exist(err);
329-
should.exist(doc);
330-
doc.name.should.equal('Test Food');
329+
should.exist(docs);
330+
// Storage normalizes to array internally
331+
docs.should.be.an.Array();
332+
docs[0].name.should.equal('Test Food');
331333
done();
332334
});
333335
});

0 commit comments

Comments
 (0)