I'm trying to create custom controller for my Strapi (v3.6.10) model, but have a problem with populating one specific field. Here are my models:
Parent model:
{
"kind": "collectionType",
"collectionName": "clinic_pages",
"info": {
"name": "clinic-page",
},
"attributes": {
"clinic": {
"model": "clinic"
},
...otherParentModelFields,
}
}
Clinic:
{
"kind": "collectionType",
"collectionName": "clinics",
"attributes": {
...otherFields,
"schedule": {
"type": "component",
"repeatable": true,
"component": "clinic.schedule"
},
}
}
Schedule:
{
"collectionName": "components_clinic_schedules",
"info": {
"name": "schedule",
},
"attributes": {
"time": {...},
"day": {...}
}
}
I need to populate only few fields from parent model, including clinic. So I'm using this query:
await strapi.services[MODEL].findOne(ctx.query) and receiving full schedule data in response:
{
...otherFieldsOfModel,
clinic: {
id: 1,
...otherFields,
schedule: [
{ id: 1, time: '9:00', day: 'monday' },
{ id: 2, time: '10:00', day: 'tuesday' }
]
}
}
That's fine, I see clinic and schedule, and all other fields in parent model. But then I wand to add populate to reduce size of those other fields and what I got?. Every time I'm getting anything instead of populated data for clinic.schedule even if I'm adding it to populate list. In all this cases:
await strapi.services[MODEL].findOne(ctx.query, {});
await strapi.services[MODEL].findOne(ctx.query, {clinic: true});
await strapi.services[MODEL].findOne(ctx.query, ['clinic']);
await strapi.services[MODEL].findOne(ctx.query, ['clinic', 'clinic.schedule', 'clinic.schedule.day', 'clinic.schedule.time']);
and so on... I mean I tried any possible syntax variant and don't even got a schedule: null. This field not returning anymore in the response. How do I populate this collection?
P.S. Yes, I know that Strapi is v4 already, unfortunately I can't update it for now.
Related
thank you in advance for reading me. So I have been working in a filte. Right now my filter works, however doesn't do what I want. The current status is. When I select 2 options or more. I get all the values inside the data that contains either optionA oder optionB.
See my example data below:
{
"_uid": "1",
"body": [
{
"_uid": "2",
"name": "John",
"image": {
"id": 6807178,
"filename": "https://",
"copyright": "",
"fieldtype": "asset",
"is_external_url": false
},
"gewerk": "Project Owner",
"skill": ["vuejs", "react", "symfony"],
"component": "person",
},
{
"_uid": "3",
"name": "Jean",
"image": {
"id": 6807182,
"filename": "https://",
"copyright": "",
"fieldtype": "asset",
"is_external_url": false
},
"gewerk": "UI",
"skill": ["svelte"],
"component": "person",
},
{
"_uid": "4",
"name": "Martha",
"gewerk": "Frontend",
"skill": ["vuejs", "react"],
"component": "person",
},
{
"_uid": "5",
"name": "Tom",
"gewerk": "UI",
"skill": ["svelte", "angular", "vuejs"],
"component": "person",
}
],
}
With that being says when I filter using this example combi(screenshot). I get Martha, Tom and John as a result. When what I actually want is to have only Tom as a result. because only Tom have both criterias together inside his skills data.
This is my current computed function:
filterPersonSkill() {
return this.getComponentPerson.filter((e) =>
e.skill.map((skill) => this.multiValue.includes(skill)).includes(true)
);
}
At the beginning I used includes instead of map and that worked half. Because I was getting the result only if I selected in the same order(in the multiselect) as the array skills was appearing. Example below
filterPersonSkill() {
return this.getComponentPerson.filter((e) =>
e.skill.includes(...this.multiValue)
);
}
Thank in advance for the advice and reading me.
I think, it will be much simpler, if you add checkbox for the user to use "exact" filtering, i.e. results which include only selected tags.
With such a checkbox you can do something like this:
// your vue component
export default {
data() {
return {
exactMatch: true,
}
},
methods: {
filterPersonSkillExactMatch() {
const result = [];
for (const p of this.getComponentPerson) {
if (p.skill.length === this.multiValue.length
&& this.multiValue.every(val => p.skill.includes(val))) {
result.push(p)
}
}
return result
}
// somewhere in your code (either computed prop or method):
filteredPersons() {
if (exactMatch) {
return this.filterPersonSkillExactMatch()
}
return this.filterPerson()
}
}
}
The problem:
I can't retrieve an include inside include with Sequelize#5.21.2.
The first include its OK. But when i try to grab the other include, i only receive null.
The code:
let planCodes = ['monthly', 'annual'];
const plans = await Plan.findAll({
where: {
code: {
[Op.or]: planCodes
}
},
include: [{
model: PlanItem,
as: 'plan_items',
include: [{
model: Product,
as: 'product',
}]
}]
});
Model associations:
Plan
Plan.hasMany(PlanItem, { foreignKey: "plan_id" });
PlanItem
PlanItem.hasOne(Product, { foreignKey: "plan_item_id" });
The response:
[
{
"id": 175231,
"name": "mensal",
"interval": "months",
"interval_count": 1,
"code": "monthly",
"status": "active",
"metadata": {},
"created_at": "XXXX-XX-XX",
"updated_at": "XXXX-XX-XX",
"plan_items": [
{
"id": 190651,
"plan_id": 175231,
"product_id": 655939,
"created_at": "XXXX-XX-XX",
"updated_at": "XXXX-XX-XX",
"product": null
}
]
}
]
I already checked all details with the IDs at database and everything is OK.
I will admit that Sequelize likes to take all the spontaneity out of relationships, but you should use your alias in both the model definition AND the query. And they have to be the same. I'm sure there is some leeway, as you are, in fact, returning plan_items values. But whether "best practice" or "mandate", give this a go:
Plan.hasMany(PlanItem, { as: "plan_items", foreignKey: "plan_id" });
PlanItem.hasOne(Product, { as: "product", foreignKey: "plan_item_id" });
// You may also try defining the Product/PlanItem relationship the other way
Product.belongsTo(PlanItem, { as: "plan_item", foreignKey: "plan_item_id" });
So I am reasonably new to using API's with Js but I am struggling a lot to understand how the Google Fit API works. I am attempting to add a new Workout's data to the API by adding a session and some data for the intensity (heart points) of the session. I can get the session to appear correctly but run into constant errors when I try to create a dataSource and add a point to it for the session. It would be greatly appreciated if someone could help me to fix my code to achieve this or could direct me to a more thorough example of similar code as the API docs don't seem to be too well detailed with examples etc. Thanks in advance.
Here's the 3 api calls that I have written so far, one for creating the DataSource, one for the DataPoint and one for the Session. The session works correctly and adds a session of 1 hr for the correct activity but I am unable to get any of the other API requests to work.
Data Source :
``gapi.client.fitness.users.dataSources.create({
"userId":"me",
"resource": {
"application": {
"name": "LittleWorkouts"
},
"dataType": {"field":[{
"format": "floatPoint",
"name": "com.google.heart_minutes"
}],
"name": "com.google.heart_minutes"
},
"device": {
"manufacturer": "op",
"model": "6",
"type": "phone",
"uid": "1000019",
"version": "1"
},
"type": "raw"
}
})
.then(function(response) {
// Handle the results here (response.result has the parsed body).
console.log("Response", response);
},
function(err) { console.error("Execute error 1", err); });
``
Data Point :
``
gapi.client.fitness.users.dataSources.datasets.patch({
"dataSourceId":"raw:com.google.heart_minutes:292824132082:op:6:1000019",
"userId": "me",
"datasetId": "1592087806561000000-1592287806561000000",
"resource": {
"minStartTimeNs": "1592087806561000000",
"maxEndTimeNs": "1592287806561000000",
"dataSourceId": "raw:com.google.heart_minutes:292824132082:op:6:1000019",
"point": [
{
"startTimeNanos": "1592087806561000000",
"endTimeNanos": "1592287806561000000",
"value": [
{
"fpVal": 89.1
}
],
"dataTypeName": "com.google.heart_minutes"
}
]
}
})
.then(function(response) {
// Handle the results here (response.result has the parsed body).
console.log("Response", response);
},
function(err) { console.error("Execute error 2", err); });
``
Session :
``gapi.client.fitness.users.sessions.update({
"userId":"me",
"sessionId": "someSessionId19",
"id": "someSessionId19",
"name": "Awesome Workout19",
"description": "A very intense workout",
"startTimeMillis": new Date().getTime() - 3600000,
"endTimeMillis": new Date().getTime(),
"version": 1,
"lastModifiedToken": "exampleToken",
"application": {
"detailsUrl": "http://example.com",
"name": "LittleWorkouts",
"version": "1.0"
},
"activityType": 21,
"activeTimeMillis": 3600000
}).then((res) => {console.log(res)});
console.log('res')
//request.execute((res) => {console.log(res);console.log('executrd')})
console.log(auth2.currentUser.get().getBasicProfile().getGivenName());
var request2 = gapi.client.fitness.users.sessions.list({
"userId":"me"
}).then((res) => {console.log(res)})
``
Error message
{message: "Unable to fetch DataSource for Dataset: raw:com.google.heart_minutes:292824132082:op:6:1000019", domain: "global", reason: "invalidArgument"}
It looks like it could be that you're trying to pass in the wrong fields for the data type: if you want to use a standard data type (like com.google.heart_minutes), you should either pass the exact fields of the standard data type (the field should be called "intensity"); or just pass the data type name, and the backend will fill them in for you.
So, if you change the data type to
"dataType": {"name": "com.google.heart_minutes"}
It should work.
Then, you need to use the data source ID returned from that request for the data points.
Awesome, so after some support in the comments I have some working code to add a new session with data from a previously defined data source using 3 API calls. The first call is to create a data source and only needs to be run once. The second and third then add a data point to a data set and creates a new session for the workout respectively. Here's the final working code:
Data Source:
/*
gapi.client.fitness.users.dataSources.create({
"userId":"me",
"resource": {
"application": {
"name": "LittleWorkouts"
},
"dataType": {
"name": "com.google.heart_minutes"
},
"device": {
"manufacturer": "op",
"model": "6",
"type": "phone",
"uid": "1000020",
"version": "1"
},
"type": "raw"
}
})
.then(function(response) {
// Handle the results here (response.result has the parsed body).
console.log("Response", response);
},
function(err) { console.error("Execute error 1", err); });
*/
Data and Data Set:
gapi.client.fitness.users.dataSources.datasets.patch({
"dataSourceId":"raw:com.google.heart_minutes:108881196053:op:6:1000020",
"userId": "me",
"datasetId": z,
"resource": {
"minStartTimeNs": workoutStartTime * 1000000,
"maxEndTimeNs": workoutEndTime * 1000000,
"dataSourceId": "raw:com.google.heart_minutes:108881196053:op:6:1000020",
"point": [
{
"originDataSourceId": "raw:com.google.heart_minutes:108881196053:op:6:1000020",
"value": [
{
"fpVal": 8
}
],
"dataTypeName": "com.google.heart_minutes",
"endTimeNanos": workoutEndTime * 1000000,
"startTimeNanos": workoutStartTime * 1000000,
}
]
}
})
.then(function(response) {
// Handle the results here (response.result has the parsed body).
console.log("Response", response);
},
function(err) { console.error("Execute error 2", err); });
Session:
gapi.client.fitness.users.sessions.update({
"userId":"me",
"sessionId": id,
"id": id,
"name": "Morning Workout",
"description": "A very intense workout",
"startTimeMillis": workoutStartTime,
"endTimeMillis": workoutEndTime,
"version": 1,
"lastModifiedToken": "exampleToken",
"application": {
"detailsUrl": "http://example.com",
"name": "LittleWorkouts",
"version": "1.0"
},
"activityType": 21,
"activeTimeMillis": workoutEndTime - workoutStartTime
}).then((res) => {console.log(res)});
console.log('res')
I am building a project using sequelize.js that includes a Tags table and a Stories table. They have a many to many relationship, which I created in sequelize with a through table of StoryTag. This all works perfectly so far, but I want to get a list of most popluar tags, as in how many stories they are associated with in the StoryTag table, and order them by the number of stories that use this tag.
This is the MySQL syntax of what I am trying to do. This works perfectly in MySQL Workbench:
SELECT tagName, COUNT(StoryTag.TagId)
FROM Tags
LEFT JOIN StoryTag on Tags.id = StoryTag.TagId
GROUP BY Tags.tagName ORDER BY COUNT(StoryTag.TagId) DESC;
This is what works in sequelize.js. It's a raw query, which is not ideal, but since this doesn't handle any sensitive information, it's not a huge worry, just very inelegant.
//DIRECT QUERY METHOD (TEST)
app.get("/api/directags", function (req, res) {
db.sequelize.query("select tags.id, tags.TagName, COUNT(stories.id) as num_stories
from tags left join storytag on storytag.TagId = tags.id
left join stories on storytag.StoryId = stories.id
group by tags.id order by num_stories desc;", {
type: db.Sequelize.QueryTypes.SELECT
}).then(function(result) {
res.send(result);
});
});
This outputs
[
{
"id": 3,
"TagName": "fiction",
"num_stories": 3
},
{
"id": 5,
"TagName": "Nursery Rhyme",
"num_stories": 2
},
...
{
"id": 4,
"TagName": "nonfiction",
"num_stories": 0
}
]
As it should. What doesn't quite work is:
//Sequelize count tags
//Known issues: will not order by the count
//Includes a random 'storytag' many-to-many table row for some reason
app.get("/api/sequelizetags", function (req, res) {
db.Tag.findAll({
attributes: ["id","TagName"],
include: [{
model: db.Story,
attributes: [[db.sequelize.fn("COUNT", "stories.id"), "Count_Of_Stories"]],
duplicating: false
}],
group: ["id"]
}).then(function (dbExamples) {
res.send(dbExamples);
});
});
Which outputs:
[
{
"id": 1,
"TagName": "horror",
"Stories": [
{
"Count_Of_Stories": 1,
"StoryTag": {
"createdAt": "2018-11-29T21:09:46.000Z",
"updatedAt": "2018-11-29T21:09:46.000Z",
"StoryId": 1,
"TagId": 1
}
}
]
},
{
"id": 2,
"TagName": "comedy",
"Stories": []
},
{
"id": 3,
"TagName": "fiction",
"Stories": [
{
"Count_Of_Stories": 3,
"StoryTag": {
"createdAt": "2018-11-29T21:10:04.000Z",
"updatedAt": "2018-11-29T21:10:04.000Z",
"StoryId": 1,
"TagId": 3
}
}
]
},
{
"id": 4,
"TagName": "nonfiction",
"Stories": []
},
...
{
"id": 8,
"TagName": "Drama",
"Stories": [
{
"Count_Of_Stories": 1,
"StoryTag": {
"createdAt": "2018-11-30T01:13:56.000Z",
"updatedAt": "2018-11-30T01:13:56.000Z",
"StoryId": 3,
"TagId": 8
}
}
]
},
{
"id": 9,
"TagName": "Tragedy",
"Stories": []
}
]
This is not in order, and the count of stories is buried. This seems like the sort of thing that would be a common and frequent request from a database, but I am at a loss of how to do this correctly with sequelize.js.
Resources that have failed me:
Sequelize where on many-to-many join
Sequelize Many to Many Query Issue
How to query many-to-many relationship data in Sequelize
Select from many-to-many relationship sequelize
The official documentation for sequelize: http://docs.sequelizejs.com/manual/tutorial/
Some less official and more readable documentation for sequelize: https://sequelize.readthedocs.io/en/v3/docs/querying/
Here's what finally worked, in case anyone else has this question. We also added a where to the include Story, but that's optional.
This resource is easier to understand than the official sequelize docs: https://sequelize-guides.netlify.com/querying/
I also learned that being familiar with promises is really helpful when working with sequelize.
db.Tag.findAll({
group: ["Tag.id"],
includeIgnoreAttributes:false,
include: [{
model: db.Story,
where: {
isPublic: true
}
}],
attributes: [
"id",
"TagName",
[db.sequelize.fn("COUNT", db.sequelize.col("stories.id")), "num_stories"],
],
order: [[db.sequelize.fn("COUNT", db.sequelize.col("stories.id")), "DESC"]]
}).then(function(result){
return result;
});
Please, use the same name if you mean the same thing (num_stories - Count_Of_Stories, etc.).
For ordering use order option.
Include count in top level attributes for get it on top level of instance.
I can't find include[].duplicating option in doc.
Your case:
db.Tag.findAll({
attributes: [
"id",
"TagName",
[db.sequelize.fn("COUNT", "stories.id"), "Count_Of_Stories"]
],
include: [{
model: db.Story,
attributes: [],
duplicating: false
}],
group: ["id"],
order: [
[db.sequelize.literal("`Count_Of_Stories`"), "DESC"]
]
});
Use through: {attributes: []} in options
Does anyone know if it's possible to populate a list of IDs for another model using waterline associations? I was trying to get the many-to-many association working but I don't think it applies here since one side of the relationship doesn't know about the other. Meaning, a user can be a part of many groups but groups don't know which users belong to them. For example, I'm currently working with a model with data in mongodb that looks like:
// Group
{
_id: group01,
var: 'somedata',
},
{
_id: group02,
var: 'somedata',
},
{
_id: group03,
var: 'somedata',
}
// User
{
_id: 1234,
name: 'Jim',
groups: ['group01', 'group03']
}
And I'm trying to figure out if it's possible to setup the models with an association in such a way that the following is returned when querying the user:
// Req: /api/users/1234
// Desired result
{
id: 1234,
name: 'Jim',
groups: [
{
_id: group01,
var: 'somedata',
},
{
_id: group03,
var: 'somedata',
}
]
}
Yes, associations are supported in sails 0.10.x onwards. Here is how you can setup the models
Here is how your user model will look like:
// User.js
module.exports = {
tableName: "users",
attributes: {
name: {
type: "string",
required: true
},
groups: {
collection: "group",
via: "id"
}
}
};
Here is how your group model will look like:
// Group.js
module.exports = {
tableName: "groups",
attributes: {
name: {
type: "string",
required: "true"
}
}
};
Setting up models like this will create three tables in your DB:
users,
groups and
group_id__user_group
The last table is created by waterline to save the associations. Now go on and create groups. Once groups are created, go ahead and create user.
Here is a sample POST request for creation a new user
{
"name": "user1",
"groups": ["547d84f691bff6663ad08147", "547d850c91bff6663ad08148"]
}
This will insert data into the group_id__user_group in the following manner
{
"_id" : ObjectId("547d854591bff6663ad0814a"),
"group_id" : ObjectId("547d84f691bff6663ad08147"),
"user_groups" : ObjectId("547d854591bff6663ad08149")
}
/* 1 */
{
"_id" : ObjectId("547d854591bff6663ad0814b"),
"group_id" : ObjectId("547d850c91bff6663ad08148"),
"user_groups" : ObjectId("547d854591bff6663ad08149")
}
The column user_groups is the user id. And group_id is the group id. Now if you fetch the user using GET request, your response will look like this:
{
"groups": [
{
"name": "group1",
"createdAt": "2014-12-02T09:23:02.510Z",
"updatedAt": "2014-12-02T09:23:02.510Z",
"id": "547d84f691bff6663ad08147"
},
{
"name": "group2",
"createdAt": "2014-12-02T09:23:24.851Z",
"updatedAt": "2014-12-02T09:23:24.851Z",
"id": "547d850c91bff6663ad08148"
}
],
"name": "user1",
"createdAt": "2014-12-02T09:24:21.182Z",
"updatedAt": "2014-12-02T09:24:21.188Z",
"id": "547d854591bff6663ad08149"
}
Please note that groups are not embedded in the user collection. Waterline does the fetch from groups, users and group_id__user_group to show this result to you.
Also, if you want to do this in your controller, you will need to execute like this
User.findOne({'id': "547d854591bff6663ad08149"})
.populate('groups')
.exec(function (err, user){
// handle error and results in this callback
});
Without populate('groups'), you won't get the groups array. Hope this serves your purpose