Can I sync calendar events with the Microsoft Graph API? - outlook

I'm working with the Microsoft Graph api to try and sync calendar events from Outlook. I was looking at this article concerning the Outlook api, which suggested I add the header odata.track-changes to my request and I would receive a deltaToken, which I could use on a later request to fetch only those events which had been updated or created since the last sync.
I have been successful fetching events, but I'm not getting a deltaToken back :/
Is this only supported in the Outlook api? Graph's response has Preference-Applied: odata.track-changes, so it's acknowledging my header. Here's my sample request:
GET /v1.0/me/calendar/calendarView
?startDateTime=2016-09-01T00:00:00.0000000
&endDateTime=2099-01-01T00:00:00.0000000
HTTP/1.1
Host: graph.microsoft.com
Authorization: Bearer XXX
Prefer: odata.track-changes
Prefer: odata.maxpagesize=3 //for testing
Cache-Control: no-cache
And my sample response:
{
"#odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('')/calendar/calendarView",
"value": [
{
"#odata.etag": "",
"id": "",
"createdDateTime": "2016-08-04T14:00:25.8552351Z",
"lastModifiedDateTime": "2016-08-25T14:43:54.9950828Z",
"changeKey": "",
"categories": [
"Orange category"
],
"originalStartTimeZone": "Eastern Standard Time",
"originalEndTimeZone": "Eastern Standard Time",
"responseStatus": {
"response": "organizer",
"time": "0001-01-01T00:00:00Z"
},
"iCalUId": "",
"reminderMinutesBeforeStart": 15,
"isReminderOn": true,
"hasAttachments": false,
"subject": "Closing on House",
"body": {
"contentType": "html",
"content": ""
},
"bodyPreview": "",
"importance": "normal",
"sensitivity": "normal",
"start": {
"dateTime": "2016-09-08T19:30:00.0000000",
"timeZone": "UTC"
},
"end": {
"dateTime": "2016-09-08T21:30:00.0000000",
"timeZone": "UTC"
},
"location": {
"displayName": "245 E Main St",
"address": {
"street": "245 E Main St",
"city": "Somewhere",
"state": "NY",
"countryOrRegion": "United States",
"postalCode": ""
}
},
"isAllDay": false,
"isCancelled": false,
"isOrganizer": true,
"recurrence": null,
"responseRequested": true,
"seriesMasterId": null,
"showAs": "busy",
"type": "singleInstance",
"attendees": [],
"organizer": {
"emailAddress": {
"name": "",
"address": ""
}
},
"webLink": "https://outlook.office365.com/owa/?ItemID="
},
{
"#odata.etag": "",
"id": "",
"createdDateTime": "2016-08-19T18:02:39.0607411Z",
"lastModifiedDateTime": "2016-08-19T18:04:10.548447Z",
"changeKey": "",
"categories": [
"Green category"
],
"originalStartTimeZone": "UTC",
"originalEndTimeZone": "UTC",
"responseStatus": {
"response": "organizer",
"time": "0001-01-01T00:00:00Z"
},
"iCalUId": "",
"reminderMinutesBeforeStart": 15,
"isReminderOn": true,
"hasAttachments": false,
"subject": "Moving (off work)",
"body": {
"contentType": "html",
"content": ""
},
"bodyPreview": "",
"importance": "normal",
"sensitivity": "normal",
"start": {
"dateTime": "2016-09-10T00:00:00.0000000",
"timeZone": "UTC"
},
"end": {
"dateTime": "2016-09-13T00:00:00.0000000",
"timeZone": "UTC"
},
"location": {
"displayName": "",
"address": {}
},
"isAllDay": true,
"isCancelled": false,
"isOrganizer": true,
"recurrence": null,
"responseRequested": true,
"seriesMasterId": null,
"showAs": "oof",
"type": "singleInstance",
"attendees": [],
"organizer": {
"emailAddress": {
"name": "",
"address": ""
}
},
"webLink": "https://outlook.office365.com/owa/?ItemID="
},
{
"#odata.etag": "",
"id": "",
"createdDateTime": "2016-09-13T19:05:20.8438647Z",
"lastModifiedDateTime": "2016-09-13T19:05:22.1899702Z",
"changeKey": "",
"categories": [],
"originalStartTimeZone": "America/New_York",
"originalEndTimeZone": "America/New_York",
"responseStatus": {
"response": "organizer",
"time": "0001-01-01T00:00:00Z"
},
"iCalUId": "",
"reminderMinutesBeforeStart": 15,
"isReminderOn": true,
"hasAttachments": false,
"subject": "Coffee Break",
"body": {
"contentType": "html",
"content": ""
},
"bodyPreview": "",
"importance": "normal",
"sensitivity": "normal",
"start": {
"dateTime": "2016-09-15T20:15:00.0000000",
"timeZone": "UTC"
},
"end": {
"dateTime": "2016-09-15T21:15:00.0000000",
"timeZone": "UTC"
},
"location": {
"displayName": "",
"address": {}
},
"isAllDay": false,
"isCancelled": false,
"isOrganizer": true,
"recurrence": null,
"responseRequested": true,
"seriesMasterId": null,
"showAs": "busy",
"type": "singleInstance",
"attendees": [],
"organizer": {
"emailAddress": {
"name": "",
"address": ""
}
},
"webLink": "https://outlook.office365.com/owa/?ItemID="
}
]
}
I redacted anything I thought could be mildly sensitive. Ultimately, my Laravel app is trying to sync events starting 4 months back, and go forever into the future.
If there's a more efficient/better way to do it, I'm open to suggestions. If it matters, these results were generated with Postman. Any help or clarity on this is appreciated.

I ended up using the odata filter like so:
https://graph.microsoft.com/beta/me/calendar/calendarView?startDateTime=2016-05-01T00:00:00Z&endDateTime=2099-01-01T00:00:00Z&$filter=type eq 'singleInstance' and lastModifiedDateTime eq '2016-09-20T07:30:00+00:00'
This will fetch all calendar events scheduled between 2016-05-01T00:00:00Z (May 1st, 2016, midnight, UTC and 2099-01-01T00:00:00Z (January 1st, 2099, midnight, UTC) where the event type is singleInstance (not a recurring event) and the lastModifiedDateTime is after the last sync (in this example, 2016-09-20T07:30:00+00:00).
A few pitfalls with this:
Obviously, this is not url encoded. You would need to do that.
Make sure the + in the lastModifiedDateTime example is properly encoded to %2B, otherwise the Graph API will treat it as a space and reject it.
If you don't filter out recurring events, you will get each recurring event from now until 2099. This is the nature of fetching a list of calendarViews as opposed to events.
If I could do this again, I would probably go back and just do the full calendar sync, which Graph supports (I believe). I just didn't want to sync the whole calendar, only a date range, but it seems that was a doomed endeavor.
But despite the lack of recurring events, it works.
UPDATE
I ended up scrapping this implementation mostly because of the continuing pitfalls I've encountered with maintaining data sync integrity, lack of recurring events, and etc. Instead, I pull calendar events in real time, and maintain a cache. Just some advice in case anyone else ends up in my situation.

Related

How can I write a strapi query to get my posts and image from a category content type

I created a collection type 'posts' and created many posts. Now I made another collection-type of category with ['softwaresApps','gifts','technology'] title that holds relations of posts. then i tried fetching with http://localhost:1337/api/categories?populate=*" and get:
{
"data": [
{
"id": 1,
"attributes": {
"title": "Technology",
"createdAt": "2022-01-09T00:43:05.899Z",
"updatedAt": "2022-01-09T00:45:26.131Z",
"publishedAt": "2022-01-09T00:44:04.298Z"
}
},
{
"id": 2,
"attributes": {
"title": "Gifts",
"createdAt": "2022-01-09T00:43:48.196Z",
"updatedAt": "2022-01-09T00:43:53.979Z",
"publishedAt": "2022-01-09T00:43:53.961Z"
}
},
{
"id": 3,
"attributes": {
"title": "softwares&apps",
"createdAt": "2022-01-09T00:48:23.706Z",
"updatedAt": "2022-01-11T00:06:48.130Z",
"publishedAt": "2022-01-09T00:49:59.571Z"
}
},
{
"id": 4,
"attributes": {
"title": "HomeSlider",
"createdAt": "2022-01-15T15:06:23.122Z",
"updatedAt": "2022-01-15T15:06:30.272Z",
"publishedAt": "2022-01-15T15:06:30.259Z"
}
}
],
"meta": {
"pagination": {
"page": 1,
"pageSize": 25,
"pageCount": 1,
"total": 4
}
}
}
I can't seem to find my posts relations in the data. I need a query that can help me fetch all the posts & images from the cateories.
I have figured it out. thanks to #IceJonas, his link:
https://stackoverflow.com/a/70251184/17908449
It turns out populating with http://localhost:1337/api/categories?populate=*" is not going to cut it. I found the solution whereby you have to populate deeper using - http://localhost:1337/api/categories?populate[posts][populate]=* and you will populate all the relations including their images.
{
"data": [
{
"id": 1,
"attributes": {
"softwareapps": "softwareapps",
"createdAt": "2022-01-18T01:42:01.223Z",
"updatedAt": "2022-01-18T01:43:37.935Z",
"publishedAt": "2022-01-18T01:43:37.931Z",
"posts": {
"data": [
{
"id": 3,
"attributes": {
"title": "The Best Drones for Photos and Video",
"date": "2022-01-11",
"article": "The **most** of your time with a _laptop_ is spent in a browser, you can get a better experience for your money with a [Chromebook.](wirecutter.com) \n\nNena Farrell has covered technology and connected home products since 2016, originally at Sunset Magazine (where she was an associate home editor) and now as an updates writer on the audio, visual, and smart-home team at Wirecutter.\n\nBrendan Nystedt contributed to an earlier version of this guide. He’s been an enthusiast photographer and a writer covering consumer electronics and tech for the better part of a decade. Nystedt has worked for Wirecutter, Reviewed, and Wired, and he has written for numerous other outlets.\n\nWhile preparing this guide, we consulted reviews both from owners and from trustworthy outlets, such as Wired and PCMag. Unfortunately for the layperson shopping for these devices, there are many SEO-driven clickbait blogs offering hands-off reviews. We ignored those websites.\n\n",
"createdAt": "2022-01-09T00:07:30.402Z",
"updatedAt": "2022-01-16T23:27:06.589Z",
"publishedAt": "2022-01-09T00:07:33.230Z",
"author": "chisom ifem",
"subCategory": "phone",
"postId": "3",
"readTime": "4",
"category": "softwareapps",
"popular": true,
"homeStartHere": false,
"slug": "The-Best-Drones-for-Photos-and-Video",
"image": {
"data": {
"id": 17,
"attributes": {
"name": "pg5.jpg",
"alternativeText": "pg5.jpg",
"caption": "pg5.jpg",
"width": 1470,
"height": 735,
"formats": {
"thumbnail": {
"name": "thumbnail_pg5.jpg",
"hash": "thumbnail_pg5_cbcb14de57",
"ext": ".jpg",
"mime": "image/jpeg",
"width": 245,
"height": 123,
"size": 12.24,
"path": null,
"url": "/uploads/thumbnail_pg5_cbcb14de57.jpg"
},
"large": {
"name": "large_pg5.jpg",
"hash": "large_pg5_cbcb14de57",
"ext": ".jpg",
"mime": "image/jpeg",
"width": 1000,
"height": 500,
"size": 63.18,
"path": null,
"url": "/uploads/large_pg5_cbcb14de57.jpg"
},
"medium": {
"name": "medium_pg5.jpg",
"hash": "medium_pg5_cbcb14de57",
"ext": ".jpg",
"mime": "image/jpeg",
"width": 750,
"height": 375,
"size": 39.5,
"path": null,
"url": "/uploads/medium_pg5_cbcb14de57.jpg"
},
"small": {
"name": "small_pg5.jpg",
"hash": "small_pg5_cbcb14de57",
"ext": ".jpg",
"mime": "image/jpeg",
"width": 500,
"height": 250,
"size": 23.05,
"path": null,
"url": "/uploads/small_pg5_cbcb14de57.jpg"
}
},
"hash": "pg5_cbcb14de57",
"ext": ".jpg",
"mime": "image/jpeg",
"size": 93.38,
"url": "/uploads/pg5_cbcb14de57.jpg",
"previewUrl": null,
"provider": "local",
"provider_metadata": null,
"createdAt": "2022-01-12T23:37:47.189Z",
"updatedAt": "2022-01-12T23:37:47.189Z"
}
}
}
}
},
{
"id": 2,
"attributes": {
"title": "The Best Mop-Vacuum Combo Is the Bissell CrossWave",
"date": "2022-01-10",
"article": "The Aeezo Portrait 01 is considerably cheaper than our other picks (it’s nearly half the price of the Aura Mason), and it’s surprisingly good. The frame is minimalist but doesn’t look cheap, and the Aeezo has a unique feature that allows you to easily re-crop a photo so that no matter the orientation, it always looks good. The downside is the uploader, which limits you to 10 pictures at a time and is overall frustrating for the less-tech-savvy to navigate and understand.\n\n# This is the end hommies",
"createdAt": "2022-01-09T00:00:30.997Z",
"updatedAt": "2022-01-17T00:19:05.116Z",
"publishedAt": "2022-01-11T22:24:52.541Z",
"author": "Emeka olumma",
"subCategory": "phone",
"postId": "2",
"readTime": "3",
"category": "gifts",
"popular": true,
"homeStartHere": false,
"slug": "The Best-Mop-Vacuum-Combo-is-the-Bissell-CrossWave",
"image": {
"data": {
"id": 21,
"attributes": {
"name": "slide2.jpg",
"alternativeText": "slide2.jpg",
"caption": "slide2.jpg",
"width": 1470,
"height": 735,
"formats": {
"thumbnail": {
"name": "thumbnail_slide2.jpg",
"hash": "thumbnail_slide2_de6b7dcd32",
"ext": ".jpg",
"mime": "image/jpeg",
"width": 245,
"height": 123,
"size": 11.51,
"path": null,
"url": "/uploads/thumbnail_slide2_de6b7dcd32.jpg"
},
"large": {
"name": "large_slide2.jpg",
"hash": "large_slide2_de6b7dcd32",
"ext": ".jpg",
"mime": "image/jpeg",
"width": 1000,
"height": 500,
"size": 43.34,
"path": null,
"url": "/uploads/large_slide2_de6b7dcd32.jpg"
},
"medium": {
"name": "medium_slide2.jpg",
"hash": "medium_slide2_de6b7dcd32",
"ext": ".jpg",
"mime": "image/jpeg",
"width": 750,
"height": 375,
"size": 30.53,
"path": null,
"url": "/uploads/medium_slide2_de6b7dcd32.jpg"
},
"small": {
"name": "small_slide2.jpg",
"hash": "small_slide2_de6b7dcd32",
"ext": ".jpg",
"mime": "image/jpeg",
"width": 500,
"height": 250,
"size": 19.62,
"path": null,
"url": "/uploads/small_slide2_de6b7dcd32.jpg"
}
},
"hash": "slide2_de6b7dcd32",
"ext": ".jpg",
"mime": "image/jpeg",
"size": 52.52,
"url": "/uploads/slide2_de6b7dcd32.jpg",
"previewUrl": null,
"provider": "local",
"provider_metadata": null,
"createdAt": "2022-01-15T15:33:30.925Z",
"updatedAt": "2022-01-15T15:33:30.925Z"
}
}
}
}
},
{
"id": 6,
"attributes": {
"title": "Laptop you can buy now in 2022",
"date": "2022-01-14",
"article": "A digital photo frame lets you easily add images—including beautiful travel shots and family photos—to the frame from anywhere. Whether you’re giving a frame as a gift and plan to upload photos remotely, or you just want a great frame for yourself, the Aura Mason is the best frame we’ve used. Its 8.57-inch display is sharp, bright, and vivid, and in our tests it was the simplest to set up. On top of that, it has a good-looking design.\n\nOf all the frames we tried, the Aura Mason came the closest to mimicking a regular photo frame. The Mason is straightforward to set up and use, and remotely loading photos is a breeze (the free Aura app works with Android and iOS phones). With this frame it is easy to add photos on the app or through a web uploader. From the intuitive design of the app to the frame’s pared-down interface, the Mason is not just for the tech-savvy.\n\n\n### The Aura Mason\nThe Aura Mason Luxe is an upgrade to the Mason, with a larger price tag. That extra cost adds a slightly larger 2K screen and video capabilities onto everything we already like about the Mason frame. But even though the video capabilities are nice, we don’t think they’re truly necessary in order to enjoy a digital picture frame.\n\n\n\nThe [Aeezo Portrait](wirecutter.com) 01 is considerably cheaper than our other picks (it’s nearly half the price of the Aura Mason), and it’s surprisingly good. The frame is minimalist but doesn’t look cheap, and the Aeezo has a unique feature that allows you to easily re-crop a photo so that no matter the orientation, it always looks good. The downside is the uploader, which limits you to 10 pictures at a time and is overall frustrating for the less-tech-savvy to navigate and understand.\n\n",
"createdAt": "2022-01-09T00:18:35.467Z",
"updatedAt": "2022-01-16T23:05:11.379Z",
"publishedAt": "2022-01-09T00:18:38.306Z",
"author": "John juzzy",
"subCategory": "phone",
"postId": "6",
"readTime": "3",
"category": "softwareapps",
"popular": false,
"homeStartHere": false,
"slug": "Laptop-you-can-buy-now-in-2022",
"image": {
"data": {
"id": 13,
"attributes": {
"name": "kids3.png",
"alternativeText": "kids3.png",
"caption": "kids3.png",
"width": 206,
"height": 142,
"formats": null,
"hash": "kids3_eee4bde396",
"ext": ".png",
"mime": "image/png",
"size": 54.89,
"url": "/uploads/kids3_eee4bde396.png",
"previewUrl": null,
"provider": "local",
"provider_metadata": null,
"createdAt": "2022-01-09T00:16:21.166Z",
"updatedAt": "2022-01-09T00:16:21.166Z"
}
}
}
}
}
]
}
}
}
],
"meta": {
"pagination": {
"page": 1,
"pageSize": 25,
"pageCount": 1,
"total": 1
}
}
}
I hope this helps someone.

Bot Framework save user data in the database by UserID

I want to save data that the user has entered in my database.
I need an ID that will be unique for all conversations with the same user rather than for a single conversation.
According to the documentation, I need to use the From.ID field from the Channel Account data.
But when I use Bot Framework Emulator and click Restart with same User ID, the ID of the user changes and what remains is the bot ID.
What field should I use to identify a user across different calls (in the same channel, of course)?
In this example, I see that the the Recipient.id identifier that the bot sends does not change.
Is this the user's unique identifier? Why is it be different from the From.ID?
Conversation 1:
Message from the bot:
{
"channelId": "emulator",
"conversation": {
"id": "202d2d60-4c7f-11e9-b1fa-8b3537dcca45|livechat"
},
"from": {
"id": "2",
"name": "Bot",
"role": "bot"
},
"id": "208f2380-4c7f-11e9-98ea-9595460a8f6e",
"inputHint": "acceptingInput",
"localTimestamp": "2019-03-22T10:47:30+02:00",
"locale": "",
"recipient": {
"id": "d4d1b5a6-1797-4d2a-b78e-257de71d3a69",
"role": "user"
},
"replyToId": "20559cf0-4c7f-11e9-98ea-9595460a8f6e",
"serviceUrl": "http://localhost:53634",
"showInInspector": true,
"text": "conversationUpdate event detected",
"timestamp": "2019-03-22T08:47:30.232Z",
"type": "message"
}
Message from the user:
{
"channelData": {
"clientActivityID": "15532445742330.iqwrgb646rq",
"state": "sent"
},
"channelId": "emulator",
"conversation": {
"id": "202d2d60-4c7f-11e9-b1fa-8b3537dcca45|livechat"
},
"entities": [
{
"requiresBotState": true,
"supportsListening": true,
"supportsTts": true,
"type": "ClientCapabilities"
}
],
"from": {
"id": "r_wg30czmqjt",
"name": "User",
"role": "user"
},
"id": "6a791af0-4c7f-11e9-98ea-9595460a8f6e",
"localTimestamp": "2019-03-22T10:49:34+02:00",
"locale": "",
"recipient": {
"id": "2",
"name": "Bot",
"role": "bot"
},
"serviceUrl": "http://localhost:53634",
"showInInspector": true,
"text": "hi",
"textFormat": "plain",
"timestamp": "2019-03-22T08:49:34.239Z",
"type": "message"
}
Conversation 2 (After Restart with same user ID):
Message from the bot:
{
"channelId": "emulator",
"conversation": {
"id": "a10fba20-4c83-11e9-b1fa-8b3537dcca45|livechat"
},
"from": {
"id": "2",
"name": "Bot",
"role": "bot"
},
"id": "a15611f0-4c83-11e9-98ea-9595460a8f6e",
"inputHint": "acceptingInput",
"localTimestamp": "2019-03-22T11:19:44+02:00",
"locale": "",
"recipient": {
"id": "d4d1b5a6-1797-4d2a-b78e-257de71d3a69",
"role": "user"
},
"replyToId": "a116e610-4c83-11e9-98ea-9595460a8f6e",
"serviceUrl": "http://localhost:53634",
"showInInspector": true,
"text": "conversationUpdate event detected",
"timestamp": "2019-03-22T09:19:44.271Z",
"type": "message"
}
Message from the user:
{
"channelData": {
"clientActivityID": "15532464069120.36lccv6nsg3",
"state": "sent"
},
"channelId": "emulator",
"conversation": {
"id": "a10fba20-4c83-11e9-b1fa-8b3537dcca45|livechat"
},
"entities": [
{
"requiresBotState": true,
"supportsListening": true,
"supportsTts": true,
"type": "ClientCapabilities"
}
],
"from": {
"id": "r_xl9pb24o5o",
"name": "User",
"role": "user"
},
"id": "aed62f90-4c83-11e9-98ea-9595460a8f6e",
"localTimestamp": "2019-03-22T11:20:06+02:00",
"locale": "",
"recipient": {
"id": "2",
"name": "Bot",
"role": "bot"
},
"serviceUrl": "http://localhost:53634",
"showInInspector": true,
"text": "hi",
"textFormat": "plain",
"timestamp": "2019-03-22T09:20:06.921Z",
"type": "message"
}
This was a bug in the Emulator, and fixed with https://github.com/Microsoft/BotFramework-Emulator/pull/1348
Please make sure you are on version >= 4.3.3:
Also, the Recipient.Id is the bot id if the message is coming from the Emulator.
Being that the bots do not track or identify a user, that part is up to you.
You will need to:
Create a "back channel" that captures Authentication of user. Give the bot access to the userid/username of the user who is interacting or having a conversation.
When the user logs in and authenticates to the application, you identify the user - retrieve this.
When the user interacts with the bot you get the conversation ID - you already have this.
Tie them together in a JSON object and store back to cloud storage, or app storage or SQL db.

process json using jq in bash script

Below is my json which I need to process using jq in bash script. I need to get the "Id" column value. Since in this json there are 3 records, record with maximum value id would be returned. So after processing of below json I should get 170.
I am newbie and have very limited exposure to bash.
{
"count": 3,
"value": [
{
"properties": {},
"tags": [], "validationResults": [],
"plans": [
{
"planId": "49699e0f-b893-4633-bc05-754b8a562d07"
}
], "triggerInfo": {},
"id": 170,
"buildNumber": "20181011.8", "status": "completed", "result": "succeeded", "queueTime": "2018-10-11T15:56:24.9611153Z", "startTime": "2018-10-11T15:56:28.3668144Z", "finishTime": "2018-10-11T15:57:20.5163422Z",
"url": "https://indiatelecom.visualstudio.com/d354caa2-2e88-414a-829b-25df3aceaaaf/_apis/build/Builds/170",
"buildNumberRevision": 8, "uri": "vstfs:///Build/Build/170",
"sourceBranch": "refs/heads/master", "sourceVersion": "4303c19f8fda79e35fcb598219d5dca6bb274c2d",
"priority": "normal", "reason": "manual", "lastChangedDate": "2018-10-11T15:57:20.797Z", "parameters": "{\"system.debug\":\"false\"}",
"orchestrationPlan": {
"planId": "49699e0f-b893-4633-bc05-754b8a562d07"
}, "keepForever": false, "retainedByRelease": false, "triggeredByBuild": null
},
{ "properties": {}, "tags": [], "validationResults": [],
"plans": [ { "planId": "15026a2f-c725-4e52-974b-61e01a940661"
} ],
"triggerInfo": {},
"id": 160,
"buildNumber": "20181009.20", "status": "completed", "result": "succeeded", "queueTime": "2018-10-09T16:47:42.2954075Z", "startTime": "2018-10-09T16:47:43.8034575Z",
"finishTime": "2018-10-09T16:48:35.8340469Z", "url": "https://indiatelecom.visualstudio.com/d354caa2-2e88-414a-829b-25df3aceaaaf/_apis/build/Builds/160",
"buildNumberRevision": 20, "uri": "vstfs:///Build/Build/160",
"sourceBranch": "refs/heads/master", "sourceVersion": "19a55c7482083785265b86015150521b40230c11",
"priority": "normal", "reason": "manual",
"lastChangedDate": "2018-10-09T16:48:36.057Z", "parameters": "{\"system.debug\":\"false\"}",
"orchestrationPlan": {
"planId": "15026a2f-c725-4e52-974b-61e01a940661"
},
"keepForever": false, "retainedByRelease": false,
"triggeredByBuild": null },
{
"properties": {}, "tags": [],
"validationResults": [], "plans": [
{
"planId": "e45d9da8-4d95-42b7-aa23-478e1c1c49f5"
}
],
"triggerInfo": {},
"id": 147,
"buildNumber": "20181009.7", "status": "completed",
"result": "succeeded", "queueTime": "2018-10-09T15:15:47.0248009Z",
"startTime": "2018-10-09T15:15:50.8899892Z", "finishTime": "2018-10-09T15:16:47.7866356Z",
"url": "https://indiatelecom.visualstudio.com/d354caa2-2e88-414a-829b-25df3aceaaaf/_apis/build/Builds/147",
"buildNumberRevision": 7, "uri": "vstfs:///Build/Build/147",
"sourceBranch": "refs/heads/master", "sourceVersion": "70fccb138a2f2a9dfe18290c468959102f504067",
"priority": "normal", "reason": "manual",
"lastChangedDate": "2018-10-09T15:16:48.16Z",
"parameters": "{\"system.debug\":\"false\"}", "orchestrationPlan": {
"planId": "e45d9da8-4d95-42b7-aa23-478e1c1c49f5"
}, "keepForever": false, "retainedByRelease": false,
"triggeredByBuild": null }
] }
The id's are stored in an array under the key value. .value[].id lists the ids, if you put them into an array, you can call max on it:
jq '[.value[].id] | max' < file.json

How to get proposed time using Microsoft Graph API?

When the other person declined the meeting and proposed a new time. In Outlook, you can see the proposed time.
Now I am trying to use Microsoft Graph API to get that proposed time.
For example, the original meeting date is 2018-03-08, and the other person declined and proposed a new date 2018-03-12.
I tried
GET /beta/me/messages/{messageId}=?$expand=microsoft.graph.eventMessage/event
However, I cannot find the proposed time from the result returned. How can I get it? Thanks
{
"#odata.context": "https://graph.microsoft.com/beta/$metadata#users('576552d5-3bc0-42a6-a53d-bfceb405db23')/messages/$entity",
"#odata.type": "#microsoft.graph.eventMessage",
"#odata.etag": "W/\"DAAAABYAAACpTc/InBsuTYwTUBb+VIb4AADoRAyI\"",
"id": "AAMkADBlZTUwNTkxLWVmODgtNDVhNC1iZjhlLTdjNjA1ODZlMDI5MgBGAAAAAACUbnk-iwQZRbXMgkfKtmYhBwCpTc-InBsuTYwTUBb_VIb4AAAAAAEMAACpTc-InBsuTYwTUBb_VIb4AADnwc8mAAA=",
"createdDateTime": "2018-03-06T22:29:10Z",
"lastModifiedDateTime": "2018-03-06T22:29:11Z",
"changeKey": "DAAAABYAAACpTc/InBsuTYwTUBb+VIb4AADoRAyI",
"categories": [],
"receivedDateTime": "2018-03-06T22:29:11Z",
"sentDateTime": "2018-03-06T22:29:05Z",
"hasAttachments": false,
"internetMessageId": "<MWHPR15MB18399806CC97C61817C9A2B18BD90#MWHPR15MB1839.namprd15.prod.outlook.com>",
"subject": "New Time Proposed: Test",
"bodyPreview": "",
"importance": "normal",
"parentFolderId": "AAMkADBlZTUwNTkxLWVmODgtNDVhNC1iZjhlLTdjNjA1ODZlMDI5MgAuAAAAAACUbnk-iwQZRbXMgkfKtmYhAQCpTc-InBsuTYwTUBb_VIb4AAAAAAEMAAA=",
"conversationId": "AAQkADBlZTUwNTkxLWVmODgtNDVhNC1iZjhlLTdjNjA1ODZlMDI5MgAQAOnuCMgoRLdGs-1scw6i7EU=",
"conversationIndex": "AdO1mnWL6e4IyChEt0az/WxzDqLsRQAABO5D",
"isDeliveryReceiptRequested": null,
"isReadReceiptRequested": false,
"isRead": false,
"isDraft": false,
"webLink": "https://outlook.office365.com/owa/?ItemID=AAMkADBlZTUwNTkxLWVmODgtNDVhNC1iZjhlLTdjNjA1ODZlMDI5MgBGAAAAAACUbnk%2FiwQZRbXMgkfKtmYhBwCpTc%2FInBsuTYwTUBb%2BVIb4AAAAAAEMAACpTc%2FInBsuTYwTUBb%2BVIb4AADnwc8mAAA%3D&exvsurl=1&viewmodel=ReadMessageItem",
"inferenceClassification": "focused",
"unsubscribeData": [],
"unsubscribeEnabled": false,
"meetingMessageType": "meetingDeclined",
"type": "singleInstance",
"isOutOfDate": false,
"isAllDay": false,
"isDelegated": false,
"body": {
"contentType": "html",
"content": "Hi"
},
"sender": {
"emailAddress": {
"name": "Rose",
"address": "rose#example.com"
}
},
"from": {
"emailAddress": {
"name": "Rose",
"address": "rose#example.com"
}
},
"toRecipients": [
{
"emailAddress": {
"name": "Jack",
"address": "jack#example.com"
}
}
],
"ccRecipients": [],
"bccRecipients": [],
"replyTo": [],
"mentionsPreview": null,
"flag": {
"flagStatus": "notFlagged"
},
"startDateTime": {
"dateTime": "2018-03-08T04:00:00.0000000",
"timeZone": "UTC"
},
"endDateTime": {
"dateTime": "2018-03-08T05:00:00.0000000",
"timeZone": "UTC"
},
"location": {
"displayName": "Test",
"locationType": "default",
"uniqueIdType": "unknown"
},
"recurrence": null,
"event#odata.context": "https://graph.microsoft.com/beta/$metadata#users('576552d5-3bc0-42a6-a53d-bfceb405db23')/messages('AAMkADBlZTUwNTkxLWVmODgtNDVhNC1iZjhlLTdjNjA1ODZlMDI5MgBGAAAAAACUbnk-iwQZRbXMgkfKtmYhBwCpTc-InBsuTYwTUBb_VIb4AAAAAAEMAACpTc-InBsuTYwTUBb_VIb4AADnwc8mAAA%3D')/microsoft.graph.eventMessage/event/$entity",
"event": {
"#odata.etag": "W/\"qU3PyJwbLk2ME1AW/lSG+AAA6EQMeA==\"",
"id": "AAMkADBlZTUwNTkxLWVmODgtNDVhNC1iZjhlLTdjNjA1ODZlMDI5MgBGAAAAAACUbnk-iwQZRbXMgkfKtmYhBwCpTc-InBsuTYwTUBb_VIb4AAAAAAENAACpTc-InBsuTYwTUBb_VIb4AADnwkmiAAA=",
"createdDateTime": "2018-03-06T22:28:32.3852279Z",
"lastModifiedDateTime": "2018-03-06T22:29:11.4604154Z",
"changeKey": "qU3PyJwbLk2ME1AW/lSG+AAA6EQMeA==",
"categories": [],
"originalStartTimeZone": "Pacific Standard Time",
"originalEndTimeZone": "Pacific Standard Time",
"iCalUId": "040000008200E00074C5B7101A82E0080000000000000000000000000000000000000000310000007643616C2D5569640100000033324633333433392D433744452D344338362D393046452D44424639314131363444323900",
"reminderMinutesBeforeStart": 15,
"isReminderOn": true,
"hasAttachments": false,
"subject": "Test",
"bodyPreview": "Test",
"importance": "normal",
"sensitivity": "normal",
"isAllDay": false,
"isCancelled": false,
"isOrganizer": true,
"responseRequested": true,
"seriesMasterId": null,
"showAs": "busy",
"type": "singleInstance",
"webLink": "https://outlook.office365.com/owa/?itemid=AAMkADBlZTUwNTkxLWVmODgtNDVhNC1iZjhlLTdjNjA1ODZlMDI5MgBGAAAAAACUbnk%2FiwQZRbXMgkfKtmYhBwCpTc%2FInBsuTYwTUBb%2BVIb4AAAAAAENAACpTc%2FInBsuTYwTUBb%2BVIb4AADnwkmiAAA%3D&exvsurl=1&path=/calendar/item",
"onlineMeetingUrl": null,
"responseStatus": {
"response": "organizer",
"time": "0001-01-01T00:00:00Z"
},
"body": {
"contentType": "html",
"content": "Hi"
},
"start": {
"dateTime": "2018-03-08T04:00:00.0000000",
"timeZone": "UTC"
},
"end": {
"dateTime": "2018-03-08T05:00:00.0000000",
"timeZone": "UTC"
},
"location": {
"displayName": "Test",
"locationType": "default",
"uniqueId": "Test",
"uniqueIdType": "private"
},
"locations": [
{
"displayName": "Test",
"locationType": "default",
"uniqueId": "Test",
"uniqueIdType": "private"
}
],
"recurrence": null,
"attendees": [
{
"type": "required",
"status": {
"response": "declined",
"time": "2018-03-06T22:29:05Z"
},
"emailAddress": {
"name": "Rose",
"address": "rose#example.com"
}
}
],
"organizer": {
"emailAddress": {
"name": "Jack",
"address": "jack#example.com"
}
}
}
}
The API doesn't expose this information directly. The related entities just don't have the fields defined to show this information. However, the data is obviously there, you just need to know how to get to it. The answer lies in extended properties.
Basically since Graph doesn't expose these values, we need to dig into the MAPI properties that store this data. After a little digging through the Exchange server protocol docs I found PidLidAppointmentProposedStartWhole and PidLidAppointmentProposedEndWhole. We just need to translate those MAPI property definitions into the Graph extended property syntax.
From the doc, those are both in the PSETID_Appointment namespace, which uses the GUID {00062002-0000-0000-C000-000000000046}. PidLidAppointmentProposedStartWhole uses id 0x8250, and PidLidAppointmentProposedEndWhole uses id 0x8251. So those should translate to:
PidLidAppointmentProposedStartWhole: 'SystemTime {00062002-0000-0000-C000-000000000046} Id 0x8250'
PidLidAppointmentProposedEndWhole: 'SystemTime {00062002-0000-0000-C000-000000000046} Id 0x8251'
If we use those in an $expand clause as per Get singleValueLegacyExtendedProperty, we get something like:
GET /me/mailfolders/inbox/messages?$expand=singleValueExtendedProperties($filter=id eq 'SystemTime {00062002-0000-0000-C000-000000000046} Id 0x8250' or id eq 'SystemTime {00062002-0000-0000-C000-000000000046} Id 0x8251')
And the response looks something like this (other properties omitted):
{
"subject": "New Time Proposed: Let's meet",
"singleValueExtendedProperties": [
{
"id": "SystemTime {00062002-0000-0000-c000-000000000046} Id 0x8250",
"value": "2018-03-20T20:00:00Z"
},
{
"id": "SystemTime {00062002-0000-0000-c000-000000000046} Id 0x8251",
"value": "2018-03-20T21:00:00Z"
}
]
}

Webhook from shopify with laravel 4

Using laravel to do a webhook for shopify on order creation
I specify my url in the webhook to my site.
I use postcatcher to receive the data from the webhook call so that I know what data they are passing in.
the data looks something like this
{
"customer": {
"default_address": {
"default": true,
"country_name": "Singapore",
"country_code": "SG",
"province_code": null,
"name": "Own Card",
"zip": "234567",
"province": "Singapore",
"phone": "",
"last_name": "Card",
"id": 275454813,
"first_name": "Own",
"country": "Singapore",
"company": "",
"city": "Singapore",
"address2": "",
"address1": ""
},
"last_order_name": null,
"tags": "",
"verified_email": true,
"updated_at": "2014-03-14T06:10:59-04:00",
"total_spent": "0.00",
"state": "disabled",
"orders_count": 0,
"note": null,
"multipass_identifier": null,
"last_order_id": null,
"last_name": "Koh",
"id": 219085425,
"first_name": "Kwang",
"email": "Kwang#a.com.sg",
"created_at": "2014-03-14T05:55:55-04:00",
"accepts_marketing": true
},
"client_details": {
"user_agent": "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36",
"session_hash": "c30217b1565a1f69ca5c1cb944ee6769c39de0f9acc8d6685975d551a2ff246f",
"browser_ip": "118.200.236.161",
"accept_language": "en-GB,en-US;q=0.8,en;q=0.6"
},
"fulfillments": [],
"shipping_address": {
"province_code": null,
"country_code": "SG",
"name": "Own Card",
"zip": "123456",
"province": "Singapore",
"phone": "12345676",
"longitude": "1.151376",
"latitude": "1.2646",
"last_name": "Card",
"first_name": "Own",
"country": "Singapore",
"company": "",
"city": "Singapore",
"address2": "",
"address1": ""
},
"billing_address": {
"province_code": null,
"country_code": "SG",
"name": "Own Card",
"zip": "312133",
"province": "Singapore",
"phone": "",
"longitude": "103.351376",
"latitude": "1.33146",
"last_name": "Card",
"first_name": "Own",
"country": "Singapore",
"company": "",
"city": "Singapore",
"address2": "",
"address1": ""
},
"payment_details": {
"credit_card_company": "Bogus",
"credit_card_number": "XXXX-XXXX-XXXX-1",
"cvv_result_code": null,
"credit_card_bin": "1",
"avs_result_code": null
},
"shipping_lines": [
{
"tax_lines": [],
"title": "Standard Shipping",
"source": "shopify",
"price": "10.00",
"code": "Standard Shipping"
}
],
"line_items": [
{
"tax_lines": [],
"product_exists": true,
"properties": [],
"variant_inventory_management": null,
"name": "test",
"vendor": "Kwang",
"variant_title": "",
"variant_id": 315650553,
"title": "test",
"taxable": true,
"sku": "1",
"requires_shipping": true,
"quantity": 1,
"product_id": 265080025,
"price": "12.00",
"id": 432417197,
"grams": 0,
"fulfillment_status": null,
"fulfillment_service": "manual"
}
],
"tags": "",
"tax_lines": [],
"checkout_id": 221740105,
"processing_method": "direct",
"note_attributes": [],
"discount_codes": [],
"order_number": 1003,
"landing_site_ref": null,
"browser_ip": "118.200.236.122",
"user_id": null,
"updated_at": "2014-03-14T06:10:59-04:00",
"total_weight": 0,
"total_tax": "0.00",
"total_price_usd": "17.37",
"total_price": "22.00",
"total_line_items_price": "12.00",
"total_discounts": "0.00",
"token": "3c71c6a830eee3b7cb0c8627e4d48e03",
"test": true,
"taxes_included": false,
"subtotal_price": "12.00",
"source_url": null,
"source_name": "web",
"source_identifier": null,
"source": "browser",
"referring_site": "",
"reference": null,
"number": 3,
"note": null,
"name": "#1003",
"location_id": null,
"landing_site": "/products/test",
"id": 244075413,
"gateway": "bogus",
"fulfillment_status": null,
"financial_status": "authorized",
"email": "kwank#a.com.sg",
"currency": "SGD",
"created_at": "2014-03-14T06:10:58-04:00",
"confirmed": true,
"closed_at": null,
"checkout_token": "95dc1ed9b867df5b1ecdf770de4eba3b",
"cart_token": "ab1795435caebccb6d96ee69f716a4c9",
"cancelled_at": null,
"cancel_reason": null,
"buyer_accepts_marketing": true
}
How do I actually access it from my php laravel function? I did something like this but when using POSTMAN to send this over i dont get anything from $c.
$inputs = Input::all();
$c = $inputs ['customer'];
Anyone can help?
It seems like you are passing the data as a json body. The code you've shown could've worked but lets try this.
First off do this:
$inputs = Input::all();
$c = $inputs->customer;
Now lets try:
$inputs = Input::json()->all();
$c = $inputs->customer;
Does it return anything?
Finally lets try:
$c = Input::get('customer');
$cd = Input::get('customer.default_address');
Based on the docs:
Some JavaScript libraries such as Backbone may send input to the application as JSON. You may access this data via Input::get like normal.
on postman you have to post that request as a json body.
Select the raw tab then change text to json... and copy paste the whole request into the textarea.
So the actual query you are looking for is:
$customer = Input::get('customer');
now you have everything that is inside the customer in $inputs.
access it like:
$default_address = $customer->default_address; // returns object
$state = $customer->state; // returns string
You can even wrap it around:
if (Request::isJson())
{
// make sure it is from a json body
}
update
try this:
$inputs = Input::all();
dd($inputs); // what does this return?

Resources