Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
56b842b
Add names of team members to readme
weechen Feb 19, 2018
66b3c49
Merge pull request #1 from MXs-Org/update-readme
weechen Feb 19, 2018
d836518
Update files to start DB container and link app to it
weechen Feb 26, 2018
b27342a
Add sleep, wait for db to finish loading
weechen Feb 26, 2018
473678a
Allow persistent storage for db
weechen Feb 27, 2018
2bc94d1
Update README.md
weechen Feb 27, 2018
1054a53
Merge pull request #2 from MXs-Org/start-docker-db
weechen Feb 27, 2018
f5d47f4
Update run.sh
mxchai Feb 28, 2018
85a0951
Update comments, replace python packages
mxchai Feb 28, 2018
c58e437
Update app, add database and models
mxchai Feb 28, 2018
9eb639f
add entry and token model and their relationships with user
garbanzos Feb 28, 2018
9bd0e62
added diary routes skeleton code
garbanzos Feb 28, 2018
97c6226
Update User model for password hashing
mxchai Feb 28, 2018
d4f425b
Add /users/register route
mxchai Feb 28, 2018
883da1a
added base ui html that is the parent of all htmls
garbanzos Feb 28, 2018
5d2de5f
Merge branch 'origin/diary-api' into users
mxchai Mar 1, 2018
512b973
Merge pull request #4 from MXs-Org/base-ui
mxchai Mar 1, 2018
55d1c02
Update User model
mxchai Mar 1, 2018
645e69e
Add /users/authenticate
mxchai Mar 1, 2018
08d8393
Fix error in User model
mxchai Mar 1, 2018
a54fa47
Add /users/expire
mxchai Mar 1, 2018
2f14c5d
Add /users
mxchai Mar 1, 2018
2d3076d
Fix minor route errors
mxchai Mar 1, 2018
9b1f54d
Update run.sh to remove MySQL-related commands
mxchai Mar 1, 2018
630dbad
Update team members
mxchai Mar 1, 2018
eb7e89f
Merge pull request #3 from MXs-Org/users
mxchai Mar 1, 2018
9f9cbda
Update ENDPOINT_LIST
mxchai Mar 1, 2018
e58645c
Rewrite to handle application/json instead of form-data
mxchai Mar 1, 2018
d732073
Update Entry model to change 'publish_date', add method to dump JSON
mxchai Mar 2, 2018
267cc58
Add /diary and /diary/create
mxchai Mar 2, 2018
524e41d
Merge pull request #5 from MXs-Org/diary
mxchai Mar 2, 2018
7a21116
Clean and update README
mxchai Mar 2, 2018
1360b67
Merge branch 'master' of https://github.com/MXs-Org/rest-api-development
mxchai Mar 2, 2018
48f7545
Clean up
mxchai Mar 2, 2018
6ebe201
add delete and change_permissions api for diary
garbanzos Mar 2, 2018
d38f19b
moved templates into some dir as app.py so that they can be rendered …
garbanzos Mar 2, 2018
37ab7b0
added create post ui and base form ui
garbanzos Mar 2, 2018
9340bb6
added skeleton login and register form template
garbanzos Mar 2, 2018
c339bfc
add static folder to hold css, js and img
garbanzos Mar 2, 2018
3c01552
add login and register form ui
garbanzos Mar 2, 2018
c4a2266
attempt to link api to register ui
garbanzos Mar 2, 2018
b06ca42
change register_form ajax contenttype to json and added toasts
garbanzos Mar 2, 2018
626f8f7
change auth api to match specs and added login_form submit handler
garbanzos Mar 2, 2018
6d84824
Merge remote-tracking branch 'origin/shift_ui_port_80' into diary-api-ui
garbanzos Mar 2, 2018
83e43d3
Move views to port 80
weechen Mar 2, 2018
81d924f
WIP
mxchai Mar 3, 2018
6cecdd5
Update auth.js skeleton
mxchai Mar 3, 2018
68835c6
Merge branch 'port_80_extra' into diary-api-ui
mxchai Mar 3, 2018
e6e8320
login now stores token in localstorage and minor refactoring
garbanzos Mar 3, 2018
48fdd6f
added logout js and add links to the navbar
garbanzos Mar 3, 2018
addc03c
Add route for token validation. Not part of testing API
mxchai Mar 3, 2018
41de448
Modify navbar to display different options according to auth status
mxchai Mar 3, 2018
edaa974
/diary now shows all public shows of users
garbanzos Mar 3, 2018
30eb6ab
Remove extra if-else block and debug messages
weechen Mar 3, 2018
e851b6e
Merge pull request #12 from MXs-Org/fix-navbar
weechen Mar 3, 2018
c507bde
added my_entries view
garbanzos Mar 3, 2018
b8e677f
Merge branch 'diary-api-ui' of github.com:MXs-Org/rest-api-developmen…
garbanzos Mar 3, 2018
b82c8a9
Make all external JS local
mxchai Mar 3, 2018
438e44a
Fix login failure materialize toast
mxchai Mar 3, 2018
a684bc9
Resolve merge conflicts, remove auth.js in views and add create_entry
weechen Mar 3, 2018
9431025
Remove endpoints
weechen Mar 3, 2018
4cc1962
Fix create entry bugs
weechen Mar 3, 2018
cd3ec26
Add missing fonts
weechen Mar 3, 2018
235861b
Merge pull request #16 from MXs-Org/Add-fonts
weechen Mar 3, 2018
1ec45e8
Merge remote-tracking branch 'origin/diary-api-ui' into create-form
weechen Mar 3, 2018
919de2e
Prepare js methods and views
weechen Mar 3, 2018
ef3cf70
Implement delete post
weechen Mar 4, 2018
cbe5648
Merge pull request #17 from MXs-Org/delete-diary-ui
weechen Mar 4, 2018
8f4da03
Fix bug in delete and implement change in visibility
weechen Mar 4, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
venv/
*.pyc
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ RUN apt-get install -y apache2
RUN pip install -U pip
RUN pip install -U flask
RUN pip install -U flask-cors
RUN pip install flask-sqlalchemy
ENV FLASK_APP app.py
RUN echo "ServerName localhost " >> /etc/apache2/apache2.conf
RUN echo "$user hard nproc 20" >> /etc/security/limits.conf
ADD ./src/service /service
Expand Down
27 changes: 13 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ sudo docker run hello-world

sudo ./run.sh
```

(Docker CE installation instructions are from this
[link](https://docs.docker.com/install/linux/docker-ce/ubuntu/#install-using-the-repository).)

Expand Down Expand Up @@ -96,16 +95,20 @@ Please fill out this section with details relevant to your team.

### Team Members

1. Member 1 Name
2. Member 2 Name
3. Member 3 Name
4. Member 4 Name
1. Chai Ming Xuan
2. Tan Yi Yan
3. Tan Wee Chen William

### Short Answer Questions

#### Question 1: Briefly describe the web technology stack used in your implementation.

Answer: Please replace this sentence with your answer.
Answer:
On the backend, we used the Flask microframework for the web application,
sqlite as the database and Flask-SQLAlchemy as the database ORM.

As for the frontend, we used basic HTML, Bootstrap CSS for styling, and mostly
vanilla JS without any frontend libraries or frameworks.

#### Question 2: Are there any security considerations your team thought about?

Expand All @@ -131,13 +134,9 @@ Answer: Please replace this sentence with your answer.

#### Please declare your individual contributions to the assignment:

1. Member 1 Name
- Integrated feature x into component y
- Implemented z
2. Member 2 Name
1. Chai Ming Xuan
- Implemented the Users and some of Diary routes
2. Tan Yi Yan
- Wrote the front-end code
3. Member 3 Name
3. Tan Wee Chen William
- Designed the database schema
4. Member 4 Name
- Implemented x

11 changes: 7 additions & 4 deletions run.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
#!/bin/bash

if [ "$EUID" -ne 0 ]
then echo "Please run as root"
exit
then echo "Please run as root"
exit
fi

TEAMID=`md5sum README.md | cut -d' ' -f 1`
# Tears down any running containers
docker kill $(docker ps -q)
docker rm $(docker ps -a -q)

# Builds web application image and runs webapp container
TEAMID=`md5sum README.md | cut -d' ' -f 1`
docker build . -t $TEAMID
docker run -p 80:80 -p 8080:8080 -t $TEAMID
docker run -p 80:80 -p 8080:8080 -t $TEAMID
236 changes: 229 additions & 7 deletions src/service/app.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,53 @@
#!/usr/bin/python

from flask import Flask
from flask_cors import CORS
import json
import os
import datetime
from uuid import uuid4

from flask_sqlalchemy import SQLAlchemy
from flask import Flask, request, render_template
from flask_cors import CORS

from database import db
from models import User, Token, Entry

##########################################
## Set up Flask application and database
##########################################
app = Flask(__name__)
# TODO: change this to False when submitting
app.config['DEBUG'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:////tmp/test.db"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# Enable cross origin sharing for all endpoints
CORS(app)
db.init_app(app)

# Remember to update this list
ENDPOINT_LIST = ['/', '/meta/heartbeat', '/meta/members']
ENDPOINT_LIST = ['/',
'/meta/heartbeat',
'/meta/members',
'/users/register',
'/users/authenticate',
'/users/expire',
'/users',
'/diary',
'/diary/create',
'/diary/delete',
'/diary/permission']

#############################
## Helper functions
#############################

def setup_database(app):
with app.app_context():
db.create_all()

def make_json_response(data, status=True, code=200):
"""Utility function to create the JSON responses."""

to_serialize = {}
if status:
to_serialize['status'] = True
Expand All @@ -30,32 +63,221 @@ def make_json_response(data, status=True, code=200):
)
return response

def make_json_false_response():
# Helper function just to return False, since it's not handled by
# make_json_response, which requires an explicit Error message
response = app.response_class(
response=json.dumps({'status': False}),
status=200,
mimetype='application/json'
)
return response

def add_user_token():
# Creates a fresh UUID and stores it in the database
# If a token for a user_id already exists, return the existing token
req_data = request.get_json()
user_id = User.query.filter_by(username=req_data['username']).first().id
user_token = Token.query.filter_by(user_id=user_id).first()
if user_token:
return user_token.token
else:
new_uuid = str(uuid4())
token = Token(token=new_uuid, user_id=user_id)
db.session.add(token)
db.session.commit()
return new_uuid

def remove_user_token(token_str):
# Removes the token from Token table
token = Token.query.filter_by(token=token_str).first()
db.session.delete(token)
db.session.commit()

def check_valid_token(token_str):
# Checks if the token is valid
token = Token.query.filter_by(token=token_str).first()
return bool(token)

def retrieve_user_id(token_str):
# Returns the user_id of the user who currently owns token_str
token = Token.query.filter_by(token=token_str).first()
user_id = token.user_id
return user_id

def create_diary_entry(req_data):
# Gets attributes of entry
title = req_data['title']
user_id = retrieve_user_id(req_data['token'])
publish_date = datetime.datetime.utcnow().isoformat()
public = req_data['public']
text = req_data['text']
# Adds entry to database
entry = Entry(title=title, user_id=user_id, publish_date=publish_date,
public=public, text=text)
db.session.add(entry)
db.session.commit()
return entry.id

#############################
## Admin Routes
#############################

@app.route("/")
def index():
"""Returns a list of implemented endpoints."""
return make_json_response(ENDPOINT_LIST)


@app.route("/meta/heartbeat")
def meta_heartbeat():
"""Returns true"""
return make_json_response(None)


@app.route("/meta/members")
def meta_members():
"""Returns a list of team members"""
with open("./team_members.txt") as f:
team_members = f.read().strip().split("\n")
return make_json_response(team_members)

#############################
## Users Routes
#############################
@app.route("/allusers") # TODO debug api, to remove before submission
def all_users():
users = User.query.all()
return make_json_response([u.json_dict() for u in users])

@app.route("/users/register", methods=['POST'])
def register_user():
if request.method == 'POST':
req_data = request.get_json()
if not all(k in req_data.keys() for k in ['username', 'password', 'fullname', 'age']):
return make_json_response("Please fill in all the required information", False)
try:
user = User(username=req_data['username'],
fullname=req_data['fullname'],
age=req_data['age'])
user.set_password(req_data['password'])
db.session.add(user)
db.session.commit()
except:
return make_json_response("User already exists!", False)
return make_json_response(None, code=201)

@app.route("/users/authenticate", methods=['POST'])
def auth_user():
req_data = request.get_json()
if not all(k in req_data.keys() for k in ['username', 'password']):
return make_json_response("Please fill in all the required information", False)
auth_user = User.query.filter_by(username=req_data['username']).first()
if auth_user:
if auth_user.check_password(req_data['password']):
token = add_user_token()
return make_json_response({"token": token}) #TODO double check this response
return make_json_false_response()

@app.route("/users/expire", methods=['POST'])
def expire_token():
req_data = request.get_json()
if req_data['token']:
token = Token.query.filter_by(token=req_data['token']).first()
if token:
token_str = token.token
remove_user_token(token_str)
return make_json_response(None)
return make_json_false_response()

@app.route("/users", methods=['POST'])
def retrieve_user_info():
req_data = request.get_json()
if req_data['token']:
token = Token.query.filter_by(token=req_data['token']).first()
if token:
user_id = token.user_id
user = User.query.filter_by(id=user_id).first()
return make_json_response(user.json_dict())
return make_json_response("Invalid authentication token.", False)

@app.route("/users/check", methods=['POST'])
def retrieve_token_validity():
req_data = request.get_json()
if req_data['token']:
token = Token.query.filter_by(token=req_data['token']).first()
if token:
return make_json_response(None)
return make_json_false_response()

#############################
## Diary Routes
#############################

@app.route('/diary', methods=['GET', 'POST'])
def diary():
if request.method == 'GET':
entries = Entry.query.filter_by(public=True).all()
return make_json_response([e.json_dict() for e in entries])
else:
req_data = request.get_json()
if check_valid_token(req_data['token']):
user_id = retrieve_user_id(req_data['token'])
entries = Entry.query.filter_by(user_id=user_id).all()
return make_json_response([e.json_dict() for e in entries])
return make_json_response("Invalid authentication token.", False)

@app.route('/diary/create', methods=['POST'])
def diary_create():
req_data = request.get_json()
if not all(k in req_data.keys() for k in ['token', 'title', 'public', 'text']):
return make_json_response("Invalid authentication token.", False)
# Check if token is valid
if check_valid_token(req_data['token']):
entry_id = create_diary_entry(req_data)
return make_json_response({'id': entry_id}, status=201)
return make_json_response("Invalid authentication token.", False)

@app.route('/diary/delete', methods=['POST'])
def diary_delete():
req_data = request.get_json()
if not all(k in req_data.keys() for k in ['token', 'id']):
return make_json_response("Invalid authentication token.", False)
if not check_valid_token(req_data['token']):
return make_json_response("Invalid authentication token.", False)

entry = Entry.query.get(req_data['id']) # TODO maybe use get_or_404
req_user_id = retrieve_user_id(req_data['token'])
if entry.user_id != req_user_id: # only owner of entry can delete entry
return make_json_response("Invalid authentication token.", False)

db.session.delete(entry)
db.session.commit()

return make_json_response(None)

@app.route('/diary/permission', methods=['POST'])
def diary_permission():
req_data = request.get_json()
if not all(k in req_data.keys() for k in ['token', 'id', 'public']):
return make_json_response("Invalid authentication token.", False)
if not check_valid_token(req_data['token']):
return make_json_response("Invalid authentication token.", False)

entry = Entry.query.get(req_data['id']) # TODO maybe use get_or_404
req_user_id = retrieve_user_id(req_data['token'])
if entry.user_id != req_user_id: # only owner of entry can change the permission
return make_json_response("Invalid authentication token.", False)

entry.public = req_data['public']
db.session.commit()
return make_json_response(None)

if __name__ == '__main__':
# Change the working directory to the script directory
abspath = os.path.abspath(__file__)
dname = os.path.dirname(abspath)
os.chdir(dname)

# Run the application
# If sqlite database does not exist, create it
if not os.path.isfile('/tmp/test.db'):
setup_database(app)
app.run(debug=False, port=8080, host="0.0.0.0")
3 changes: 3 additions & 0 deletions src/service/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()
Loading