...
 
Commits (6)
......@@ -21,6 +21,15 @@ class BaseConfig(object):
FIREBASE_DRY_RUN = False
PUSHBULLET_API_KEY = False
STATUSCAKE_MONITOR_URL = False
LOCATIONIQ_API_KEY = False # register at https://locationiq.org
MAIL_DEFAULT_SENDER = 'tut-noreply@hesafredrika.se'
MAIL_SERVER = ''
MAIL_USERNAME = ''
MAIL_PASSWORD = ''
MAIL_USE_TLS = True
CAPTCHA_ENABLE = True
CAPTCHA_NUMERIC_DIGITS = 5
SESSION_TYPE = 'sqlalchemy'
class ProductionConfig(BaseConfig):
SQLALCHEMY_DATABASE_URI = 'postgresql://postgresq://<user>:<password>@localhost/<database>'
......
......@@ -8,7 +8,6 @@ from flask.ext.debugtoolbar import DebugToolbarExtension
from flask.ext.login import LoginManager
from flask.ext.bcrypt import Bcrypt
from flask.ext.cache import Cache
from celery import Celery
from pyfcm import FCMNotification
import requests
import urllib
......@@ -19,17 +18,48 @@ import base64
from bs4 import BeautifulSoup
from lxml import html
# add logging
import logging
from logging.handlers import SysLogHandler
# time to config
app = Flask(__name__)
app.config.from_object(os.getenv('APP_SETTINGS', 'config.DevelopmentConfig'))
celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
# setup logging handler
handler = SysLogHandler(address='/dev/log')
handler.setLevel(logging.INFO)
formatter = logging.Formatter( 'HesaFredrikaServer [%(process)d]: %(levelname)s: %(message)s')
handler.setFormatter(formatter)
app.logger.addHandler(handler)
# session captcha
# captcha anyone?
from flask_session import Session
from flask_session_captcha import FlaskSessionCaptcha
import uuid
Session(app)
from flask_mail import Mail
from flask_mail import Message
mail = Mail()
mail.init_app(app)
# update celery
#celery.conf.update(app.config)
#celery = Celery(__name__, broker=Config.CELERY_BROKER_URL)
def send_to_email(topic, subject, message, location, message_id, raw, users):
with mail.connect() as conn:
for user in users:
message = raw['info'][0]['description']
message = message + '\n\n' + u'URL: https://hesafredrika.se/message/%d' % message_id
subject = subject
msg = Message(recipients=[user.email],
body=message,
html=message,
subject=subject)
conn.send(msg)
@celery.task
def send_to_topic(topic, subject, message, location, message_id, raw):
"""Background task to send to firebase ip"""
with app.app_context():
......@@ -47,26 +77,28 @@ def send_to_topic(topic, subject, message, location, message_id, raw):
'topic_name' : topic,
'description' : raw['info'][0]['description'],
}
app.logger.info(data_message)
result = push_service.notify_topic_subscribers(
topic_name = "%s" % topic,
topic_name = "%d" % topic,
data_message = data_message,
dry_run = app.config['FIREBASE_DRY_RUN'],
content_available = True,
)
app.logger.info("firebase: %s" % result)
if app.config['FIREBASE_DRY_RUN']:
notify_admin('Sending message to hosts %s' % subject)
# TODO: here we should have some potential error handling/logging
# Just remember the call is ansyncronus, so we dont know when it returns
if result['failure'] > 0:
if result['failure'] != 0:
msg = "FCM did not succeed with send %s to %s, notifying admin " % (subject, topic)
notify_admin(msg)
return result['results']
@celery.task
def notify_admin(message):
with app.app_context():
url = 'https://api.pushbullet.com/v2/pushes'
......@@ -81,10 +113,10 @@ def notify_admin(message):
return r.json()
@celery.task
def locationfinder(location):
''' takes a list of location names and tries to find them in openstreetmap '''
base_url = 'http://nominatim.openstreetmap.org/search?format=json&limit=1&countrycodes=se&'
# base_url = 'http://nominatim.openstreetmap.org/search?format=json&limit=1&countrycodes=se&'
base_url = 'http://locationiq.org/v1/search.php?key=' + app.config['LOCATIONIQ_API_KEY'] + '&format=json&'
with app.app_context():
# probably only for tests:
if 'hela landet' in location:
......@@ -94,13 +126,16 @@ def locationfinder(location):
try:
r = requests.get(url)
except:
except Exception as error:
msg = 'error doing query to openstreetmap novatim: %s' % error
notify_admin(msg)
return False
if r.json():
return r.json()
else:
return False
def b64html2text(html):
try:
......@@ -126,7 +161,8 @@ bcrypt = Bcrypt(app)
toolbar = DebugToolbarExtension(app)
cache = Cache(app)
db = SQLAlchemy(app)
celery = Celery(app)
captcha = FlaskSessionCaptcha(app)
babel = Babel(app)
@babel.localeselector
......
......@@ -82,6 +82,7 @@ class MessageForm(Form):
class MessageEditForm(Form):
active = BooleanField(_('Active'))
delivered = BooleanField(_('Active'))
description = TextField(_('Description'))
message = TextAreaField(_('Message'), [DataRequired()])
subject = TextField(_('Subject'))
......@@ -123,8 +124,7 @@ class VolunteerForm(Form):
return True
class SignupForm(Form):
email = TextField(_("Email"), [Required()])
name = TextField(_("Name") )
email = TextField(_("E-mail"), [Required()])
def validate(self):
check_validate = super(SignupForm, self).validate()
......
......@@ -6,7 +6,7 @@ from sqlalchemy.orm import load_only
from fredrika.forms import LoginForm, MessageForm, UserForm, TopicForm, MessageEditForm, VolunteerForm, SignupForm
from fredrika.models import User, Org, Municipality, Topic, Logging, Volunteer, Message, Signup
from fredrika import db, locationfinder, notify_admin, send_to_topic, b64html2text
from fredrika import db, locationfinder, notify_admin, send_to_topic, send_to_email, b64html2text, captcha
from flask.ext.babel import gettext
import requests
import json
......@@ -14,8 +14,6 @@ import base64
import datetime
import time
main = Blueprint('main', __name__)
@main.route('/', methods=["GET", "POST"])
......@@ -24,16 +22,40 @@ def home():
if current_user.is_authenticated():
return redirect(url_for('.dashboard'))
return render_template('index.html')
form = SignupForm()
if form.validate_on_submit():
if captcha.validate():
pass
else:
flash( gettext(u'Felaktig Captcha'), 'error')
return redirect(url_for('.home'))
# time to validate email
email = Signup.query.filter(Signup.email==form.email.data).first()
if not email:
signup = Signup()
form.populate_obj(signup)
db.session.add(signup)
db.session.commit()
flash( gettext(u'Din e-mail finns nu på VMA-utskickslistan: ') + signup.email, 'success')
return redirect(url_for('.home'))
else:
flash( gettext(u'Din e-mail finns redan i VMA-utskickslistan! '), 'error')
return redirect(url_for('.home'))
return render_template('index.html', form=form)
# return render_template('index.html')
@main.route('/om')
def about():
return render_template('about.html')
@main.route('/dashboard', methods=["GET", "POST"])
@main.route('/send', methods=["GET", "POST"])
@login_required
def dashboard():
def send():
form = MessageForm()
topics = []
......@@ -119,10 +141,10 @@ def topic_edit(topic=False):
return render_template('topics.html', data=False, form=form)
@main.route('/logging', methods=['GET', 'POST'])
@main.route('/logging/<int:page>', methods=['GET', 'POST'])
@main.route('/dashboard', methods=['GET', 'POST'])
@main.route('/dashboard/<int:page>', methods=['GET', 'POST'])
@login_required
def logging(page=1):
def dashboard(page=1):
# handle toggle of active to inactive
inactive = request.args.get("inactivate")
......@@ -131,7 +153,7 @@ def logging(page=1):
msg.active = False
db.session.add(msg)
db.session.commit()
return redirect(url_for('.logging'))
return redirect(url_for('.dashboard'))
active = request.args.get("activate")
if active:
......@@ -139,7 +161,7 @@ def logging(page=1):
msg.active = True
db.session.add(msg)
db.session.commit()
return redirect(url_for('.logging'))
return redirect(url_for('.dashboard'))
# post per page for pagination
ppp = 50
......@@ -195,6 +217,20 @@ def user_delete(delete=0):
flash(u'User deleted')
return redirect(url_for('.users'))
@main.route('/signups')
@login_required
def signups():
delete = request.args.get('delete')
if delete:
Signup.query.filter_by(id=int(delete)).delete()
db.session.commit()
flash(gettext("Deleted email ") + str(delete), "success")
return redirect(url_for(".signups"))
users = Signup.query.order_by(Signup.created).all()
return render_template('signups.html', data=users)
''' Volunteer stuff '''
@main.route('/volunteer', methods=['GET', 'POST'])
@login_required
......@@ -246,7 +282,7 @@ def message_edit(mid=0):
Message.query.filter_by(id=mid).delete()
db.session.commit()
flash(gettext("Deleted Message ") + str(mid), "success")
return redirect(url_for('.logging'))
return redirect(url_for('.dashboard'))
message = Message.query.filter_by(id=mid).first()
form = MessageEditForm(obj=message)
......@@ -266,7 +302,7 @@ def message_edit(mid=0):
db.session.add(message)
db.session.commit()
flash(gettext("Message") + str(message.id) + gettext(" edited.") )
return redirect(url_for('.logging'))
return redirect(url_for('.dashboard'))
return render_template("message_edit.html", data=message, form=form)
......@@ -384,6 +420,45 @@ def fix():
def sendalarm():
e = False
messages = Message.query.filter(Message.delivered == 'f', Message.location != None, Message.error == 'f').all()
users = Signup.query.order_by(Signup.created).all()
for m in messages:
# Loop over messages, make sure that we could parse something from the location
if m.location:
if not 'boundingbox' in m.location[0]:
try:
m.location = locationfinder(m.location.encode('utf-8'))
except:
m.location = locationfinder(m.location)
if m.raw['info'][0]['description']:
text_message = m.raw['info'][0]['description']
else:
text_message = m.text
try:
send = send_to_email(m.topic.id, m.subject, text_message, m.location, m.id, m.raw, users)
except requests.exceptions.RequestException as e:
pass
else:
e = 'No location available'
if not e:
notify_admin('Delivered message %s!' % (m.id))
m.delivered='t'
else:
notify_admin('Could not deliver message %s: %s' % (m.id, e))
m.error='t'
db.session.add(m)
db.session.commit()
return "0"
@main.route("/sendalarm_old", methods=['GET'])
def sendalarm_old():
e = False
messages = Message.query.filter(Message.delivered == 'f', Message.location != None, Message.error == 'f').all()
for m in messages:
# Loop over messages, make sure that we could parse something from the location
......@@ -399,6 +474,7 @@ def sendalarm():
else:
text_message = m.text
try:
#send = send_to_topic(m.topic.id, m.subject, text_message, m.location, m.id, m.raw)
send = send_to_topic(m.topic.id, m.subject, text_message, m.location, m.id, m.raw)
except requests.exceptions.RequestException as e:
pass
......
......@@ -26,6 +26,7 @@
<h3>Teknik</h3>
<p>Appen använder push-notifieringar för att skicka ut alarm. Ingen användarinformation sparas på våra servrar utan appen kollar om enheten är i det område som push-notifieringen täcker, eller om något av de områden som man själv har lagt till i appen matchar alarmets område.</p>
<p>Om du valt att ange din epostadress, kommer vi enbart att maila ut VMA, och inget annat.</p>
<h3>Finansiering och källkod</h3>
<p><b>Detta projekt har gjorts möjligt genom finansiering av <a href="https://www.iis.se" target="_blank">Internetfonden IIS</a> (Internetstiftelsen i Sverige).</b></p>
......@@ -33,6 +34,7 @@
<p><b>Hesa Fredrika är öppen källkod (Open Source) och du kan hitta hela projektet här: <a href="https://gitlab.com/hesafredrika" target="_blank">gitlab.com/hesafredrika</a>.</b></p>
<br>
<a href="/">&laquo; {{ gettext('Tillbaka') }}</a>
<h3>Datalagring</h3>
</div>
</div>
......
......@@ -9,7 +9,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<meta name="flattr:id" content="voqoy3">
<meta name="flattr:id" content="14zd8j">
{% block css %}
{% endblock %}
......
......@@ -38,7 +38,7 @@
</div>
<div class="form-group", style="width: 50%">
{{ form.location.label }}
{{ form.location(class_="form-control", value="Malmö, Sverige", size=10) }}
{{ form.location(class_="form-control", value="Torshavn Faroe Islands", size=10) }}
{% if form.location.errors %}
{% for e in form.location.errors %}
<p class="help-block">{{ e }}</p>
......
......@@ -15,7 +15,7 @@
<ul class="nav navbar-nav">
{% if current_user.is_authenticated() %}
{% if current_user.admin %}
<li><a href="{{ url_for('.dashboard') }}"><span class="icon-feed"></span> {{ _("Send") }} </a></li>
<li><a href="{{ url_for('.send') }}"><span class="icon-feed"></span> {{ _("Send") }} </a></li>
{% endif %}
{% endif %}
</ul>
......@@ -24,9 +24,10 @@
<ul class="nav navbar-nav">
{% if current_user.is_authenticated() %}
{% if current_user.admin %}
<li><a href="{{ url_for('.logging') }}"><span class="icon-list"></span> {{ _("Logs") }} </a></li>
<li><a href="{{ url_for('.dashboard') }}"><span class="icon-list"></span> {{ _("Logs") }} </a></li>
<li><a href="{{ url_for('.topics') }}"><span class="icon-list"></span> {{ _("Topics") }} </a></li>
<li><a href="{{ url_for('.volunteer') }}"><span class="icon-list"></span> {{ _("Volunteers") }} </a></li>
{# <li><a href="{{ url_for('.volunteer') }}"><span class="icon-list"></span> {{ _("Volunteers") }} </a></li> #}
<li><a href="{{ url_for('.signups') }}"><span class="icon-people"></span> {{ _("Subs") }} </a></li>
{% endif %}
<li><a href="{{ url_for('.users') }}"><span class="icon-people"></span> {{ _("Users") }} </a></li>
{% if current_user.name %}
......
......@@ -18,6 +18,11 @@
<h1 class="text-center">Hesa Fredrika</h1>
<h2 class="text-center">Viktigt Meddelande till Allmänheten!</h1>
<p class="text-center">{{ gettext('Då Hesa Fredrika inte fått den spridning vi hoppats på samt p.g.a. kostnader för apputveckling har vi ingen möjlighet att driva projektet vidare och utveckla appen. Vill du fortfarande få varningar vid VMA så kan du istället prenumerera på dem via E-mail. Du kommer då få ett medelande varje gång det kommer ett nytt VMA, oavsett din plats.') }}</p>
{#
<p class="text-center">{{ gettext('Få viktiga meddelanden till allmänheten, vädervarningar och annan kritisk samhällsinformation direkt till din mobiltelefon.') }}</p>
<p class="text-center">{{ gettext('Ställ upp som frivillig när hjälporganisationer i din närhet behöver hjälp i en krissituation') }}</p>
<div class="row" style="margin-bottom: 1em;">
......@@ -31,12 +36,51 @@
<a href="https://play.google.com/store/apps/details?id=org.fredrika.hesa.android"><img class="img-responsive" src="{{url_for('static', filename='images/googleplay.png')}}"></a>
</div>
</div><!-- .row -->
#}
</div>
<p>&nbsp;</p>
<div class="hidden-xs col-sm-5 col-md-4 col-md-offset-1">
<img class="img-responsive" src="{{url_for('static', filename='images/splash.png')}}"></p>
</div>
</div>
{# form stuff #}
<form role="form" action="" method="post"> {{ form.hidden_tag() }}
<div class="row">
<p>{{ gettext('Lägg till din emailadress för att få en uppdatering när det kommer en ny VMA:') }}</p>
</div> {# end row #}
<div class="row">
<div class="col-md-2">
Email
</div>{# end col #}
<div class="col-md-8">
<div class="form-group">
{{ form.email(size=60) }}
{% if form.email.errors %}
{% for e in form.email.errors %}
<p class="help-block">{{ e }}</p>
{% endfor %}
{% endif %}
</div>
</div>{# end col #}
</div> {# end row #}
<div class="row">
<p>
<div class="col-md-2">
{{ gettext('Skriv in captchan') }}
</div>
<div class="col-md-8">
<input type="text" name="captcha">
{{ captcha() }} <!-- This renders an <img> tag with the captcha img. -->
</div>
</p>
</div>
<button type="submit" class="btn btn-primary">{{ gettext('Send') }}</button>
</form>
{% endblock %}
......@@ -46,9 +46,9 @@
</div>
<div class="col-sm-1" align=center>
{% if l.active %}
<a href="{{ url_for('.logging', inactivate=l.id ) }} "> <span class="icon-check"></a>
<a href="{{ url_for('.dashboard', inactivate=l.id ) }} "> <span class="icon-check"></a>
{% else %}
<a href="{{ url_for('.logging', activate=l.id ) }} "> <span class="icon-plus"></a>
<a href="{{ url_for('.dashboard', activate=l.id ) }} "> <span class="icon-plus"></a>
{%endif%}
</div>
<div class="col-sm-1" align=center>
......@@ -87,14 +87,14 @@
{% for page in data.iter_pages(left_edge=2, left_current=2, right_current=2, right_edge=2) %}
{% if page %}
{% if page != data.page %}
<a href="{{ url_for('main.logging', page=page) }}">{{ page }}</a>
<a href="{{ url_for('main.dashboard', page=page) }}">{{ page }}</a>
{% else %}
<strong>{{ page }}</strong>
{% endif %}
{% endif %}
{% endfor %}
{% if data.has_next %}
| <a href="{{ url_for('main.logging', page=data.next_num) }}">Next page &gt;&gt;</a>
| <a href="{{ url_for('main.dashboard', page=data.next_num) }}">Next page &gt;&gt;</a>
{% endif %}
</div>
<div>
......
......@@ -33,11 +33,11 @@
<div class="container-fluid">
<div class="row">
<form role="form" action="" method="post"> {{ form.hidden_tag() }}
<div class="col-sm-4">
<div class="col-sm-2">
To:<br/>
{{ data.topic.name }}
</div>
<div class="col-sm-4">
<div class="col-sm-1">
Active: <br/>{{ form.active() }}
{% if form.active.errors %}
{% for e in form.active.errors %}
......@@ -45,9 +45,12 @@
{% endfor %}
{% endif %}
</div>
<div class="col-sm-4">
<div class="col-sm-1">
Error: <br/>{{ form.error() }}
</div>
<div class="col-sm-1">
Delivered: <br/>{{ form.delivered() }}
</div>
</div>{# end row #}
<div class="row">
<div class="col-sm-12">
......
......@@ -28,16 +28,13 @@
</div>
{% endblock %}
<div class="container">
<h2>Signups</h2>
<h2>Subscribers</h2>
{% if data %}
{% for u in data %}
<div class="row" style="background-color: #{{ loop.cycle('eee', 'ddd') }};">
<div class="col-md-5">
<div class="col-md-6">
{{ u.email }}
</div>
<div class="col-md-3">
{% if u.name != 'false' %} {{ u.name }} {% else %} n/a {% endif %}
</div>
<div class="col-md-3">
{{ u.created.strftime('%Y-%m-%d %H:%M:%S') }}
</div>
......