Skip to content

Commit

Permalink
User API cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
tsileo committed Jun 1, 2018
1 parent 45afd99 commit f8ee19b
Show file tree
Hide file tree
Showing 11 changed files with 410 additions and 166 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Expand Up @@ -15,6 +15,7 @@ script:
- mypy --ignore-missing-imports .
- flake8 activitypub.py
- cp -r tests/fixtures/me.yml config/me.yml
- docker build . -t microblogpub:latest
- docker-compose up -d
- docker-compose ps
- WEB_PORT=5006 CONFIG_DIR=./tests/fixtures/instance1/config docker-compose -p instance1 -f docker-compose-tests.yml up -d
Expand Down
11 changes: 11 additions & 0 deletions Makefile
Expand Up @@ -4,7 +4,18 @@ css:
password:
python -c "import bcrypt; from getpass import getpass; print(bcrypt.hashpw(getpass().encode('utf-8'), bcrypt.gensalt()).decode('utf-8'))"

docker:
mypy . --ignore-missing-imports
docker build . -t microblogpub:latest

reload-fed:
docker-compose -p instance2 -f docker-compose-tests.yml stop
docker-compose -p instance1 -f docker-compose-tests.yml stop
WEB_PORT=5006 CONFIG_DIR=./tests/fixtures/instance1/config docker-compose -p instance1 -f docker-compose-tests.yml up -d --force-recreate --build
WEB_PORT=5007 CONFIG_DIR=./tests/fixtures/instance2/config docker-compose -p instance2 -f docker-compose-tests.yml up -d --force-recreate --build

update:
docker-compose stop
git pull
docker build . -t microblogpub:latest
docker-compose up -d --force-recreate --build
144 changes: 142 additions & 2 deletions README.md
Expand Up @@ -87,6 +87,20 @@ $ docker-compose -f docker-compose-dev.yml up -d
$ MICROBLOGPUB_DEBUG=1 FLASK_APP=app.py flask run -p 5005 --with-threads
```

## ActivityPub API

### GET /

Returns the actor profile, with links to all the "standard" collections.

### GET /tags/:tag

Special collection that reference notes with the given tag.

### GET /stream

Special collection that returns the stream/inbox as displayed in the UI.

## User API

The user API is used by the admin UI (and requires a CSRF token when used with a regular user session), but it can also be accessed with an API key.
Expand All @@ -95,7 +109,7 @@ All the examples are using [HTTPie](https://httpie.org/).

### POST /api/note/delete{?id}

Deletes the given note `id`.
Deletes the given note `id` (the note must from the instance outbox).

Answers a **201** (Created) status code.

Expand All @@ -104,7 +118,7 @@ You can pass the `id` via JSON, form data or query argument.
#### Example

```shell
$ http POST https://microblog.pub/api/note/delete Authorization:'Bearer <token>' id=http://microblob.pub/outbox/<node_id>/activity
$ http POST https://microblog.pub/api/note/delete Authorization:'Bearer <token>' id=http://microblob.pub/outbox/<note_id>/activity
```

#### Response
Expand All @@ -115,6 +129,132 @@ $ http POST https://microblog.pub/api/note/delete Authorization:'Bearer <token>'
}
```

### POST /api/like{?id}

Likes the given activity.

Answers a **201** (Created) status code.

You can pass the `id` via JSON, form data or query argument.

#### Example

```shell
$ http POST https://microblog.pub/api/like Authorization:'Bearer <token>' id=http://activity-iri.tld
```

#### Response

```json
{
"activity": "https://microblog.pub/outbox/<like_id>"
}
```

### POST /api/boost{?id}

Boosts/Announces the given activity.

Answers a **201** (Created) status code.

You can pass the `id` via JSON, form data or query argument.

#### Example

```shell
$ http POST https://microblog.pub/api/boost Authorization:'Bearer <token>' id=http://activity-iri.tld
```

#### Response

```json
{
"activity": "https://microblog.pub/outbox/<announce_id>"
}
```

### POST /api/block{?actor}

Blocks the given actor, all activities from this actor will be dropped after that.

Answers a **201** (Created) status code.

You can pass the `id` via JSON, form data or query argument.

#### Example

```shell
$ http POST https://microblog.pub/api/block Authorization:'Bearer <token>' actor=http://actor-iri.tld/
```

#### Response

```json
{
"activity": "https://microblog.pub/outbox/<block_id>"
}
```

### POST /api/follow{?actor}

Follows the given actor.

Answers a **201** (Created) status code.

You can pass the `id` via JSON, form data or query argument.

#### Example

```shell
$ http POST https://microblog.pub/api/follow Authorization:'Bearer <token>' actor=http://actor-iri.tld/
```

#### Response

```json
{
"activity": "https://microblog.pub/outbox/<follow_id>"
}
```

### POST /api/new_note{?content,reply}

Creates a new note. `reply` is the IRI of the "replied" note if any.

Answers a **201** (Created) status code.

You can pass the `content` and `reply` via JSON, form data or query argument.

#### Example

```shell
$ http POST https://microblog.pub/api/new_note Authorization:'Bearer <token>' content=hello
```

#### Response

```json
{
"activity": "https://microblog.pub/outbox/<create_id>"
}
```


### GET /api/stream


#### Example

```shell
$ http GET https://microblog.pub/api/stream Authorization:'Bearer <token>'
```

#### Response

```json
```


## Contributions

PRs are welcome, please open an issue to start a discussion before your start any work.
23 changes: 11 additions & 12 deletions activitypub.py
Expand Up @@ -9,13 +9,12 @@
from html2text import html2text
from feedgen.feed import FeedGenerator

from utils.linked_data_sig import generate_signature
from utils.actor_service import NotAnActorError
from utils.errors import BadActivityError, UnexpectedActivityTypeError
from utils import activitypub_utils
from config import USERNAME, BASE_URL, ID
from config import CTX_AS, CTX_SECURITY, AS_PUBLIC
from config import KEY, DB, ME, ACTOR_SERVICE
from config import DB, ME, ACTOR_SERVICE
from config import OBJECT_SERVICE
from config import PUBLIC_INSTANCES
import tasks
Expand Down Expand Up @@ -350,7 +349,6 @@ def post_to_outbox(self) -> None:
except NotImplementedError:
logger.debug('post to outbox hook not implemented')

generate_signature(activity, KEY.privkey)
payload = json.dumps(activity)
for recp in recipients:
logger.debug(f'posting to {recp}')
Expand Down Expand Up @@ -571,7 +569,6 @@ def _process_from_inbox(self):
# Update the meta counter if the object is published by the server
DB.outbox.update_one({'activity.object.id': obj.id}, {
'$inc': {'meta.count_like': 1},
'$addToSet': {'meta.col_likes': self.to_dict(embed=True, embed_object_id_only=True)},
})
# XXX(tsileo): notification??

Expand All @@ -580,7 +577,6 @@ def _undo_inbox(self) -> None:
# Update the meta counter if the object is published by the server
DB.outbox.update_one({'activity.object.id': obj.id}, {
'$inc': {'meta.count_like': -1},
'$pull': {'meta.col_likes': {'id': self.id}},
})

def _undo_should_purge_cache(self) -> bool:
Expand All @@ -592,7 +588,6 @@ def _post_to_outbox(self, obj_id: str, activity: ObjectType, recipients: List[st
# Unlikely, but an actor can like it's own post
DB.outbox.update_one({'activity.object.id': obj.id}, {
'$inc': {'meta.count_like': 1},
'$addToSet': {'meta.col_likes': self.to_dict(embed=True, embed_object_id_only=True)},
})

# Keep track of the like we just performed
Expand All @@ -603,7 +598,6 @@ def _undo_outbox(self) -> None:
# Unlikely, but an actor can like it's own post
DB.outbox.update_one({'activity.object.id': obj.id}, {
'$inc': {'meta.count_like': -1},
'$pull': {'meta.col_likes': {'id': self.id}},
})

DB.inbox.update_one({'activity.object.id': obj.id}, {'$set': {'meta.liked': False}})
Expand Down Expand Up @@ -646,15 +640,13 @@ def _process_from_inbox(self) -> None:

DB.outbox.update_one({'activity.object.id': obj.id}, {
'$inc': {'meta.count_boost': 1},
'$addToSet': {'meta.col_shares': self.to_dict(embed=True, embed_object_id_only=True)},
})

def _undo_inbox(self) -> None:
obj = self.get_object()
# Update the meta counter if the object is published by the server
DB.outbox.update_one({'activity.object.id': obj.id}, {
'$inc': {'meta.count_boost': -1},
'$pull': {'meta.col_shares': {'id': self.id}},
})

def _undo_should_purge_cache(self) -> bool:
Expand Down Expand Up @@ -1079,11 +1071,12 @@ def embed_collection(total_items, first_page_id):
return {
"type": ActivityType.ORDERED_COLLECTION.value,
"totalItems": total_items,
"first": first_page_id,
"first": f'{first_page_id}?page=first',
"id": first_page_id,
}


def build_ordered_collection(col, q=None, cursor=None, map_func=None, limit=50, col_name=None):
def build_ordered_collection(col, q=None, cursor=None, map_func=None, limit=50, col_name=None, first_page=False):
col_name = col_name or col.name
if q is None:
q = {}
Expand Down Expand Up @@ -1127,6 +1120,9 @@ def build_ordered_collection(col, q=None, cursor=None, map_func=None, limit=50,
if len(data) == limit:
resp['first']['next'] = BASE_URL + '/' + col_name + '?cursor=' + next_page_cursor

if first_page:
return resp['first']

return resp

# If there's a cursor, then we return an OrderedCollectionPage
Expand All @@ -1141,6 +1137,9 @@ def build_ordered_collection(col, q=None, cursor=None, map_func=None, limit=50,
if len(data) == limit:
resp['next'] = BASE_URL + '/' + col_name + '?cursor=' + next_page_cursor

# TODO(tsileo): implements prev with prev=<first item cursor>
if first_page:
return resp['first']

# XXX(tsileo): implements prev with prev=<first item cursor>?

return resp

0 comments on commit f8ee19b

Please sign in to comment.