I am doing a Node.js REST API tutorial. I use Express, Knex.js (0.19.0) and PostgreSQL.
I have two database tables, users:
// user_migration.js
exports.up = function(knex) {
return knex.schema.createTable('users', function(table) {
table
.increments('id')
.primary()
.unsigned();
table.string('firstName');
table
.string('lastName')
.index()
.notNullable();
table
.string('email')
.unique()
.index()
.notNullable();
table.string('password').notNullable();
table.string('role').defaultTo('STAFF');
table.boolean('isActive').defaultTo(false);
table.timestamp('createdAt').defaultTo(knex.fn.now());
table.timestamp('updatedAt').defaultTo(knex.fn.now());
});
};
and posts:
// post_migration.js
exports.up = function(knex) {
return knex.schema.createTable('posts', function(table) {
table
.increments('id')
.primary()
.unsigned();
table.string('title').notNullable();
table.text('body');
table.boolean('published').defaultTo(false);
table
.integer('author')
.unsigned()
.index()
.references('id')
.inTable('users')
.onDelete('SET NULL');
table.timestamp('createdAt').defaultTo(knex.fn.now());
table.timestamp('updatedAt').defaultTo(knex.fn.now());
});
};
I want to make a GET request at http://localhost:8081/users/1/posts to show user.id 1's posts.
// user_get.js
async getPosts(req, res, next) {
try {
// Check if user exists
const user = await this.knex('users')
.where('id', req.params.id)
.first();
// If not, return NOT FOUND status code
if (!user) return next(createError(404, 'User not found'));
/**
* Right here, I am not sure if I am doing it right.
*/
// Get from database and filter
const result = await this.knex('users')
.join('posts', 'posts.author', '=', 'users.id')
.select()
.then(posts => posts.filter(post => post.author === user.id));
// Return OK status code and related posts
res.status(200).send(result);
} catch (error) {
// Return BAD REQUEST status code
return next(createError(400, error);
}
}
What I expected is an array of posts belong to user 1:
[
{
"id": 1,
"title": "Number One Post",
"body": "This is the one body",
"published": true,
"author": 1,
"createdAt": "2019-07-23T06:14:04.281Z",
"updatedAt": "2019-07-23T06:14:04.281Z"
},
{
"id": 2,
"title": "Number Two Post",
"body": "This is two body",
"published": false,
"author": 1,
"createdAt": "2019-07-23T06:14:04.281Z",
"updatedAt": "2019-07-23T06:14:04.281Z"
}
]
But I got like this:
[
{
"id": 1,
"firstName": "Some",
"lastName": "One",
"email": "some#one.com",
"password": "password789",
"role": "STAFF",
"isActive": false,
"createdAt": "2019-07-23T06:14:04.281Z",
"updatedAt": "2019-07-23T06:14:04.281Z",
"title": "Number One Post",
"body": "This is the one body",
"published": true,
"author": 1
},
{
"id": 2,
"firstName": "Some",
"lastName": "One",
"email": "some#one.com",
"password": "password789",
"role": "STAFF",
"isActive": false,
"createdAt": "2019-07-23T09:21:34.285Z",
"updatedAt": "2019-07-23T09:21:34.285Z",
"title": "Number Two Post",
"body": "This is two body",
"published": false,
"author": 1
}
]
How should I query user 1's posts without mashing up with user info?
Please help.
P.S. Also updatedAt in Knex.js does not work correctly. It does not update the timestamp when I update. How do I fix this?
Just drop your join on users in the second query
const result = await this.knex('posts')
.where('posts.author', user.id)
.select()
// Return OK status code and related posts
res.status(200).send(result);
Related
I've just started using StrapiJS, and it's really great so far, but now I've come across a problem.
When creating adding some data to the database, Strapi automatically creates some fields - like created_by and updated_by fields, but I'm not getting them in the API response.
This is the data stored in MongoDB:
{
"_id": {
"$oid": "606fd90b0057c05954d29f50"
},
"likes": 0,
"dislikes": 0,
"Title": "Why is this not working?",
"Subtitle": "I really don't know",
"slug": "why-is-this-not-working",
"content": "**I DO NOT KNOW**",
"published_at": {
"$date": "2021-04-09T04:33:17.304Z"
},
"createdAt": {
"$date": "2021-04-09T04:33:15.232Z"
},
"updatedAt": {
"$date": "2021-04-09T04:33:17.316Z"
},
"__v": 0,
"created_by": {
"$oid": "606f1a45af15265f780983ce"
},
"updated_by": {
"$oid": "606f1a45af15265f780983ce"
}
}
And this is the API response:
{
"likes": 0,
"dislikes": 0,
"_id": "606fd90b0057c05954d29f50",
"Title": "Why is this not working?",
"Subtitle": "I really don't know",
"slug": "why-is-this-not-working",
"content": "**I DO NOT KNOW**",
"published_at": "2021-04-09T04:33:17.304Z",
"createdAt": "2021-04-09T04:33:15.232Z",
"updatedAt": "2021-04-09T04:33:17.316Z",
"__v": 0,
"id": "606fd90b0057c05954d29f50"
}
Is there a way to get the created of the data in the API response?
You will have to override the findOne method in your controller like below without the sanitize:
async findOne(ctx) {
const { id } = ctx.params;
const entity = await strapi.services.blogPost.findOne({ id });
// return sanitizeEntity(entity, { model: strapi.models.blogPost});
return entity;
}
refer:Backend customization
set populateCreatorFields=true in model.settings.json
This seem to work for me
Add
"populateCreatorFields": true
Add Optional
"publicAttributes": ["created_at", "updated_by"]
to
api/blog-post/models/blog-post.settings.json
...
"options": {
"increments": true,
"timestamps": true,
"draftAndPublish": true,
"populateCreatorFields": true
}
...
I've got the following json response:
{
"vin": "BAUV114MZ18091106",
"users": [
{
"role": "PRIMARY_USER",
"status": "ACTIVE",
"securityLevel": "HG_2_B",
"firstName": "Etienne",
"lastName": "Rumm",
"nickName": "BastieW",
"isInVehicle": false
},
{
"role": "SECONDARY_USER",
"status": "ACTIVE",
"securityLevel": "HG_2_B",
"firstName": "Test",
"lastName": "DEde",
"isInVehicle": false
}
]
}
I want to test the "isInVehicle" key and pass the test, if it's true and fail the test, if it's false.
I was trying to do so with following test code, but it didn't work, tests are always passed, no matter what response I got.
pm.test("User is in Vehicle", () => {
_.each(pm.response.json(), (arrItem) => {
if (arrItem.isInVehicle === 'true') {
throw new Error(`Array contains ${arrItem.isInVehicle}`)
}
})
});
Are there any ideas on how to solve my problem?
I think you are iterating through an object(root object of your response) instead of the user array. The revised version would be:
var users = pm.response.users;
_.each(users, (arrItem) => {
if (arrItem.isInVehicle) {
//Do something if isInVehicle is true
}
})
});
You can do these using array properties,
some - returns true if at least one match the condition
every - returns true if all items match the condition
const response = {
"vin": "BAUV114MZ18091106",
"users": [{
"role": "PRIMARY_USER",
"status": "ACTIVE",
"securityLevel": "HG_2_B",
"firstName": "Etienne",
"lastName": "Rumm",
"nickName": "BastieW",
"isInVehicle": false
},
{
"role": "SECONDARY_USER",
"status": "ACTIVE",
"securityLevel": "HG_2_B",
"firstName": "Test",
"lastName": "DEde",
"isInVehicle": false
}
]
};
pm.test("User is in Vehicle", () => {
// I'm assuming you are looking for atleast one match
const atleastOneMatch = response.users.some(user => user.isInVehicle);
// if you are looking for all should match, uncomment the following code
// const allShouldMatch = response.users.every(user => user.isInVehicle);
if(atleastOneMatch) {
// do your stuffs here
}
})
This is my posting document in MongoDB:
{
"_id": {
"$oid": "5b4e60ab24210138f5746402"
},
"type": [
"full",
"temp"
],
"applications": [
{
"_id": {
"$oid": "5b52113d1123631744fa9f39"
},
"applicationDate": {
"date": 20,
"day": 5,
"hours": 18,
"minutes": 43,
"month": 6,
"seconds": 41,
"time": 1532105021753,
"timezoneOffset": -120,
"year": 2018
},
"userId": {
"$oid": "5b51fb6f9686430cee31a0d9"
},
"resume": {
"fieldname": "resume",
"originalname": "resume_acc.pdf",
"encoding": "7bit",
"mimetype": "application/pdf",
"destination": "./public/resumes/",
"filename": "765d650b9014cc3ddadb801d10d495a5",
"path": "public/resumes/765d650b9014cc3ddadb801d10d495a5",
"size": 8
},
"coverLetter": {
"fieldname": "docs",
"originalname": "cover letter.pdf",
"encoding": "7bit",
"mimetype": "application/pdf",
"destination": "./public/resumes/",
"filename": "e5892869b24f3fc5e72d3e057b4dd61d",
"path": "public/resumes/e5892869b24f3fc5e72d3e057b4dd61d",
"size": 5
}
}
],
"creatorId": {
"$oid": "5b4b95cc16778c325010a55d"
},
"title": "Developer",
"salary": "50/h",
"timeLine": "One year",
"description": "You'll be building apps",
"duties": "Building apps",
"experience": "2 years",
"province": "ON",
"visible": true,
"__v": 0
}
Postings is an array of posting, which look like the above document. applications is an array which is in every posting. I want to search all postings.applications to see get all postings the user applied to. For now I tried to do it like this:
var Posting = require('../models/posting');
var postings = await Posting
.find({'visible': true});
console.log('posts', postings);
var applications = await Posting
.find({'visible': true})
.where(postings
.map(posting => posting.applications
.map(application => application.userId.equals(req.user._id)))
);
But obviously this failed.
I tried this as well:
var postings = await Posting
.find({'visible': true, 'applications[$].userId': req.user._id});
or
var postings = await Posting
.find({'visible': true, 'applications.$.userId': req.user._id});
But no luck. They both return an empty array.
Posting model:
var mongoose = require('mongoose');
jobPostingSchema = mongoose.Schema({
"creatorId": mongoose.Schema.Types.ObjectId, //ObjectID('aaaa'), // ID of the User or Account
"eventId": {'type': mongoose.Schema.Types.ObjectId, 'default': undefined},
"title": String,
"type": [], //"Full", // What this means? I did not understand.
"salary": String,
"timeLine": String, // What this means? I did not understand.
"description": String,
"duties": String,
"experience": String,
"province": String, // Employer will post job postings based on the province and region
// Applications means, how many people applied for this job post?
"applications": [
// {
// ObjectID: cccc,
// userId: dddd,
// Resume: {},
// coverLetter: String,
// },
],
"visible": Boolean,
});
module.exports = mongoose.model('posting', jobPostingSchema);
So how can I get all applications where userId equals req.user._id?
Maybe this works as a solution ( sourcing from the SO link shared by #DSCH here ):
Posting.find({
'applications': {
$elemMatch: { userId: req.user._id }
},
'visible:': true
});
If you wish to seek clarification on how it works, you may refer to the link here
Posting.find({
'visibile:': true,
'applications': {
$elemMatch: { userId: req.user._id }
}
});
$elemMatch is the mongo operator that you probably need.
Hope that one helps better.
I am a newbie to NodeJS and Sails.js.
I want create a REST API that allows me to expand a resource based on query parameter. For eg
HTTP GET /snippets
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"url": "http://localhost:8000/snippets/1/",
"highlight": "htep://localhost:8000/snippets/1/highlight/",
"title": "test",
"code": "def test():\r\n pass",
"linenos": false,
"language": "Clipper",
"style": "autumn",
"owner": "http://localhost:8000/users/2/",
"extra": "http://localhost:8000/snippetextras/1/"
}
]}
HTTP GET /snippets?expand=owner
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"url": "http://localhost:8000/snippets/1/",
"highlight": "http://localhost:8000/snippets/1/highlight/",
"title": "test",
"code": "def test():\r\n pass",
"linenos": false,
"language": "Clipper",
"style": "autumn",
"owner": {
"url": "http://localhost:8000/users/2/",
"username": "test",
"email": "test#test.com"
},
"extra": "http://localhost:8000/snippetextras/1/"
}
]}
Wondering how can I do that in Sails.js or NodeJS?
You should use assocations.
Here is how you would create a one-to-many association between your User model and your Snippet model:
// User.js
module.exports = {
// ...
attributes: {
// ...
snippets: {
collection: 'Snippet',
via: 'owner'
}
}
};
// Snippet.js
module.exports = {
// ...
attributes: {
// ...
owner: {
model: 'User'
}
}
};
Then you can hit /snippets if you want a list of snippets, and hit /snippets?populate=[owner] if you need details about the owners of the snippets.
I've having trouble removing a item from the upload [] object.
The below represents a User, keys[] represents a key for which file uploads get associated with, and uploads[] are files beneath that key. These are all documents embedded within the User model. I realize now I'd have been way better off using references but I am stuck with this for now. Here is the function I'm using right now to find the uploads item,
______________THIS IS MY CURRENT FUNCTION_____________________
I'll be honest I am using async and I don't exactly understand it well. Is there a different async function or way to lookup these items in mongo that would work better?
Current issues: 1) This will continue looping through until the end even after it finds the correct items. 2) How can I delete the upload item?
exports.getApiDelete = function (req, res, next) {
User.findById(req.user.id, function(err, user) {
if (err) return next(err);
console.log("User ID found: "+ user._id);
//loop though user keys
async.forEach(user.profile.keys, function(item, callback) {
//verify key exists
if(item.key==req.params.scriptkey){console.log("KEY FOUND")};
async.forEach(item.uploads, function(item, callback) {
//verify file exits
console.log(req.params.file_id);
if(item._id == req.params.file_id){
// DELETE FUNCTION HERE?
};
}, function(err){
console.log('Error during async lookup: '+err);
});
}, function(err){
console.log('Error during async lookup: '+err);
});
});
};
______________________________THIS MY USER MODEL_____________________
{
"__v": 19,
"_id": {
"$oid": "53c812c4e75ab0b013f3c6bc"
},
"email": "fake#mailinator.com",
"password": "fake",
"profile": {
"gender": "",
"keys": [
{
"_id": {
"$oid": "53c8130ae75ab0b013f3c6bd"
},
"status": false,
"iteration": 0,
"created": {
"$date": "2014-07-17T18:16:42.568Z"
},
"uploads": [],
"description": "This is being run from my Windows Desktop.",
"location": "Front Row",
"name": "fake_Desktop",
"key": "80f94c80-0dde-11e4-ae14-43922f7b8f23"
},
{
"_id": {
"$oid": "53c814ade75ab0b013f3c6be"
},
"created": {
"$date": "2014-07-17T18:23:41.777Z"
},
"description": "Windows VM test.",
"iteration": 12,
"key": "7ad78410-0ddf-11e4-ae14-43922f7b8f23",
"location": "Back Right",
"name": "fake2_Desktop",
"status": false,
"uploads": [
{
"_id": {
"$oid": "53c81517e75ab0b013f3c6bf"
},
"ip": "10.0.1.156",
"fname": "hklm_1.txt",
"iteration": 1,
"created": {
"$date": "2014-07-17T18:25:27.241Z"
},
"filepath": "script_uploads/7ad78410-0ddf-11e4-ae14-43922f7b8f23_1_hklm_1.txt"
},
{
"_id": {
"$oid": "53c8151ae75ab0b013f3c6c0"
},
"ip": "10.0.1.156",
"fname": "hklm_1.txt",
"iteration": 2,
"created": {
"$date": "2014-07-17T18:25:30.634Z"
},
"filepath": "script_uploads/7ad78410-0ddf-11e4-ae14-43922f7b8f23_2_hklm_1.txt"
}
]
}
}
You want to remove only from array? If yes, use
for(var i =0, j = item.uploads.length; i < j; i++) {
//verify file exits
console.log(req.params.file_id);
if(item.uploads[i]._id == req.params.file_id){
item.uploads.slice(i, 1);
};
And at the end use: user.save(function(err){});
If there is anything you want to delete from file system, use:
fs = require('fs');
fs.unlink( FILE PATH , function(err) {
console.log(err);
});
Also you don't really need async version of forEach, cause User.findById is asynchronous itself and whole process goes on background.
This will continue looping through until the end even after it finds
the correct items.
There is no "break" for async.forEach. So if you don't want to do unwanted process, use for as I did and append a break point.
There is no async call inside your loops, so you don't need async.forEach(). Using javascript native loops would be just fine:
exports.getApiDelete = function (req, res, next) {
User.findById(req.user.id, function(err, user) {
if (err) return next(err);
console.log("User ID found: "+ user._id);
user.profile.keys.forEach(function(el) {
if(el.key==req.params.scriptkey){console.log("KEY FOUND");}
el.uplaods.forEach(function(item) {
console.log(req.params.file_id);
if(item._id == req.params.file_id){
// DELETE FUNCTION HERE?
}
});
});
});
};