#!/usr/bin/env python3
# this is a quick hack, to be improved later. trying to do the simplest
# thing that will possibly work.
## # TODO
##
## * put page content into templates/static files
## * clean up FIXME
import bottle
from bottle import route , run , request , response , redirect
from bottle import HTTPError , template
from sqlalchemy . ext . declarative import declarative_base
from bottle . ext import sqlalchemy
from pprint import pprint
from sqlalchemy import create_engine
import urllib . parse
import os
import random
import string
from datetime import datetime , timedelta
VERSION = " 1.0.0 "
PORT = os . environ . get ( " PORT " , 8080 )
DEBUG = os . environ . get ( " DEBUG " , False )
SQLITE_FILENAME = os . environ . get ( " SQLITE_FILENAME " , " /data/db.sqlite " )
DATABASE_URL = os . environ . get ( " DATABASE_URL " , " sqlite:/// " + SQLITE_FILENAME )
ADMIN_PSK = os . environ . get ( " ADMIN_PSK " , " hunter2 " )
# sorry for global
SQLBASE = declarative_base ( )
# FIXME make this skip letters not in base58
def randomUpper ( count ) :
return " " . join ( random . choice ( string . ascii_uppercase ) for _ in range ( count ) )
# FIXME maybe make the IDs longer
def genRandomTVID ( ) :
tvid = str ( randomUpper ( 3 ) + " - " + randomUpper ( 3 ) )
return tvid
def serve ( ) :
app = bottle . Bottle ( )
# pull in models
from . db import TV
engine = create_engine ( DATABASE_URL , echo = False )
plugin = sqlalchemy . Plugin (
engine ,
SQLBASE . metadata ,
keyword = " db " ,
create = True ,
commit = True ,
use_kwargs = False ,
)
app . install ( plugin )
# here we cookie them if they have no cookie, then redirect them to the
# cookie'd value (whether preexisting or new).
@app . get ( " / " )
def indexpage ( ) :
c = request . get_cookie ( " displayid " )
if c :
# redirect
return redirect ( " /tv/ " + c )
else :
newid = genRandomTVID ( )
response . set_cookie ( " displayid " , newid )
return redirect ( " /tv/ " + newid )
@app . get ( " /style.css " )
def stylesheet ( ) :
response . content_type = " text/css "
return template ( " style " )
# here we check to see if they have a redirect URL in the db. if they do
# we send them there. if they don't, we display their ID really big,
# reloading the page once per hour.
@app . get ( " /tv/<displayid> " )
def tvpage ( db , displayid = None ) :
# FIXME check for cookie, this is broken
if displayid is None :
return template ( " nocookie " )
# check db for tv id
tv = db . query ( TV ) . filter_by ( displayid = displayid ) . first ( )
if tv :
tv . lastSeen = datetime . now ( )
db . add ( tv )
if tv . target :
return redirect ( tv . target )
else :
return template ( " displayid " , id = displayid , version = VERSION )
else :
# otherwise, just show their display ID bigly and keep them
# bouncing periodically until some admin configures them
# a target:
# update lastseen here:
newtv = TV ( displayid = displayid , lastSeen = datetime . now ( ) )
db . add ( newtv )
return template ( " displayid " , id = displayid , version = VERSION )
@app . get ( " /admin/edit/<displayid> " )
def displayeditform ( db , displayid = None ) :
c = request . get_cookie ( " psk " )
if not c :
return redirect ( " /login " )
if c != ADMIN_PSK :
return redirect ( " /logout " )
if not displayid :
return redirect ( " /admin " )
tv = db . query ( TV ) . filter_by ( displayid = displayid ) . first ( )
if tv is None :
return redirect ( " /admin " )
return template ( " displayeditform " , tv = tv , version = VERSION )
@app . post ( " /admin/edit " )
def displayedithandler ( db ) :
# FIXME SECURITY csrf issue
c = request . get_cookie ( " psk " )
if not c :
return redirect ( " /login " )
if c != ADMIN_PSK :
return redirect ( " /logout " )
displayid = request . forms . get ( " displayid " )
tv = db . query ( TV ) . filter_by ( displayid = displayid ) . first ( )
if tv is None :
return redirect ( " /admin " )
# FIXME make sure this is a valid URL
tv . target = request . forms . get ( " target " )
tv . memo = request . forms . get ( " formmemo " )
db . add ( tv )
db . commit ( )
return redirect ( " /admin " )
# here we display the administration list of TVs if logged in
# if logged out then redirect to /login
# FIXME make this use sessions instead of just storing PSK in a cookie
# https://bottlepy.org/docs/dev/recipes.html
@app . get ( " /admin " )
def adminpage ( db ) :
c = request . get_cookie ( " psk " )
if not c :
return redirect ( " /login " )
if c != ADMIN_PSK :
return redirect ( " /logout " )
# first, cleanup db of old entries:
week_ago = datetime . now ( ) - timedelta ( days = 7 )
db . query ( TV ) . filter ( TV . lastSeen < week_ago ) . delete ( )
db . commit ( )
tvs = db . query ( TV ) . order_by ( TV . lastSeen . desc ( ) )
response . headers [ " Cache-Control " ] = " no-cache "
return template ( " adminpanel " , tvs = tvs , version = VERSION )
# here we ask for a password:
@app . get ( " /login " )
def loginform ( ) :
msg = request . GET . msg
return template ( " loginform " , version = VERSION , msg = msg )
@app . post ( " /checklogin " )
def checklogin ( ) :
attemptedPass = request . forms . get ( " password " )
if not attemptedPass :
return redirect (
" /login?msg= " + urllib . parse . quote_plus ( u " Incorrect password. " )
)
if attemptedPass != ADMIN_PSK :
return redirect (
" /login?msg= " + urllib . parse . quote_plus ( u " Incorrect password. " )
)
# password is right, cookie them:
response . set_cookie ( " psk " , attemptedPass )
return redirect ( " /admin " )
@app . get ( " /logout " )
def logout ( ) :
response . set_cookie ( " psk " , " " )
return redirect ( " /login " )
app . run ( host = " 0.0.0.0 " , port = PORT , debug = DEBUG )