2020-03-10 13:44:28 +00:00
|
|
|
#!/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
|
2020-03-10 19:33:57 +00:00
|
|
|
import urllib.parse
|
2020-03-10 13:44:28 +00:00
|
|
|
import os
|
|
|
|
import random
|
|
|
|
import string
|
2020-03-11 01:52:30 +00:00
|
|
|
from datetime import datetime, timedelta
|
2020-03-10 13:44:28 +00:00
|
|
|
|
2020-03-11 16:13:50 +00:00
|
|
|
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")
|
2020-03-10 13:44:28 +00:00
|
|
|
|
|
|
|
# sorry for global
|
|
|
|
SQLBASE = declarative_base()
|
|
|
|
|
|
|
|
# FIXME make this skip letters not in base58
|
|
|
|
def randomUpper(count):
|
2020-03-11 16:13:50 +00:00
|
|
|
return "".join(random.choice(string.ascii_uppercase) for _ in range(count))
|
|
|
|
|
2020-03-10 13:44:28 +00:00
|
|
|
|
|
|
|
# FIXME maybe make the IDs longer
|
|
|
|
def genRandomTVID():
|
|
|
|
tvid = str(randomUpper(3) + "-" + randomUpper(3))
|
|
|
|
return tvid
|
|
|
|
|
2020-03-11 16:13:50 +00:00
|
|
|
|
2020-03-10 13:44:28 +00:00
|
|
|
def serve():
|
|
|
|
app = bottle.Bottle()
|
|
|
|
|
|
|
|
# pull in models
|
|
|
|
from .db import TV
|
|
|
|
|
2020-03-11 01:52:30 +00:00
|
|
|
engine = create_engine(DATABASE_URL, echo=False)
|
2020-03-10 13:44:28 +00:00
|
|
|
|
|
|
|
plugin = sqlalchemy.Plugin(
|
2020-03-11 01:52:30 +00:00
|
|
|
engine,
|
|
|
|
SQLBASE.metadata,
|
2020-03-11 16:13:50 +00:00
|
|
|
keyword="db",
|
2020-03-11 01:52:30 +00:00
|
|
|
create=True,
|
|
|
|
commit=True,
|
2020-03-11 16:13:50 +00:00
|
|
|
use_kwargs=False,
|
2020-03-10 13:44:28 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
app.install(plugin)
|
|
|
|
|
|
|
|
# here we cookie them if they have no cookie, then redirect them to the
|
|
|
|
# cookie'd value (whether preexisting or new).
|
2020-03-11 16:13:50 +00:00
|
|
|
@app.get("/")
|
2020-03-10 13:44:28 +00:00
|
|
|
def indexpage():
|
2020-03-11 01:52:30 +00:00
|
|
|
c = request.get_cookie("displayid")
|
2020-03-10 13:44:28 +00:00
|
|
|
if c:
|
|
|
|
# redirect
|
2020-03-11 16:13:50 +00:00
|
|
|
return redirect("/tv/" + c)
|
2020-03-10 13:44:28 +00:00
|
|
|
else:
|
|
|
|
newid = genRandomTVID()
|
2020-03-11 01:52:30 +00:00
|
|
|
response.set_cookie("displayid", newid)
|
2020-03-11 16:13:50 +00:00
|
|
|
return redirect("/tv/" + newid)
|
2020-03-10 13:44:28 +00:00
|
|
|
|
2020-03-11 16:13:50 +00:00
|
|
|
@app.get("/style.css")
|
2020-03-10 13:44:28 +00:00
|
|
|
def stylesheet():
|
2020-03-11 16:13:50 +00:00
|
|
|
response.content_type = "text/css"
|
|
|
|
return template("style")
|
2020-03-10 13:44:28 +00:00
|
|
|
|
|
|
|
# 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.
|
2020-03-11 16:13:50 +00:00
|
|
|
@app.get("/tv/<displayid>")
|
2020-03-10 13:44:28 +00:00
|
|
|
def tvpage(db, displayid=None):
|
2020-03-11 01:52:30 +00:00
|
|
|
# FIXME check for cookie, this is broken
|
|
|
|
if displayid is None:
|
2020-03-11 16:13:50 +00:00
|
|
|
return template("nocookie")
|
2020-03-10 13:44:28 +00:00
|
|
|
|
|
|
|
# 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:
|
2020-03-11 01:52:30 +00:00
|
|
|
return redirect(tv.target)
|
2020-03-10 13:44:28 +00:00
|
|
|
else:
|
2020-03-11 16:13:50 +00:00
|
|
|
return template("displayid", id=displayid, version=VERSION)
|
2020-03-10 13:44:28 +00:00
|
|
|
else:
|
|
|
|
# otherwise, just show their display ID bigly and keep them
|
|
|
|
# bouncing periodically until some admin configures them
|
|
|
|
# a target:
|
|
|
|
# update lastseen here:
|
2020-03-11 16:13:50 +00:00
|
|
|
newtv = TV(displayid=displayid, lastSeen=datetime.now())
|
2020-03-10 13:44:28 +00:00
|
|
|
db.add(newtv)
|
2020-03-11 16:13:50 +00:00
|
|
|
return template("displayid", id=displayid, version=VERSION)
|
2020-03-10 13:44:28 +00:00
|
|
|
|
2020-03-11 16:13:50 +00:00
|
|
|
@app.get("/admin/edit/<displayid>")
|
2020-03-11 01:52:30 +00:00
|
|
|
def displayeditform(db, displayid=None):
|
|
|
|
c = request.get_cookie("psk")
|
|
|
|
if not c:
|
2020-03-11 16:13:50 +00:00
|
|
|
return redirect("/login")
|
2020-03-11 01:52:30 +00:00
|
|
|
if c != ADMIN_PSK:
|
2020-03-11 16:13:50 +00:00
|
|
|
return redirect("/logout")
|
2020-03-11 01:52:30 +00:00
|
|
|
if not displayid:
|
2020-03-11 16:13:50 +00:00
|
|
|
return redirect("/admin")
|
2020-03-11 01:52:30 +00:00
|
|
|
tv = db.query(TV).filter_by(displayid=displayid).first()
|
|
|
|
if tv is None:
|
2020-03-11 16:13:50 +00:00
|
|
|
return redirect("/admin")
|
|
|
|
return template("displayeditform", tv=tv, version=VERSION)
|
2020-03-11 01:52:30 +00:00
|
|
|
|
2020-03-11 16:13:50 +00:00
|
|
|
@app.post("/admin/edit")
|
2020-03-11 01:52:30 +00:00
|
|
|
def displayedithandler(db):
|
|
|
|
# FIXME SECURITY csrf issue
|
|
|
|
c = request.get_cookie("psk")
|
|
|
|
if not c:
|
2020-03-11 16:13:50 +00:00
|
|
|
return redirect("/login")
|
2020-03-11 01:52:30 +00:00
|
|
|
if c != ADMIN_PSK:
|
2020-03-11 16:13:50 +00:00
|
|
|
return redirect("/logout")
|
|
|
|
displayid = request.forms.get("displayid")
|
2020-03-11 01:52:30 +00:00
|
|
|
tv = db.query(TV).filter_by(displayid=displayid).first()
|
|
|
|
if tv is None:
|
2020-03-11 16:13:50 +00:00
|
|
|
return redirect("/admin")
|
2020-03-11 01:52:30 +00:00
|
|
|
# FIXME make sure this is a valid URL
|
2020-03-11 16:13:50 +00:00
|
|
|
tv.target = request.forms.get("target")
|
|
|
|
tv.memo = request.forms.get("formmemo")
|
2020-03-11 01:52:30 +00:00
|
|
|
db.add(tv)
|
|
|
|
db.commit()
|
2020-03-11 16:13:50 +00:00
|
|
|
return redirect("/admin")
|
2020-03-11 01:52:30 +00:00
|
|
|
|
2020-03-10 13:44:28 +00:00
|
|
|
# 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
|
2020-03-11 16:13:50 +00:00
|
|
|
@app.get("/admin")
|
2020-03-10 19:33:57 +00:00
|
|
|
def adminpage(db):
|
|
|
|
c = request.get_cookie("psk")
|
|
|
|
if not c:
|
2020-03-11 16:13:50 +00:00
|
|
|
return redirect("/login")
|
2020-03-10 19:33:57 +00:00
|
|
|
if c != ADMIN_PSK:
|
2020-03-11 16:13:50 +00:00
|
|
|
return redirect("/logout")
|
2020-03-11 01:52:30 +00:00
|
|
|
|
|
|
|
# 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())
|
2020-03-11 16:13:50 +00:00
|
|
|
response.headers["Cache-Control"] = "no-cache"
|
|
|
|
return template("adminpanel", tvs=tvs, version=VERSION)
|
2020-03-10 13:44:28 +00:00
|
|
|
|
2020-03-10 19:33:57 +00:00
|
|
|
# here we ask for a password:
|
2020-03-11 16:13:50 +00:00
|
|
|
@app.get("/login")
|
2020-03-10 19:33:57 +00:00
|
|
|
def loginform():
|
|
|
|
msg = request.GET.msg
|
2020-03-11 16:13:50 +00:00
|
|
|
return template("loginform", version=VERSION, msg=msg)
|
2020-03-10 13:44:28 +00:00
|
|
|
|
2020-03-11 16:13:50 +00:00
|
|
|
@app.post("/checklogin")
|
2020-03-10 19:33:57 +00:00
|
|
|
def checklogin():
|
2020-03-11 16:13:50 +00:00
|
|
|
attemptedPass = request.forms.get("password")
|
2020-03-10 19:33:57 +00:00
|
|
|
if not attemptedPass:
|
2020-03-11 01:52:30 +00:00
|
|
|
return redirect(
|
2020-03-11 16:13:50 +00:00
|
|
|
"/login?msg=" + urllib.parse.quote_plus(u"Incorrect password.")
|
2020-03-10 19:33:57 +00:00
|
|
|
)
|
|
|
|
if attemptedPass != ADMIN_PSK:
|
2020-03-11 01:52:30 +00:00
|
|
|
return redirect(
|
2020-03-11 16:13:50 +00:00
|
|
|
"/login?msg=" + urllib.parse.quote_plus(u"Incorrect password.")
|
2020-03-10 19:33:57 +00:00
|
|
|
)
|
|
|
|
# password is right, cookie them:
|
|
|
|
response.set_cookie("psk", attemptedPass)
|
2020-03-11 16:13:50 +00:00
|
|
|
return redirect("/admin")
|
2020-03-10 19:33:57 +00:00
|
|
|
|
2020-03-11 16:13:50 +00:00
|
|
|
@app.get("/logout")
|
2020-03-10 13:44:28 +00:00
|
|
|
def logout():
|
2020-03-10 19:33:57 +00:00
|
|
|
response.set_cookie("psk", "")
|
2020-03-11 16:13:50 +00:00
|
|
|
return redirect("/login")
|
2020-03-10 13:44:28 +00:00
|
|
|
|
2020-03-11 16:13:50 +00:00
|
|
|
app.run(host="0.0.0.0", port=PORT, debug=DEBUG)
|