Add /send endpoint, basic Dusken API. Remove more unneeded stuff.

This commit is contained in:
ponas committed 2017-08-04 23:08:51 +02:00
1 parent 4b75b45ce2
commit 5fc7e2a8a0
5 files changed
+95 -119

No files matched your search

+5 -6
View File
@@ -9,13 +9,13 @@ DB_USER = ''
DB_PASS = '' DB_PASS = ''
DB_NAME = 'tekstmelding' DB_NAME = 'tekstmelding'
# Authenticates Inside API_KEYS = {
INSIDE_API_KEY = '' 'somesecretkey': 'appname'
}
MEMBERSHIP_PRICE_KR = 1 MEMBERSHIP_PRICE_KR = 1
## Logging ## # Logging
#############
LOGGING = { LOGGING = {
'version': 1, 'version': 1,
@@ -52,8 +52,7 @@ LOGGING = {
} }
} }
## Sendega ## # Sendega
#############
# Endpoint for HTTP POST/GET # Endpoint for HTTP POST/GET
SENDEGA_URL = 'https://smsc.sendega.com/SendSms.ashx' SENDEGA_URL = 'https://smsc.sendega.com/SendSms.ashx'
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
class Dusken(object):
""" Dusken API """
url = None
api_key = None
def __init__(self, url, api_key):
self.url = url
self.api_key = api_key
@property
def dusken_auth(self):
return {'Authorization': 'Token {}'.format(self.api_key)}
def get_user_by_phone(self, phone_number):
url = '{}users/'.format(self.url)
payload = {'phone_number': phone_number}
users = requests.get(url, params=payload, headers=self.dusken_auth).json()
if users.get('count') == 1:
return users.get('results')[0]
return None
def get_full_name(self, user):
full_name = '{} {}'.format(
user.get('first_name', '').strip(),
user.get('last_name', '').strip()
)
return full_name[:50].strip()
+1
View File
@@ -1,3 +1,4 @@
Flask>=0.12 Flask>=0.12
mysqlclient==1.3.10 mysqlclient==1.3.10
suds-jurko==0.6 suds-jurko==0.6
requests
+37 -104
View File
@@ -7,8 +7,9 @@ import datetime
import logging import logging
import logging.config import logging.config
from utils import generate_activation_code, MyJSONEncoder from utils import require_token, MyJSONEncoder
import sendega import sendega
import dusken
import config import config
@@ -145,19 +146,6 @@ def update_event(event_id, outgoing_id):
[outgoing_id, event_id]) [outgoing_id, event_id])
def log_dlr(**kwargs):
return query_db("""
INSERT INTO dlr
(msgid, extID, msisdn, status, statustext,
registered, sent, delivered, errorcode,
errormessage, operatorerrorcode)
VALUES (
%(msgid)s, %(extID)s, %(msisdn)s, %(status)s, %(statustext)s,
%(registered)s, %(sent)s, %(delivered)s, %(errorcode)s,
%(errormessage)s, %(operatorerrorcode)s)
""", kwargs, lastrowid=True)
def send_sms(destination, content, incoming_id=None, def send_sms(destination, content, incoming_id=None,
billing=False, price=0, activation_code=None): billing=False, price=0, activation_code=None):
args = get_sendega().create_sms( args = get_sendega().create_sms(
@@ -187,11 +175,11 @@ def send_sms(destination, content, incoming_id=None,
def notify_valid_membership(incoming_id=None, number=None, user=None): def notify_valid_membership(incoming_id=None, number=None, user=None):
assert user assert user
name = get_inside().get_full_name(user) name = dusken.get_full_name(user)
if user.get('expires_lifelong'): if user.get('is_lifelong_member'):
expires = 'verdens undergang' expires = 'verdens undergang'
else: else:
expires = str(user.get('expires')) expires = str(user.get('last_membership').get('end_date'))
content = u"Hei %(name)s! Ditt medlemskap er gyldig til %(expires)s. Last ned app: %(app)s" % ({ content = u"Hei %(name)s! Ditt medlemskap er gyldig til %(expires)s. Last ned app: %(app)s" % ({
'name': name, 'name': name,
@@ -216,18 +204,14 @@ def notify_valid_membership(incoming_id=None, number=None, user=None):
@app.route('/kassa/new-membership-card', methods=['POST']) @app.route('/kassa/new-membership-card', methods=['POST'])
@require_token
def kassa_notify_new_card(): def kassa_notify_new_card():
""" Notify user Adds a pending membership to specified number """ """ Notify user Adds a pending membership to specified number """
api_key = request.args.get('api_key')
data = request.get_json() data = request.get_json()
phone_number = data.get('phone_number') phone_number = data.get('phone_number')
card_number = data.get('card_number') card_number = data.get('card_number')
action = data.get('action') # FIXME: not used action = data.get('action') # FIXME: not used
if api_key != app.config['INSIDE_API_KEY']:
abort(403) # Forbidden
if action is None or card_number is None: if action is None or card_number is None:
return jsonify(**{'error': 'Missing required param action or card_number'}), 400 return jsonify(**{'error': 'Missing required param action or card_number'}), 400
@@ -258,6 +242,32 @@ def kassa_notify_new_card():
return jsonify(**{'result': 'SMS sent OK', 'content': content, 'outgoing_id': outgoing_id}) return jsonify(**{'result': 'SMS sent OK', 'content': content, 'outgoing_id': outgoing_id})
@app.route('/send', methods=['POST'])
@require_token
def send():
data = request.get_json()
number = data.get('to')
message = data.get('message')
if not number or not message:
return jsonify(**{'error': 'Missing required param number or message'}), 400
if number[0] == '+':
number = number.replace('+', '')
if not number.isdigit():
return jsonify(**{'error': "Param number is not numerical '{}'".format(number)}), 400
app.logger.info('Sending to number={}, service={}, message={}'.format(
number, g.user, message))
outgoing_id = send_sms(destination=number, content=message)
log_event(action='send', outgoing_id=outgoing_id)
return jsonify(**{'result': 'sent', 'message': message, 'outgoing_id': outgoing_id})
@app.route('/sendega-incoming', methods=['POST']) @app.route('/sendega-incoming', methods=['POST'])
def incoming(): def incoming():
args = {'ip': request.headers.get('X-Real-IP') or request.remote_addr} args = {'ip': request.headers.get('X-Real-IP') or request.remote_addr}
@@ -280,10 +290,10 @@ def incoming():
# What the fuck, man, that's not a phone number, man. # What the fuck, man, that's not a phone number, man.
abort(400) # Bad Request abort(400) # Bad Request
if not keyword in ('DNS', 'DNSMEDLEM'): if keyword not in ('DNS', 'DNSMEDLEM'):
abort(400) # Bad Request abort(400) # Bad Request
if not shortcode in ('2454'): if shortcode not in ('2454'):
abort(400) # Bad Request abort(400) # Bad Request
incoming_id = log_incoming(**args) incoming_id = log_incoming(**args)
@@ -292,15 +302,13 @@ def incoming():
"Incoming SMS from number:%s saved with id:%s", "Incoming SMS from number:%s saved with id:%s",
args['msisdn'], incoming_id) args['msisdn'], incoming_id)
user = get_inside().get_user_by_phone(msisdn) user = dusken.get_user_by_phone(msisdn)
if user: if user:
expired = get_inside().user_is_expired(user) if user.get('is_member'):
if not expired:
return notify_valid_membership( return notify_valid_membership(
incoming_id=incoming_id, number=msisdn, user=user) incoming_id=incoming_id, number=msisdn, user=user)
elif expired: else:
return renew_membership( return renew_membership(
incoming_id=incoming_id, number=msisdn, user=user) incoming_id=incoming_id, number=msisdn, user=user)
else: else:
@@ -316,81 +324,6 @@ def incoming():
app.logger.error('Unhandled SMS, incoming_id=%s', incoming_id) app.logger.error('Unhandled SMS, incoming_id=%s', incoming_id)
@app.route('/sendega-dlr', methods=['POST'])
def dlr():
args = {'ip': request.headers.get('X-Real-IP') or request.remote_addr}
for key in ('msgid', 'extID', 'msisdn', 'status', 'statustext',
'registered', 'sent', 'delivered',
'errorcode', 'errormessage', 'operatorerrorcode'):
args[key] = request.form.get(key)
app.logger.debug("Dlr, args: %s", args)
dlr_id = log_dlr(**args)
# We need to check what the original message was all about
incoming_id = args['extID']
success = (args['status'] == '4')
if not success:
return notify_could_not_charge(
incoming_id=incoming_id, dlr_id=dlr_id, number=args['msisdn'])
event = query_db("""
SELECT * FROM event
WHERE incoming_id=%s
AND action IN ('new_membership', 'renew_membership')
""", [incoming_id], one=True)
if not event:
app.logger.error('Got an unknown delivery report, dlr_id=%s', dlr_id)
return 'what'
action = event['action']
if action == 'renew_membership':
return renew_membership_delivered(
incoming_id=incoming_id, dlr_id=dlr_id, user_id=event['user_id'])
elif action == 'new_membership':
return new_membership_delivered(
incoming_id=incoming_id, dlr_id=dlr_id)
app.logger.error('Unhandled DLR, dlr_id=%s', dlr_id)
@app.route('/stats/memberships/', methods=['GET'])
def stats_memberships():
start_datetime = request.args.get('start', '2015-08-01')
sale_events = query_db("""
SELECT DATE_FORMAT(timestamp, '%%Y-%%m-%%d') as date,count(*) as sales FROM event
WHERE action IN ('new_membership_delivered', 'renew_membership_delivered')
AND timestamp > %s
GROUP BY DATE_FORMAT(timestamp, '%%Y-%%m-%%d')
ORDER BY timestamp""", [start_datetime])
result = {'meta': {'num_results': len(sale_events)}, 'memberships': sale_events}
headers = {'Access-Control-Allow-Origin': '*'}
return (jsonify(**result), 200, headers)
@app.route('/stats/memberships/series', methods=['GET'])
def stats_memberships_stats():
start_datetime = request.args.get('start', '2015-08-01')
sale_events = query_db("""
SELECT DATE_FORMAT(timestamp, '%%Y-%%m-%%d %%T') as date FROM event
WHERE action IN ('new_membership_delivered', 'renew_membership_delivered')
AND timestamp > %s
ORDER BY timestamp""", [start_datetime])
result = {
'meta': {'num_results': len(sale_events)},
'memberships': [x['date'] for x in sale_events]
}
headers = {'Access-Control-Allow-Origin': '*'}
return (jsonify(**result), 200, headers)
@app.route('/') @app.route('/')
def main(): def main():
return 'Tekstmelding!' return 'Tekstmelding!'
+18 -9
View File
@@ -1,14 +1,7 @@
import string
import random
from datetime import datetime from datetime import datetime
from flask.json import JSONEncoder from flask.json import JSONEncoder
from flask import current_app, request, abort, g
from functools import wraps
def generate_activation_code(length=5):
chars = string.ascii_lowercase + string.digits
# remove easily misinterpreted chars
chars = chars.translate(None, '0oil1wv')
return ''.join(random.choice(chars) for _ in range(length))
class MyJSONEncoder(JSONEncoder): class MyJSONEncoder(JSONEncoder):
@@ -17,3 +10,19 @@ class MyJSONEncoder(JSONEncoder):
return o.isoformat() return o.isoformat()
return super(MyJSONEncoder, self).default(o) return super(MyJSONEncoder, self).default(o)
def require_token(func):
@wraps(func)
def check_token(*args, **kwargs):
if 'Authorization' not in request.headers:
abort(401)
auth_type, token = request.headers.get('Authorization').split(' ')
if auth_type != 'Token':
abort(401)
users = current_app.config.get('API_KEYS', {})
if token not in users.keys():
abort(401)
g.user = users.get(token)
return func(*args, **kwargs)
return check_token