multilingual model attributes in sails js advice - internationalization

I'm about to start a project that's going to require some attributes on a model to be translated and not sure what the best approach would be.
One option would be to create a JSON type attribute and store the translations as
{
title: [{ "en": "cheese" }, {"de": "Käse"}, {"es": "queso"}, etc... ]
}
but I'm also wondering if would be better to store these values in a separate collection and create an association, then when getting the parent model I could just populate with the appropriate language. So something like
Product Model
module.exports = {
attributes: {
sku: 'string',
values:{
collection: 'productValues',
via: 'product'
}
}
}
Product Values Model
module.exports = {
attributes: {
title: 'string',
body: 'string',
language: 'string',
product:{
model: 'product'
}
}
}

I would just add a JSON for translations to the model.
module.exports = {
attributes: {
defaultValue: 'string',
translations: 'json'
}
}
Then you could simply work with the translation object. Delete translations, add new ones etc.
Model.findById(id).then(function(record){
var translations = record.translations;
translations.en = 'Hello';
delete translations.fr;
Model.update({id: id},{translations: translations}, function(){});
});
(Just an example code, didnt test it)
But if you want 1 translation to work with multiple records than it is insufficient. And then you could create seperate collection for translations and reference records of it as needed.

Related

hotchocolate (GraphQL) filter by extended schema (stitching)

Is it possible to 'filter' (where) by an extended schema in stitching file in graphQL?
For example:
{
contractSKUCost(where: { contractSKU: { products: { productId: 1}} }) {
items {
id
contractSKU {
id
products {
productId
}
}
}
}
}
In this case, products is in a different database, that is why we need to define it in stitching file:
extend type ContractSKU {
products():[Product]
#delegate(
schema: "products",
path: "productsBySkuId(skuId: $fields:id)"
)
Essentially, what I want to do is "GET All ContractSKUCost that have ProductId = 1". But as you see, ContractSKUCost does not have direct relationship with Product, only its parent ContractSKU.
Edit:
Code above does not work; because ContractSkuCost schema technically does not know the 'product' extension yet.

Is there a way to get a structure of a Strapi CMS Content Type?

A content-type "Product" having the following fields:
string title
int qty
string description
double price
Is there an API endpoint to retrieve the structure or schema of the "Product" content-type as opposed to getting the values?
For example: On endpoint localhost:1337/products, and response can be like:
[
{
field: "title",
type: "string",
other: "col-xs-12, col-5"
},
{
field: "qty",
type: "int"
},
{
field: "description",
type: "string"
},
{
field: "price",
type: "double"
}
]
where the structure of the schema or the table is sent instead of the actual values?
If not in Strapi CMS, is this possible on other headless CMS such as Hasura and Sanity?
You need to use Models, from the link:
Link is dead -> New link
Models are a representation of the database's structure. They are split into two separate files. A JavaScript file that contains the model options (e.g: lifecycle hooks), and a JSON file that represents the data structure stored in the database.
This is exactly what you are after.
The way I GET this info is by adding a custom endpoint - check my answers here for how to do this - https://stackoverflow.com/a/63283807/5064324 & https://stackoverflow.com/a/62634233/5064324.
For handlers you can do something like:
async getProductModel(ctx) {
return strapi.models['product'].allAttributes;
}
I needed the solution for all Content Types so I made a plugin with /modelStructure/* endpoints where you can supply the model name and then pass to a handler:
//more generic wrapper
async getModel(ctx) {
const { model } = ctx.params;
let data = strapi.models[model].allAttributes;
return data;
},
async getProductModel(ctx) {
ctx.params['model'] = "product"
return this.getModel(ctx)
},
//define all endpoints you need, like maybe a Page content type
async getPageModel(ctx) {
ctx.params['model'] = "page"
return this.getModel(ctx)
},
//finally I ended up writing a `allModels` handler
async getAllModels(ctx) {
Object.keys(strapi.models).forEach(key => {
//iterate through all models
//possibly filter some models
//iterate through all fields
Object.keys(strapi.models[key].allAttributes).forEach(fieldKey => {
//build the response - iterate through models and all their fields
}
}
//return your desired custom response
}
Comments & questions welcome
This answer pointed me in the right direction, but strapi.models was undefined for me on strapi 4.4.3.
What worked for me was a controller like so:
async getFields(ctx) {
const model = strapi.db.config.models.find( model => model.collectionName === 'clients' );
return model.attributes;
},
Where clients is replaced by the plural name of your content-type.

Hapijs Joi reference schema keys to reuse in other models or in routes

I have an example model constructed like so:
const MyModelResponse = Joi.object().keys({
id: Joi.number().integer().required()
.description('ID of the example model'),
description: Joi.string()
.description('Description of the example model'),
})
.description('example instance of MyModel with the unique ID present')
.label('MyModelResponse');
In my route, I want to make sure that my input parameter is validated against the id property of MyModeResponse like so:
validate: {
params: {
id: MyModelResponse.id,
},
options: { presence: 'required' },
failAction: failAction('request'),
}
This results in the schema validation error upon server start:
AssertionError [ERR_ASSERTION]: Invalid schema content: (id)
Is there a way to reference a key of a schema? Currently, I have to resort to either of the following:
Not referencing my MyModelResponse schema at all:
validate: {
params: {
id: Joi.number().integer().description('id of MyModel instance to get'),
},
options: { presence: 'required' },
failAction: failAction('request'),
}
Not using the Joi.object.keys() constructor by defining my model like this:
const MyModelResponse = {
id: Joi.number().integer().required()
.description('ID of the example model'),
description: Joi.string()
.description('Description of the example model'),
}
The bottom approach allows me to reference the id property in my route but doesn't allow me to add descriptions and labels to my schema. I have tried using MyModel.describe().children.id in my route validation and I have made some attempts to deserialize the id object into a schema object to no avail.
Any suggestions would be appreciated.
Remove the keys() and use as follows
const MyModelResponse = Joi.object({
id: Joi.number().integer().required()
.description('ID of the example model'),
description: Joi.string()
.description('Description of the example model'),
})
.description('example instance of MyModel with the unique ID present')
.label('MyModelResponse');

Mongoose populate with match condition for nested document

I have a mongoose schema like this:
var Address = {
doorNo:String,
city:String,
state:String,
country:String,
district:String,
zipCode:String,
area:String,
locality:String
};
var StoreSchema = {
name:String,
address:Address,
category:[String],
products:[{ type:Schema.ObjectId, ref:"Product" }],
};
var ProductSchema = {
name:String,
description:String,
category:String,
subCategory:String,
store: { type:Schema.ObjectId, ref:"Store", childPath:"products" }
};
I need to filter products based on product-category and address-city. Get only those products which belong to a particular location(city in address schema) and a particular category.
I tried the following code:
Product
.find({'category':'accessories'})
.populate({
path: 'store',
match: { 'address.city': req.params.location},
select:'_id'
})
Now this returns all the products where category matches but wherever the location filter is not-satisfied it returns a store with null and returns store wherever location filter is satisfied.
In Mongoose 5.2.7 the syntax match: { 'x.y': 'something' } works as a charm
Stated in the documentation here: https://mongoosejs.com/docs/populate.html#query-conditions
The solution should be:
Product
.find({...})
.populate({
...
match: { 'city': req.params.location},
...
})
You don't need to do 'address.city' just match: { 'city': req.params.location}
May be you can try this, it worked for me this way.
Product
.find({ category: 'accessories' })
.populate({
path: 'store',
model: 'Store',
match: {
address.city: { $eq: 'YOUR_CITY' }
}
})
.exec(function (err, res){})

Translating JSON into custom dijit objects

I am looking for an example where JSON constructed from the server side is used to represent objects that are then translated into customized widgets in dojo. The JSON would have to be very specific in its structure, so it would not be a very general solution. Could someone point me to an example of this. It would essentially be the reverse of this
http://docs.dojocampus.org/dojo/formToJson
First of all let me point out that JSON produced by dojo.formToJson() is not enough to recreate the original widgets:
{"field1": "value1", "field2": "value2"}
field1 can be literally anything: a checkbox, a radio button, a select, a text area, a text box, or anything else. You have to be more specific what widgets to use to represent fields. And I am not even touching the whole UI presentation layer: placement, styling, and so on.
But it is possible to a certain degree.
If we want to use Dojo widgets (Dijits), we can leverage the fact that they all are created uniformly:
var myDijit = new dijit.form.DijitName(props, node);
In this line:
dijit.form.DijitName is a dijit's class.
props is a dijit-specific properties.
node is an anchor node where to place this dijit. It is optional, and you don't need to specify it, but at some point you have to insert your dijit manually.
So let's encode this information as a JSON string taking this dijit snippet as an example:
var myDijit = new dijit.form.DropDownSelect({
options: [
{ label: 'foo', value: 'foo', selected: true },
{ label: 'bar', value: 'bar' }
]
}, "myNode");
The corresponding JSON can be something like that:
{
type: "DropDownSelect",
props: {
options: [
{ label: 'foo', value: 'foo', selected: true },
{ label: 'bar', value: 'bar' }
]
},
node: "myNode"
}
And the code to parse it:
function createDijit(json){
if(!json.type){
throw new Error("type is missing!");
}
var cls = dojo.getObject(json.type, false, dijit.form);
if(!cls){
// we couldn't find the type in dijit.form
// dojox widget? custom widget? let's try the global scope
cls = dojo.getObject(json.type, false);
}
if(!cls){
throw new Error("cannot find your widget type!");
}
var myDijit = new cls(json.props, json.node);
return myDijit;
}
That's it. This snippet correctly handles the dot notation in types, and it is smart enough to check the global scope too, so you can use JSON like that for your custom dijits:
{
type: "my.form.Box",
props: {
label: "The answer is:",
value: 42
},
node: "answer"
}
You can treat DOM elements the same way by wrapping dojo.create() function, which unifies the creation of DOM elements:
var myWidget = dojo.create("input", {
type: "text",
value: "42"
}, "myNode", "replace");
Obviously you can specify any placement option, or no placement at all.
Now let's repeat the familiar procedure and create our JSON sample:
{
tag: "input",
props: {
type: "text",
value: 42
},
node: "myNode",
pos: "replace"
}
And the code to parse it is straightforward:
function createNode(json){
if(!json.tag){
throw new Error("tag is missing!");
}
var myNode = dojo.create(json.tag, json.props, json.node, json.pos);
return myNode;
}
You can even categorize JSON items dynamically:
function create(json){
if("tag" in json){
// this is a node definition
return createNode(json);
}
// otherwise it is a dijit definition
return createDijit(json);
}
You can represent your form as an array of JSON snippets we defined earlier and go over it creating your widgets:
function createForm(array){
dojo.forEach(array, create);
}
All functions are trivial and essentially one-liners — just how I like it ;-)
I hope it'll give you something to build on your own custom solution.

Resources