CMPUT 404

Web Applications and Architecture

Project: Distributed Social Networking


Disclaimer

This spec is subject to change as we go through the semester!

Description

The web is fundamentally interconnected and peer to peer. There's no really great reason why we should all use facebook.com or google+ or MySpace or something like that. If these social networks came up with an API you could probably link between them and use the social network you wanted. Furthermore, you might gain some autonomy.

Thus, in the spirit of diaspora https://diasporafoundation.org/ (Diaspora was the predecessor to Mastodon) we want to build something like diaspora but far, far simpler.

This blogging/social network platform will allow the importing of other sources of information (GitHub) as well allow the distribution and sharing of posts and content.

An author sitting on one node can aggregate the posts of authors they follow on other nodes.

We are going to go with an inbox model where you share posts to your followers by sending them your posts. This is similar to activity pub: https://www.w3.org/TR/activitypub/ ActivityPub is the protocol that powers most distributed social media such as Mastodon. Activity Pub is great, but too complex for a class project.

We also won't be adding much in the way of encryption or security to this platform. We're keeping it simple and restful.

Choose at least 4 other groups to work with!

Scenario

I log into SocialDistribution. I see my stream which is filled with posts that have arrived in my inbox combined with public posts that my node knows about. I browse them and I click like on anything by my friend Steph who is on another node.

When I click like, my node sends a like object to Steph's inbox that references her post.

I then comment on Steph's post. This sends a comment object to Steph's inbox that references her post.

Steph's node will process events at her inbox and record the comments and likes appropriately.

Then I write a public post, a public service announcement (PSA) about how public service announcements are pretentious preformative social media, and you shouldn't make them. The irony is lost on me. I make an unlisted image post that contains an image for the PSA and reference it from my PSA post. Nonetheless, my node records my post and makes a URL for both posts and then proceeds to send my public posts to the inboxes of everyone who follows me. My node knows who follows me and thus can just send the public post to each of those inboxes. Perhaps there will be a scaling problem in the future.

Later I write a friends-only post about how much I hate the movie The Room (2003). Tommy Wiseau can't see it because I'm not friends with Tommy Wiseau, but this post is sent to Steph's inbox, and she can see it because she's my friend.

When Steph logs into her node she'll see her stream and my public post and my friends-only post will be on her stream. She should also see that I've liked and commented her post.

Scenario Summary

All actions from authors are routed through the inbox of the receiving authors by the node itself.

Nodes will store copies of posts because they receive them in the inbox.

Likes and comments on posts are all sent to the inbox of the author.

Public posts are sent to the inboxes of all followers of the author.

Friends-only posts are sent to the inboxes of all friends of the author.

Posts, likes, comments, posts, are all sent to the inboxes of the authors that should be able to see them.

Project Parts

Collaboration

User Stories

Main Concepts

Frontend/API Visibility Admin Friend Follower Everyone
Public control panel link + stream link + stream link + stream
Unlisted control panel link + stream link + stream link
Friends Only control panel authenticated
Deleted control panel

Database/Node2Node  Type Author Friend Follower Anyone
New Public push from to inbox to inbox
New Unlisted push to inbox to inbox
New Friends Only push to inbox
Deleted Public push to inbox to inbox to inboxes post was sent to before
Deleted Unlisted push to inbox to inbox to inboxes post was sent to before
Deleted Friends Only push to inbox to inboxes post was sent to before
Edited Public push to inbox to inbox to inboxes post was sent to before
Edited Unlisted push from to inbox to inbox to inboxes post was sent to before
Edited Friends Only push from to inbox to inboxes post was sent to before
Commented Public push to inbox from from from
Commented Unlisted push to inbox from from from
Commented Friends Only push to inbox from
Liked Public push to inbox from from from
Liked Unlisted push to inbox from from from
Liked Friends Only push to inbox from
Follow push to inbox from
Follow-back (friend) push from to inbox
Unfollow push not yet implemented not yet implemented
View Public pull to post optional from optional from optional from
View Unlisted pull to post optional from optional from optional from
View Friends-Only pull not yet implemented
View Deleted pull
View Following pull to followers optional from optional from

Notes on the above tables: * This can work entirely on push from the author's server to another author's inbox. * Yes, this means that only the local node (node where the post came from) will have a complete list of comments/likes. Mastodon/Diaspora also have this problem. * "Unfollow" the API is missing this functionality. * Yes, a node may have an out of date list of followers if a remote follower unfollows. * "View Friends-Only" the API is missing this functionality.

Authentication

Pagination

If something is paginated it has query options:

Communication

Who talks to Who

Overview

Almost all node-to-node communication proceeds by the node where something (post/like/comment/follow request) was created POSTing that thing that was created to the relevant authors inbox on a remote node.

Node-to-node (marked as "[remote]") request other than "POST to inbox" are rarely needed, but you should support them in case the remote node needs to check something.

Example (node-to-node API View)

The Frontend-to-Backend (also known as [local]) communication for this scenario is up to your team, this only describes the Backend-to-Backend communication between two different nodes.

  1. I sign up for SocialDistribution on http://node1
  2. node1 administrator approves my account.
  3. Now an author object exists that represents me at http://node1/api/authors/555555555
  4. I use the interface to make a follow request to Steph, who is node2/api/authors/777777777
  5. My node POSTs the follow request to Steph's inbox at http://node2/api/authors/777777777/inbox
  6. When Steph logs in, she sees my follow request and approves it.
  7. Now Steph's node (node2) knows that I am following her.
  8. Steph makes a public post.
  9. Steph's node (node2) sends Steph's new post to my inbox with POST http://node1/api/authors/555555555/inbox.
  10. I eventually see Steph's new post, and click like on it.
  11. My node sends the like to Steph's inbox with POST http://node2/api/authors/777777777/inbox

Note: When sending a follow request to a foreign author, you (as the sending author/node) do not need to await any form of confirmation from the receiving author.

For example, suppose author A sends a follow request to author B. Once the follow request is sent from A to B's inbox, A's node can assume that A is following B, even before author B accepts or denies the request.

If B later denies author A's request, then B's posts will never be sent to A's inbox. But from the perspective of A's node the acceptance or rejection of a follow request is immaterial.

Example (How sharing public posts propagates them to new nodes.)

Let's say we have author 1 on server A, author 2 on server B, and author 3 on server C.

If 2 follows 1 but no one on C follows 1, then C won't be aware of the public posts made by 1.

But if 3 follows 2, and 2 shares it, then the post will be sent to 3's inbox and C will become aware of it.

IDs

Posts may be generated a UUID, or ID#, or whatever for internal database/model use. However, on the API the post or author should always have a fully qualified URL as its ID, and you must always identify remote objects (authors, posts, likes, comments, ...) as their full URL, including the URL of the node they came from. This means you will need to look up authors and posts in your database by their full URL ID, even if they are local.

Consider the following scenario: if two nodes both have an author with primary key 2, your node should never be able to be confused about which node's author the database is referring to, because all posts/likes/comments/follows from that author contain the author's full URL.

Consider the following scenario #2: http://node1/api/authors/0192019f-b832-74b2-b2c4-f7aadc972cb2 is Jane, and http://node2/api/authors/0192019f-b832-74b2-b2c4-f7aadc972cb2 is John. Jane and John still need to be able to like/comment/follow/post with each other. That means, your database should not connect models using the field containing 0192019f-b832-74b2-b2c4-f7aadc972cb2, but instead, the field containing http://node1/api/authors/0192019f-b832-74b2-b2c4-f7aadc972cb2.

Hint: In Django, set unique=True on the field. Then use models.ForeignKey with source and destination field names to relate the two models (join the two tables).

API Objects

Example Author Objects

{
    // Author object must always have type author
    "type":"author",
    // The full API URL for the author
    "id":"http://nodeaaaa/api/authors/111",
    // The full API URL for the author's node
    "host":"http://nodeaaaa/api/",
    // How the user would like the name to be displayed
    "displayName":"Greg Johnson",
    // URL of the user's github
    "github": "http://github.com/gjohnson",
    // URL of the user's profile image (external image in this example)
    "profileImage": "https://i.imgur.com/k7XVwpB.jpeg"
    // URL of the user's HTML profile page
    // It could include an id number/uuid or not
    "page": "http://nodeaaaa/authors/greg"
}
{
    "type":"author",
    "id":"http://nodebbbb/api/authors/222",
    "host":"http://nodebbb/api/",
    "displayName":"Lara Croft",
    "github": "http://github.com/laracroft",
    // This author used an image they posted
    "profileImage": "http://nodebbb/api/authors/222/posts/217/image"
    // URL of the user's HTML profile page
    // It could include an id number/uuid or not
    "page":"http://nodebbb/authors/222",
}

Example Follow Request Object

{
    "type": "follow",      
    "summary":"Greg wants to follow Lara",
    "actor":{
        "type":"author",
        "id":"http://nodeaaaa/api/authors/111",
        "host":"http://nodeaaaa/api/",
        "displayName":"Greg Johnson",
        "github": "http://github.com/gjohnson",
        "profileImage": "https://i.imgur.com/k7XVwpB.jpeg",
        // URL of the user's HTML profile page
        "page": "http://nodeaaaa/authors/greg"
    },
    "object":{
        "type":"author",
        "id":"http://nodebbbb/api/authors/222",
        "host":"http://nodebbbb/api/",
        "displayName":"Lara Croft",
        // URL of the user's HTML profile page
        "page":"http://nodebbbb/authors/222",
        "github": "http://github.com/laracroft",
        "profileImage": "http://nodebbbb/api/authors/222/posts/217/image"
    }
}

Example Post Object

{
    "type":"post",
    // title of a post
    "title":"A post title about a post about web dev",
    // id of the post
    // must be the original URL on the node the post came from
    "id":"http://nodebbbb/api/authors/222/posts/249",
    // URL of the user's HTML profile page
    "page": "http://nodebbbb/authors/222/posts/293",
    // a brief description of the post
    "description":"This post discusses stuff -- brief",
    // The content type of the post
    // assume either
    // text/markdown -- common mark
    // text/plain -- UTF-8
    // application/base64 # this an image that is neither a jpeg or png
    // image/png;base64 # this is an png -- images are POSTS. So you might have a user make 2 posts if a post includes an image!
    // image/jpeg;base64 # this is an jpeg
    // for HTML you will want to strip tags before displaying
    "contentType":"text/plain",
    "content":"Þā wæs on burgum Bēowulf Scyldinga, lēof lēod-cyning, longe þrāge folcum gefrǣge (fæder ellor hwearf, aldor of earde), oð þæt him eft onwōc hēah Healfdene; hēold þenden lifde, gamol and gūð-rēow, glæde Scyldingas. Þǣm fēower bearn forð-gerīmed in worold wōcun, weoroda rǣswan, Heorogār and Hrōðgār and Hālga til; hȳrde ic, þat Elan cwēn Ongenþēowes wæs Heaðoscilfinges heals-gebedde. Þā wæs Hrōðgāre here-spēd gyfen, wīges weorð-mynd, þæt him his wine-māgas georne hȳrdon, oð þæt sēo geogoð gewēox, mago-driht micel. Him on mōd bearn, þæt heal-reced hātan wolde, medo-ærn micel men gewyrcean, þone yldo bearn ǣfre gefrūnon, and þǣr on innan eall gedǣlan geongum and ealdum, swylc him god sealde, būton folc-scare and feorum gumena. Þā ic wīde gefrægn weorc gebannan manigre mǣgðe geond þisne middan-geard, folc-stede frætwan. Him on fyrste gelomp ǣdre mid yldum, þæt hit wearð eal gearo, heal-ærna mǣst; scōp him Heort naman, sē þe his wordes geweald wīde hæfde. Hē bēot ne ālēh, bēagas dǣlde, sinc æt symle. Sele hlīfade hēah and horn-gēap: heaðo-wylma bād, lāðan līges; ne wæs hit lenge þā gēn þæt se ecg-hete āðum-swerian 85 æfter wæl-nīðe wæcnan scolde. Þā se ellen-gǣst earfoðlīce þrāge geþolode, sē þe in þȳstrum bād, þæt hē dōgora gehwām drēam gehȳrde hlūdne in healle; þǣr wæs hearpan swēg, swutol sang scopes. Sægde sē þe cūðe frum-sceaft fīra feorran reccan",
    // the author has an ID where by authors can be disambiguated
    "author":{
        "type":"author",
        // ID of the Author
        "id":"http://nodebbbb/api/authors/222",
        // the home host of the author
        "host":"http://nodebbbb/api/",
        // the display name of the author
        "displayName":"Lara Croft",
        // URL of the user's HTML profile page
        "page":"http://nodebbbb/authors/222",
        // HATEOS url for Github API
        "github": "http://github.com/laracroft",
        "profileImage": "http://nodebbbb/api/authors/222/posts/217/image"
    },
    // comments about the post
    "comments":{
        "type":"comments",
        // this may or may not be the same as page for the post,
        // depending if there's a seperate URL to just see the comments
        "page":"http://nodebbbb/authors/222/posts/249",
        "id":"http://nodebbbb/api/authors/222/posts/249/comments"
        // comments.page_number, comments.size, comments.count,
        // comments.src are only sent if:
        // * public
        // * unlisted
        // * friends-only and sending it to a friend
        // You should return ~ 5 comments per post.
        // should be sorted newest(first) to oldest(last)
        // this is to reduce API call counts
        // number of the first page of comments
        "page_number":1,
        // size of comment pages
        "size":5,
        // total number of comments for this post
        "count": 1023,
        // the first page of comments
        "src":[
            {
                "type":"comment",
                "author":{
                    "type":"author",
                    "id":"http://nodeaaaa/api/authors/111",
                    "page":"http://nodeaaaa/authors/greg",
                    "host":"http://nodeaaaa/api/",
                    "displayName":"Greg Johnson",
                    "github": "http://github.com/gjohnson",
                    "profileImage": "https://i.imgur.com/k7XVwpB.jpeg"
                },
                "comment":"Sick Olde English",
                "contentType":"text/markdown",
                // ISO 8601 TIMESTAMP
                "published":"2015-03-09T13:07:04+00:00",
                // ID of the Comment
                "id":"http://nodeaaaa/api/authors/111/commented/130",
                "post": "http://nodebbbb/api/authors/222/posts/249",
                // this may or may not be the same as page for the post,
                // depending if there's a seperate URL to just see the one comment in html
                "page": "http://nodebbbb/authors/222/posts/249"
                // it could also be something like
                // "page":"http://nodeaaaa/api/authors/greg/comments/130"
                // likes on the comment, not to be confused with likes on the post
                "likes": {
                    "type": "likes",
                    "id": "http://nodeaaaa/api/authors/111/commented/130/likes",
                    // in this example nodebbbb has a html page just for the likes
                    "page": "http://nodeaaaa/authors/greg/comments/130/likes"
                    "page_number": 1,
                    "size": 50,
                    "count": 0,
                    "src": [],
                },
            }
        ]
    },
    // likes on the post
    "likes":{
        "type":"likes",
        // this may or may not be the same as page for the post,
        // depending if there's a seperate URL to just see the comments
        "page":"http://nodeaaaa/authors/222/posts/249"
        "id":"http://nodeaaaa/api/authors/222/posts/249/likes"
        // likes.page, likes.size, likes.count,
        // likes.src should be sent for public and unlisted posts
        // in order to reduce API calls
        // You should return ~ 5 likes per post.
        // should be sorted newest(first) to oldest(last)
        // this is to reduce API call counts
        // number of the first page of likes
        "page_number":1,
        // size of a page of likes
        "size":50,
        // total number of likes
        "count": 9001,
        // the first page of likes
        "src":[
            {
                "type":"like",
                "author":{
                    "type":"author",
                    "id":"http://nodeaaaa/api/authors/111",
                    "page":"http://nodeaaaa/authors/greg",
                    "host":"http://nodeaaaa/api/",
                    "displayName":"Greg Johnson",
                    "github": "http://github.com/gjohnson",
                    "profileImage": "https://i.imgur.com/k7XVwpB.jpeg"
                },
                // ISO 8601 TIMESTAMP
                "published":"2015-03-09T13:07:04+00:00",
                // ID of the Comment (UUID)
                "id":"http://nodeaaaa/api/authors/111/liked/166",
                // this should be the object they liked
                "object": "http://nodebbbb/authors/222/posts/249"
            }
        ]
    },
    // ISO 8601 TIMESTAMP
    "published":"2015-03-09T13:07:04+00:00",
    // visibility ["PUBLIC","FRIENDS","UNLISTED","DELETED"]
    "visibility":"PUBLIC"
    // for visibility PUBLIC means it is open to the wild web
    // FRIENDS means if we're friends I can see the post
    // FRIENDS should've already been sent the post so they don't need thi
    // "DELETED" should never show up in the restful API or frontend, but will need to be marked in the database
}
{
    "type":"post",
    "title":"DID YOU READ MY POST YET?",
    "id": "http://nodebbbb/api/authors/222/posts/293",
    // The frontend URL of this post
    "page": "http://nodebbbb/authors/222/posts/293",
    "description":"Whatever",
    "contentType":"text/plain",
    "content":"Are you even reading my posts Arjun?",
    "author":{
        "type":"author",
        "id":"http://nodebbbb/api/authors/222",
        "host":"http://nodebbbb/api/",
        "displayName":"Lara Croft",
        "page":"http://nodebbbb/authors/222",
        "github": "http://github.com/laracroft",
        "profileImage": "https://i.imgur.com/k7XVwpB.jpeg"
    },
    "comments": {
        "type": "comments",
        "id": "http://nodebbbb/api/authors/222/posts/293/comments",
        // in this example nodebbbb has a html page just for the comments
        "page": "http://nodebbbb/authors/222/posts/293/comments",
        "page_number": 1,
        "size": 5,
        "count": 0,
        "src": [],
    },
    "likes": {
        "type": "likes",
        "id": "http://127.0.0.1:5454/api/authors/222/posts/293/likes",
        // in this example nodebbbb has a html page just for the likes
        "page": "http://nodebbbb/authors/222/posts/293/likes"
        "page_number": 1,
        "size": 50,
        "count": 0,
        "src": [],
    },
    "published":"2015-03-09T13:07:04+00:00",
    "visibility":"FRIENDS"
}

Example Posts Object

{
    "type":"posts",
    // page number we're on (counting from 1)
    "page_number":23,
    // size of a page of posts
    "size":10,
    // total number of posts
    "count": 9001,
    // the first page of posts
    "src":[
        { "type":"post", /* ... the rest of the post object */ },
        { "type":"post", /* ... the rest of the post object */ },
        { "type":"post", /* ... the rest of the post object */ },
        { "type":"post", /* ... the rest of the post object */ },
        { "type":"post", /* ... the rest of the post object */ },
        { "type":"post", /* ... the rest of the post object */ },
        { "type":"post", /* ... the rest of the post object */ },
        { "type":"post", /* ... the rest of the post object */ },
        { "type":"post", /* ... the rest of the post object */ },
        { "type":"post", /* ... the rest of the post object */ },
    ]
}

Example Comment Object

{
    "type":"comment",
    "author":{
        "type":"author",
        "id":"http://nodeaaaa/api/authors/111",
        "page":"http://nodeaaaa/authors/greg",
        "host":"http://nodeaaaa/api/",
        "displayName":"Greg Johnson",
        "github": "http://github.com/gjohnson",
        "profileImage": "https://i.imgur.com/k7XVwpB.jpeg"
    },
    "comment":"Sick Olde English",
    "contentType":"text/markdown",
    // ISO 8601 TIMESTAMP
    "published":"2015-03-09T13:07:04+00:00",
    // ID of the Comment
    "id": "http://nodeaaaa/api/authors/111/commented/130",
    "post": "http://nodebbbb/api/authors/222/posts/249",
    // likes on the comment
    "likes":{
        "type":"likes",
        // this may or may not be the same as page for the post
        // this may or may not be the same as page for the comment
        // depending if there's a seperate URL to just see the comments
        "page":"http://nodeaaaa/authors/222/posts/249"
        "id":"http://nodeaaaa/api/authors/111/commented/130/likes"
        // likes.page, likes.size, likes.count,
        // likes.src should be sent for comments on public and unlisted posts
        // in order to reduce API calls
        // You should return ~ 5 likes per post.
        // should be sorted newest(first) to oldest(last)
        // this is to reduce API call counts
        // number of the first page of likes
        "page_number":1,
        // size of a page of likes
        "size":50,
        // total number of likes
        "count": 9001,
        // the first page of likes
        "src":[
            {
                "type":"like",
                "author":{
                    "type":"author",
                    "id":"http://nodebbbb/api/authors/222",
                    "host":"http://nodebbbb/api/",
                    "displayName":"Lara Croft",
                    "page":"http://nodebbbb/authors/222",
                    "github": "http://github.com/laracroft",
                    "profileImage": "http://nodebbbb/api/authors/222/posts/217/image"
                },
                // ISO 8601 TIMESTAMP
                "published":"2015-03-09T13:07:04+00:00",
                // ID of the Comment (UUID)
                "id": "http://nodeaaaa/api/authors/222/liked/255",
                "object": "http://nodeaaaa/api/authors/111/commented/130"
            }
        ]
    },
}

Example Comments Object

{
    "type":"comments",
    // this may or may not be the same as page for the post,
    // depending if there's a seperate URL to just see the comments
    "page":"http://nodebbbb/authors/222/posts/249",
    "id":"http://nodebbbb/api/authors/222/posts/249/comments"
    // comments.page, comments.size, comments.count,
    // comments.src are only sent if:
    // * public
    // * unlisted
    // * friends-only and sending it to a friend
    // You should return ~ 5 comments per post.
    // should be sorted newest(first) to oldest(last)
    // this is to reduce API call counts
    // number of the first page of comments
    "page_number":1,
    // size of comment pages
    "size":5,
    // total number of comments for this post
    "count": 1023,
    // the first page of comments
    "src":[
        {
            "type":"comment",
            "author":{
                "type":"author",
                "id":"http://nodeaaaa/api/authors/111",
                "page":"http://nodeaaaa/authors/greg",
                "host":"http://nodeaaaa/api/",
                "displayName":"Greg Johnson",
                "github": "http://github.com/gjohnson",
                "profileImage": "https://i.imgur.com/k7XVwpB.jpeg"
            },
            "comment":"Sick Olde English",
            "contentType":"text/markdown",
            // ISO 8601 TIMESTAMP
            "published":"2015-03-09T13:07:04+00:00",
            // ID of the Comment
            "id":"http://nodeaaaa/api/authors/111/commented/130",
            "post": "http://nodebbbb/api/authors/222/posts/249",
            // this may or may not be the same as page for the post,
            // depending if there's a seperate URL to just see the one comment in html
            "page": "http://nodebbbb/authors/222/posts/249"
            // it could also be something like
            // "page":"http://nodeaaaa/api/authors/greg/comments/130"
            // likes on the comment, not to be confused with likes on the post
            "likes": {
                "type": "likes",
                "id": "http://nodeaaaa/api/authors/111/commented/130/likes",
                // in this example nodebbbb has a html page just for the likes
                "page": "http://nodeaaaa/authors/greg/comments/130/likes"
                "page_number": 1,
                "size": 50,
                "count": 0,
                "src": [],
            },
        }
    ]
}

Example Like Object

{
    "type":"like",
    "author":{
        "type":"author",
        "id":"http://nodeaaaa/api/authors/111",
        "page":"http://nodeaaaa/authors/greg",
        "host":"http://nodeaaaa/api/",
        "displayName":"Greg Johnson",
        "github": "http://github.com/gjohnson",
        "profileImage": "https://i.imgur.com/k7XVwpB.jpeg"
    },
    // ISO 8601 TIMESTAMP
    "published":"2015-03-09T13:07:04+00:00",
    // ID of the Comment (UUID)
    "id":"http://nodeaaaa/api/authors/111/liked/166",
    "object": "http://nodebbbb/api/authors/222/posts/249"
}
{
    "type":"like",
    "author":{
        "type":"author",
        "id":"http://nodebbbb/api/authors/222",
        "host":"http://nodebbbb/api/",
        "displayName":"Lara Croft",
        "page":"http://nodebbbb/authors/222",
        "github": "http://github.com/laracroft",
        "profileImage": "http://nodebbbb/api/authors/222/posts/217/image"
    },
    // ISO 8601 TIMESTAMP
    "published":"2015-03-09T13:07:04+00:00",
    // ID of the Comment (UUID)
    "id": "http://nodeaaaa/api/authors/222/liked/255",
    "object": "http://nodeaaaa/api/authors/111/commented/130"
}

Example Likes Object

{
    "type":"likes",
    // this may or may not be the same as page for the post,
    // depending if there's a seperate URL to just see the comments
    "page":"http://nodeaaaa/authors/222/posts/249"
    "id":"http://nodeaaaa/api/authors/222/posts/249/likes"
    // likes.page, likes.size, likes.count,
    // likes.src should be sent for public and unlisted posts
    // in order to reduce API calls
    // You should return ~ 5 likes per post.
    // should be sorted newest(first) to oldest(last)
    // this is to reduce API call counts
    // number of the first page of likes
    "page_number":1,
    // size of a page of likes
    "size":50,
    // total number of likes
    "count": 9001,
    // the first page of likes
    "src":[
        {
            "type":"like",
            "author":{
                "type":"author",
                "id":"http://nodeaaaa/api/authors/111",
                "page":"http://nodeaaaa/authors/greg",
                "host":"http://nodeaaaa/api/",
                "displayName":"Greg Johnson",
                "github": "http://github.com/gjohnson",
                "profileImage": "https://i.imgur.com/k7XVwpB.jpeg"
            },
            // ISO 8601 TIMESTAMP
            "published":"2015-03-09T13:07:04+00:00",
            // ID of the Comment (UUID)
            "id":"http://nodeaaaa/api/authors/111/liked/166",
            // this should be the object they liked
            "object": "http://nodebbbb/authors/222/posts/249"
        }
    ]
}
{
    "type": "likes",
    "id": "http://nodeaaaa/api/authors/111/commented/130/likes",
    // in this example nodebbbb has a html page just for the likes
    "page": "http://nodeaaaa/authors/greg/comments/130/likes"
    "page_number": 1,
    "size": 50,
    "count": 0,
    "src": [],
}

API Endpoints

Please see the definitions for FQID and serial above!

Authors API

{
    "type": "authors",      
    "authors":[
        {
            "type":"author",
            "id":"http://nodeaaaa/api/authors/111",
            "host":"http://nodeaaaa/api/",
            "displayName":"Greg Johnson",
            "github": "http://github.com/gjohnson",
            "profileImage": "https://i.imgur.com/k7XVwpB.jpeg",
            "page": "http://nodeaaaa/authors/greg"
        },
        {
            // A second author object...
        },
        {
            // A third author object...
        }
    ]
}

Single Author API

{
    // must be type author
    "type":"author",
    // should match the URL for get
    // must match the logged in author for PUT
    "id":"http://nodeaaaa/api/authors/111",
    // should match the host we're making the request to
    "host":"http://nodeaaaa/api/",
    // get/update the display name of the author
    "displayName":"Lara Croft",
    // get/update the github of the author
    "github": "http://github.com/gjohnson",
    // get/update the profile picture of the author
    "profileImage": "https://i.imgur.com/k7XVwpB.jpeg",
    // get the HTML profile page
    "page": "http://nodeaaaa/authors/greg"
}

Followers API

{
    "type": "followers",      
    "followers":[
        {
            "type":"author",
            "id":"http://nodebbbb/api/authors/222",
            "host":"http://nodebbbb/api/",
            "displayName":"Lara Croft",
            "page":"http://nodebbbb/authors/222",
            "github": "http://github.com/laracroft",
            "profileImage": "http://nodebbbb/api/authors/222/posts/217/image"
        },
        {
            // Second follower author object
        },
        {
            // Third follower author object
        }
    ]
}
// if laura follows greg, otherwise 404
{
        "type":"author",
        "id":"http://nodebbbb/api/authors/222",
        "host":"http://nodebbbb/api/",
        "displayName":"Lara Croft",
        "page":"http://nodebbbb/authors/222",
        "github": "http://github.com/laracroft",
        "profileImage": "http://nodebbbb/api/authors/222/posts/217/image"
}

Follow Request API

{
    "type": "follow",      
    "summary":"actor wants to follow object",
    "actor":{
        "type":"author",
        // The rest of the author object for the author who wants to follow
    },
    "object":{
        "type":"author",
        // The rest of the author object for the author they want to follow
    }
}

Posts API

Image Posts

Image Posts are just posts that are images. But they are encoded as base64 data. You can inline an image post using a data URL, or you can use this shortcut to get the image if authenticated to see it.

Comments API

{
    "type":"comment",
    "author":{
        "type":"author",
        "id":"http://nodeaaaa/api/authors/111",
        "page":"http://nodeaaaa/authors/greg",
        "host":"http://nodeaaaa/api/",
        "displayName":"Greg Johnson",
        "github": "http://github.com/gjohnson",
        "profileImage": "https://i.imgur.com/k7XVwpB.jpeg"
    },
    "comment":"Sick Olde English",
    "contentType":"text/markdown",
    // ISO 8601 TIMESTAMP
    "published":"2015-03-09T13:07:04+00:00",
    // ID of the Comment
    "id": "http://nodeaaaa/api/authors/111/commented/130",
    "post": "http://nodebbbb/api/authors/222/posts/249",
}

Commented API

[
    {
        "type":"comment",
        "author":{
            "type":"author",
            "id":"http://nodeaaaa/api/authors/111",
            "page":"http://nodeaaaa/authors/greg",
            "host":"http://nodeaaaa/api/",
            "displayName":"Greg Johnson",
            "github": "http://github.com/gjohnson",
            "profileImage": "https://i.imgur.com/k7XVwpB.jpeg"
        },
        "comment":"Sick Olde English",
        "contentType":"text/markdown",
        // ISO 8601 TIMESTAMP
        "published":"2015-03-09T13:07:04+00:00",
        // ID of the Comment
        "id":"http://nodeaaaa/api/authors/111/commented/130",
        "post": "http://nodebbbb/api/authors/222/posts/249",
        // this may or may not be the same as page for the post,
        // depending if there's a seperate URL to just see the one comment in html
        "page": "http://nodebbbb/authors/222/posts/249"
        // it could also be something like
        // "page":"http://nodeaaaa/authors/greg/comments/130"
    }
]
{
    "type":"comment",
    "author":{
        "type":"author",
        "id":"http://nodeaaaa/api/authors/111",
        "page":"http://nodeaaaa/authors/greg",
        "host":"http://nodeaaaa/api/",
        "displayName":"Greg Johnson",
        "github": "http://github.com/gjohnson",
        "profileImage": "https://i.imgur.com/k7XVwpB.jpeg"
    },
    "comment":"Sick Olde English",
    "contentType":"text/markdown",
    // ISO 8601 TIMESTAMP
    "published":"2015-03-09T13:07:04+00:00",
    // ID of the Comment
    "id":"http://nodeaaaa/api/authors/111/commented/130",
    "post": "http://nodebbbb/api/authors/222/posts/249",
    // this may or may not be the same as page for the post,
    // depending if there's a seperate URL to just see the one comment in html
    "page": "http://nodebbbb/authors/222/posts/249"
    // it could also be something like
    // "page":"http://nodeaaaa/authors/greg/comments/130"
}

Likes API

Liked API

Other Local APIs

Local APIs, such as "stream", that aren't specified here, are up to your design. However, you must document and test them.

Inbox

Requirements

API Requirements

When building your API, try to adhere to these rules for easy compatibility with other groups:

Test Requirements

The REST API must be fully tested.

These would be system/acceptance tests if the project didn't have a frontend.

Testing the user stories at the API level should give you the tests you need for every API endpoint, every API functionality, and every API method. Pretend you are writing tests to make sure a future Android client will work with your API.

Not every user story has an API to test. For example, adding/removing nodes to connect with usually does not have an API. For these functions, please test them in whatever way is most convenient for your project, and then verify them using the API. You could write a test that adds a node to connect with by calling Python code or modifying the database directly, then use the API to check that the new node connection is working.

Frontend (Selenium, etc.) tests are not required. Code coverage (line coverage, statement coverage, branch coverage, MC/DC, etc.) is not required. Unit testing is not required.

Documentation Requirements

Code Requirements

Tool Use Requirements

Infrastructure and Software Requirements

Takeaways

Things that are allowed

Teamwork Tips

The most successful teams:

Submission Instructions

Marking

Overall Marking

UI amount complete is marked by your TA testing user stories manually.

Project Parts

Project Part 0: Group Formation

You must form a group with only students from your same lab section. You can have different lecture sections, but your lab section must be the same. Furthermore, everyone must attend their registered lab section.

Do not submit a clone link or a link to a branch or file.

Project Part 1: Halfway Prototype

Requirements

For this part you need:

Submission

Due 4PM on Monday.

Create a git tag "part1" in your production branch before 4PM and submit only the link to your tag.

Don't forget to push the tag to GitHub.

Submit only the link to the tag in the following format:

https://github.com/uofa-cmput404/f24-project-example-team/tree/part1

Marking

Project Part 2: Centralized Prototype

Submission

Due 4PM on Monday.

Create a git tag "part2" in your production branch before 4PM and submit only the link to your tag.

Don't forget to push the tag to GitHub.

Submit only the link to the tag in the following format:

https://github.com/uofa-cmput404/f24-project-example-team/tree/part1

Marking

Project Part 3: Distribution

Requirements
Submission

Due 4PM Monday.

eClass has a limitation where it only shows the due date for the last lab section of the week, but for Monday labs it is due Monday. For Wednesday labs it is due Wednesday.

Create a git tag "part3" before 4PM and submit only the link to your tag.

Don't forget to push the tag to GitHub.

Submit only the link to the tag in the following format:

https://github.com/uofa-cmput404/w24-project-example-team/tree/part2

Marking

Project Part 4: Federation

Requirements
Submission

Due 4PM Monday.

eClass has a limitation where it only shows the due date for the last lab section of the week, but for Monday labs it is due Monday. For Wednesday labs it is due Wednesday.

Create a git tag "part4" before 4PM and submit only the link to your tag.

Don't forget to push the tag to GitHub.

Submit only the link to the tag in the following format:

https://github.com/uofa-cmput404/w24-project-example-team/tree/part3

Marking

Project Part 5: Polish

Submission

Due 4PM Monday.

eClass has a limitation where it only shows the due date for the last lab section of the week, but for Monday labs it is due Monday. For Wednesday labs it is due Wednesday.

Create a git tag "part5" before 4PM and submit only the link to your tag.

Don't forget to push the tag to GitHub.

Submit only the link to the tag in the following format:

https://github.com/uofa-cmput404/w24-project-example-team/tree/part4

Presentation

Your presentation will be 5 minutes max. Then you will have a minute or two for questions.

Your presentation should focus on:

Guide for a good presentation:

During the other team's presentations:

Marking

License

 * Parts of this document are derived from the W3C Documentation for Activity Pub
 * Additional Authors: Karim Baaba, Ali Sajedi, Kyle Richelhoff, Chris Pavlicek, Derek Dowling, Olexiy Berjanskii, Erin Torbiak, Abram Hindle, Braedy Kuzma, Nhan Nguyen, Hazel Victoria Campbell
 * Copyright © 2018 W3C® (MIT, ERCIM, Keio, Beihang). W3C liability, trademark and permissive document license rules apply. 
 * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
     License

     By obtaining and/or copying this work, you (the licensee) agree that you have read, understood, and will comply with the following terms and conditions.

     Permission to copy, modify, and distribute this work, with or without modification, for any purpose and without fee or royalty is hereby granted, provided that you include the following on ALL copies of the work or portions thereof, including modifications:

         The full text of this NOTICE in a location viewable to users of the redistributed or derivative work.
         Any pre-existing intellectual property disclaimers, notices, or terms and conditions. If none exist, the W3C Software and Document Short Notice should be included.
         Notice of any changes or modifications, through a copyright statement on the new code or document such as "This software or document includes material copied from or derived from [title and URI of the W3C document]. Copyright © [YEAR] W3C® (MIT, ERCIM, Keio, Beihang)." 

     Disclaimers

     THIS WORK IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENT WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.

     COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENT.

     The name and trademarks of copyright holders may NOT be used in advertising or publicity pertaining to the work without specific, written prior permission. Title to copyright in this work will at all times remain with copyright holders.