diff --git a/anonkun.py b/anonkun.py index d358463..dfae295 100644 --- a/anonkun.py +++ b/anonkun.py @@ -2,6 +2,7 @@ """ A Flask-based questing platform. """ +#TODO: look into how cloudflare proxying interacts with nginx import os import time @@ -9,6 +10,7 @@ from flask import Flask from flask_paranoid import Paranoid from flask_session import Session from flask_socketio import SocketIO +from werkzeug.contrib.fixers import ProxyFix import database as db from views import views @@ -51,6 +53,7 @@ class ReverseProxied(object): app = Flask(__name__) app.wsgi_app = ReverseProxied(app.wsgi_app) +app.wsgi_app = ProxyFix(app.wsgi_app) app.register_blueprint(views) app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 app.config['SESSION_TYPE'] = 'filesystem' @@ -96,6 +99,38 @@ def get_dice_challenge(post_id): return db.get_dice_call(post_id)[3] +@app.template_filter("num_votes") +def num_votes(option_id): + """ + Returns the number of IPs who voted for this option. + """ + return db.get_num_votes(option_id) + + +@app.template_filter("split_options") +def split_options(options): + """ + Splits a polls options into a list. + """ + return options.split("\n") + + +@app.template_filter("get_rolls") +def get_rolls(post_id): + """ + Gets the dice rolls for the template. + """ + return db.get_dice_rolls(post_id=post_id) + + +@app.template_filter("get_options") +def get_options(post_id): + """ + Gets the poll options for the template. + """ + return db.get_poll_options(post_id=post_id) + + @app.after_request def minify(res): """ diff --git a/anonkun.sql b/anonkun.sql index 8151b24..dc84b12 100644 --- a/anonkun.sql +++ b/anonkun.sql @@ -6,57 +6,6 @@ CREATE TABLE `users` ( PRIMARY KEY (`user_id`) ) ENGINE=InnoDB CHARSET utf8mb4; -CREATE TABLE `quest_meta` ( - `quest_id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, - `canon_title` VARCHAR(300) DEFAULT NULL, - `ident_title` VARCHAR(300) DEFAULT NULL, - `owner_id` SMALLINT UNSIGNED DEFAULT NULL, - `open_post_id` SMALLINT UNSIGNED DEFAULT NULL, - PRIMARY KEY (`quest_id`) -) ENGINE=InnoDB CHARSET utf8mb4; - -CREATE TABLE `quest_data` ( - `post_id` MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT, - `quest_id` SMALLINT UNSIGNED NOT NULL, - `post_type` ENUM('text', 'dice', 'poll') NOT NULL, - `post` MEDIUMTEXT NOT NULL, - `timestamp` INT UNSIGNED NOT NULL, - PRIMARY KEY (`post_id`) -) ENGINE=InnoDB CHARSET utf8mb4; - -CREATE TABLE `dice_calls` ( - `post_id` MEDIUMINT UNSIGNED NOT NULL, - `dice_roll` TEXT NOT NULL, - `strict` BOOLEAN DEFAULT FALSE, - `dice_challenge` SMALLINT UNSIGNED, - `rolls_taken` TINYINT UNSIGNED, - PRIMARY KEY (`post_id`) -) ENGINE=InnoDB CHARSET utf8mb4; - -CREATE TABLE `dice_rolls` ( - `message_id` MEDIUMINT UNSIGNED NOT NULL, - `quest_id` SMALLINT UNSIGNED NOT NULL, - `post_id` MEDIUMINT UNSIGNED NOT NULL, - `roll_dice` TEXT NOT NULL, - `roll_results` TEXT NOT NULL, - `roll_total` SMALLINT UNSIGNED NOT NULL, - PRIMARY KEY (`message_id`) -) ENGINE=InnoDB CHARSET utf8mb4; - -CREATE TABLE `polls` ( - `post_id` MEDIUMINT UNSIGNED NOT NULL, - `multi_choice` BOOLEAN DEFAULT FALSE, - `allow_writein` BOOLEAN DEFAULT FALSE, - PRIMARY KEY (`post_id`) -) ENGINE=InnoDB CHARSET utf8mb4; - -CREATE TABLE `poll_options` ( - `post_id` MEDIUMINT UNSIGNED NOT NULL, - `quest_id` SMALLINT UNSIGNED NOT NULL, - `option_text` VARCHAR(200) NOT NULL, - `ips_voted` TEXT DEFAULT NULL -) ENGINE=InnoDB CHARSET utf8mb4; - CREATE TABLE `chat_messages` ( `message_id` MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT, `room_id` MEDIUMINT UNSIGNED NOT NULL, @@ -65,3 +14,66 @@ CREATE TABLE `chat_messages` ( `message` TEXT NOT NULL, PRIMARY KEY (`message_id`) ) ENGINE=InnoDB CHARSET utf8mb4; + +CREATE TABLE `quest_meta` ( + `quest_id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, + `canon_title` VARCHAR(300) DEFAULT NULL, + `ident_title` VARCHAR(300) DEFAULT NULL, + `owner_id` SMALLINT UNSIGNED DEFAULT NULL, + `open_post_id` SMALLINT UNSIGNED DEFAULT NULL, + PRIMARY KEY (`quest_id`), + FOREIGN KEY (`owner_id`) REFERENCES `users`(`user_id`) +) ENGINE=InnoDB CHARSET utf8mb4; + +CREATE TABLE `quest_data` ( + `post_id` MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT, + `quest_id` SMALLINT UNSIGNED NOT NULL, + `post_type` ENUM('text', 'dice', 'poll') NOT NULL, + `post` MEDIUMTEXT NOT NULL, + `timestamp` INT UNSIGNED NOT NULL, + PRIMARY KEY (`post_id`), + FOREIGN KEY (`quest_id`) REFERENCES `quest_meta`(`quest_id`) +) ENGINE=InnoDB CHARSET utf8mb4; + +CREATE TABLE `dice_calls` ( + `post_id` MEDIUMINT UNSIGNED NOT NULL, + `dice_roll` TEXT NOT NULL, + `strict` BOOLEAN DEFAULT FALSE, + `dice_challenge` SMALLINT UNSIGNED, + `rolls_taken` TINYINT UNSIGNED, + FOREIGN KEY (`post_id`) REFERENCES `quest_data`(`post_id`) +) ENGINE=InnoDB CHARSET utf8mb4; + +CREATE TABLE `dice_rolls` ( + `message_id` MEDIUMINT UNSIGNED NOT NULL, + `quest_id` SMALLINT UNSIGNED NOT NULL, + `post_id` MEDIUMINT UNSIGNED NOT NULL, + `roll_dice` TEXT NOT NULL, + `roll_results` TEXT NOT NULL, + `roll_total` SMALLINT UNSIGNED NOT NULL, + FOREIGN KEY (`message_id`) REFERENCES `chat_messages`(`message_id`), + FOREIGN KEY (`post_id`) REFERENCES `dice_calls`(`post_id`) +) ENGINE=InnoDB CHARSET utf8mb4; + +CREATE TABLE `polls` ( + `post_id` MEDIUMINT UNSIGNED NOT NULL, + `quest_id` SMALLINT UNSIGNED NOT NULL, + `multi_choice` BOOLEAN DEFAULT FALSE, + `allow_writein` BOOLEAN DEFAULT FALSE, + FOREIGN KEY (`post_id`) REFERENCES `quest_data`(`post_id`), + FOREIGN KEY (`quest_id`) REFERENCES `quest_meta`(`quest_id`) +) ENGINE=InnoDB CHARSET utf8mb4; + +CREATE TABLE `poll_options` ( + `option_id` MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT, + `post_id` MEDIUMINT UNSIGNED NOT NULL, + `option_text` VARCHAR(200) NOT NULL, + PRIMARY KEY (`option_id`), + FOREIGN KEY (`post_id`) REFERENCES `polls`(`post_id`) +) ENGINE=InnoDB CHARSET utf8mb4; + +CREATE TABLE `poll_votes` ( + `option_id` MEDIUMINT UNSIGNED NOT NULL, + `ip_address` VARCHAR(32) NOT NULL, + FOREIGN KEY (`option_id`) REFERENCES `poll_options`(`option_id`) +) ENGINE=InnoDB CHARSET utf8mb4; diff --git a/database.py b/database.py index adc8a81..1487942 100644 --- a/database.py +++ b/database.py @@ -272,28 +272,35 @@ def get_dice_rolls(quest_id=None, post_id=None): return data -def insert_poll(post_id, multi_choice, allow_writein): +def insert_poll(post_id, quest_id, multi_choice, allow_writein): """Inserts a new poll post.""" _DB.execute( "INSERT INTO `polls` " \ - + "(`post_id`, `multi_choice`, `allow_writein`) " \ - + "VALUES (%s, %s, %s)", - (post_id, multi_choice, allow_writein)) + + "(`post_id`, `quest_id`, `multi_choice`, `allow_writein`) " \ + + "VALUES (%s, %s, %s, %s)", + (post_id, quest_id, multi_choice, allow_writein)) + + +def get_polls(quest_id): + """Gets poll information.""" + data = _DB.execute( + "SELECT * FROM `polls` WHERE `quest_id` = %s", (quest_id,)).fetchall() + return data def get_poll(post_id): """Gets poll information.""" - data = _DB.excute( - "SELECT * FROM `polls` WHERE `post_id` = %s", (post_id,)) + data = _DB.execute( + "SELECT * FROM `polls` WHERE `post_id` = %s", (post_id,)).fetchall() return data -def insert_poll_option(post_id, quest_id, option_text): +def insert_poll_option(post_id, option_text): """Insert a new poll option. ips_voted will be NULL to start.""" _DB.execute( "INSERT INTO `poll_options` " \ - + "(`post_id`, `quest_id`, `option_text`) VALUES (%s, %s, %s)", - (post_id, quest_id, option_text)) + + "(`post_id`, `option_text`) VALUES (%s, %s)", + (post_id, option_text)) def get_poll_options(quest_id=None, post_id=None): @@ -307,5 +314,28 @@ def get_poll_options(quest_id=None, post_id=None): ins = (post_id,) else: return - data = _DB.execute(sql, ins) + data = _DB.execute(sql, ins).fetchall() + return data + + +def insert_poll_vote(option_id, ip): + """Inserts a new vote for a poll option.""" + res = _DB.execute( + "INSERT ") + + +def insert_poll_vote(option_id, ip): + """Removes a vote from a poll.""" + pass + + +def get_poll_votes(quest_id): + pass + + +def get_num_votes(option_id): + """Returns the number of votes for an option.""" + data = _DB.execute( + "SELECT COUNT(`ip_address`) FROM `poll_votes` WHERE `option_id` = %s", + (option_id,)).fetchone()[0] return data diff --git a/events.py b/events.py index 755ea5e..bce7460 100644 --- a/events.py +++ b/events.py @@ -9,7 +9,7 @@ import random import functools import bleach -from flask import session +from flask import session, request from flask_socketio import SocketIO, emit, join_room import tools @@ -101,7 +101,7 @@ def message(data): db.insert_quest_roll(message_id, room, dice_call_id, roll_data) if len(db.get_dice_rolls(post_id=dice_call_id)) == dice_call[4]: - db.set_dice_call_closed(room) + db.set_post_closed(room) emit("close_dice_call", {"post_id": dice_call_id}, room=room) room = data["room"] @@ -277,7 +277,7 @@ def open_dice_call(data): @qm_only() def poll_post(data): """ - Called when the QM posts a new dice call. + Called when the QM posts a new poll. """ room = data.pop("room", None) multi_choice = bool(data.pop("pollAllowMultipleChoices", None)) @@ -288,15 +288,16 @@ def poll_post(data): continue if len(value) >= 200: return + value = bleach.clean(value).replace("\n", "") options.append(value) post = "Poll" date = int(time.time()) post_id = db.insert_quest_post(room, "poll", post, date) - db.insert_poll(post_id, multi_choice, allow_writein) + db.insert_poll(post_id, room, multi_choice, allow_writein) for option in options: - db.insert_poll_option(post_id, room, option) + db.insert_poll_option(post_id, option) db.set_post_open(post_id, room) data = {} @@ -306,3 +307,14 @@ def poll_post(data): data["date"] = int(time.time()) data["options"] = options emit("new_post", data, room=room) + + +@socketio.on("vote") +def vote(data): + """ + Called when a user changes their vote on a poll. + """ + print(data) + room = data.get("room") + option_id = data.get("option_id") + polarity = data.get("polarity") diff --git a/static/anonkun.css b/static/anonkun.css index d8a46bd..21c1977 100644 --- a/static/anonkun.css +++ b/static/anonkun.css @@ -37,8 +37,8 @@ h3 { } #questPane { - padding-left: 10px; - padding-right: 32%; + padding-left: 5%; + padding-right: 35%; min-width: 0; } @@ -54,6 +54,7 @@ h3 { .questPostData { word-wrap: break-word; min-width: 0; + width: 100%; } #QMPostPane { @@ -110,6 +111,58 @@ h3 { margin: 0.1em; } +.pollPost { + width: 100%; +} + +.poll { + border-collapse: collapse; + width: 100%; + table-layout: fixed; + border: 1px solid #ddd; +} + +.optionVotes { + width: 10%; + text-align: center; +} + +.poll td { + padding: 0.5em; + word-wrap: break-word; + border-bottom: 1px solid #ddd; +} + +.pollCheckBox input { + display: none; +} + +.pollCheckBox { + width: 1.5em; +} + +.pollCheckBox label { + cursor: pointer; + background: #eee; + border: 1px solid #ddd; + padding: 0.2em; +} + +.pollCheckBox label:after { + content: "\2713"; + color: #bfbfbf; + opacity: 0.3; +} + +.pollCheckBox label:hover::after { + opacity: 0.5; +} + +.pollCheckBox input[type=checkbox]:checked + label:after { + opacity: 1; + color: black; +} + #chatPane { height: 100%; width: 30%; diff --git a/templates/quest.html b/templates/quest.html index d2209ad..95e2be2 100644 --- a/templates/quest.html +++ b/templates/quest.html @@ -75,12 +75,20 @@ post.children[0].textContent = post.children[0].textContent.replace('Open', 'Closed'); document.getElementById('close_post_id-' + data.post_id).style.display = 'none'; document.getElementById('open_post_id-' + data.post_id).style.display = 'initial'; + if (post.parentElement.classList.contains('pollPost')) { + table = document.getElementById('poll-' + data.post_id); + table.getElementsByTagName("col")[0].style.visibility = 'collapse'; + } }); socket.on('open_post', function(data) { let post = document.getElementById('questPostData-' + data.post_id); post.firstElementChild.textContent = post.firstElementChild.textContent.replace('Closed', 'Open'); document.getElementById('close_post_id-' + data.post_id).style.display = 'initial'; document.getElementById('open_post_id-' + data.post_id).style.display = 'none'; + if (post.parentElement.classList.contains('pollPost')) { + table = document.getElementById('poll-' + data.post_id); + table.getElementsByTagName("col")[0].style.visibility = ''; + } }); let mtarea = document.getElementById('messageTextArea'); mtarea.addEventListener('keypress', function(event) { @@ -104,6 +112,9 @@ date_str += padToTwo(date.getHours()) + ':' + padToTwo(date.getMinutes()) + ':' + padToTwo(date.getSeconds()); return date_str; } + function pollVote(option_id) { + socket.emit('vote', {option_id: option_id, polarity: document.getElementById('pollInput-' + option_id).checked, room: {{ room_id }}}); + } {% if session.get("user_id") == owner_id %}