User-defined template string - javascript

I'm looking for a way to give a user an option to define template literal. That template will be sent to backend and used with the data he provided.
Example scenario:
const dataSentToBackend = [
{
name: 'Adam',
country: 'Spain'
},{
name: 'Eve',
country: 'Germany'
}
]
User wants to generate two files that are like that: {name} from {country} (or anything else that let's him use tags how he like). So files in this example would be named as Adam from Spain and Eve from Germany.
The trick is that template needs to be preserved as a template so it can be used when data is actually being processed at the backend. So it can be used in a loop that names files one by one by the dataSentToBackend array.
So far I only found String.raw function (thanks to this page), but I didn't found it much helpful for me (or I didn't understood it).

Here's one way of doing it. I used the regular expression [a-z]+ for the property name which you could expand if you need to allow more characters (uppercase, underscores, numbers etc.)
const dataSentToBackend = [
{
name: 'Adam',
country: 'Spain'
}, {
name: 'Eve',
country: 'Germany'
}
]
const templateSentToBackend = "{name} from {country}";
serverSideReplace = (template, datas) =>
datas.map(data => template.replace(/\{([a-z]+)\}/g, (_, key) => data[key]));
console.log(serverSideReplace(templateSentToBackend, dataSentToBackend));

Assuming that your user is inputing is template string in an input field. You can simple save an escaped string in your database, and unescape the same before compiling it into template string.
And to actually use it back you can try something like this.
const templateString = "Hello ${this.name}!"; // replace it with unescaped template
const templateVars = {
name: "world"
}
const fillTemplate = function(templateString, templateVars){
return new Function("return `"+templateString +"`;").call(templateVars);
}
console.log(fillTemplate(templateString, templateVars));
But i would definitely say don't let user define templates and use it, unless you have a really solid sanitizing script.

Related

Filter inside the fetch with mongoDB query operators

how are you? I'm trying to move a filter inside the fetch I'm doing to bring my data from Builder.io and I'm struggling with one of them here. The title search works fine, but the second one don't. My objective is to filter between the entries to catch only the ones that match at least one of the annotationArray items.
The annotationArray can be, for example:
const annotationArray = ['Video', 'Image', 'GPS']
or just
const annotationArray = ['Video']
or whatever.
And the entries have an annotation field that consists in a string where I pass the annotations, like this:
const entries = [{title: 'First', annotation: 'Video, GPS'}, {title: 'Second', annotation: 'GPS'}, {title: 'Third', annotation: 'Video, Image'}]
So, for example, if the annotationArray is ['Video', 'GPS'], I want to fetch all of them. But if it's ['Video'], only the third and first, and so.
Currently I have this code
const sets = await builder.getAll('open-dataset', {
options: { noTargeting: true },
omit: 'data.blocks',
limit: 100,
query: {
data: {
title: { $regex: search, $options: 'i' },
annotation: { $regex: annotationArray && annotationArray.join(' '), $options: 'i' },
}
}
});
The result of annotationArray.join(' ') can be, for example, Video Image GPS or just Image. And annotation Video Image or whatever.
So I need to filter between the entries and fetch only the ones that contain at least one of the annotationArray strings.
My code is failing because currently it only fetches the ones that have all the annotationArray items, and not the ones that have at least one. I don't know how to do it with MondoDB query operators... previously, I had this code with javascript and it worked fine.
const filtered = entries.filter(item => annotationArray.some(data => item.annotation.includes(data)));
can somebody help me? thanks

How to group GraphQL query objects into namespaces?

How can I group my queries into namespaces in GraphQL? I have something like this right now:
const queryType = new g.GraphQLObjectType({
name: "Query",
fields: fields,
});
and in fields I have field -> object mappings and it works fine, but I'd like to group these mappings into two groups (live and historical). If I modify the above code to this however:
const queryType = new g.GraphQLObjectType({
name: "Query",
fields: {
historical: {
type: new g.GraphQLObjectType({
name: "historical",
fields: fields,
})
}
},
});
everything resolves to null. How can I write a resolver for this grouping? Is it possible at all?
so often people want namespaces for the sake of splitting up code, not sure if this is your end goal but you could achieve that this way aswell:
# in one file
type Mutation {
login(username: String, password: String): User
}
# in other file
extend type Mutation {
postX(title: String, message: String): X
}

How to structure various interdependent Schemas in mongoose OR how to use 'reference' properly?

I'm currently working on a vocabulary application using node.js, Express, MongoDB and mongoose.
My aim: Putting out translations for various languages depending on the choices made in the front-end (E. g.: German > English, English > Portuguese etc.)
Main problem: Interdependent Schemas. The translation of a word stored in WordSchema depends on the language represented by the LanguageSchema.
For me, there appear two different ways on how to structure the relevant Schemas:
1.
There is one Schema representing the language (e.g. German, Englisch,...). It stores several words according to the language. Because Word represents another Schema it is referenced to the WordSchema inside the LanguageSchema. The problem which appears here is that the values of the word depend on the chosen language.
var Schema = mongoose.Schema;
var LanguageSchema = new Schema({
language: String, // 'German'
words: [{type: Schema.ObjectId, ref: 'Word'}]
// word: 'Haus' instead of e. g. 'house'
});
module.exports = mongoose.model('Language', LanguageSchema);
var WordSchema = new Schema({
name: String // 'house', 'Haus', 'casa' depending on the language
});
module.exports = mongoose.model('Word', WordSchema);
2. I could solve this by using just the WordSchema and adding all the languages which exist as a property and add the according translation of the word. But this doesn't seem the best working solution for me as I won't translate the words into all languages right from the beginning. So there just should be stored those translations for a word where there actually exists a translation.
LanguageSchema
var Schema = mongoose.Schema;
var LanguageSchema = new Schema({
language_name: {type:String}, // English
language_code: {type:String} // en
});
module.exports = mongoose.model('Language', LanguageSchema);
In Word Schema , you need to push objects with word_name and word_language
WordSchema
var WordSchema = new Schema({
words:[{
word_name:{type:String},
word_language:{type:String}
}]
});
module.exports = mongoose.model('Word', WordSchema);
Example : Language in Database
languages : [
{
"_id":"54ef3f374849dcaa649a3abc",
"language_name":"English" ,
"language_code":"en"
},
{
"_id":54ef3f374849dcaa649a3asd",
"language_name":"Portuguese" ,
"language_code":"pt"
},
{
"_id":54ef3f374849dcaa649a3xxx",
"language_name":"German" ,
"language_code":"de"},
]
Example : Words in Database
words:[
{
word:[
{
"_id":"54ef3f374849dcaa649azzz",
"word_name":"Friend" ,
"word_language":"English"
},
{
"_id":"54ef3f374849dcaa6491111",
"word_name":"Amigo" ,
"word_language":"Portuguese"
},
{
"_id":"54ef3f374849dcaa649a233",
"word_name":"Freund" ,
"word_language":"German"
},
]
},
{ word: [...] },
{ word: [...] },
{ word: [...] },
{ word: [...] }
]
from frontend you have to pass 3 parameters
word , input_language , output_language
Example : You want "Friend" meaning from English to Portuguese
so in this case :
word="Friend" , input_language="English" ,
output_language="Portuguese"
Now Applying Mongoose Find Query and search Word in WordSchema
Word.findOne({word_name:{ $regex:word, $options: "$i" },word_language:input_language},function(err,result){
if(err){ return err;}
if(!err && result){
// now you have to use underscore.js and find out result by output language
// http://underscorejs.org
// . npm i --save underscore
var outputObj= _.find(result.word, { word_language :output_language});
res.json(outputObj);
}
})

Cloud Firestore Case Insensitive Sorting Using Query

I tried to read sorted data from Cloud Firestore using OrderBy.
And Firestore returned data as Following Order:
AAA
BBB
aaa
bbb
Now, what I want is something like following:
AAA
aaa
BBB
bbb
I want this result only using OrderBy not by manual Sorting.
Is there any way to sort like this in Firestore?
Please provide me a solution for this.
Thanks in Advance.
Sorting and filtering in Cloud Firestore are case sensitive. There is no flag to make the sorting or filtering ignore the case.
The only way to achieve your use-case is to store the field twice.
Let's say your field that stores 'AAA' & 'aaa' is called myData. In your client code you'll need to store a second field called myData_insensitive where you store a case-insensitive copy of the data.
DocA:
-> myData = 'AAA'
-> myData_insensitive = 'AAA'
DocB:
-> myData = 'aaa'
-> myData_insensitive = 'AAA'
DocC:
-> myData = 'BBB'
-> myData_insensitive = 'BBB'
DocD:
-> myData = 'bbb'
-> myData_insensitive = 'BBB'
Now you can query and/or order by myData_insensitive, but display myData.
Two interesting thing about this area is:
With Unicode, removing case is more complex than just 'toLowerCase'
Different human languages will sort the same characters differently
Without creating separate indexes for each collation to solve (2), one implementation approach to deal with (1) is via case folding. If you want to only support modern browser versions, then the following gives you a JavaScript example:
caseFoldNormalize = function (s){
return s.normalize('NFKC').toLowerCase().toUpperCase().toLowerCase()
};
caseFoldDoc = function(doc, field_options) {
// Case fold desired document fields
if (field_options != null) {
for (var field in field_options) {
if (field_options.hasOwnProperty(field)) {
switch(field_options[field]) {
case 'case_fold':
if (doc.hasOwnProperty(field) && Object.prototype.toString.call(doc[field]) === "[object String]") {
doc[field.concat("_insensitive")] = caseFoldNormalize(doc[field])
}
break;
}
}
}
}
return doc;
}
var raw_document = {
name: "Los Angeles",
state: "CA",
country: "USA",
structure: 'Waſſerſchloß',
message: 'quıt quit' // Notice the different i's
};
var field_options = {
name: 'case_fold',
country: 'case_fold',
structure: 'case_fold',
message: 'case_fold'
}
var firestore_document = caseFoldDoc(raw_document, field_options);
db.collection("cities").doc("LA").set(firestore_document).then(function() {
console.log("Document successfully written!");
}).catch(function(error) {
console.error("Error writing document: ", error);
});
This will give you a document in Cloud Firestore with the following fields:
{
"name": "Los Angeles",
"state": "CA",
"country": "USA",
"structure": "Waſſerſchloß",
"message": "quıt quit",
"name_casefold": "los angeles",
"country_casefold": "usa",
"structure_casefold": "wasserschloss",
"message_casefold": "quit quit"
}
To handle older browser, you can see one solution in How do I make toLowerCase() and toUpperCase() consistent across browsers
You could also do it manually after you get your results:
docArray.sort((a, b) => {
if (a.myData.toLowerCase() < b.myData.toLowerCase()) {
return -1;
}
if (a.myData.toLowerCase() > b.myData.toLowerCase()) {
return 1;
}
return 0;
});
It's 2022. Firebase is awesome. Firestore is awesome. You don't need to stop using Firestore because of this limitation. Some things on the noSQL world are made on purpose just to speed up things.
What you can do in cases like this is just to create another property on the document, where you would parse the source value and lowercase it. Then you can use the parsed property to sort/order things.
Example:
interface ICompany {
dateAdded: Date
dateEdited: Date
description: string
id?: string
logo?: string
managers?: ICompanyManager
name: string
nameLowercase: string
website?: string
}
Here if you want to sort companies by name.
What you can do:
query(
companiesCollection,
orderBy('nameLowercase', 'asc'),
)
And when adding/editing:
const company = await addDoc(companiesCollection, {
name: data.name,
nameLowercase: data.name.toLowerCase(),
description: data.description,
website: data.website,
dateAdded: new Date(),
dateEdited: new Date(),
} as ICompany)
Voilà.

How to reformat data only for saving to MongoDB with Mongoose to avoid "not okForStorage"?

I have Schema defined in Mongoose and I just realized one attribute is being saved as object (kind of hash), but it can contain prohibited characters in it's keys. By prohibited I mean those which are not very much liked by MongoDB, causing not okForStorage errors: dots, dollar signs, etc.
As I don't want to change all my application, I want to define something on my model which reformats the object to array before passing it to MongoDB and, of course, I need also something reformatting it back when loading such data from MongoDB.
I tried getters and setters and played a while with Middleware, but could not make it working. Is there a best practise on this? What would be the best approach? I really wish I could just stick two functions somewhere on the schema and it would be pure blackbox for the rest of my app.
UPDATE: What I want to achieve (example):
toMongo = function (mapping) {
// from {'k': 'v', ...} makes [{key: 'k', value: 'v'}, ...]
return ...
}
fromMongo = function (mapping) {
// from [{key: 'k', value: 'v'}, ...] makes {'k': 'v', ...}
return ...
}
schema = mongoose.Schema({
mapping: mongoose.Schema.Types.Mixed
});
var Foo = mongoose.model('Foo', schema);
var foo = new Foo({ mapping: {'tricky.key': 'yes', 'another$key': 'no'} });
foo.mapping // results in {'tricky.key': 'yes', 'another$key': 'no'}
foo.save(function(err, doc) {
// mapping is actually saved as
// [{key: 'tricky.key', value: 'yes'}, {key: 'another$key', value: 'no'}] in mongo!
doc.mapping // results in {'tricky.key': 'yes', 'another$key': 'no'}
});
Foo.find(function (err, foos) {
foos[0].mapping // results in {'tricky.key': 'yes', 'another$key': 'no'}
});
The question is: Where should I hook my two magic functions toMongo and fromMongo so the interface works exactly as I shown in the example?
(Disclaimer: At the time of this question is asked, I am Mongoose & Node.js noob, so even silly details could be helpful to me)
I think I found the answer myself. It can be solved with Middlewares, this way:
schema.post('init', function (doc) {
doc.mapping = fromMongo(doc.mapping);
}
schema.pre('save', function (next) {
this.mapping = toMongo(this.mapping);
next();
}
This way it's pretty isolated from the rest of the app and so far I didn't have any problems with this solution. I'll try to keep updating this answer in case any problems rise up.

Categories