import flask from flask import Blueprint, session, redirect, url_for, escape, request, abort, g import flask_login; from flask.ext.cors import cross_origin from flask.ext.login import login_required, current_user import urllib2 import lxml.html from . import database import urlparse import json from marrow_config import config import dateutil.parser bone_blueprint = Blueprint('bone', __name__) useragent = [ ('User-agent', 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36')] for handler in [urllib2.HTTPSHandler,urllib2.HTTPHandler]: handler = handler() opener = urllib2.build_opener(handler) opener.addheaders = useragent urllib2.install_opener(opener) import functools def as_json(f): @functools.wraps(f) def _inner(*a, **k): res = f(*a, **k) res = (json.dumps(res), 200, {'Content-Type': 'application/json'}) return res return _inner @bone_blueprint.route('/link/<linkid>', methods=['GET','POST','DELETE']) @login_required def delete_link(linkid): db = database.get_db() linkid = int(linkid) result = '' with db.cursor() as cur: if request.method == 'GET': cur.execute('SELECT id,url,title,posted FROM links WHERE id=%s', (linkid,)) nid,url,title,posted = cur.fetchone() result = dict(id=nid,url=url,title=title,posted=posted.isoformat()) elif request.method == 'POST': result = False if 'username' in session: cur.execute('SELECT subscribe_link(%s,%s)', (current_user.id,linkid)) result = cur.fetchone()[0] elif request.method == 'DELETE': result = False if 'username' in session: cur.execute('SELECT delete_link(%s,%s)', (current_user.id,linkid)) result = cur.fetchone()[0] db.commit() return json.dumps(result) def clean_url(url): scheme, netloc, path, params, query, fragment = urlparse.urlparse(url, 'http') if path and not netloc: netloc, path = path, netloc return urlparse.urlunparse((scheme, netloc, path, params, query, fragment)) def get_siteinfo(url): return config.titlegetter.get_title(url) @bone_blueprint.route('/vote/total') @login_required @as_json def vote_link_total(): url = request.args['url'].encode('utf-8') db = database.get_db() result = dict(success=False, votes=None) with db.cursor() as cur: cur.callproc('total_votes', (url,)) dbresult, = cur.fetchone() if dbresult is not None: result['success'] = True result['votes'] = dbresult return result def vote_link_common(vote): obj = request.get_json() url = obj['url'] db = database.get_db() result = dict(success=False, votes=None, myvote=None) url = url.encode('utf-8') with db.cursor() as cur: cur.callproc('vote_link', (url, current_user.id, vote)) dbresult = cur.fetchone() print(dbresult, "<--- dbresult") if dbresult is not None: myvote, total = dbresult result['success'] = True result['myVote'] = myvote result['votes'] = total db.commit() else: db.rollback(); print 'done!' print result, '<-- result' return (json.dumps(result), 200, {'Content-Type': 'application/json'}) @bone_blueprint.route('/vote/zero', methods=['POST']) @login_required def vote_link_zero(): return vote_link_common(0) @bone_blueprint.route('/vote/down', methods=['POST']) @login_required def vote_link_down(): return vote_link_common(-1) @bone_blueprint.route('/vote/up', methods=['POST']) @login_required def vote_link_up(): return vote_link_common(1) @bone_blueprint.route('/add', methods=['POST']) @bone_blueprint.route('/submit', methods=['POST']) @cross_origin(allow_headers='Content-Type') def submit_link(): result = dict(success=False, id={}); username = None db = database.get_db() obj = request.get_json() if 'username' in obj: _username = obj['username'] _username = _username.lower().strip() if 'ak' in obj and database.check_ak(db, _username, obj['ak']): username = _username else: abort(401); return elif 'username' in session: username = session['username'] # Note that we need to figger out a better way to do the extension # auth before we can change this to Flask-Login if username is not None: url, title = obj['url'],obj['title'] url = clean_url(url) title, url = get_siteinfo(url) # this makes sure that the url is the site's preferred URL # TODO: this might need sanity checks . . . like make sure same site? with db.cursor() as cur: cur.callproc('put_link', (username, url, title)) ## This returns (link_id, user_id) res = cur.fetchone() if cur.rowcount > 0: db.commit() result['success'] = True result['id'] = res[0] else: db.rollback() return json.dumps(result), 200, {'Content-Type':'application/json'} @bone_blueprint.route('', methods=['GET']) @login_required def default_data(): print current_user.id print 'username' in session result = data(current_user.id) return result @bone_blueprint.route('/u/<username>', methods=['GET']) def data(username): sectionTitle = username result = {'marrow':[], 'sectionTitle': sectionTitle} with database.get_db().cursor() as cur: cur_username = 'anonymous' if current_user.is_authenticated: cur_username = current_user.id cur.execute("SELECT url, title, posted, linkid, votes, has_user_shared(%s, url) from get_bone(%s);", (cur_username, username,)) result['marrow'] = [ dict(id=linkid, url=url,title=title,posted=posted.isoformat(),votes=votes,shared=shared) for url,title,posted,linkid,votes,shared in cur.fetchall() ] return json.dumps(result) # TODO: rethink variable names here @bone_blueprint.route('/unsubscribe', methods=['POST']) @login_required def unsubscribe(): data = request.get_json() result = False if 'username' in session: fro_user = current_user.id to_user = data['from'] db = database.get_db() with db.cursor() as cur: cur.callproc('unsubscribe', (fro_user,to_user)) db.commit() result = True return json.dumps(result) @bone_blueprint.route('/subscribe', methods=['POST']) @login_required def subscribe(): data = request.get_json() result = False if 'username' in session: fro_user = current_user.id to_user = data['to'] db = database.get_db() with db.cursor() as cur: cur.callproc('subscribe', (fro_user,to_user)) db.commit() result = True return json.dumps(result); @bone_blueprint.route('/subscriptions', defaults={'before':None, 'count': None}) @bone_blueprint.route('/subscriptions/<before>', defaults={'count': None}) @bone_blueprint.route('/subscriptions/count/<int:count>', defaults={'before': None}) @login_required @cross_origin(allow_headers='Content-Type') def subscriptions(before, count): result = {'marrow':[], 'sectionTitle': 'Subscriptions'} username = None db = database.get_db() with db: # Start transaction to make sure that the ak really is deleted. # Otherwise, a malicious attacker could sniff the ak and reuse # it. if 'username' in request.args: username = request.args['username'] if 'ak' not in request.args: username = None elif not database.check_ak(db, username, request.args['ak']): username = None if username is None and 'username' in session: username = current_user.id if username is not None: with db.cursor() as cur: if count is None or count > 200: count = 50 # 50 results or up to 200 results args = (username,count) if before is not None: before = dateutil.parser.parse(before) args = args + (before,) cur.callproc("get_bones", args) result['marrow'] = [ dict(id=id,poster=poster, url=url,title=title,posted=posted.isoformat(), votes=votes, myVote=myvote, shared=shared) for id,url,title,posted,poster,votes,myvote,shared in cur.fetchall() ] return (json.dumps(result), 200, {'Content-Type': 'application/json'}) import random @bone_blueprint.route('/random') @login_required def random(): db = database.get_db() with db.cursor() as cur: if 'username' in session: exclude = [current_user.id] if 'last' in request.args: exclude.append(request.args['last']) cur.execute( 'SELECT name FROM users WHERE name NOT IN %s ORDER BY random() LIMIT 1', (tuple(exclude),) ) else: cur.execute('SELECT name FROM users ORDER BY random() LIMIT 1') username = cur.fetchone()[0] return redirect(url_for('bone.data', username=username))