Skip to content

Commit

Permalink
Merge pull request #1 from judynjagi/develop
Browse files Browse the repository at this point in the history
Peer review for CP2
  • Loading branch information
judynjagi committed Apr 11, 2017
2 parents 5ede80b + 5e575c7 commit d78045a
Show file tree
Hide file tree
Showing 25 changed files with 1,271 additions and 1 deletion.
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
__pycache__/
*.py[cod]

.coverage
.coverage.*

coverage.xml

cover
.DS_Store
.env

tmp/
./migrations/
164 changes: 163 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,163 @@
# Bucket_list
[![CircleCI](https://circleci.com/gh/judynjagi/Bucket_list/tree/develop.svg?style=svg)](https://circleci.com/gh/judynjagi/Bucket_list/tree/develop)
[![Coverage Status](https://coveralls.io/repos/github/judynjagi/Bucket_list/badge.svg)](https://coveralls.io/github/judynjagi/Bucket_list)
# `BUCKETLIST`

## `1. Synopsis`
Bucketlist is an API for an online Bucket List service built with Flask.

According to Merriam-Webster Dictionary, **a Bucket List is a list of things that one has not done before but wants to do before dying.**

This service implements Token Based Authentication for the Bucket List API such that some methods are not accessible to unauthenticated users. Endpoints listed as `Public` do not require the Authentication token to be accessed. Below is a list of Access control mapping.


| Endpoint | Allowed Methods | Functionality | Public |
|-------------------------------------|------------------|----------------------------------------------------------|----------------|
| `/auth/login` | POST | Log a user in | Yes |
| `/auth/register` | POST | Register a user | Yes |
| `/bucketlists` | POST, GET | Create and Retrieve all bucket lists | No |
| `/bucketlists/<id>` | GET, PUT, DELETE | Retrieve, Update and Delete a single bucket list | No |
| `/bucketlists/<id>/items` | GET, POST | Create and Retrieve a new item in bucket list | No |
| `/bucketlists/<id>/items/<item_id>` | GET, PUT, DELETE | Retrieve, Edit, Delete an item in a bucket list | No |

## `2. Prerequisites`
Bucketlist API requires `Python 3`to run

## `3. Installation`
#### Clone the github repository
1. $ git clone https://github.com/judynjagi/Bucket_list.git
2. Change directory into package $ cd bucketlist
3. install virtualenvwrapper
$ pip install virtualenvwrapper
$ export WORKON_HOME=~/Envs
$ mkdir -p $WORKON_HOME
$ source /usr/local/bin/virtualenvwrapper.sh
$ mkvirtualenv bucketlist
4. Activate the virtual environment using: $ workon bucketlist
5. Install dependencies $ pip install requirements.txt


#### For more instructions on installing virtualenvwrapper use this link: <https://virtualenvwrapper.readthedocs.io/>


#### Configurations
Creating a `.env` file and set these environment.

```
workon buckeklist
export APP_SETTINGS="config.DevelopmentConfig"
export TEST_DATABASE_URI="sqlite:///../bucketlist.db"
export PRODUCTION_DATABASE_URI="sqlite:///models/bucketlist.db"
export DEVELOPMENT_DATABASE_URI="sqlite:///../bucketlist.db"
export SECRET_KEY="privatekey-cannot-be-public"
export FLASK_APP="bucketlist"
export FLASK_DEBUG=true
flask run
```
#### Run Migrations
Create a database and run migrations by running these commands.

```
$ python manage.py db init
$ python manage.py db migrate
$ python manage.py db upgrade
```

#### Run Bucketlist application
Finally, after everything is set, run your application by:

```
1. Navigating to the project folder
2. Run python base.py
3. You can access the app at http://127.0.0.1:5000
```



## `4. Usage`

### Tools
To interact with the Bucketlist API, send it HTTP requests using your favourite tool (cURL, Postman etc).

I prefer ***Postman**, if you would like to try it download it here: <https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=en>

Note that all requests ***MUST** be of the `Content-Type application/json`

###Examples

##### User Registration
Make a **POST** request to the route `/auth/register` with the following data:

![Alt text](http://i.imgur.com/mGkLuWD.png)

##### User Login
---
Make a **POST** request to the route `/auth/login` with the following data:
See below how to set the `Headers` when loggig in
![Alt text](http://i.imgur.com/mkM5sIk.png)

![Alt text](http://i.imgur.com/mIEFAZl.png)


##### Create a bucket list
---
Make a **POST** request to the route `/bucketlists/` with the
![Alt text](http://i.imgur.com/3HTE5kq.png)

##### Get all bucket lists
---
Make a **GET** request to the route `/bucketlists/`:
The request fetchs all user's bucket lists
![Alt text](http://i.imgur.com/3HTE5kq.png)
##### Get a single bucket list
---
Make a **GET** request to the route `/bucketlists/<id>`:
The request fetches a single bucketlist requested by id
![Alt text](http://i.imgur.com/QJdErsO.png)

##### Edit a bucket list name
---
Make a **PUT** request to the route `/bucketlists/<id>` to update an existing bucketlist
![Alt text](http://i.imgur.com/jaRKf1H.png)

##### Delete a bucket list
---
Make a **DELETE** request to the route `/bucketlists/<id>` to delete a bucketlist
![Alt text](http://i.imgur.com/hOpttE2.png)

##### Create a new bucket list item
---
Make a **POST** request to the route `/bucketlists/<id>/items/` to create a new bucketlist item
![Alt text](http://i.imgur.com/enJhf1t.png)

##### Get a single bucket list item
---
Make a **GET** request to the following route `/bucketlists/<id>/items/<id>`

![Alt text](http://i.imgur.com/si0c3Pl.png)

##### Edit a specific bucket list item
---
Make a **PUT** request to the route `/bucketlists/<id>/items/<id>` with the following payload.
![Alt text](http://i.imgur.com/NCQXIHg.png)

##### Delete a specific bucket list item
---
Make a **DELETE** request to the route `/bucketlist/<id>/<items>/<id>`
![Alt text](http://i.imgur.com/KG8lbbV.png)

## `5. Testing`
---

```
To run the tests;
```
1. Naviagate to the project folder and run
$ python manage.py cov
```
19 changes: 19 additions & 0 deletions base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from bucketlist import api, app, db
from bucketlist.resources.user_authentication import Register, Login
from bucketlist.resources.bucketlists import BucketListAPI, BucketListsAPI
from bucketlist.resources.bucketlistitems import BucketListItems


""" Defining the API endpoints """
api.add_resource(Register, '/auth/register', endpoint='Register')
api.add_resource(Login, '/auth/login', endpoint='Login')

api.add_resource(BucketListAPI, '/bucketlists/<int:list_id>', endpoint='Bucketlists')
api.add_resource(BucketListsAPI, '/bucketlists/', endpoint='Bucketlistsedit')

api.add_resource(BucketListItems, '/bucketlists/<int:item_id>/items/', endpoint='BucketlistItems')
api.add_resource(BucketListItems, '/bucketlists/<int:bucketlist_id>/items/<int:item_id>', endpoint='updatedeleteitems')


if __name__ == "__main__":
app.run()
19 changes: 19 additions & 0 deletions bucketlist/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_restful import Api
from configsettings.config import config

db = SQLAlchemy()


def create_app(configuration):
app = Flask(__name__)
app.config.from_object(config[configuration])
db.app = app
db.init_app(app)

return app


app = create_app("development")
api = Api(app=app)
Empty file.
26 changes: 26 additions & 0 deletions bucketlist/functionalities/permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from flask import g, jsonify
from flask_httpauth import HTTPTokenAuth

from bucketlist.resources.models import Users

auth = HTTPTokenAuth(scheme='Token')


@auth.error_handler
def unauthorized(message=None):
"""
return 401 status_code
"""
if not message:
message = "Error: You are not authorized to access this resource."
return jsonify({"message": message}), 401


@auth.verify_token
def verify_token(token):
"""Validates the token sent by the user """
user = Users.verify_auth_token(token)
if not user:
return False
g.user = user
return True
27 changes: 27 additions & 0 deletions bucketlist/functionalities/serializer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from flask_restful import fields

bucketlist_item_serializer = {
"item_id": fields.Integer,
"item_title": fields.String,
"item_description": fields.String,
"done": fields.Boolean,
"date_created": fields.DateTime,
"date_modified": fields.DateTime,
"created_by": fields.Integer,
"bucketlist_id": fields.Integer
}

bucketlist_serializer = {
"list_id": fields.Integer,
"list_title": fields.String,
"list_description": fields.String,
"items": fields.Nested(bucketlist_item_serializer),
"created_by": fields.Integer,
"date_created": fields.DateTime,
"date_modified": fields.DateTime
}

user_serializer = {
"id": fields.Integer,
"username": fields.String
}
Binary file added bucketlist/resources/.DS_Store
Binary file not shown.
Empty file.
92 changes: 92 additions & 0 deletions bucketlist/resources/bucketlistitems.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from flask_restful import reqparse, Resource, marshal
from flask import g

from bucketlist.resources.models import BucketList, BucketlistItem, db
from bucketlist.functionalities.serializer import bucketlist_item_serializer
from bucketlist.functionalities.permissions import auth


class BucketListItems(Resource):
decorators = [auth.login_required]

def post(self, item_id):
"""
Endpoint to create a bucketlist item
"""

bucket_list = BucketList.query.filter_by(list_id=item_id,
created_by=g.user.user_id).first()

if bucket_list:
parser = reqparse.RequestParser()
parser.add_argument('item_name', type=str,
required=True,
help='Provide a bucketlist item'
)
parser.add_argument('description', type=str)
args = parser.parse_args(strict=True)
name, description = args['item_name'], args['description']

bucketlistitems = BucketlistItem(item_title=name, item_description=description,
bucketlist_id=item_id,
created_by=g.user.user_id,
done=False)
db.session.add(bucketlistitems)
db.session.commit()
items = marshal(bucketlistitems, bucketlist_item_serializer)

return {'message': 'You have successfully created a bucketlist item', 'bucketlistitems': items}, 200
return {'message': 'That list was not found'}, 404

def put(self, bucketlist_id, item_id):
"""
Endpoint to update a bucketlist item
"""

bucketlistitem = BucketlistItem.query.filter_by(created_by=g.user.user_id, item_id=item_id, bucketlist_id=bucketlist_id).first()

if bucketlistitem is None:
return {'message': 'Bucketlist not found'}, 404
else:

parser = reqparse.RequestParser()
parser.add_argument('item_name', type=str,
required=True,
help='Provide a bucketlist item'
)
parser.add_argument('description', type=str)
parser.add_argument('done', type=str, required=True,
help='This field takes a True of False value depending on whether you have accomplished it or not ')

args = parser.parse_args(strict=True)
done, name, description = args['done'], args['item_name'], args['description']

if name: bucketlistitem.item_title = name

if description: bucketlistitem.description = description

if done == 'True' or done == 'true':
bucketlistitem.done = True
elif done == 'False' or done == 'false':
bucketlistitem.done = False

bucketlistitem.bucketlist_id = bucketlist_id
bucketlistitem.item_id = item_id

db.session.commit()
response = marshal(bucketlistitem, bucketlist_item_serializer)

return {"bucket_list": response, "message": "Successfully updated a bucketlistitem"}, 200

def delete(self, bucketlist_id, item_id):
"""
Endpoint to delete a bucketlist item by its id
"""
bucketlistitem = BucketlistItem.query.filter_by(created_by=g.user.user_id,
item_id=item_id,
bucketlist_id=bucketlist_id).first()
if bucketlistitem:
db.session.delete(bucketlistitem)
db.session.commit()
return {"message": "You have successfully deleted bucketlist with ID:%s" % item_id}, 200
return {'message': 'Bucketlist not found'}, 404
Loading

0 comments on commit d78045a

Please sign in to comment.