Why Pouchdb sync filter throws 404 not found error - couchdb

Does anyone know why I would get error using filters but hardcoded function works?
I am using PouchDB, pouchdb-authentication
When I use this Sync and login with uName hard coded for testing:
const db = new PouchDB('http://admin:pass#<couch_ip>:5984/dvir/', {skip_setup: true});
db.logIn('01WIMA', 'uName', function (err, response) {
//do stuff here
}).then((result) => {
const dvirStoreData = new PouchDB("dvir-StoreData");
dvirStoreData.sync(db, {
live: true,
retry: true,
auto_compaction: true
,filter: 'app/by_user',
query_params: { "user": "uName" }
});
});
With Filter doc:
dvirStoreData.put({
"_id": "_design/app",
"filters": {
"by_user": "function(doc, req) { return doc._id === '_design/app' || return doc.user === req.query.user; }.toString()"
}
})
I get error:
GET http://<couchdb_ip>/dvir/_changes?style=all_docs&filter=app%2Fby_user&user=01WIMA&since=0&limit=100 404 (Object Not Found)
This seems to work if I hardcode the filter:
dvirStoreData.sync(db, {
live: true,
retry: true,
auto_compaction: true
,filter: function (doc) { return doc._id === "_design/app" || doc.user === '01WIMA'; }
})

Related

How can I use switch button to set value to database

Please how can i achieve this!
I have a switch button i'll like to use to control boolean value (true/false) in my MongoDB database
isBanned: {
type: Boolean,
default: false,
},
my button
<b-form-checkbox
v-model="post.isBanned"
switch
#change="isBannedUser($event, post._id)"
>
{{ post.isBanned }})
</b-form-checkbox>
What I expect to happen!
If I toggle the switch checkbox from the frontend (Nuxt), I want isbanned to be set to true and change the default false value in database. If I toggle the same checkbox again next time, I want false value to be sent to the backend and change the db value from true to false and vice versa
<script>
export default {
data() {
return {
post: {
isBanned: null,
},
}
},
methods: {
async isBannedUser(e, id) {
const data = {
isBanned: this.post.isBanned,
}
try {
const response = await this.$axios.$patch(`/api/v1/posts/${id}`, data)
} catch (error) {
console.log(error)
}
},
},
}
</script>
and my API
router.patch('/posts/:id', async (req, res) => {
try {
const banned = await Post.findByIdAndUpdate(req.params.id, req.body)
res.status(201).json({ banned })
} catch (err) {
res.json({
message: err.message,
})
}
})

Automate NodeJS Express Get and Post request using Cron

I have an existing get and post request from database which is:
router.post('/stackExample', async (req, res) => {
try {
//MAKE GET REQUEST FROM MONGODB
const stackCron = await Borrower.aggregate([
{ $unwind: { path: "$application", preserveNullAndEmptyArrays: true } },
{
$project: {
'branch': '$branch',
'status': '$application.status',
},
},
{ $match: { status: 'Active' } },
]);
//MAKE POST REQUEST TO MONGODB
for (let k = 0; k < stackCron.length; k++) {
const branch = stackCron[k].branch;
const status = stackCron[k].status;
const lrInterest = await Financial.updateOne({ accountName: 'Processing Fee Income'},
{
$push:
{
"transactions":
{
type: 'Credit',
firstName: 'SysGen',
lastName: 'SysGen2',
amount: 100,
date: new Date(),
}
}
})
}
res.json({ success: true, message: "Success" });
} catch (err) { res.json({ success: false, message: 'An error occured' }); }
});
This code works fine if request is made using the client but I want to automate this via cron:
Here is what I did:
var CronJob = require('cron').CronJob;
var job = new CronJob('* * * * * *', function () {
makeRequest()
}, null, true, 'America/Los_Angeles');
job.start();
function makeRequest(message){
//Copy-paste entire router post request.
}
There seems to be no response if I copy-paste my code in the function. What have I missed?
There is no response from a cron job because there is no request coming to your makeRequest function. That makes sense because a cron job is independent of any incoming requests.
One other reason, you might not be getting any data from your updateOne operation is that it doesn't return the updated document. It returns the status of that operation instead. Take a look here. If you want to get the updated document you might want to use findOneAndUpdate.
const response = await Todo.findOneAndUpdate(
{ _id: "a1s2d3f4f4d3s2a1s2d3f4" },
{ title: "Get Groceries" },
{ new: true }
);
// response will have updated document
// We won't need this here. This is just to tell you how to get the updated document without making another database query explicitly
The body of your router function is performing an async/await operation. But you didn't specify the makeRequest function to be async. This could also be the issue.
cron job will update the database but if you want to get the updated documents, you'll have to make a GET call to the server and define a new route, with required parameters/query.
Your makeRequest function will look something like this
async function makeRequest() {
try {
//MAKE GET REQUEST FROM MONGODB
const stackCron = await Borrower.aggregate([
{ $unwind: { path: "$application", preserveNullAndEmptyArrays: true } },
{
$project: {
branch: "$branch",
status: "$application.status",
},
},
{ $match: { status: "Active" } },
]);
//MAKE POST REQUEST TO MONGODB
for (let k = 0; k < stackCron.length; k++) {
const branch = stackCron[k].branch;
const status = stackCron[k].status;
const lrInterest = await Financial.updateOne(
{ accountName: "Processing Fee Income" },
{
$push: {
transactions: {
type: "Credit",
firstName: "SysGen",
lastName: "SysGen2",
amount: 100,
date: new Date(),
},
},
}
);
}
/**
* Write to a log file if you want to keep the record of this operation
*/
} catch (err) {
/**
* Similarly write the error to the same log file as well.
*/
}
}
In your cron job
var job = new CronJob(
"* * * * * *",
async function () {
await makeRequest();
},
null,
true,
"America/Los_Angeles"
);
Your new route
router.get("/stack/:accountName", async (req, res, next) => {
const { accountName } = req.params;
try {
const financial = await Financial.find({ accountName });
res.status(200).json({ message: "success", data: financial });
} catch (err) {
res.status(500).json({ message: "error", reason: err.message });
}
});
Simply call it as
fetch(
`http://example.net/stack/${encodeURIComponent("Processing Fee Income")}`,
{ method: "GET" }
);

Mongoose Update Doesn't Run Before My Res.Send()

I currently have a controller which is handling the onboarding of a user. When the user completes their onboarding flow, I update their status in Mongo from New to Active, then send them to a new page. As a method of security, I also have a middleware function on every authenticated route which checks if the user is logged in, as well as their status. If their status is New, I send them to the onboarding flow (because theoretically they haven't seen the onboarding flow).
As I run through my experience, when I submit the onboarding flow, I get redirected back to the beginning of the flow. I check Mongo and my status is no longer New, so I was confused why this was happening. Eventually I realized when I am sending the user to a new page, the authentication route is checking the user's status before my findOneAndUpdate() has had a chance to complete. So the user gets redirected back to the onboarding flow because the last query didn't finish in time.
Any idea how to fix this? I assume it has something to do with async/await but I'm not sure. Here's my code below, I'm working in Node.JS with an express framework. Also, in my post onboarding I am using a mapbox api to get the lat/long of their zip code, which is why I have the request.get() in the code.
Onboarding Controller
exports.postOnboarding = (req, res, next) => {
var data = req.params.data;
var final = data.split(',');
location = final[4].toString();
url = "https://api.mapbox.com/geocoding/v5/mapbox.places/" + location + ".json";
request.get(url)
.query({access_token: "private_key"})
.end(function(err, result) {
User.findOneAndUpdate(
{"credentials.userId": req.session.user.credentials.userId },
{ practiceSettings: {
businessType: final[2],
experienceType: final[0],
fullFee: final[3]
},
credentials: {
userType: "Active",
active: true,
userId: req.session.user.credentials.userId,
provider: "local"
},
paySettings: {
q1: "undeternmined"
},
license: final[1],
zip: final[4],
latLong: result.body.features[0].center
}, (err, result) => {
if (err) {
console.log(err);
} else {
console.log("settings updated");
res.redirect('/dashboard');
}
}
)
}) };
Dashboard Route
router.get('/dashboard', isAuth, adminController.getDashboard);
isAuth Middleware
const User = require('../models/user');
module.exports = (req, res, next) => {
if (!req.session.isLoggedIn) {
return res.redirect('/login');
} else if (req.session.user.credentials.userType == 'Unverified') {
return res.redirect('/login?verified=false');
} else if (req.url == '/onboarding') {
return next();
}
User.findOne({"credentials.userId" : req.session.user.credentials.userId})
.then(result => {
res.locals.user = result;
if (req.session.sidebarStatus == 'closed') {
res.locals.sidebarStatus = 'closed';
}
if (result.credentials.userType == 'New') {
return res.redirect('/onboarding');
}
next();
})
}
And for reference, below is a snippet of my onboarding.ejs file which calls the post route. This isn't the whole thing, I have a lot of nested Sweet Alert modals, but this is the important part.
Swal.fire({
text: "Question",
width: "90%",
input: "text",
inputPlaceholder: "92805",
inputValidator: (value) => {
if (!value) {
return 'You must fill in this field.'
}
if (value.length != 5) {
return 'Please use a 5 digit zip-code as your answer.'
}
},
showCancelButton: false,
confirmButtonText: 'Submit',
backdrop: '#FFFFFF',
allowOutsideClick: false
})
.then((result5) => {
res3 = result3.value.replace(",", "");
final = [result1.value, result2.value, res3, result4.value, result5.value];
$.ajax({
url: "/post-onboarding/" + final,
dataType: 'json',
type: 'post',
success: function (data) {
if ( data.length ) {
Swal.fire({
title: 'Error!',
text: 'Something bad happened',
icon: 'error',
confirmButtonText: 'OK'
});
} else {
//redirect user
}
}
});

How to remove multiple records in nodejs using mongoose

Tried to remove multiple records in single call from mongodb using mongoose but not working.Where i want to change in my code.Please help to find solution.
In my code if i use like this.. it is working..
({ p_id: { $in: ['Cs1', 'Cs2', 'Cs3']} }
but if use below like
({ p_id: { $in: [records_pids] } } it is not working.Because i am getting this array values by api call.
MongoDB:
{
p_id:"Cs1",
name:"Test",
value:"Power"
},
{
p_id:"Cs2",
name:"Test",
value:"Power"
},
{
p_id:"Cs3",
name:"Test",
value:"Power"
},
{
p_id:"Cs4",
name:"Test",
value:"Power"
},
{
p_id:"Cs5",
name:"Test",
value:"Power"
}
data.controller.js:
module.exports.deleteMultipleRecord = (req, res, next) => {
var collectionMDName = req.query.collectionname;
var records_pids = req.query.pids; //Array value Cs1, Cs2, Cs3
var tableMDModal = mongoose.model(collectionMDName);
tableMDModal.deleteMany({ p_id: { $in: [records_pids] } }, function(err, docs) {
if (err) {
console.log('ss' + err);
return
} else {
console.log("Successful deleted selected records");
res.json({ data: docs, success: true, msg: 'Successful deleted selected records.', cname: collectionMDName });
}
})
}
module.exports.deleteMultipleRecord = (req, res, next) => {
var collectionMDName = req.query.collectionname;
var records_pids = req.query.pids; //Array value CS1, CS2, CS3
var tableMDModal = mongoose.model(collectionMDName);
tableMDModal.deleteMany({ p_id: { $in: records_pids } }, function(err, docs) {
if (err) {
console.log('ss' + err);
return
} else {
console.log("Successful deleted selected records");
res.json({ data: docs, success: true, msg: 'Successful deleted selected records.', cname: collectionMDName });
}
})
}
the error is semantic , rather than searching for values $in: [CS1, CS2, CS3], the search is being made as [[CS1, CS2, CS3]]
Also,have a look at https://mongoosejs.com/docs/models.html for defining models.
MongoDB Enterprise Cluster0-shard-0:PRIMARY> use new
switched to db new
MongoDB Enterprise Cluster0-shard-0:PRIMARY> use neo
switched to db neo
MongoDB Enterprise Cluster0-shard-0:PRIMARY> db.coll.insertMany([{ p_id:"Cs1", name:"Test", value:"Power" }, { p_id:"Cs2", name:"Test", value:"Power" }, { p_id:"Cs3", name:"Test", value:"Power" }, { p_id:"Cs4", name:"Test", value:"Power" }, { p_id:"Cs5", name:"Test", value:"Power" }])
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("5ecb90001a40be1d77da2aa8"),
ObjectId("5ecb90001a40be1d77da2aa9"),
ObjectId("5ecb90001a40be1d77da2aaa"),
ObjectId("5ecb90001a40be1d77da2aab"),
ObjectId("5ecb90001a40be1d77da2aac")
]
}
MongoDB Enterprise Cluster0-shard-0:PRIMARY> const records_id =["Cs1","Cs2","Cs3"]
MongoDB Enterprise Cluster0-shard-0:PRIMARY> db.coll.deleteMany({p_id:{$in:records_id}})
{ "acknowledged" : true, "deletedCount" : 3 }

Routing to sub docs with express 4 and mongoose

EDIT: It's possible the problem is an issue with pathing. my current query looks like this:
router.route('/projects/:project_id/techDetails')
.get(function(req, res) {
Project.findById(req.params.project_Id, function(err, project) {
if (err)
return res.send(err);
res.json(project);
console.log('get success (project techDetails)');
});
});
this returns null. even though it's identical to a working line of code in every way except for the addition of `/techDetails' to the route.
original question:
I'm building a MEAN stack app with express and mongo. I can't figure out how to route to nested documents properly.
here is my Project schema:
const ProjectSchema = new Schema({
idnumber: { type: Number, required: true },
customername: String,
projectdetails: String,
jobaddress: String,
techDetails: [{
scope: String,
edgedetail: String,
lamination: String,
stonecolour: String,
slabnumber: String,
slabsupplier: String,
purchaseordernum: String,
splashbacks: String,
apron: String,
hotplate: String,
sink: String,
sinkdetails: String,
tappos: String
}],
sitecontactname: String,
sitecontactnum: String,
specialreq: String,
install_date: String,
created_on: { type: Date, default: Date.now },
created_by: { type: String, default: 'SYSTEM' },
active: { type: Boolean, default: true },
flagged: { type: Boolean, default: false },
});
I can successfully route to /projects with GET and POST, and /projects/:project_id with GET, PUT and DEL.
using the PUT route and a project's _ID i can push new entries to a project's techDetails subdoc array. the resulting JSON data looks like this:
{
"_id": "59e577e011a3f512b482ef13",
"idnumber": 52,
"install_date": "10/20/2017",
"specialreq": "some...",
"sitecontactnum": "987654321",
"sitecontactname": "bill",
"jobaddress": "123 st",
"projectdetails": "some stuff",
"customername": "B Builders",
"__v": 16,
"flagged": false,
"active": true,
"created_by": "SYSTEM",
"created_on": "2017-10-17T03:24:16.423Z",
"techDetails": [
{
"scope": "Howitzer",
"edgedetail": "12mm",
"lamination": "No",
"stonecolour": "Urban™",
"slabnumber": "1",
"slabsupplier": "Caesarstone",
"purchaseordernum": "no",
"splashbacks": "No",
"apron": "No",
"hotplate": "N/A",
"sink": "N/A",
"sinkdetails": "no",
"tappos": "no",
"_id": "59e577e011a3f512b482ef14"
},
{
"scope": "kitchen",
"edgedetail": "12mm",
"lamination": "etc",
"_id": "59e7da445d9d7e109c18f38b"
},
{
"scope": "Vanity",
"edgedetail": "12mm",
"lamination": "No",
"stonecolour": "Linen™",
"slabnumber": "1",
"slabsupplier": "Caesarstone",
"purchaseordernum": "1",
"splashbacks": "No",
"apron": "No",
"hotplate": "N/A",
"sink": "N/A",
"sinkdetails": "no",
"tappos": "woo",
"_id": "59e81e3324fb750fb46f8248"
}//, more entries omitted for brevity
]
}
as you can see everything so far is working as expected. However now i need to edit and delete individual entries in this techDetails array. i'd also like to route to them directly using projects/:project_id/techDetails and projects/:project_id/techDetails/:techdetails_id.
From what i can see there are two approaches to this. either i can:
A) use a new routing file for the techDetails that uses mergeParams. this is the approach i'm trying currently, however I can't figure out how to complete the .find to return all techDetails, since i can only use the Project model schema and i'm unsure how to access the sub docs.
an excerpt from my routes.js:
const techDetails = require('./techDetails');
//other routes here
//see techdetails file
router.use('/projects/:project_id/techdetails', techDetails);
//here lies an earlier, failed attempt
/* router.route('/projects/:project_id/techdetails/:techDetails_id')
.get(function(req, res) {
Project.findById(req.params.project_id.techDetails_id, function(err,
project) {
if (err)
return res.send(err);
res.json(project.techDetails);
console.log('get success (techDetails)');
});
})
; */
and my techdetails.js:
const express = require('express');
const Project = require('./models/project');
const router = express.Router({mergeParams: true});
router.get('/', function (req, res, next) {
/* Project.find(function(err, techDetails) {
if (err)
return res.send(err);
res.json(techDetails);
console.log('get success (all items)');
}); */
res.send('itemroutes ' + req.params);
})
router.get('/:techDetails_id', function (req, res, next) {
res.send('itemroutes ' + req.params._id)
})
module.exports = router
I can successfully check that the routes work with Postman, both will receive the response. now the problem is, instead of res.send i want to use res.json with Project.find (or similar) to get the techDetails.
however there is also another option:
B) put the techDetails document into it's own schema and then populate an array of IDs inside projects.
however this seems more complex so i'd rather avoid having to do so if i can.
any thoughts and suggestions welcome. let me know if more of my code is needed.
In this particular case I would put techDetails in a separate schema:
const ProjectSchema = new Schema({
idnumber: { type: Number, required: true },
customername: String,
projectdetails: String,
jobaddress: String,
techDetails: [techDetailsSchema],
sitecontactname: String,
sitecontactnum: String,
specialreq: String,
install_date: String,
created_on: { type: Date, default: Date.now },
created_by: { type: String, default: 'SYSTEM' },
active: { type: Boolean, default: true },
flagged: { type: Boolean, default: false },
});
Don't register the techDetails schema with mongoose.model as it is a subdocument. Put it in a separate file and require it in the project model file (const techDetailsSchema = require('./techDetails.model');).
I would create the controller functions like this:
Getting with GET (all):
module.exports.techDetailsGetAll = function (req, res) {
const projectId = req.params.projectId;
Project
.findById(projectId)
.select('techDetails')
.exec(function (err, project) {
let response = { };
if (err) {
response = responseDueToError(err);
} else if (!project) {
response = responseDueToNotFound();
} else {
response.status = HttpStatus.OK;
response.message = project.techDetails;
}
res.status(response.status).json(response.message);
});
}
Getting with GET (one):
module.exports.techDetailsGetOne = function (req, res) {
const projectId = req.params.projectId;
const techDetailId = req.params.techDetailId;
Project
.findById(projectId)
.select('techDetails')
.exec(function (err, project) {
let response = { };
if (err) {
response = responseDueToError(err);
} else if (!project) {
response = responseDueToNotFound();
} else {
let techDetails = project.techDetails.id(techDetailId);
if (techDetails === null) {
response = responseDueToNotFound();
} else {
response.status = HttpStatus.OK;
response.message = techDetails;
}
}
res.status(response.status).json(response.message);
});
}
For adding with POST:
module.exports.techDetailsAddOne = function (req, res) {
const projectId = req.params.projectId;
let newTechDetails = getTechDetailsFromBody(req.body);
Project
.findByIdAndUpdate(projectId,
{ '$push': { 'techDetails': newTechDetails } },
{
'new': true,
'runValidators': true
},
function (err, project) {
let response = { };
if (err) {
response = responseDueToError(err);
} else if (!project) {
response = responseDueToNotFound();
} else {
response.status = HttpStatus.CREATED;
response.message = project.techDetails; // for example
}
res.status(response.status).json(response.message);
});
}
For updating with PUT
module.exports.techDetailsUpdateOne = function (req, res) {
const projectId = req.params.projectId;
const techDetailId = req.params.techDetailId;
let theseTechDetails = getTechDetailsFromBody(req.body);
theseTechDetails._id = techDetailId; // can be skipped if body contains id
Project.findOneAndUpdate(
{ '_id': projectId, 'techDetails._id': techDetailId },
{ '$set': { 'techDetails.$': theseTechDetails } },
{
'new': true,
'runValidators': true
},
function (err, project) {
let response = { };
if (err) {
response = responseDueToError(err);
res.status(response.status).json(response.message);
} else if (!project) {
response = responseDueToNotFound();
res.status(response.status).json(response.message);
} else {
project.save(function (err) {
if (err) {
response = responseDueToError(err);
} else {
response.status = HttpStatus.NO_CONTENT;
}
res.status(response.status).json(response.message);
})
}
});
}
And deleting with DELETE:
module.exports.techDetailsDeleteOne = function (req, res) {
const projectId = req.params.projectId;
const techDetailId = req.params.techDetailId;
Project
.findById(projectId)
.select('techDetails')
.exec(function (err, project) {
let response = { }
if (err) {
response = responseDueToError(err);
res.status(response.status).json(response.message);
} else if (!project) {
response = responseDueToNotFound();
res.status(response.status).json(response.message);
} else {
let techDetail = project.techDetails.id(techDetailId);
if (techDetail !== null) {
project.techDetails.pull({ '_id': techDetailId });
project.save(function (err) {
if (err) {
response = responseDueToError(err);
} else {
response.status = HttpStatus.NO_CONTENT;
}
res.status(response.status).json(response.message);
})
} else {
response = responseDueToNotFound();
res.status(response.status).json(response.message);
}
}
});
}
And finally routing like this:
router
.route('/projects')
.get(ctrlProjects.projectsGetAll)
.post(ctrlProjects.projectsAddOne);
router
.route('/projects/:projectId')
.get(ctrlProjects.projectsGetOne)
.put(ctrlProjects.projectsUpdateOne)
.delete(ctrlProjects.projectsDeleteOne);
router
.route('/projects/:projectId/techDetails')
.get(ctrlTechDetails.techDetailsGetAll)
.post(ctrlTechDetails.techDetailsAddOne);
router
.route('/projects/:projectId/techDetails/:techDetailId')
.get(ctrlTechDetails.techDetailsGetOne)
.put(ctrlTechDetails.techDetailsUpdateOne)
.delete(ctrlTechDetails.techDetailsDeleteOne);
This is what I prefer when I'm constantly updating the subdocument independently of the rest of the document. It doesn't create a separate collection, so no need for populate.
EDIT:
This answer goes more into detail on whether you should use embedding or referencing. My answer uses embedding.
So, the solution i came to was a combo of A) and B). I used a separate routing file and put ({mergeParams: true}) in the router declaration, and i created a separate file for the techDetails nested model, without declaring it. However I don't believe either of these actually made any significance... but anyway.
the working code i ended up with was, in my routes:
router.use('/projects/:project_id/techDetails', TechDetails);
and in techDetails.js:
const router = express.Router({mergeParams: true});
router.route('/')
.get(function(req, res) {
Project.findById(req.params.project_id,
'techDetails', function(err, project) {
if (err)
return res.send(err);
res.json(project);
console.log('get success (project techDetails)');
});
});
What's different about it? namely, the 'techDetails', parameter in the Project.findById line. According to the mongoose API this acts as a select statement. The only other major difference is I fixed a typo in my original code ( project_id was written project_Id. dubious... ). I probably would have noticed this if i was using VS or something instead of notepad++, but it is my preferred coding arena.
It may be possible to return res.json(project.techDetails) and remove the 'techDetails', select parameter, but I likely won't test this.
Edit: Turns out migrating techDetails to a separate file meant they no longer generated with objectIds, which is crucial for PUT and DEL. I might've been able to work around them with a simple pair of curly braces inside the array declaration, but I didn't think of that until after i re-migrated it back to the project schema...

Resources