├── .gitignore ├── README.md ├── app.py ├── authentication.py ├── models.py ├── requirements.txt ├── routes.py ├── schema.py ├── tests.py └── views.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | *.db 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # todo-star 2 | This is an API for a todo list application implemented using API Star 3 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from apistar.backends.sqlalchemy_backend import commands, components 2 | from apistar.frameworks.wsgi import WSGIApp as App 3 | 4 | from authentication import BasicAuthentication 5 | 6 | from models import Base 7 | 8 | from routes import routes 9 | 10 | 11 | settings = { 12 | "DATABASE": { 13 | "URL": "sqlite:///todo_list.db", 14 | "METADATA": Base.metadata 15 | }, 16 | "AUTHENTICATION": [BasicAuthentication()] 17 | } 18 | 19 | 20 | app = App( 21 | routes=routes, 22 | settings=settings, 23 | commands=commands, 24 | components=components 25 | ) 26 | 27 | 28 | if __name__ == '__main__': 29 | app.main() 30 | -------------------------------------------------------------------------------- /authentication.py: -------------------------------------------------------------------------------- 1 | import base64 2 | from apistar import http 3 | from apistar.authentication import Authenticated 4 | from apistar.backends.sqlalchemy_backend import Session 5 | 6 | from models import User 7 | 8 | 9 | class BasicAuthentication(): 10 | def authenticate(self, authorization: http.Header, session: Session): 11 | """ 12 | Determine the user associated with a request, 13 | using HTTP Basic Authentication. 14 | """ 15 | if authorization is None: 16 | return None 17 | 18 | scheme, token = authorization.split() 19 | if scheme.lower() != 'basic': 20 | return None 21 | 22 | username, password = base64.b64decode(token).decode('utf-8').split(':') 23 | print(username) 24 | user = session.query(User).filter_by(username=username).first() 25 | if user: 26 | if user.validate_password(password): 27 | return Authenticated(username, user=user) 28 | 29 | return None 30 | -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | import bcrypt 2 | 3 | from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String 4 | from sqlalchemy.ext.declarative import declarative_base 5 | from sqlalchemy.orm import relationship 6 | 7 | 8 | Base = declarative_base() 9 | 10 | 11 | class TodoList(Base): 12 | __tablename__ = "TodoList" 13 | id = Column(Integer, primary_key=True) 14 | title = Column(String) 15 | itens = relationship("TodoItem", back_populates="todo_list") 16 | user_id = Column(Integer, ForeignKey("User.id")) 17 | user = relationship("User", back_populates="todo_lists") 18 | 19 | 20 | class TodoItem(Base): 21 | __tablename__ = "TodoItem" 22 | id = Column(Integer, primary_key=True) 23 | description = Column(String) 24 | deadline = Column(DateTime) 25 | is_done = Column(Boolean) 26 | todo_list_id = Column(Integer, ForeignKey("TodoList.id")) 27 | todo_list = relationship("TodoList", back_populates="itens") 28 | 29 | 30 | class User(Base): 31 | __tablename__ = "User" 32 | id = Column(Integer, primary_key=True) 33 | username = Column(String, nullable=False, unique=True) 34 | password = Column(String, nullable=False) 35 | todo_lists = relationship("TodoList", back_populates="user") 36 | 37 | def __init__(self, username, password): 38 | self.username = username 39 | self.password = bcrypt.hashpw( 40 | password.encode('utf-8'), 41 | bcrypt.gensalt()) 42 | 43 | def validate_password(self, password): 44 | return bcrypt.checkpw(password.encode('utf-8'), self.password) 45 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | apistar==0.3.9 2 | asyncio-redis==0.14.3 3 | certifi==2017.7.27.1 4 | chardet==3.0.4 5 | coreapi==2.3.3 6 | coreschema==0.0.4 7 | Django==1.11.5 8 | greenlet==0.4.12 9 | gunicorn==19.7.1 10 | httptools==0.0.9 11 | idna==2.6 12 | itypes==1.1.0 13 | Jinja2==2.9.6 14 | MarkupSafe==1.0 15 | meinheld==0.6.1 16 | psycopg2==2.7.3.1 17 | py==1.4.34 18 | pytest==3.2.3 19 | pytz==2017.2 20 | requests==2.18.4 21 | SQLAlchemy==1.1.14 22 | uritemplate==3.0.0 23 | urllib3==1.22 24 | uvicorn==0.0.15 25 | uvitools==0.0.4 26 | uvloop==0.8.1 27 | websockets==3.4 28 | Werkzeug==0.12.2 29 | wheel==0.24.0 30 | whitenoise==3.3.1 31 | -------------------------------------------------------------------------------- /routes.py: -------------------------------------------------------------------------------- 1 | from apistar import Include, Route 2 | from apistar.handlers import docs_urls, static_urls 3 | 4 | from views import ( 5 | add_todo_item, create_todo_list, 6 | create_user, list_todo_lists, welcome 7 | ) 8 | 9 | 10 | routes = [ 11 | Route('/', 'GET', welcome), 12 | Route('/create_todo_list', 'POST', create_todo_list), 13 | Route('/list_todo_lists', 'GET', list_todo_lists), 14 | Route('/add_todo_item', 'POST', add_todo_item), 15 | Route('/create_user', 'POST', create_user), 16 | Include('/docs', docs_urls), 17 | Include('/static', static_urls) 18 | ] 19 | -------------------------------------------------------------------------------- /schema.py: -------------------------------------------------------------------------------- 1 | from apistar import typesystem 2 | 3 | 4 | class TodoListType(typesystem.Object): 5 | properties = { 6 | 'title': typesystem.string( 7 | max_length=100, 8 | description="A title for your TODO list" 9 | ) 10 | } 11 | 12 | 13 | class TodoItemType(typesystem.Object): 14 | properties = { 15 | 'description': typesystem.string( 16 | description="Your TODO item description" 17 | ), 18 | 'deadline': typesystem.string( 19 | description="A deadline for your item", 20 | pattern=r'^\d{2}/\d{2}/\d{4}$' 21 | ), 22 | 'todo_list_id': typesystem.Integer 23 | } 24 | 25 | 26 | class UserType(typesystem.Object): 27 | properties = { 28 | 'username': typesystem.string(description="Your username"), 29 | 'password': typesystem.string(description="Your password") 30 | } 31 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | from apistar.test import TestClient 2 | 3 | from app import app, welcome 4 | 5 | 6 | def test_welcome(): 7 | """ 8 | Testing a view directly. 9 | """ 10 | data = welcome() 11 | assert data == {'message': 'Welcome to API Star!'} 12 | 13 | 14 | def test_http_request(): 15 | """ 16 | Testing a view, using the test client. 17 | """ 18 | client = TestClient(app) 19 | response = client.get('http://localhost/') 20 | assert response.status_code == 200 21 | assert response.json() == {'message': 'Welcome to API Star!'} 22 | -------------------------------------------------------------------------------- /views.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from apistar import http, annotate 4 | from apistar.backends.sqlalchemy_backend import Session 5 | from apistar.interfaces import Auth 6 | 7 | from models import TodoList, TodoItem, User 8 | from schema import TodoListType, TodoItemType, UserType 9 | from authentication import BasicAuthentication 10 | 11 | 12 | def welcome(name=None): 13 | if name is None: 14 | return {'message': 'Welcome to API Star!'} 15 | return {'message': 'Welcome to API Star, %s!' % name} 16 | 17 | 18 | @annotate(authentication=[BasicAuthentication()]) 19 | def create_todo_list(session: Session, todo_list: TodoListType, auth: Auth): 20 | user = session.query(User).filter_by(username=auth.get_user_id()).first() 21 | if user: 22 | new_todo_list = TodoList( 23 | title=todo_list.get('title'), 24 | user_id=user.id 25 | ) 26 | session.add(new_todo_list) 27 | session.flush() 28 | 29 | user.todo_lists.append(new_todo_list) 30 | session.commit() 31 | return http.Response({'id': new_todo_list.id}, status=201) 32 | return http.Response({"message": "User not found"}, status=400) 33 | 34 | 35 | @annotate(authentication=[BasicAuthentication()]) 36 | def list_todo_lists(session: Session, auth: Auth): 37 | user = session.query(User).filter_by(username=auth.get_user_id()).first() 38 | if user: 39 | queryset = session.query(TodoList).filter_by(user_id=user.id) 40 | return [ 41 | { 42 | 'id': todo_list.id, 43 | 'title': todo_list.title, 44 | 'user_id': todo_list.user_id, 45 | 'itens': [ 46 | { 47 | 'description': item.description, 48 | 'deadline': item.deadline.strftime('%d/%m/%Y'), 49 | 'is_done': item.is_done 50 | } 51 | for item in todo_list.itens 52 | ] 53 | } 54 | for todo_list in queryset 55 | ] 56 | return http.Response({"id": "User not found"}, status=400) 57 | 58 | 59 | def add_todo_item(session: Session, todo_item: TodoItemType): 60 | todo_list = session.query(TodoList).get(todo_item.get('todo_list_id')) 61 | if todo_list: 62 | item = TodoItem( 63 | description=todo_item.get('description'), 64 | deadline=datetime.datetime.strptime( 65 | todo_item.get('deadline'), 66 | '%d/%m/%Y' 67 | ), 68 | is_done=False, 69 | todo_list_id=todo_item.get('todo_list_id') 70 | ) 71 | session.add(item) 72 | session.flush() 73 | 74 | todo_list.itens.append(item) 75 | session.commit() 76 | return http.Response({'id': item.id}, status=201) 77 | return http.Response({'message': 'TODO List does not exists'}, status=400) 78 | 79 | 80 | def create_user(session: Session, user: UserType): 81 | user = User( 82 | username=user.get('username'), 83 | password=user.get('password') 84 | ) 85 | session.add(user) 86 | session.flush() 87 | return http.Response({'id': user.id}, status=201) 88 | --------------------------------------------------------------------------------