From 9799707fe1585ed39ac51d282d3e70c3250fb7a7 Mon Sep 17 00:00:00 2001 From: Anton Sarukhanov <code@ant.sr> Date: Thu, 27 Oct 2016 21:23:49 -0400 Subject: [PATCH] Added contact form processor --- .gitignore | 1 + api/app.py | 45 +++++++++++++++++----------- api/contact.py | 21 +++++++++++++ api/decorators.py | 4 +-- api/models.py | 26 ++++++++++++++++ api/strava.py | 2 +- api/templates/index.html | 4 +-- api/util.py | 5 +--- config.py | 1 + migrations/versions/f640a1d27e31_.py | 33 ++++++++++++++++++++ requirements.txt | 2 +- 11 files changed, 116 insertions(+), 28 deletions(-) create mode 100644 api/contact.py create mode 100644 migrations/versions/f640a1d27e31_.py diff --git a/.gitignore b/.gitignore index 1451eee..1859bb4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.swp venv instance/* +api.db diff --git a/api/app.py b/api/app.py index 1da83ce..a503184 100644 --- a/api/app.py +++ b/api/app.py @@ -1,9 +1,9 @@ import json -from flask import Flask, redirect, render_template, url_for -from decorators import admin -from models import db -from strava import Strava +from flask import Flask, redirect, render_template, request, url_for from stravalib import unithelper +from api.decorators import admin +from api.models import db, ContactFormSubmission +from api.strava import Strava app = Flask(__name__, instance_relative_config=True) app.config.from_object('config') @@ -23,23 +23,32 @@ def strava_auth(): else: return Strava.authorize() -@app.route('/api/<api_name>') -def api(api_name): - if api_name == "strava": - s = Strava() - return json.dumps([{ - 'name': a.name, - 'type': a.type, - 'moving_time': a.moving_time.seconds, - 'date': str(a.start_date), - 'distance': unithelper.miles(a.distance).num, - 'athlete_id': a.athlete.id, - 'url': 'https://strava.com/activity/{}'.format(a.id) - } for a in s.activities]) +@app.route('/api/strava') +def api_strava(): + s = Strava() + r = app.make_response(json.dumps([{ + 'name': a.name, + 'type': a.type, + 'moving_time': a.moving_time.seconds, + 'date': str(a.start_date), + 'distance': unithelper.miles(a.distance).num, + 'athlete_id': a.athlete.id, + 'url': 'https://strava.com/activity/{}'.format(a.id) + } for a in s.activities])) + r.mimetype = 'application/json' + return r + +@app.route('/api/contact', methods=['POST']) +def api_contact(): + json = request.json + db.session.add(ContactFormSubmission(email=json['email'], + name=json['name'], text=json['text'])) + db.session.commit() + return 'ok' @app.context_processor def cp_is_admin(): - from util import is_admin + from api.util import is_admin return dict(is_admin=is_admin) @app.context_processor diff --git a/api/contact.py b/api/contact.py new file mode 100644 index 0000000..2ad0060 --- /dev/null +++ b/api/contact.py @@ -0,0 +1,21 @@ +from flask import request, redirect, current_app +from stravalib.client import Client +from requests.exceptions import HTTPError +from sqlalchemy.orm.exc import NoResultFound +from api.models import db, StravaApiToken + +class Contact(): + """Website contact form handler""" + + @classmethod + def check_auth(cls, token = None): + if not token: + try: + token = db.session.query(StravaApiToken).one().token + except NoResultFound: + return False + strava = Client(token) + try: + return strava.get_athlete().email + except (HTTPError, AttributeError): + return False diff --git a/api/decorators.py b/api/decorators.py index 51947d0..e0ce6aa 100644 --- a/api/decorators.py +++ b/api/decorators.py @@ -1,6 +1,6 @@ from functools import wraps from flask import abort -from util import is_admin +from api.util import is_admin def admin(f): @wraps(f) @@ -8,4 +8,4 @@ def admin(f): if is_admin(): return abort(403) return f(*args, **kwargs) - + diff --git a/api/models.py b/api/models.py index 2bf2c79..7922f80 100644 --- a/api/models.py +++ b/api/models.py @@ -1,4 +1,8 @@ +import datetime +import smtplib +from email.mime.text import MIMEText from flask_sqlalchemy import SQLAlchemy +from flask import current_app db = SQLAlchemy() @@ -7,3 +11,25 @@ class StravaApiToken(db.Model): __tablename__ = "strava_api_token" id = db.Column(db.Integer, primary_key=True) token = db.Column(db.String) + +class ContactFormSubmission(db.Model): + """Submission via the contact form""" + __tablename__ = "contact_form_submission" + id = db.Column(db.Integer, primary_key=True) + date = db.Column(db.DateTime) + name = db.Column(db.String) + email = db.Column(db.String) + text = db.Column(db.Text) + + + def __init__(self, **kwargs): + kwargs['date'] = datetime.datetime.now() + super(ContactFormSubmission, self).__init__(**kwargs) + footer = "\n\nSubmitted via website contact form." + msg = MIMEText("{body}\n\n{footer}".format(body=self.text, footer=footer)) + msg['Subject'] = 'Website Contact Form Submission' + msg['From'] = '{name} <{email}>'.format(name=self.name, email=self.email) + msg['To'] = current_app.config.get('ADMIN_EMAIL') + s = smtplib.SMTP(current_app.config.get('SMTP_HOST', 'localhost')) + s.send_message(msg) + s.quit() diff --git a/api/strava.py b/api/strava.py index 04b85df..2fb908d 100644 --- a/api/strava.py +++ b/api/strava.py @@ -2,7 +2,7 @@ from flask import request, redirect, current_app from stravalib.client import Client from requests.exceptions import HTTPError from sqlalchemy.orm.exc import NoResultFound -from models import db, StravaApiToken +from api.models import db, StravaApiToken class Strava(Client): """Wrapper for stavalib Client, which is a Strava API client. diff --git a/api/templates/index.html b/api/templates/index.html index 276b125..58d418f 100644 --- a/api/templates/index.html +++ b/api/templates/index.html @@ -17,7 +17,7 @@ </ul> </nav> {% else %} - <header><h1>Hi</h1></header> - <p>There is nothing here for you.</p> + <header><h1>Oops</h1></header> + <p>This is not my website. My website is <a href="https://ant.sr">here</a>.</p> {% endif %} {% endblock %} diff --git a/api/util.py b/api/util.py index bdbbcc4..03fbc8b 100644 --- a/api/util.py +++ b/api/util.py @@ -1,8 +1,5 @@ from flask import request, current_app def is_admin(): - try: - admin_ip = current_app.config.ADMIN_IP - except AttributeError: - admin_ip = '127.0.0.1' + admin_ip = current_app.config.get('ADMIN_IP', '127.0.0.1') return request.remote_addr == admin_ip diff --git a/config.py b/config.py index ca7d9db..ca590d0 100644 --- a/config.py +++ b/config.py @@ -1,6 +1,7 @@ STRAVA_APP_ID = '12345' STRAVA_APP_SECRET = 'your-strava-app-secret' ADMIN_IP = '127.0.0.1' +ADMIN_EMAIL = 'mail@ant.sr' SQLALCHEMY_DATABASE_URI = 'sqlite:///api.db' SQLALCHEMY_TRACK_MODIFICATIONS = True diff --git a/migrations/versions/f640a1d27e31_.py b/migrations/versions/f640a1d27e31_.py new file mode 100644 index 0000000..6e06984 --- /dev/null +++ b/migrations/versions/f640a1d27e31_.py @@ -0,0 +1,33 @@ +"""empty message + +Revision ID: f640a1d27e31 +Revises: 28df97a5e20f +Create Date: 2016-10-13 00:51:38.915838 + +""" + +# revision identifiers, used by Alembic. +revision = 'f640a1d27e31' +down_revision = '28df97a5e20f' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.create_table('contact_form_submission', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('date', sa.DateTime(), nullable=True), + sa.Column('name', sa.String(), nullable=True), + sa.Column('email', sa.String(), nullable=True), + sa.Column('text', sa.Text(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + ### end Alembic commands ### + + +def downgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.drop_table('contact_form_submission') + ### end Alembic commands ### diff --git a/requirements.txt b/requirements.txt index aa10f9b..3af8b96 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,4 @@ Flask-Migrate==1.8.1 Flask-SQLAlchemy==2.1 psycopg2==2.6.2 SQLAlchemy==1.0.14 -stravalib==0.5.0 +stravalib==0.6.3 -- GitLab