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_NAME = 'tekstmelding'
|
||||
|
||||
# Authenticates Inside
|
||||
INSIDE_API_KEY = ''
|
||||
API_KEYS = {
|
||||
'somesecretkey': 'appname'
|
||||
}
|
||||
|
||||
MEMBERSHIP_PRICE_KR = 1
|
||||
|
||||
## Logging ##
|
||||
#############
|
||||
# Logging
|
||||
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
@@ -52,8 +52,7 @@ LOGGING = {
|
||||
}
|
||||
}
|
||||
|
||||
## Sendega ##
|
||||
#############
|
||||
# Sendega
|
||||
|
||||
# Endpoint for HTTP POST/GET
|
||||
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
|
||||
mysqlclient==1.3.10
|
||||
suds-jurko==0.6
|
||||
requests
|
||||
+37
-104
@@ -7,8 +7,9 @@ import datetime
|
||||
import logging
|
||||
import logging.config
|
||||
|
||||
from utils import generate_activation_code, MyJSONEncoder
|
||||
from utils import require_token, MyJSONEncoder
|
||||
import sendega
|
||||
import dusken
|
||||
|
||||
import config
|
||||
|
||||
@@ -145,19 +146,6 @@ def update_event(event_id, outgoing_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,
|
||||
billing=False, price=0, activation_code=None):
|
||||
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):
|
||||
assert user
|
||||
|
||||
name = get_inside().get_full_name(user)
|
||||
if user.get('expires_lifelong'):
|
||||
name = dusken.get_full_name(user)
|
||||
if user.get('is_lifelong_member'):
|
||||
expires = 'verdens undergang'
|
||||
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" % ({
|
||||
'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'])
|
||||
@require_token
|
||||
def kassa_notify_new_card():
|
||||
""" Notify user Adds a pending membership to specified number """
|
||||
api_key = request.args.get('api_key')
|
||||
|
||||
data = request.get_json()
|
||||
phone_number = data.get('phone_number')
|
||||
card_number = data.get('card_number')
|
||||
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:
|
||||
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})
|
||||
|
||||
|
||||
@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'])
|
||||
def incoming():
|
||||
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.
|
||||
abort(400) # Bad Request
|
||||
|
||||
if not keyword in ('DNS', 'DNSMEDLEM'):
|
||||
if keyword not in ('DNS', 'DNSMEDLEM'):
|
||||
abort(400) # Bad Request
|
||||
|
||||
if not shortcode in ('2454'):
|
||||
if shortcode not in ('2454'):
|
||||
abort(400) # Bad Request
|
||||
|
||||
incoming_id = log_incoming(**args)
|
||||
@@ -292,15 +302,13 @@ def incoming():
|
||||
"Incoming SMS from number:%s saved with id:%s",
|
||||
args['msisdn'], incoming_id)
|
||||
|
||||
user = get_inside().get_user_by_phone(msisdn)
|
||||
user = dusken.get_user_by_phone(msisdn)
|
||||
|
||||
if user:
|
||||
expired = get_inside().user_is_expired(user)
|
||||
|
||||
if not expired:
|
||||
if user.get('is_member'):
|
||||
return notify_valid_membership(
|
||||
incoming_id=incoming_id, number=msisdn, user=user)
|
||||
elif expired:
|
||||
else:
|
||||
return renew_membership(
|
||||
incoming_id=incoming_id, number=msisdn, user=user)
|
||||
else:
|
||||
@@ -316,81 +324,6 @@ def incoming():
|
||||
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('/')
|
||||
def main():
|
||||
return 'Tekstmelding!'
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
import string
|
||||
import random
|
||||
from datetime import datetime
|
||||
from flask.json import JSONEncoder
|
||||
|
||||
|
||||
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))
|
||||
from flask import current_app, request, abort, g
|
||||
from functools import wraps
|
||||
|
||||
|
||||
class MyJSONEncoder(JSONEncoder):
|
||||
@@ -17,3 +10,19 @@ class MyJSONEncoder(JSONEncoder):
|
||||
return o.isoformat()
|
||||
|
||||
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