From d0969bd6886a81dc59c5c02b12468a97b3c4cf58 Mon Sep 17 00:00:00 2001 From: Mark Powers Date: Mon, 2 Jan 2023 21:36:54 -0600 Subject: Initial commit --- Dockerfile | 10 +++ index.html | 248 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ insert_book.sh | 6 ++ server.py | 136 +++++++++++++++++++++++++++++++ 4 files changed, 400 insertions(+) create mode 100644 Dockerfile create mode 100644 index.html create mode 100755 insert_book.sh create mode 100644 server.py diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..412b929 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM ubuntu + +RUN apt update -y && apt install -y python3-pip +RUN pip install psycopg2-binary + +COPY index.html /src/index.html +COPY server.py /src/server.py + +WORKDIR /src +CMD python3 server.py diff --git a/index.html b/index.html new file mode 100644 index 0000000..64490b3 --- /dev/null +++ b/index.html @@ -0,0 +1,248 @@ + + + + + + + +
+
+
+
+ +
+ + diff --git a/insert_book.sh b/insert_book.sh new file mode 100755 index 0000000..cd7c782 --- /dev/null +++ b/insert_book.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +[[ -z $1 ]] && echo "Usage: ./insert_book " && exit 1 + +docker-compose exec db psql -U tracking -d tracking -c \ + "INSERT INTO book (title, completed) VALUES ('$1', FALSE)" diff --git a/server.py b/server.py new file mode 100644 index 0000000..d970860 --- /dev/null +++ b/server.py @@ -0,0 +1,136 @@ +from urllib.parse import urlparse, parse_qs +from datetime import datetime +from http.server import HTTPServer, BaseHTTPRequestHandler +import json +import psycopg2 +import re + +class TrackerHTTPRequestHandler(BaseHTTPRequestHandler): + def __init__(self, *args): + BaseHTTPRequestHandler.__init__(self, *args) + + def insert(self, datatype=None, key=None, value=None, created=None): + if value: + print("inserting", datatype, key, value) + with conn.cursor() as cur: + if key: + cur.execute("INSERT INTO datapoint (datatype, key, value, created) VALUES (%s, %s, %s, %s)", (datatype, key, value, created)) + else: + cur.execute("INSERT INTO datapoint (datatype, value, created) VALUES (%s, %s, %s)", (datatype, value, created)) + conn.commit() + + def insert_book_datapoint(self, title, pages, complete, created=None): + with conn.cursor() as cur: + cur.execute("SELECT id FROM book where title = %s", (title,)) + row = cur.fetchone() + book_id = row[0] + print(book_id, pages, created) + cur.execute("INSERT INTO book_datapoint (book_id, pages, created) VALUES (%s, %s, %s)", (book_id, pages, created)) + if complete: + cur.execute("UPDATE book SET completed = TRUE where id = %s", (book_id,)) + conn.commit() + + def do_GET(self): + u = urlparse(self.path) + print("GET", u.path) + if u.path == "/": + with open("index.html", "rb") as f: + self.send_response(200) + self.end_headers() + self.wfile.write(f.read()) + elif date_pattern.match(u.path[1:]): + with conn.cursor() as cur: + cur.execute("SELECT datatype, key, value FROM datapoint where date(created) = %s", (u.path[1:],)) + items = [] + for datatype, key, value in cur: + items.append({ + "datatype": datatype, + "key": key, + "value": value + }) + self.send_response(200) + self.end_headers() + self.wfile.write(json.dumps(items).encode("utf-8")) + elif u.path == "/books": + with conn.cursor() as cur: + cur.execute("SELECT title FROM book WHERE completed = FALSE") + items = [] + for row in cur: + items.append(row[0]) + self.send_response(200) + self.end_headers() + self.wfile.write(json.dumps(items).encode("utf-8")) + elif u.path == "/forms": + # multiple_select, prompt, prompt_id, items [{id, display}] + # number, prompt, prompt_id + # range, prompt, prompt_id, min, max + # textarea, prompt, prompt_id + with conn.cursor() as cur: + cur.execute("SELECT type, prompt, prompt_id, extra FROM form") + items = [] + for row in cur: + items.append({ + "type": row[0], + "prompt": row[1], + "prompt_id": row[2], + "extra": row[3], + }) + self.send_response(200) + self.end_headers() + self.wfile.write(json.dumps(items).encode("utf-8")) + else: + self.send_response(404) + self.end_headers() + self.wfile.write(b'not found') + + def do_POST(self): + u = urlparse(self.path) + length = int(self.headers['Content-Length']) + post_data = json.loads(self.rfile.read(length).decode('utf-8')) + print("POST", u.path) + if u.path == "/submit_payload": + timestamp = datetime.fromtimestamp(int(post_data["timestamp"]/1000)) + del post_data["timestamp"] + + for book in post_data.get("books", []): + try: + self.insert_book_datapoint(**book, created=timestamp) + except Exception as e: + # Eventually add error messaging + print("ERROR", e) + pass + post_data.pop("books", None) + + for k in post_data.keys(): + if isinstance(post_data[k], dict): + for key, value in post_data[k].items(): + self.insert(datatype=k, key=key, value=value, created=timestamp) + else: + self.insert(datatype=k, value=post_data[k], created=timestamp) + self.send_response(204) + self.end_headers() + self.wfile.write(b"") + else: + self.send_response(404) + self.end_headers() + self.wfile.write(b'not found') + + +def setup_db(): + with conn.cursor() as cur: + cur.execute("CREATE TABLE IF NOT EXISTS datapoint (id SERIAL PRIMARY KEY, created TIMESTAMP, datatype TEXT, key TEXT, value TEXT);") + cur.execute("CREATE TABLE IF NOT EXISTS outside_weather (id SERIAL PRIMARY KEY, created TIMESTAMP, temp FLOAT8, humidity FLOAT8, pressure FLOAT8, uvi FLOAT8, dew_point FLOAT8, wind_speed FLOAT8, wind_guest FLOAT8, wind_deg FLOAT8);") + cur.execute("CREATE TABLE IF NOT EXISTS book (id SERIAL PRIMARY KEY, title TEXT, completed BOOLEAN);") + cur.execute("CREATE TABLE IF NOT EXISTS book_datapoint (id SERIAL PRIMARY KEY, created TIMESTAMP, book_id SERIAL, pages TEXT, CONSTRAINT fk_book FOREIGN KEY(book_id) REFERENCES book(id));") + cur.execute("CREATE TABLE IF NOT EXISTS email (id SERIAL PRIMARY KEY, created TIMESTAMP, username TEXT, domain TEXT);") + cur.execute("CREATE TABLE IF NOT EXISTS form (id SERIAL PRIMARY KEY, type TEXT, prompt TEXT, prompt_id TEXT, extra JSON);") + conn.commit() + +date_pattern = re.compile(r"(\d{4})-(\d{2})-(\d{2})") +conn = psycopg2.connect(host="db", dbname="tracking", user="tracking", password="password") +if __name__ == "__main__": + setup_db() + print("Starting http server") + http = HTTPServer(("", 8000), TrackerHTTPRequestHandler) + print("serving forever") + http.serve_forever() -- cgit v1.2.3