view lookup.py @ 39:c538dc39b851

user login fixes
author drewp@bigasterisk.com
date Sat, 19 Nov 2022 17:06:36 -0800
parents f3a15a724483
children 94181d521d6d
line wrap: on
line source

"""
serve some queries over bookmarks:

/user
/user/tag+tag+tag

and the add-bookmark stuff
"""
import datetime
import json
import logging
import os
import time
import urllib.error
import urllib.parse
import urllib.request
from collections import defaultdict

import bottle
import pymongo
from bottle import static_file
from dateutil.tz import tzlocal

from jadestache import Renderer
from link import Links, NotFound
from pagetitle import PageTitle
from mongo_required import open_mongo_or_die, die_on_mongo_connection_errors
from get_agent import bottleGetAgent
db = open_mongo_or_die()['href']
pageTitle = PageTitle(db)
links = Links(db)
renderer = Renderer(search_dirs=['template'], debug=bottle.DEBUG)
log = logging.getLogger()


def getUser():
    try:
        agent = bottleGetAgent()
        username = db['user'].find_one({'_id': str(agent)})['username'] if agent else None
    except KeyError:
        username = agent = None
    return username, agent


def siteRoot():
    try:
        return bottle.request.headers['x-site-root'].rstrip('/')
    except KeyError:
        log.warn(repr(bottle.request.__dict__))
        raise


@bottle.route('/static/<path:path>')
def server_static(path):
    return static_file(path, root='static')


def recentLinks(user, tags, allowEdit):
    out = {'links': []}
    t1 = time.time()
    spec = {'user': user}
    if tags:
        spec['extracted.tags'] = {'$all': tags}
    for doc in db['links'].find(spec, sort=[('t', -1)], limit=50):
        link = links.forDisplay(doc)
        link['allowEdit'] = allowEdit
        out['links'].append(link)
    out['stats'] = {'queryTimeMs': round((time.time() - t1) * 1000, 2)}
    return out


def allTags(user, withTags=[]):
    """withTags limits results to other tags that have been used with
    those tags"""
    withTags = set(withTags)
    count = defaultdict(lambda: 0)  # tag : count
    for doc in db['links'].find({'user': user}, projection=['extracted.tags']):
        docTags = set(doc.get('extracted', {}).get('tags', []))
        if withTags and not withTags.issubset(docTags):
            continue
        for t in docTags.difference(withTags):
            count[t] = count[t] + 1
    byFreq = [(n, t) for t, n in count.items()]
    byFreq.sort(key=lambda n_t: (-n_t[0], n_t[1]))
    return [{'label': t, 'count': n} for n, t in byFreq]


def renderWithTime(name, data):
    t1 = time.time()
    rendered = renderer.render_name(name, data)
    dt = (time.time() - t1) * 1000
    rendered = rendered.replace('TEMPLATETIME', "%.02f ms" % dt)
    return rendered


@bottle.route('/addLink')
def addLink():
    out = {
        'toRoot': siteRoot(),
        'absRoot': siteRoot(),
        'user': getUser()[0],
        'withKnockout': True,
        'fillHrefJson': json.dumps(bottle.request.params.get('url', '')),
    }
    return renderWithTime('add.jade', out)


@bottle.route('/addOverlay')
def addOverlay():
    p = bottle.request.params

    return ""


@bottle.route('/addLink/proposedUri')
def proposedUri():
    uri = bottle.request.params.uri
    user, _ = getUser()

    try:
        prevDoc = links.find(uri, user)
    except NotFound:
        prevDoc = None

    return {
        'description': prevDoc['description'] if prevDoc else pageTitle.pageTitle(uri),
        'tag': prevDoc['tag'] if prevDoc else '',
        'extended': prevDoc['extended'] if prevDoc else '',
        'shareWith': prevDoc.get('shareWith', []) if prevDoc else [],
        'suggestedTags': ['tag1', 'tag2'],
        'existed': prevDoc is not None,
    }


# proposal check existing links, get page title (stuff that in db), get tags from us and other serviecs. maybe the deferred ones ater


@bottle.route('/tags')
def tagFilterComplete():
    params = bottle.request.params
    haveTags = [_f for _f in params['have'].split(',') if _f]
    if haveTags and len(haveTags[-1]) > 0:
        haveTags, partialTerm = haveTags[:-1], haveTags[-1]
    else:
        partialTerm = ""

    out = []
    for t in allTags(params.user, withTags=haveTags):
        if partialTerm and partialTerm not in t['label']:
            continue
        out.append({'id': t['label'], 'text': "%s (%s%s)" % (t['label'], t['count'], " left" if haveTags else "")})

    return {'tags': out}


@bottle.route('/<user>/')
def userSlash(user):
    bottle.redirect(siteRoot() + "/%s" % urllib.parse.quote(user))


@bottle.route('/<user>.json', method='GET')
def userAllJson(user):
    data = recentLinks(user, [], allowEdit=getUser()[0] == user)
    data['toRoot'] = siteRoot()
    return json.dumps(data)


@bottle.route('/<user>', method='GET')
def userAll(user):
    return userLinks(user, "")


@bottle.route('/<user>', method='POST')
def userAddLink(user):
    u=getUser()[0]
    if u is None:
        raise ValueError('not logged in')
    if u != user:
        raise ValueError("not logged in as %s" % user)
    print(repr(bottle.request.params.__dict__))
    doc = links.fromPostdata(bottle.request.params, user, datetime.datetime.now(tzlocal()))
    links.insertOrUpdate(doc)

    print("notify about sharing to", repr(doc['shareWith']))

    bottle.redirect(siteRoot() + '/' + user)


def parseTags(tagComponent):
    # the %20 is coming from davis.js, not me :(
    return [_f for _f in tagComponent.replace("%20", "+").split('+') if _f]


@bottle.route('/<user>/<tags:re:.*>.json')
def userLinksJson(user, tags):
    tags = parseTags(tags)
    data = recentLinks(user, tags, allowEdit=getUser()[0] == user)
    data['toRoot'] = siteRoot()
    return json.dumps(data)


@bottle.route('/<user>/<tags>')
@die_on_mongo_connection_errors()
def userLinks(user, tags):
    tags = parseTags(tags)
    log.info('userLinks user=%r tags=%r', user, tags)
    data = recentLinks(user, tags, allowEdit=getUser()[0] == user)
    data['desc'] = ("%s's recent links" % user) + (" tagged %s" % (tags,) if tags else "")
    data['toRoot'] = siteRoot()
    data['allTags'] = allTags(user)
    data['user'] = user
    data['showPrivateData'] = (user == getUser()[0])

    data['pageTags'] = [{"word": t} for t in tags]
    data['stats']['template'] = 'TEMPLATETIME'
    return renderWithTime('links.jade', data)


@bottle.route('/templates')
def templates():
    return json.dumps({'linklist': renderer.load_template("linklist.jade")})


@bottle.route('/metrics')
def metrics():
    return ''


@bottle.route('/')
def root():
    data = {
        'toRoot': siteRoot(),
        'stats': {
            'template': 'TEMPLATETIME'
        },
        'users': [{
            'user': doc['username']
        } for doc in db['user'].find()],
    }
    return renderWithTime('index.jade', data)


if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO)
    bottle.run(server='gunicorn', host='0.0.0.0', port=10002, workers=1)