annotate laterlinks3/laterlinks_flask_app.py @ 118:65db090a697e

laterlinks3: add GCS support
author paulo
date Fri, 18 Sep 2020 01:13:29 -0700
parents 476cb019ad9f
children 26cc1a16e7a3
rev   line source
paulo@117 1 import csv
paulo@117 2 import datetime
paulo@118 3 import io
paulo@117 4 import os
paulo@118 5 import tempfile
paulo@117 6
paulo@117 7 import flask
paulo@118 8 import google.cloud.storage
paulo@117 9 from html3.html3 import HTML
paulo@117 10
paulo@117 11 app = flask.Flask(__name__)
paulo@117 12
paulo@118 13 GCS_BUCKET = google.cloud.storage.Client().get_bucket(os.environ.get("GCS_BUCKET"))
paulo@118 14 PIN = os.environ.get("LLPIN")
paulo@117 15 STRTIME_FMT = "%Y-%m-%d %H:%M:%S"
paulo@117 16
paulo@117 17
paulo@117 18 class LLDialect(csv.Dialect):
paulo@117 19 delimiter = '\t'
paulo@117 20 quoting = csv.QUOTE_NONE
paulo@117 21 lineterminator = '\n'
paulo@117 22
paulo@117 23
paulo@117 24 LLDIALECT = LLDialect()
paulo@117 25 LLDB_FN = "lldb.tsv"
paulo@117 26 LLDB_UNREAD_FN = "lldb_unread.tsv"
paulo@117 27
paulo@117 28
paulo@117 29 class LLError(Exception):
paulo@117 30 pass
paulo@117 31
paulo@117 32 class PinFailError(LLError):
paulo@117 33 def __str__(self):
paulo@117 34 return "PIN FAIL!"
paulo@117 35
paulo@117 36 class PinSetupError(LLError):
paulo@117 37 def __str__(self):
paulo@117 38 return "PIN SETUP ERROR!"
paulo@117 39
paulo@117 40 class MissingFieldsError(LLError):
paulo@117 41 def __str__(self):
paulo@117 42 return "MISSING FIELD(s)!"
paulo@117 43
paulo@117 44
paulo@118 45 def gcs_download(fn):
paulo@118 46 tmp_f = tempfile.TemporaryFile("w+")
paulo@118 47 blob = GCS_BUCKET.get_blob(fn)
paulo@118 48 if blob:
paulo@118 49 tmp_f.write(str(blob.download_as_string(), encoding="utf-8"))
paulo@118 50 return tmp_f
paulo@117 51
paulo@117 52
paulo@118 53 def gcs_upload(fn, tmp_f):
paulo@118 54 blob = GCS_BUCKET.blob(fn)
paulo@118 55 blob.upload_from_file(tmp_f, rewind=True)
paulo@118 56
paulo@118 57
paulo@118 58 def lldb_unread_load(lldb_unread_tmp_f):
paulo@118 59 lldb_unread_tmp_f.seek(0)
paulo@118 60 return csv.reader(lldb_unread_tmp_f, LLDIALECT)
paulo@118 61
paulo@118 62
paulo@118 63 def lldb_add(inp, lldb_tmp_f, lldb_unread_tmp_f):
paulo@117 64 title = inp.get("title")
paulo@117 65 url = inp.get("url")
paulo@117 66 if not (title and url):
paulo@117 67 raise MissingFieldsError()
paulo@117 68
paulo@117 69 dt_str = datetime.datetime.now().strftime(STRTIME_FMT)
paulo@117 70
paulo@118 71 lldb_tmp_f.seek(0, io.SEEK_END)
paulo@118 72 csv.writer(lldb_tmp_f, LLDIALECT).writerow([title, url, dt_str])
paulo@118 73 gcs_upload(LLDB_FN, lldb_tmp_f)
paulo@117 74
paulo@118 75 lldb_unread_tmp_f.seek(0, io.SEEK_END)
paulo@118 76 csv.writer(lldb_unread_tmp_f, LLDIALECT).writerow([title, url, dt_str])
paulo@118 77 gcs_upload(LLDB_UNREAD_FN, lldb_unread_tmp_f)
paulo@118 78
paulo@118 79
paulo@118 80 def lldb_unread_delete(inp, lldb_unread_tmp_f):
paulo@117 81 delete = inp.getlist("delete")
paulo@117 82 if not delete:
paulo@117 83 raise MissingFieldsError()
paulo@117 84
paulo@118 85 lldb_unread = [i for i in lldb_unread_load(lldb_unread_tmp_f)]
paulo@117 86
paulo@117 87 try:
paulo@117 88 for i in delete:
paulo@117 89 for j in lldb_unread:
paulo@117 90 dt_str = j[2]
paulo@117 91 if i == dt_str:
paulo@117 92 lldb_unread.remove(j)
paulo@117 93 finally:
paulo@118 94 lldb_unread_tmp_f.truncate(0)
paulo@118 95 lldb_unread_tmp_f.seek(0)
paulo@118 96 csv.writer(lldb_unread_tmp_f, LLDIALECT).writerows(lldb_unread)
paulo@118 97 gcs_upload(LLDB_UNREAD_FN, lldb_unread_tmp_f)
paulo@117 98
paulo@117 99
paulo@117 100 @app.route("/", methods=["GET", "POST"])
paulo@117 101 def index():
paulo@117 102 is_post = (flask.request.method == "POST")
paulo@117 103 inp = flask.request.form
paulo@117 104 cookies = flask.request.cookies
paulo@117 105
paulo@118 106 with gcs_download(LLDB_UNREAD_FN) as lldb_unread_tmp_f:
paulo@118 107 if is_post:
paulo@118 108 if not PIN:
paulo@118 109 raise PinSetupError
paulo@118 110 elif cookies.get("llpin") != PIN:
paulo@118 111 raise PinFailError
paulo@117 112
paulo@118 113 if inp["submit"] == "Add":
paulo@118 114 with gcs_download(LLDB_FN) as lldb_tmp_f:
paulo@118 115 lldb_add(inp, lldb_tmp_f, lldb_unread_tmp_f)
paulo@118 116 elif inp["submit"] == "Delete":
paulo@118 117 lldb_unread_delete(inp, lldb_unread_tmp_f)
paulo@117 118
paulo@118 119 title = "later links..."
paulo@118 120 root = HTML("html")
paulo@117 121
paulo@118 122 header = root.head
paulo@118 123 header.link(rel="stylesheet", type="text/css", href=flask.url_for("static", filename="index.css"))
paulo@118 124 header.title(title)
paulo@118 125
paulo@118 126 body = root.body
paulo@118 127 body.h1(title)
paulo@117 128
paulo@118 129 form = body.form(action="/", method="post")
paulo@117 130
paulo@118 131 table = form.table
paulo@118 132 hrow = table.tr
paulo@118 133 hrow.th("Link")
paulo@118 134 hrow.th("Created")
paulo@118 135 hrow.th.input(type="submit", name="submit", value="Delete")
paulo@117 136
paulo@118 137 for (title, url, dt_str) in lldb_unread_load(lldb_unread_tmp_f):
paulo@118 138 row = table.tr
paulo@118 139 row.td.a(title, href=url)
paulo@118 140 row.td(dt_str)
paulo@118 141 row.td.input(type="checkbox", name="delete", value=dt_str)
paulo@117 142
paulo@118 143 p1 = form.p
paulo@118 144 p1.label("Title").input(type="text", name="title", size="64")
paulo@118 145 p1.br
paulo@118 146 p1.label("URL").input(type="text", name="url", size="64")
paulo@118 147 p1.br
paulo@118 148 p1.input(type="submit", name="submit", value="Add")
paulo@117 149
paulo@118 150 return str(root).encode("utf-8")