Meteor and React Native - FileCollection Access - mongodb

i posted this question some time ago at the FilesCollection Github Repo (https://github.com/veliovgroup/Meteor-Files), but I'll hope to reach out here anyone who is familiar with Meteor and React Native.
My problem is, that I'm not sure how to use a FilesCollection with React Native (RN) and Meteor.
I used the official Meteor guide to set up a RN app: https://guide.meteor.com/react-native.html
So i have access to #meteorrn/core but how is it now possible for me to access a FileCollection.
Usually you would do this with (on a non-RN-web app):
import { FilesCollection } from "meteor/ostrio:files";
export const ImageDB = new FilesCollection(chosenSettings);
where chosenSettings are some settings might be e.g.
const chosenSettings = {
collectionName: "Images",
allowClientCode: false,
onBeforeUpload(file) {
if (file.size <= 10485760 && /png|jpg|jpeg/i.test(file.extension)) {
return true;
}
return "Please upload image, with size equal or less than 10MB";
},
storagePath: `${process.env.PWD}/images`,
};
However, with RN it is not possible to access FilesCollection from meteor/ostrio:files as I don't have any meteor relation here
With other collections I can use Mongo.Collection from #meteorrn/core, however this is not possible with FilesCollection:
const collection = new Mongo.Collection("myCollection");
Any suggestions?

There is no "out-of-the-box" solution, as there is no FilesCollection on the RN client available. However, you could create a workaround strategy using a Meteor method.
Create a publication for the FilesCollection's underlying collection:
server
Meteor.publish('imagesForRN', function () {
return ImageDB.collection.find({}) // note the Mongo.collection is accessible here
})
Create a Method to resolve links:
server
Meteor.methods({
resolveRNLinks ({ ids }) {
return ImageDB.collection
.find({ _id: { $in: ids}})
.map(linkImage);
}
})
const linkImage = function (fileRef) {
const url = Images.link(fileRef, 'original', Meteor.absoluteUrl())
return { _id: fileRef._id, url }
}
Get the URLS of the subscribed docs
This assumes you have subscribed to the Image Collection as shown in the guide.
client
import Meteor from '#meteorrn/core'
const MyComponent = props => {
const { loading, myTodoTasks } = this.props;
useEffect(() => {
const ids = myTodoTasks.map(doc => doc._id)
// note, you should filter those ids, which you have already
// resolved so you only call for the new ones
Meteor.call('resolveRNLinks', { ids }, (err, urls) => {
// store urls in state or ref, depending on your usecase
})
})
}
Resources
https://github.com/TheRealNate/meteor-react-native
https://github.com/veliovgroup/Meteor-Files/blob/master/docs/collection.md
https://github.com/veliovgroup/Meteor-Files/blob/master/docs/link.md

Related

Mongoose not creating document on mongodb

I am using Next.js and MongoDB (mongoose) to make an education portal website. The school admin can add important notices from their dashboard and the student can view them from theirs. Most mongoose operations have to be done in an API that next.js provides them self so my code to create the document on MongoDB is located in /API/ directory. Code:
/**
* #param {import("next").NextApiRequest} req
* #param {import("next").NextApiResponse} res
*/
import circulars from '../../../model/hw'
export default async function stuffss(req, res) {
try{
const {number, text} = req.body
console.log("Connecting to mongo")
mongoose.connect("mongodb+srv://usernameherer:passowordhere#cluster0.clusteridhere.mongodb.net/stuff?retryWrites=true&w=majority")
console.log("Connected to mongo")
console.log("Creating document")
const createdCircular = await circulars.create(req.body)
console.log("Created document")
res.json({ createdCircular })
}
catch(error) {
console.log(error)
res.json({ error })
}
}
The circulars schema:
const mongoose = require('mongoose')
const circularsSchema = new mongoose.Schema({
number: {
unique: true,
required: true,
type: Number
},
text: {
required: true,
type: String
}
})
const circulars = mongoose.models.circularss || mongoose.model("circularss", circularsSchema)
export default circulars
Sending a post request using insomnia gives the following result:
{
"createdCircular": {
"number": 15463,
"text": "loreum ipsum",
"_id": "63cb86421ca543a731cb39c3",
"__v": 0
}
}
but when I go and look for it on the MongoDB website it doesn't have any results.
I believe it may be because of how I am connecting or that I am not sending it properly to a collection.
This is the structure of my database:
Is there a way to connect to the 'stuff' database and 'circulars' collection?
Looks like that the database needs to be specified in connection string.
E.g.
...clusteridhere.mongodb.net/stuff?retryWrites=true&w=majority
Ref: https://mongoosejs.com/docs/connections.html
Here is how I fixed this issue. In my schema I had put the same circulars which created created a new collection for it, I also had to remove await from mongoose.connect. This combined fixed the issue for me. You should also make sure that mongoose.models.name is the same as the mongoose.model("name", schema)

How to pass data for sync function using watermelondb

Good day everyone, I am working with watermelondb and I have the code below, but I don't know how to actually use it. I am new in watermelondb and I don't know how to pass data as props to the pullChanges and pushChanges objects. How do I pass necessary data like changes and lastPulledAt from the database into the sync function when I call it. And I need more explanation on the migrationsEnabledAtVersion: 1 too. Thanks in advance for your gracious answers.
import { synchronize } from '#nozbe/watermelondb/sync'
async function mySync() {
await synchronize({
database,
pullChanges: async ({ lastPulledAt, schemaVersion, migration }) => {
const urlParams = `last_pulled_at=${lastPulledAt}&schema_version=${schemaVersion}&migration=${encodeURIComponent(JSON.stringify(migration))}`
const response = await fetch(`https://my.backend/sync?${urlParams}`)
if (!response.ok) {
throw new Error(await response.text())
}
const { changes, timestamp } = await response.json()
return { changes, timestamp }
},
pushChanges: async ({ changes, lastPulledAt }) => {
const response = await fetch(`https://my.backend/sync?last_pulled_at=${lastPulledAt}`, {
method: 'POST',
body: JSON.stringify(changes)
})
if (!response.ok) {
throw new Error(await response.text())
}
},
migrationsEnabledAtVersion: 1,
})
}
Watermelondb's documentation is terrible and its link to typescript even worse.
I spent almost a week to get 100% synchronization with a simple table, now I'm having the same problems to solve the synchronization with associations.
Well, the object you need to return in pullChanges is of the following form:
return {
changes: {
//person is the name of the table in the models
person: {
created: [
{
// in created you need to send null in the id, if you don't send the id it doesn't work
id: null,
// other fields of your schema, not model
}
],
updated: [
{
// the fields of your schema, not model
}
],
deleted: [
// is a string[] consisting of the watermelondb id of the records that were deleted in the remote database
],
}
},
timestamp: new Date().getTime() / 1000
}
In my case, the remote database is not a watermelondb, it's a mySQL, and I don't have an endpoint in my API that returns everything in the watermelon format. For each table I do a search with deletedAt, updatedAt or createdAt > lastPulledAt and do the necessary filtering and preparations so that the data from the remote database is in the schema format of the local database.
In pushChanges I do the reverse data preparation process by calling the appropriate creation, update or deletion endpoints for each of the tables.
It's costly and annoying to do, but in the end it works fine, the biggest problem is watermelon's documentation which is terrible.

POST collection of objects in json-server

I am using json-server to fake the Api for the FrontEnd team.
We would like to have a feature to create the multiple objects (Eg. products) in one call.
In WebApi2 or actual RestApis, it can be done like the following:
POST api/products //For Single Creation
POST api/productCollections //For Multiple Creation
I don't know how I can achieve it by using json-server. I tried to POST the following data to api/products by using the postman, but it does not split the array and create items individually.
[
{
"id": "ff00feb6-b1f7-4bb0-b09c-7b88d984625d",
"code": "MM",
"name": "Product 2"
},
{
"id": "1f4492ab-85eb-4b2f-897a-a2a2b69b43a5",
"code": "MK",
"name": "Product 3"
}
]
It treats the whole array as the single item and append to the existing json.
Could you pls suggest how I could mock bulk insert in json-server? Or Restful Api should always be for single object manipulation?
This is not something that json-server supports natively, as far as I know, but it can be accomplished through a workaround.
I am assuming that you have some prior knowledge of node.js
You will have to create a server.js file which you will then run using node.js.
The server.js file will then make use of the json-server module.
I have included the code for the server.js file in the code snippet below.
I made use of lodash for my duplicate check. You will thus need to install lodash. You can also replace it with your own code if you do not want to use lodash, but lodash worked pretty well in my opinion.
The server.js file includes a custom post request function which accesses the lowdb instance used in the json-server instance. The data from the POST request is checked for duplicates and only new records are added to the DB where the id does not already exist. The write() function of lowdb persists the data to the db.json file. The data in memory and in the file will thus always match.
Please note that the API endpoints generated by json-server (or the rewritten endpoints) will still exist. You can thus use the custom function in conjunction with the default endpoints.
Feel free to add error handling where needed.
const jsonServer = require('json-server');
const server = jsonServer.create();
const _ = require('lodash')
const router = jsonServer.router('./db.json');
const middlewares = jsonServer.defaults();
const port = process.env.PORT || 3000;
server.use(middlewares);
server.use(jsonServer.bodyParser)
server.use(jsonServer.rewriter({
'/api/products': '/products'
}));
server.post('/api/productcollection', (req, res) => {
const db = router.db; // Assign the lowdb instance
if (Array.isArray(req.body)) {
req.body.forEach(element => {
insert(db, 'products', element); // Add a post
});
}
else {
insert(db, 'products', req.body); // Add a post
}
res.sendStatus(200)
/**
* Checks whether the id of the new data already exists in the DB
* #param {*} db - DB object
* #param {String} collection - Name of the array / collection in the DB / JSON file
* #param {*} data - New record
*/
function insert(db, collection, data) {
const table = db.get(collection);
if (_.isEmpty(table.find(data).value())) {
table.push(data).write();
}
}
});
server.use(router);
server.listen(port);
If you have any questions, feel free to ask.
The answer marked as correct didn't actually work for me. Due to the way the insert function is written, it will always generate new documents instead of updating existing docs. The "rewriting" didn't work for me either (maybe I did something wrong), but creating an entirely separate endpoint helped.
This is my code, in case it helps others trying to do bulk inserts (and modifying existing data if it exists).
const jsonServer = require('json-server');
const server = jsonServer.create()
const _ = require('lodash');
const router = jsonServer.router('./db.json');
const middlewares = jsonServer.defaults()
server.use(middlewares)
server.use(jsonServer.bodyParser)
server.post('/addtasks', (req, res) => {
const db = router.db; // Assign the lowdb instance
if (Array.isArray(req.body)) {
req.body.forEach(element => {
insert(db, 'tasks', element);
});
}
else {
insert(db, 'tasks', req.body);
}
res.sendStatus(200)
function insert(db, collection, data) {
const table = db.get(collection);
// Create a new doc if this ID does not exist
if (_.isEmpty(table.find({_id: data._id}).value())) {
table.push(data).write();
}
else{
// Update the existing data
table.find({_id: data._id})
.assign(_.omit(data, ['_id']))
.write();
}
}
});
server.use(router)
server.listen(3100, () => {
console.log('JSON Server is running')
})
On the frontend, the call will look something like this:
axios.post('http://localhost:3100/addtasks', tasks)
It probably didn't work at the time when this question was posted but now it does, call with an array on the /products endpoint for bulk insert.

Circular Reference Issue in Mongoose pre-hook

In my MongoDB/Node backend environment I am using Mongoose pre and post hook middleware to check what's changed on the document, in order to create some system notes as a result.
One problem I'm running into is that when I try and lookup the record for the document in question I get a "Customer.findOne()" is not a function error. This is ONLY a problem when I'm looking up a record from the same collection from which the model just launched this pre and post hook triggers file. In other words, if my "Customer" model kicks off functions in a pre hook function in an external file, then I get an error if I then try and lookup a Customer with a standard findOne():
My customer model looks something like this:
module.exports = mongoose.model(
"Customer",
mongoose
.Schema(
{
__v: {
type: Number,
select: false
},
deleted: {
type: Boolean,
default: false
},
// Other props
searchResults: [
{
matchKey: String,
matchValue: String
}
]
},
{
timestamps: true
}
)
.pre("save", function(next) {
const doc = this;
trigger.preSave(doc);
next();
})
.post("save", function(doc) {
trigger.postSave(doc);
})
.post("update", function(doc) {
trigger.postSave(doc);
})
.post("findOneAndUpdate", function(doc) {
trigger.postSave(doc);
})
);
... the problematic findOne() function in the triggers file being called from the model looks like this:
const Customer = require("../../models/customer");
exports.preSave = async function(doc) {
this.preSaveDoc = await Customer.findOne({
_id: doc._id
}).exec();
};
To clarify, this is NOT a problem if I'm using a findOne() to lookup a record from a different collection in this same triggers file. Then it works fine. See below when finding a Contact -- no problem here:
const Contact = require("../../models/contact");
exports.preSave = async function(doc) {
this.preSaveDoc = await Contact.findOne({
_id: doc._id
}).exec();
};
The workaround I've found is to use Mongo instead of Mongoose, like so:
exports.preSave = async function(doc) {
let MongoClient = await require("../../config/database")();
let db = MongoClient.connection.db;
db.collection("customers")
.findOne({ _id: doc._id })
.then(doc => {
this.preSaveDoc = doc;
});
}
... but I'd prefer to use Mongoose syntax here. How can I use a findOne() in a pre-hook function being called from the same model/collection as the lookup type?
I have ran similar issue few days ago.
Effectively it is a circular dependency problem. When you call .findOne() on your customer model it doesn't exist as it is not exported yet.
You should probably try something like that :
const customerSchema = mongoose.Schema(...);
customerSchema.pre("save", async function(next) {
const customer = await Customer.findOne({
_id: this._id
}).exec();
trigger.setPreSaveDoc(customer);
next();
})
const Customer = mongoose.model("Customer", customerSchema)
module.export Customer;
Here customer will be defined because it is not called (the pre hook) before its creation.
As an easier way (I am not sure about it) but you could try to move the Contact import in your Trigger file under the save function export. That way I think the decencies may works.
Did it helps ?

Uspert multiple documents with MongoDB/Mongoose

Say I have a list of models:
const documents = [{}, {}, {}];
And I want to insert these into the DB, or update them all, but only if a condition is met:
Model.update({isSubscribed: {$ne: false}}, documents, {upsert:true},(err, result) => {
});
The above signature is surely wrong - what I want to do is insert/update the documents, where the condition is met.
There is this Bulk API:
https://docs.mongodb.com/manual/reference/method/Bulk.find.upsert/
but I can't tell if it will work when inserting multiple documents.
Imagine this scenario: We have a list of employees and a form of some sorts to give them all a penalty, at once, not one by one :)
On the backend side, you would have your eg addBulk function. Something like this:
Penalty controller
module.exports = {
addBulk: (req, res) => {
const body = req.body;
for (const item of body) {
Penalty.create(item).exec((err, response) => {
if (err) {
res.serverError(err);
return;
}
});
res.ok('Penalties added successfully');
}
}
Then you'll probably have an API on your frontend that directs to that route and specific function (endpoint):
penaltyApi
import axios from 'axios';
import {baseApiUrl} from '../config';
const penaltyApi = baseApiUrl + 'penalty'
class PenaltyApi {
static addBulk(penalties) {
return axios({
method: 'post',
url: penaltyApi + '/addBulk',
data: penalties
})
}
}
export default PenaltyApi;
...and now let's make a form and some helper functions. I'll be using React for demonstration, but it's all JS by the end of the day, right :)
// Lets first add penalties to our local state:
addPenalty = (event) => {
event.preventDefault();
let penalty = {
amount: this.state.penaltyForm.amount,
unit: this.state.penaltyForm.unit,
date: new Date(),
description: this.state.penaltyForm.description,
employee: this.state.penaltyForm.employee.value
};
this.setState(prevState => ({
penalties: [...prevState.penalties, penalty]
}));
}
Here we are mapping over our formData and returning the value and passing it to our saveBulkEmployees() function
save = () => {
let penaltiesData = Object.assign([], this.state.penalties);
penaltiesData.map(penal => {
penal.employeeId = penal.employee.id;
delete penal.employee;
return penaltiesData;
});
this.saveBulkEmployees(penaltiesData);
}
...and finally, let's save all of them at once to our database using the Bulk API
saveBulkEmployees = (data) => {
PenaltyApi.addBulk(data).then(response => {
this.success();
console.log(response.config.data)
this.resetFormAndPenaltiesList()
}).catch(error => {
console.log('error while adding multiple penalties', error);
throw(error);
})
}
So, the short answer is YES, you can absolutely do that. The longer answer is above :) I hope this was helpful to you. If any questions, please let me know, I'll try to answer them as soon as I can.