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