diff --git a/.gitignore b/.gitignore index aa226fc..8d7afe7 100644 --- a/.gitignore +++ b/.gitignore @@ -15,5 +15,5 @@ node_modules/ .DS_Store __pycache__ .cache/ -Cog .vscode/ +.env \ No newline at end of file diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 2f665cd..66d6fdc 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -1,27 +1,18 @@ # Development ## Setup -- Set up a Python2 -[virtualenv](http://python-guide-pt-br.readthedocs.io/en/latest/dev/virtualenvs/) -to manage Python dependencies -- Source your virtualenv -- Run `pip install -r requirements.txt` to install all dependencies -- Install [PostgreSQL](https://www.postgresql.org/download/) to run a database locally - - If you're using Mac, install *Postgres.app* from - [here](https://www.postgresql.org/download/) -- Set three environment variables: - - `DATABASE_URL` points to the URL of a development database, -which has to be set up using Postgres on your system. A sample `DATABASE_URL` -could look like `postgres://username:password@localhost/cog`. - - `QUILL` is the URL to your Quill instance for auth. - - `SECRET` needs to be the same JWT secret used in your Quill instance. -- Run `python initialize.py` - - This initializes the database - run it if you make any changes to the models and - are fine with overwriting data. +- Install Docker +- Install Docker Compose +- Copy `sample.env` to `.env` and enter in the proper values +- `make migrate` to initialize and set up the db + +## Build +- If you need to rebuild (in case you change the Dockerfile), run `make build` ## Running -- Run `make run` -- The site will be visible at `localhost:8000` +- Run `make start` +- The site will be visible at `localhost:80` +- `make logs` for a live stream of logs. -## Tests -- Run `make test` to run all tests +## Destroying +- Run `make stop` to destroy \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index ff58fb9..dc14f8f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ -FROM python:2 +FROM python:3.6.3 -ARG APP_PATH=/hardware-checkout +ARG APP_PATH=/hardwarecheckout WORKDIR $APP_PATH @@ -9,6 +9,6 @@ RUN pip install -r requirements.txt ADD . $APP_PATH -EXPOSE 8000 -CMD ["gunicorn", "--bind", ":8000", "-k", "geventwebsocket.gunicorn.workers.GeventWebSocketWorker", "hardwarecheckout:app"] - +EXPOSE $FLASK_RUN_PORT +CMD ["python", "runserver.py"] +# CMD ["flask", "run"] diff --git a/HEROKU.md b/HEROKU.md new file mode 100644 index 0000000..50382ba --- /dev/null +++ b/HEROKU.md @@ -0,0 +1,2 @@ +# Deploying on Heroku +After deploying on Heroku, you should go into the console and then run `python initialize.py` to properly set up the database. (WARNING: doing this will clear all existing data in the database) \ No newline at end of file diff --git a/Makefile b/Makefile index 9056773..d2c18c6 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,24 @@ -test: - python -m pytest tests/ +build: + docker-compose build + +sass: + cd hardwarecheckout/static && sass --watch sass/app.scss:css/app.css +start: + docker-compose up -d + @echo "hardwarecheckout listening on port 8000, postgres on 5432" + @echo "run 'make logs' to watch logs" + +stop: + docker-compose down -PORT = 8000 -run: - gunicorn --bind 0.0.0.0:$(PORT) -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker hardwarecheckout:app +# watch the logs from hardwarecheckout +logs: + docker-compose logs -f -t hardwarecheckout +# run all the migrations +migrate: + docker-compose run hardwarecheckout python initialize.py + # db/containers still running + +test: + docker-compose run hardwarecheckout python initialize.py && python -m pytest tests/ \ No newline at end of file diff --git a/Procfile b/Procfile index 0de4e17..06a286c 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: python runserver.py $PORT \ No newline at end of file +web: python runserver.py \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..cb0f270 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +version: "3" +services: + dev_db: + image: postgres + ports: + - 5432 + environment: + - PG_PASSWORD=password + hardwarecheckout: + container_name: hardwarecheckout + image: hardwarecheckout:latest + build: + context: . + environment: + - QUILL=${QUILL} + - DATABASE_URL=${DATABASE_URL} + - SECRET=${SECRET} + - PORT=5000 + ports: + - 127.0.0.1:5000:5000 + depends_on: + - dev_db + volumes: + - .:/hardwarecheckout \ No newline at end of file diff --git a/hardwarecheckout/__init__.py b/hardwarecheckout/__init__.py index a0d8967..1e43377 100644 --- a/hardwarecheckout/__init__.py +++ b/hardwarecheckout/__init__.py @@ -3,7 +3,7 @@ from flask import Flask from flask_socketio import SocketIO -from urlparse import urlsplit +from urllib.parse import urlsplit from flaskext.markdown import Markdown from hardwarecheckout.utils import display_date, deltatimeformat diff --git a/hardwarecheckout/controllers/request.py b/hardwarecheckout/controllers/request.py index fa96978..3dea1f3 100644 --- a/hardwarecheckout/controllers/request.py +++ b/hardwarecheckout/controllers/request.py @@ -1,7 +1,7 @@ from hardwarecheckout import app from hardwarecheckout import socketio from hardwarecheckout.models import db - +from hardwarecheckout.config import EMAIL_SUBJECT from hardwarecheckout.models.request import Request, RequestStatus from hardwarecheckout.models.inventory_entry import InventoryEntry from hardwarecheckout.models.inventory_entry import ItemType @@ -10,6 +10,7 @@ from hardwarecheckout.models.request_item import RequestItem from hardwarecheckout.models.socket import Socket + from hardwarecheckout.utils import requires_auth, requires_admin, verify_token from sqlalchemy import event @@ -38,10 +39,10 @@ def get_requests(): def request_submit(): """Submits new request""" if not (user.location and user.phone): - return jsonify( - success=False, - message="""Please fill out your user info before - requesting items!""" + return jsonify( + success=False, + message="""Please fill out your user info before + requesting items!""" ) proposal = request.form.get('proposal', '') requested_quantity = int(request.form.get('quantity', 1)) @@ -91,7 +92,7 @@ def request_submit(): message='Out of stock!' ) - for _ in xrange(requested_quantity): + for _ in range(requested_quantity): item = RequestItem( InventoryEntry.query.get(request.form['item_id']), 1) @@ -148,7 +149,7 @@ def request_approve(id): quantity = request_item.quantity # get items of proper type - for _ in xrange(quantity): + for _ in range(quantity): if entry.quantity < quantity: return jsonify( success=False, @@ -184,7 +185,7 @@ def request_fulfill(id): quantity = request_item.quantity # get items of proper type - for _ in xrange(quantity): + for _ in range(quantity): item = Item.query.filter_by(entry_id = entry.id, user = None).first() if item == None: return jsonify( diff --git a/hardwarecheckout/models/inventory_entry.py b/hardwarecheckout/models/inventory_entry.py index 2df623b..03112cd 100644 --- a/hardwarecheckout/models/inventory_entry.py +++ b/hardwarecheckout/models/inventory_entry.py @@ -42,7 +42,7 @@ def __init__(self, name, description, link, category, tags, self.image_src = image self.items = [] - for i in xrange(int(qty)): + for i in range(int(qty)): self.items.append( Item(self, self.name + " " + str(i+1))) @@ -61,6 +61,7 @@ def quantity(self): .filter_by(status=hardwarecheckout.models.request.RequestStatus.APPROVED) \ .with_entities(func.sum(RequestItem.quantity)).scalar() if not requests: requests = 0 + return Item.query.filter_by(entry_id = self.id, user = None).count() - requests @property diff --git a/hardwarecheckout/utils.py b/hardwarecheckout/utils.py index 38f13e9..aecb55b 100644 --- a/hardwarecheckout/utils.py +++ b/hardwarecheckout/utils.py @@ -11,7 +11,7 @@ from babel import dates def gen_uuid(): - return str(uuid.uuid4()).replace('-', '') + return str(uuid.uuid4()).replace('-', '').decode('utf-8') def verify_token(token): try: diff --git a/requirements.txt b/requirements.txt index 16fb224..746a602 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,11 +9,10 @@ cffi==1.10.0 chardet==3.0.4 click==6.7 configparser==3.5.0 -cryptography==1.7.2 decorator==4.1.2 ecdsa==0.13 enum34==1.1.6 -eventlet +eventlet==0.25.1 Flask==0.12.2 Flask-Admin==1.5.0 Flask-Markdown==0.3 @@ -37,7 +36,7 @@ Markdown==2.6.9 MarkupSafe==1.0 mccabe==0.6.1 phonenumbers==8.8.1 -psycopg2==2.7.1 +psycopg2==2.7.3.2 py==1.4.34 pyasn1==0.2.3 pycodestyle==2.3.1 @@ -60,4 +59,4 @@ Werkzeug==0.12.2 wrapt==1.10.11 WTForms==2.0 WTForms-Alchemy==0.15.0 -WTForms-Components==0.10.3 +WTForms-Components==0.10.3 \ No newline at end of file diff --git a/runserver.py b/runserver.py index 7440841..2e617f6 100644 --- a/runserver.py +++ b/runserver.py @@ -1,9 +1,7 @@ from hardwarecheckout import app, socketio import sys +import os if __name__ == '__main__': - try: - port = int(sys.argv[1]) - except (IndexError, ValueError): - port = 5000 - socketio.run(app, host='0.0.0.0', port=port) + port = int(os.getenv("PORT", "5000")) + socketio.run(app, host='0.0.0.0', port=port) \ No newline at end of file diff --git a/runtime.txt b/runtime.txt deleted file mode 100644 index 5385682..0000000 --- a/runtime.txt +++ /dev/null @@ -1 +0,0 @@ -python-2.7.14 diff --git a/sample.env b/sample.env new file mode 100644 index 0000000..3d55399 --- /dev/null +++ b/sample.env @@ -0,0 +1,4 @@ +QUILL= +DATABASE_URL=postgres://postgres:password@dev_db/postgres +SECRET=random123456 +FORCE_SSL=False \ No newline at end of file diff --git a/tests/test_all.py b/tests/test_all.py index 96d7d99..b4f499d 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -46,7 +46,7 @@ def user(app): user = User(quill_id, 'alyssap@hacker.org', False) db.session.add(user) db.session.commit() - app.set_cookie('localhost:8000', 'jwt', token) + app.set_cookie('localhost:5000', 'jwt', token) return user @@ -145,7 +145,7 @@ def test_run_lottery(app, admin, item): .join(Request).filter_by(status=RequestStatus.APPROVED).count() == 3 def test_run_all_lotteries(app, admin): - for _ in xrange(3): + for _ in range(3): item = InventoryEntry('Item' + str(_), 'Wow lick my socks', 'http://test.co', 'Item', [], '', 3, item_type=ItemType.LOTTERY) db.session.add(item)