#!/usr/bin/env python3 """ A display interface for archived 4chan quest threads. """ from collections import defaultdict from aiohttp import web import jinja2 import aiohttp_jinja2 from aiohttp_jinja2 import render_template import asyncpg import uvloop import config import filters uvloop.install() routes = web.RouteTableDef() @routes.get('/', name='index') async def index(request): """The index page.""" async with request.app['pool'].acquire() as conn: threads = await conn.fetch("SELECT * FROM thread ORDER BY time ASC") return render_template("index.html", request, locals()) @routes.get(r'/{thread_id:\d+}', name='thread') async def thread(request): """A single thread page.""" thread_id = int(request.match_info['thread_id']) async with request.app['pool'].acquire() as conn: posts = await conn.fetch( "SELECT * FROM post WHERE thread_id = $1 ORDER BY id ASC", thread_id) tags_raw = await conn.fetch( "SELECT tag.post_id, tag.name FROM tag " "LEFT JOIN post ON (tag.post_id = post.id) " "WHERE post.thread_id = $1", thread_id) links_raw = await conn.fetch( "SELECT link.link_from, link.link_to FROM link " "LEFT JOIN post ON (link.link_from = post.id) " "WHERE post.thread_id = $1", thread_id) tags = defaultdict(list) links = defaultdict(list) backlinks = defaultdict(list) for tag_raw in tags_raw: tag = tags[tag_raw['post_id']] tag.append(tag_raw['name']) tags[tag_raw['post_id']] = tag for link_raw in links_raw: link = links[link_raw['link_from']] link.append(link_raw['link_to']) links[link_raw['link_from']] = link backlink = backlinks[link_raw['link_to']] backlink.append(link_raw['link_from']) backlinks[link_raw['link_to']] = backlink url_prefix = config.url_prefix return render_template("thread.html", request, locals()) @routes.post('/add_tag', name='add_tag') async def add_tag(request): """Adds a tag to a post.""" data = await request.json() post_id = int(data.get('post_id')) tag_name = data.get('tag_name') async with request.app['pool'].acquire() as conn: await conn.execute( "INSERT INTO tag (post_id, name) VALUES ($1, $2)", post_id, tag_name) return web.json_response({'ok': True}) @routes.post('/remove_tag', name='remove_tag') async def remove_tag(request): """Removes a tag to a post.""" data = await request.json() post_id = int(data.get('post_id')) tag_name = data.get('tag_name') async with request.app['pool'].acquire() as conn: await conn.execute( "DELETE FROM tag WHERE post_id = $1 AND name = $2", post_id, tag_name) return web.json_response({'ok': True}) async def init_app(): """Initializes the application.""" app = web.Application() aiohttp_jinja2.setup( app, trim_blocks=True, lstrip_blocks=True, undefined=jinja2.StrictUndefined, loader=jinja2.FileSystemLoader('templates'), filters={ 'quotelink': filters.quotelink, }, ) app['pool'] = await asyncpg.create_pool(**config.db) app.router.add_routes(routes) app_wrap = web.Application() app_wrap.add_subapp(config.url_prefix, app) return app_wrap if __name__ == "__main__": app = init_app() web.run_app(app, host='0.0.0.0', port=5450)