Archived
Add /send endpoint, basic Dusken API. Remove more unneeded stuff.
This commit is contained in:
1 parent
4b75b45ce2
commit
5fc7e2a8a0
5 files changed
+95
-119
No files matched your search
+5
-6
@@ -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'
|
||||||
|
|||||||
@@ -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,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
@@ -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!'
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in new issue
Block a user