In ActivityPub, how do you get an inbox URL? - activitypub

I know that servers communicate by POSTing to an inbox and outbox. But what's the URL for the inbox and outbox?

How to get the inbox or outbox URL
The URL is whatever the implementing server says it is. So it's different for each ActivityPub server.
The inbox and outbox URL for an actor is defined in the JSON-LD document for an actor:
{
"#context": ["https://www.w3.org/ns/activitystreams",
{"#language": "ja"}],
"type": "Person",
"id": "https://kenzoishii.example.com/",
// Right here!
"inbox": "https://kenzoishii.example.com/inbox.json",
"outbox": "https://kenzoishii.example.com/feed.json",
...
}
This also means that the inbox and outbox can be actor-specific, not just server specific.
How to get the actor JSON
Some ActivityPub sites like Mastodon make use of Webfinger to standardize a URL that can be used to get an actor's JSON-LD doc:
/.well-known/webfinger?resource=acct:foo#example.org
In this case, if you wanted to know the inbox for flaque#mastodon.social, you would first query the webfinger:
GET https://mastodon.technology/.well-known/webfinger?resource=acct:flaque#mastodon.technology
That would give you a JSON object like this:
{
subject: "acct:Flaque#mastodon.technology",
links: [
{
rel: "self",
type: "application/activity+json",
href: "https://mastodon.technology/users/Flaque"
}
]
}
With that href: https://mastodon.technology/users/Flaque, you can get the JSON representation with:
https://mastodon.technology/users/Flaque.json
(Note the .json!)
That would then give you a full actor object, which would include the inbox and outbox:
{
"inbox": "https://mastodon.technology/users/Flaque/inbox",
"outbox": "https://mastodon.technology/users/Flaque/outbox",
...
}

Related

/* in transcoding from HTTP to gRPC

rpc CreateBook(CreateBookRequest) returns (Book) {
option (google.api.http) = {
post: "/v1/{parent=publishers/*}/books"
body: "book"
};
}
message CreateBookRequest {
// The publisher who will publish this book.
// When using HTTP/JSON, this field is automatically populated based
// on the URI, because of the `{parent=publishers/*}` syntax.
string parent = 1 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = {
child_type: "library.googleapis.com/Book"
}];
Book book = 2 [(google.api.field_behavior) = REQUIRED];
string book_id = 3;
}
I don't understand post: "/v1/{parent=publishers/*}/books"
I thought publishers was a field in CreateBookRequest, then it populates to http, so it is something like this
post: "/v1/parent=publishers_field_value/books"
But publishers is not a field in CreateBookRequest
No, publishers is part of the expected value of the parent field. So suppose you have a protobuf request like this:
{
"parent": "publishers/pub1",
"book_id": "xyz"
"book": {
"author": "Stacy"
}
}
That can be transcoded by a client into an HTTP request with:
Method: POST
URI: /v1/publishers/pub1/books?bookId=xyz (with the appropriate host name)
Body:
{
"author": "Stacy"
}
If you try to specify a request with a parent that doesn't match publishers/*, I'd expect transcoding to fail.
That's in terms of transcoding from protobuf to HTTP, in the request. (That's the direction I'm most familiar with, having been coding it in C# just this week...)
In the server, it should just be the opposite - so given the HTTP request above, the server should come up with the original protobuf request including parent="publishers/pub1".
For a lot more information on all of this, see the proto defining HttpRule.

Updating meta fields in Shopify with GrapQL

I've never used GraphQL before so I am really lacking knowledge on how to go about this. I'm wanting to update product meta fields on Shopify and it appears this is the only way. What I've done so far is really fumbling...
My JSON is:
{
"input": {
"id": "gid://shopify/Product/749521178847",
"metafields": [
{
"id": "gid://shopify/Metafield/2223333",
"value": "Training Grounds"
}
]
}
}
I've minified this to:
{"input":{"id":"gid://shopify/Product/749521178847","metafields":[{"id":"gid://shopify/Metafield/2223333","value":"The Training Grounds"}]}}
And am then using an HTTP request to:
https://MYSTORE.myshopify.com/api/2021-10/graphql.json?query={"input":{"id":"gid://shopify/Product/749521178847","metafields":[{"id":"gid://shopify/Metafield/2223333","value":"The Training Grounds"}]}}
I get the error:
SyntaxError: JSON.parse: unexpected end of data at line 1 column 1 of the JSON data
I don't know if any of this is correct. If it is, I don't know if ?query= is the right variable to pass it through on.
I recommend you start using Postman, thunder client, or similar to write your graphql queries first, you will learn a lot about how graphql works and the error msgs will be a lot more useful.
To easily connect with Shopify on this stage, go to a store and create a private app, now you can use this for authenticating your API calls.
After that the Shopify graphql works on POST, you can't write your request on GET mode.
It needs to be a POST and you are missing type of operation mutation in this case and what it is.
Postman has https://www.postman.com/lively-moon-541169/workspace/purego-apis/example/16545848-bf0d1589-09b1-4ec6-ba63-a65a56b500eb examples of how to do the calls which can help you.
Also you can check GraphiQL app on shopify to test all the queries before making the programmatic queries
Updating an existing metafield:
mutation {
metafieldsSet(metafields: [
{namespace: "YOURNAMESPACE", ownerId: "gid://shopify/Customer/CUSTOMER_ID", type: "single_line_text_field", key: "YOURKEY", value: "THIS IS NEW VALUE"}
]) {
metafields {
key
value
}
userErrors {
field
message
}
}
}
Creating new metafield:
mutation {
customerUpdate(input: {
id: "gid://shopify/Customer/CUSTOMER_ID",
metafields: [
{key: "newkey", value: "some value", type: "single_line_text_field", namespace: "some namespace"},
]
}) {
userErrors {
field
message
}
}
}

How to add custom route for user

I have created a books content type containg books. Each book in the collection belongs to a user (user content type provided by Strapi).
I want to return list of books owned by authenticated user at /users/me/books endpoint. Where can I add this route and handler as there is /api/books directory containing books related route, controllers, etc. but not /api/users directory.
You can extend or override using the extensions system.
extensions/users-permissions/controllers
Just add the controller you want to extend or override as a .js file like so:
So to override the me endpoint under User.js you only need to define the method again:
'use strict';
module.exports = {
//Override me
async me(ctx) {
//do your thing
}
};
To extend, not override, means to add another endpoint, therefor you need to define it, add a route and set permissions for it. The routes.js files should be created at:
extensions/users-permissions/config/routes.json
Like so:
{
"routes": [
{
"method": "GET",
"path": "/users/me/books",
"handler": "User.getUserBooks",
"config": {
"policies": [],
"prefix": "",
"description": "description",
"tag": {
"plugin": "users-permissions",
"name": "User",
"actionType": "find"
}
}
}
}
The controller this time (same location as in beginning):
module.exports = {
async getUserBooks(ctx) {
//add logic
}
}
OP correctly added:
After adding custom route and controller, one has to go to Admin Panel(log in as admin)>Roles and Permission> Users-Permission. There you can find the newly added route and have to enable it by checking it.
The originals(if you need examples) are located at:
/node_modules/strapi-plugin-users-permissions/config/routes.json
/node_modules/strapi-plugin-users-permissions/controllers/User.js
I don't think you should extend the User controller as it isn't logically correct. You are trying to GET books - you should extend the book api in the same way.
From what I can tell a ContentType doesn't contain information about its creator(you're welcome to educate me if it's not true).
So to tackle that you can add to your ContentType "books" a relation to User.
Then I think you should extend the books api with a endpoint that returns books "belonging" to that user using the ctx received.
Also - check this question out
Comment if you need more info.

Getting graph-compatible Team and conversation IDs in a Teams bot

I'm trying to use the new message action feature in Teams in my bot application. I can get the task module to invoke, but I want to have the bot read the contents of the full message thread (not just the first message as passed into the context). When I examine the ChannelData though, I get this:
ChannelData {{
"channel": {
"id": "19:5e4ce488280b467198400257473cfd4e#thread.skype"
},
"team": {
"id": "19:7a81d1b1c0b24ac192de1c3d5cfd5618#thread.skype"
},
"tenant": {
"id": "8c6ae172-a3ea-4f50-994d-a0256822697f"
},
"source": {
"name": "compose"
}
}}
It looks like you should be able to get the messages in a channel using the beta API like this:
GraphServiceClient graphClient = new GraphServiceClient( authProvider );
var replies = await graphClient.Teams["303d2c1c-f1c5-40ce-b68e-544343d7f42b"].Channels["19:fec4b0f2825d4c8c82abc09027a64184#thread.skype"].Messages["1555375673184"].Replies
.Request()
.GetAsync();
The challenge is, calling this using a TeamID, formatted as "19:7a81d1b1c0b24ac192de1c3d5cfd5618#thread.skype" doesn't work, and instead it needs the TeamID specified as a standard GUID (in this case, d12f244e-fd24-4430-a58a-1b2650ba8997). Is there a way to convert between these two ID formats? Is there something that details why they're different?
You can!
You have to use the Microsoft.Bot.Builder.Teams Package / GitHub.
From the README, you can get the Team ID:
var teamInfo = await teamsContext.Operations.FetchTeamDetailsAsync(incomingTeamId);
That will be in teamInfo.AadGroupId and is formatted as the GUID.

Can't write acl rules to primary calendar in google service account

So I have set up a google service account for one of my apps. My intention is to keep a google calendar associated with the admin portal that all of the admins can post events to. I have got the JWT auth working I can post events to the calendar and perform other API actions. However, for some reason I cannot change the access control rules on the primary calendar. It is initialized with a single acl rule (role: owner, scope: {type: user, value: service_account_id}), and when I try to add public read access (role: reader, scope: {type: default}) like so:
POST https://www.googleapis.com/calendar/v3/calendars/primary/acl
Authorization: Bearer my_jwt_here
{
"role":"reader",
"scope":{
"type":"default"
}
}
I get the following error:
{
"error": {
"errors": [
{
"domain": "calendar",
"reason": "cannotRemoveLastCalendarOwnerFromAcl",
"message": "Cannot remove the last owner of a calendar from the access control list."
}
],
"code": 403,
"message": "Cannot remove the last owner of a calendar from the access control list."
}
}
This doesn't make any sense to me because this request shouldn't be trying to remove any access control rules. When I create a secondary calendar and do this I have no issues. When I do this with the primary calendar of my personal google account I have no issues. Is this some behavior specific to service accounts that I am not familiar with or what? I could settle for using a non-primary calendar but it bothers me that this isn't working. Any advice is appreciated.
so I found a weird work around for this issue and im posting here because I could not find SQUAT to help resolve this so hopefully this saves others some hassle.
I will also post some common problems I found when creating a organization-wide calendar (whether this is your use case or not I believe these tips will be helpful) - Jump to the bottom of the solution to this particular error.
First I needed to set up authentication with google calendar:
const { google } = require("googleapis");
const calendar = google.calendar("v3");
const scopes = [
"https://www.googleapis.com/auth/admin.directory.resource.calendar",
"https://www.googleapis.com/auth/calendar",
"https://www.googleapis.com/auth/admin.directory.user",
];
const path = require("path");
const key = require(path.join(__dirname, "google-cal-api.json"));
I created a service account and then allowed it domain wide delegation with the above listed scopes; then downloaded the key. Now if you want to do actions like create calendar events FOR users within this domain what you have to do is generate a JWT token that 'impersonates' the user whos calendar you wish to interact with; like so
const generateInpersonationKey = (email) => {
var jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
scopes,
email
);
return jwtClient;
};
To set up a JWT client for the service account itself (and so you can create a calendar people can subscribe to; in our case it was a google calendar to show whos on leave within the workplace; so a calendar that has ALL that people can subscribe and toggle on/off was ideal) you just replace the email with 'null' and it defaults to itself, instead of 'impersonating' someone within the domain wide org.
Creating events are simple, follow the google cal api docs, depending on the auth token will depend on where the calendar is generated
JUMP HERE FOR THE IMMEDIATE SOLUTION TO THE ABOVE
For resolving the issue you pointed out; What I did was set my personal accounts email as an owner of this service accounts calendar with the following NodeJS code:
var request = await calendar.acl.insert({
auth,
calendarId: "primary",
resource: {
role: "owner",
scope: {
type: "user",
value: "callum#orgdomain.com",
},
},
});
I set myself as an owner, then I went to Google Calendar API > Patch (Try Me) filled in the calendarId as the service account with the calendar im trying to restrict; and then rule ID would be the gsuite domain domain:orgdomain.com The body should be
{
"role": "reader",
"scope": {
"type": "domain",
"value": "orgdomain.com"
}
}
And thats how I was able to restrict people within our gsuite domain from deleting or editing custom calendar events. This solution is coming from the perspective of someone who originally inserted the domain ACL as
var request = await calendar.acl.insert({
auth,
calendarId: "primary",
resource: {
role: "owner",
scope: { type: "domain", value: "orgdomain.com" },
},
});
Because adding it as a 'reader' like this messes with the service account ownership and wont allow anything but owner
Hope this has been helpful
Callum

Resources