Include Loopback relation in POST response - loopback

I have a working chat-room loopback project, which also utilises Socket.IO.
Once I create a Message (POST to Loopback REST API), I need the response the include the 'creator' relation.
This works fine when using GET, but I cannot include the relation to the POST response.
I'm sure it's a simple remote hook, but I'm stuck...
Any help is greatly appreciated!
"relations": {
"creator": {
"type": "belongsTo",
"model": "Person",
"foreignKey": "",
"options": {
"nestRemoting": true
}
},
"event": {
"type": "belongsTo",
"model": "Event",
"foreignKey": "",
"options": {
"nestRemoting": true
}
}
},

There are two options how you can do it with Loopback 2.x or 3.x (not sure about the Loopback 4.x).
Let's assume we have the following "Note" model:
{
"name": "Note",
"properties": {
"title": {
"type": "string",
"required": true
},
"content": {
"type": "string"
},
"userId": {
"type": "number"
}
},
"relations": {
"user": {
"type": "belongsTo",
"model": "User",
"foreignKey": "userId"
}
}
}
Now, to include "user" property (which is a belongsTo relation of Note) in the response when you create (POST) a Note you have two options.
Option #1 (Recommended): Create a custom remote method and hide the default method in your model's script file. In this case your note.js file should look something like:
module.exports = function (Note) {
// Hide the default 'create' remote method
Note.disableRemoteMethod('create', true);
// Add a custom 'customCreate' remote method
Note.remoteMethod('customCreate', {
description: 'Create a new instance of the model and persist it into the data source.',
accessType: 'WRITE',
accepts: [
{
arg: 'data',
type: 'object',
model: 'Note',
allowArray: true,
description: 'Model instance data',
http: { source: 'body' },
},
{ arg: 'options', type: 'object', http: 'optionsFromRequest' },
],
returns: { arg: 'data', type: 'Note', root: true },
http: { verb: 'post', path: '/' },
isStatic: true,
});
Note.customCreate = function (data, options, cb) {
Note.create(data, options, function(err, newObj) {
if (err) {
cb(err);
}
else {
// here we try to load the user value
newObj.user(function (err, user) {
if (user) {
// if we found a user we add it to __data, so it appears in the output (a bit hacky way)
newObj.__data.user = user;
}
cb(err, newObj);
});
}
});
};
};
I would recommend using this option because you achieve what you need with minimal changes in the default logic of the loopback model, i.e. all the default methods like create, upsert, etc. continue to have default behavior.
Option 2: Use 'after save' operation hook (be careful with this approach as it changes the way how create, upsert, upsertWithWhere and other default methods work)
In this case your note.js file should look something like:
module.exports = function (Note) {
Note.observe('after save', function (ctx, next) {
ctx.instance.user(function (err, user) {
ctx.instance.__data.user = user;
next();
});
});
};
The second option has less code, but as I mentioned before you should be very careful using it because it will change the behavior of default "create" method of the model. I.e. 'after save' action will be executed each time you call Model.create, Model.upsert, etc. It will also slow down these operation as you add additional select query in the 'after save' hook.

Related

Implement I18n localization in Strapi local plugins

I generated a local plugin and created an article model using:
"pluginOptions": {
"i18n": {
"localized": true
}
},
inside his article.settings.json file, in order to make some specific fields translatables using the Internationalization(I18N) plugin
Problem is, while running the command:
strapi develop --watch-admin
I end up having the following errors:
error Something went wrong in the model "Article" with the attribute "localizations"
error TypeError: Cannot read property "uid" of undefined
Removing the "pluginOptions" instead, gives my local plugin running without any translatable field or articles__translations pivot that should be generated into my mysql database
"pluginOptions" is the very same parameter that gets generated into the model settings creating a collection type using the Content-Types Builder, but I can't have it to work while using it for a local plugin.
Here is my article.settings.json:
plugins/blog/models/article.settings.json
{
"kind": "collectionType",
"collectionName": "articles",
"info": {
"name": "article"
},
"options": {
"draftAndPublish": false,
"timestamps": true,
"populateCreatorFields": true,
"increments": true,
"comment": ""
},
"pluginOptions": {
"i18n": {
"localized": true
}
},
"attributes": {
"title": {
"pluginOptions": {
"i18n": {
"localized": true
}
},
"type": "string",
"required": true,
"maxLength": 255,
"minLength": 3
},
"slug": {
"pluginOptions": {
"i18n": {
"localized": true
}
},
"type": "uid",
"targetField": "title",
"required": true
},
"featured": {
"pluginOptions": {
"i18n": {
"localized": false
}
},
"type": "boolean",
"default": false
},
"published_date": {
"pluginOptions": {
"i18n": {
"localized": false
}
},
"type": "datetime"
},
}
}
You can use the content-type-builder plugin as a workaround. You would not create the content type under the content-types folder but create it programmatically.
As an example of a very simple tag content type:
{
"singularName": "tag",
"pluralName": "tags",
"displayName": "tag",
"description": "",
"draftAndPublish": false,
"pluginOptions": {
"i18n": {
"localized": true
}
},
"attributes": {
"label": {
"type": "string",
"pluginOptions": {
"i18n": {
"localized": true
}
},
"unique": true
}
}
}
Note, this schema of the json is a bit different from the ones in plugin/server/content-types.
Then you can create the content type programmatically like this:
import { Strapi } from "#strapi/strapi";
import tag from "../content-types/tag.json";
import page from "../content-types/page.json";
export default ({ strapi }: { strapi: Strapi }) => ({
async createContentComponent() {
if (!tag) return null;
try {
const components: any = [];
const contentType = await strapi
.plugin("content-type-builder")
.services["content-types"].createContentType({
contentType: tag,
components,
});
return contentType;
} catch (e) {
console.log("error", e);
return null;
}
},
});
This is exactly how the admin creates content types using the content builder UI.
And it works using the pluginOptions.i18n.localized: true.
One approach would be to do this, e.g., on the bootstrap phase of the plugin. Here you could also check whether or not the contents are created or not.
As a bonus, you can also create components that otherwise would not work.
Hope that helps.
Links:
Create components programmatically in a plugin: https://github.com/strapi/strapi-plugin-seo/blob/main/server/services/seo.js
Create content types:
https://github.com/strapi/strapi/blob/88caa92f878a068926255dd482180202f53fcdcc/packages/core/content-type-builder/server/controllers/content-types.js#L48
EDIT:
You could also keep the original schema and use this fn to transform it - at least for now as long as the other approach is not working:
https://github.com/strapi/strapi/blob/1eab2fb08c7a4d3d40a5a7ff3b2f137ce0afcf8a/packages/core/content-type-builder/server/services/content-types.js#L37

How do I add custom queries in GraphQL using Strapi?

I'm using graphQL to query a MongoDB database in React, using Strapi as my CMS. I'm using Apollo to handle the GraphQL queries. I'm able to get my objects by passing an ID argument, but I want to be able to pass different arguments like a name.
This works:
{
course(id: "5eb4821d20c80654609a2e0c") {
name
description
modules {
title
}
}
}
This doesn't work, giving the error "Unknown argument \"name\" on field \"course\" of type \"Query\"
{
course(name: "course1") {
name
description
modules {
title
}
}
}
From what I've read, I need to define a custom query, but I'm not sure how to do this.
The model for Course looks like this currently:
"kind": "collectionType",
"collectionName": "courses",
"info": {
"name": "Course"
},
"options": {
"increments": true,
"timestamps": true
},
"attributes": {
"name": {
"type": "string",
"unique": true
},
"description": {
"type": "richtext"
},
"banner": {
"collection": "file",
"via": "related",
"allowedTypes": [
"images",
"files",
"videos"
],
"plugin": "upload",
"required": false
},
"published": {
"type": "date"
},
"modules": {
"collection": "module"
},
"title": {
"type": "string"
}
}
}
and the
Any help would be appreciated.
Referring to Strapi GraphQL Query API
You can use where with the query courses to filter your fields. You will get a list of courses instead of one course
This should work:
{
courses(where: { name: "course1" }) {
name
description
modules {
title
}
}
}

Update All Fields which is don't have the same name in Mongoose

I'm trying to update all fields in Document based on req.body.
In this case, the Schema are not defined in Mongoose. So, I can fill freely what will be in post to schema
For example, I've 2 Scheme like this in the Document:
Schema 1:
{
"name": {
"type": "text",
"value": "Afdallah"
},
"item": {
"type": "text",
"value": "Books"
}
}
And the other is like this
Schema 2:
{
"name": {
"type": "text",
"value": "Afdallah"
},
"email": {
"type": "email",
"value": "afdallah.war#gmail.com"
}
}
My question is, how to update all fields when they don't have same fields name?
I've try like this to update the fields.
const output = await Order.findOne({ _id: req.params.id });
try {
output.set(req.body);
await output.save();
res.send("Success Update");
} catch (err) {
res.status(422).send(err);
}
I see, you can take advantage of Object.assign to duplicate your current data and merge it with upcoming data which is req.body.
const newData = Object.assign({}, output, req.body);
output = newData;

Routing with parameters does not work

I am following the tutorial here and I got stuck on routing with parameters.
The example app did not run on my local use, I therefore change it to make use of local data. However, I get the error "Uncaught Error: Invalid value "Invoices/1" for segment "{invoicePath}"" when I click on an element in the invoices list. It should open up a new Detail page and display the product name and the amount.
Here is my routing manifest:
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "sap.ui.demo.wt.view",
"controlId": "app",
"controlAggregation": "pages"
},
"routes": [
{
"pattern": "",
"name": "overview",
"target": "overview"
},
{
"pattern": "detail/{invoicePath}",
"name": "detail",
"target": "detail"
}
],
"targets": {
"overview": {
"viewName": "Overview"
},
"detail": {
"viewName": "Detail"
}
}
}
Invoices.json example data:
{
"Invoices": [
{
"ProductName": "Pineapple",
"Quantity": 21,
"ExtendedPrice": 87.2000,
"ShipperName": "Fun Inc.",
"ShippedDate": "2015-04-01T00:00:00",
"Status": "A"
}
]
}
InvoiceList.controller.js. Where I populate the Invoices list and call the view change.
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel",
"sap/ui/demo/wt/model/formatter",
"sap/ui/model/Filter",
"sap/ui/model/FilterOperator"
], function (Controller, JSONModel, formatter, Filter, FilterOperator) {
"use strict";
return Controller.extend("sap.ui.demo.wt.controller.InvoiceList", {
onPress: function (oEvent) {
var oItem = oEvent.getSource();
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
oRouter.navTo("detail", {
invoicePath: oItem.getBindingContext("invoice").getPath().substr(1)
});
}
});
});
The error message is raised by the the router library. The route is defined as detail/{invoicePath} and you pass Invoice/1 as parameter, which is not allowed as the parameter contains the slash which is considered as URL segment separator.
However, you mentioned that you could not run the example locally and did some adoptions. The path looks like you are using a JSONModel now. This means you need to adopt several parts in your example as well.
InvoiceList controller:
oItem.getBindingContext("invoice").getPath().substr(10)
The binding context should be /Invoices/1 and you want to pass the only the index. Therefore you need to cut off /Invoices/.
Detail controller:
_onObjectMatched: function (oEvent) {
this.getView().bindElement({
path: "/Invoices/"+ oEvent.getParameter("arguments").invoicePath,
model: "invoice"
});
}
This will bind your view to /Invoices/1 in the corresponding model.
#matbtt 's answer is right.
Another solution is to excaping the PATH. No substr and special offset is needed.
encodeURIComponent(oItem.getBindingContext("invoice").getPath())
_onObjectMatched: function (oEvent) {
this.getView().bindElement({
path: decodeURIComponent(oEvent.getParameter("arguments").invoicePath),
model: "invoice"
});
}
both JSON Model and OData would work well.

Add event to calendar on SharePoint through REST API

I'm trying to add a calendar event to a SharePoint Calendar through REST API but i can't seems to find the relevant resources to achieve this.
If i understand correctly, the calendar in SharePoint is a List of events object, as such I should be able to add the event via ListItem object?
Sorry if this sounds wrong as I'm not familiar with SharePoint structure.
Thanks
This is the example for OAuth token Authentication but REST part is anyway like this.
var dataObj = {
"Subject": "Birthday Party"
"Body": {
"ContentType": "Text",
"Content": "Birthday Party for Cathy",
},
"Start": {
"dateTime": "2016-07-03T09:00:00Z",
"timeZone": "Asia/Tokyo"
},
"End": {
"dateTime": "2016-07-04T11:00:00Z",
"timeZone": "Asia/Tokyo"
},
"Location": {
"DisplayName": "Conference Room 1"
},
"ShowAs": "Busy",
"Attendees": [
{
"EmailAddress": { "Name": "Alex Darrow", "Address": "darrow.alex#company.com" },
"Type": "Required"
}
]
};
var url = "https://graph.microsoft.com/v1.0/me/events/";
var data = JSON.stringify(dataObj);
$.ajax({
url: url,
type: "POST",
data: data,
beforeSend: function (XMLHttpRequest) {
XMLHttpRequest.setRequestHeader("Accept", "application/json;odata.metadata=full;odata.streaming=true");
XMLHttpRequest.setRequestHeader('Authorization', 'Bearer ' + accessToken);
XMLHttpRequest.setRequestHeader("content-type", "application/json;odata=verbose");
},
success: function (result, textStatus, jqXHR) {
//Success
},
error: function (data) {
//
}});