Skip to content

Commit

Permalink
admin: Poll voting (#6361)
Browse files Browse the repository at this point in the history
* add: Poll voting

* bundle update

* Update code/controllers/subsystem/non-firing/titlescreen.dm

Co-authored-by: PlayerUnknown14 <[email protected]>

* ngggh I'll do later. Fixes polling mostly and TGUI. but has SQL issues
Column 'createdby_ip' cannot be null INSERT INTO poll_question
{ ERROR 1048 (23000): Column 'pollid' cannot be null

* Final fixes and updates

* SQL OOPS ALL MY VERSIONS TO 34

---------

Co-authored-by: PlayerUnknown14 <[email protected]>
  • Loading branch information
BeebBeebBoob and PlayerUnknown14 authored Jan 19, 2025
1 parent c747908 commit 65724f5
Show file tree
Hide file tree
Showing 26 changed files with 2,331 additions and 89 deletions.
93 changes: 93 additions & 0 deletions SQL/paradise_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -652,3 +652,96 @@ CREATE TABLE `budget`
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Table structure for table `poll_question`
--
DROP TABLE IF EXISTS `poll_question`;
CREATE TABLE IF NOT EXISTS `poll_question` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`polltype` enum('Single Option','Text Reply','Rating','Multiple Choice') NOT NULL,
`created_datetime` datetime NOT NULL,
`starttime` datetime NOT NULL,
`endtime` datetime NOT NULL,
`question` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`subtitle` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`adminonly` tinyint(1) unsigned NOT NULL,
`multiplechoiceoptions` int(2) DEFAULT NULL,
`createdby_ckey` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
`dontshow` tinyint(1) unsigned NOT NULL,
`minimum_playtime` int(4) NOT NULL,
`allow_revoting` tinyint(1) unsigned NOT NULL,
`deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_pquest_question_time_ckey` (`question`,`starttime`,`endtime`,`createdby_ckey`),
KEY `idx_pquest_time_deleted_id` (`starttime`,`endtime`, `deleted`, `id`),
KEY `idx_pquest_id_time_type_admin` (`id`,`starttime`,`endtime`,`polltype`,`adminonly`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Table structure for table `poll_option`
--
DROP TABLE IF EXISTS `poll_option`;
CREATE TABLE IF NOT EXISTS `poll_option` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`pollid` int(11) NOT NULL,
`text` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`minval` int(3) DEFAULT NULL,
`maxval` int(3) DEFAULT NULL,
`descmin` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`descmid` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`descmax` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`default_percentage_calc` tinyint(1) unsigned NOT NULL DEFAULT '1',
`deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_pop_pollid` (`pollid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Table structure for table `poll_textreply`
--
DROP TABLE IF EXISTS `poll_textreply`;
CREATE TABLE IF NOT EXISTS `poll_textreply` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`datetime` datetime NOT NULL,
`pollid` int(11) NOT NULL,
`ckey` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
`replytext` varchar(2048) COLLATE utf8mb4_unicode_ci NOT NULL,
`adminrank` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
`deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_ptext_pollid_ckey` (`pollid`,`ckey`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Table structure for table `poll_vote`
--
DROP TABLE IF EXISTS `poll_vote`;
CREATE TABLE IF NOT EXISTS `poll_vote` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`datetime` datetime NOT NULL,
`pollid` int(11) NOT NULL,
`optionid` int(11) NOT NULL,
`ckey` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
`adminrank` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
`rating` int(2) DEFAULT NULL,
`deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_pvote_pollid_ckey` (`pollid`,`ckey`),
KEY `idx_pvote_optionid_ckey` (`optionid`,`ckey`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

DELIMITER $$
DROP PROCEDURE IF EXISTS `set_poll_deleted`;
CREATE PROCEDURE `set_poll_deleted`(
IN `poll_id` INT
)
SQL SECURITY INVOKER
BEGIN
UPDATE `poll_question` SET deleted = 1 WHERE id = poll_id;
UPDATE `poll_option` SET deleted = 1 WHERE pollid = poll_id;
UPDATE `poll_vote` SET deleted = 1 WHERE pollid = poll_id;
UPDATE `poll_textreply` SET deleted = 1 WHERE pollid = poll_id;
END
$$
DELIMITER ;
93 changes: 93 additions & 0 deletions SQL/updates/33-34.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Adds poll related. Poll question as body, options inside body, textreply and vote as player answers
--
-- Table structure for table `poll_question`
--
DROP TABLE IF EXISTS `poll_question`;
CREATE TABLE IF NOT EXISTS `poll_question` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`polltype` enum('Single Option','Text Reply','Rating','Multiple Choice') NOT NULL,
`created_datetime` datetime NOT NULL,
`starttime` datetime NOT NULL,
`endtime` datetime NOT NULL,
`question` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`subtitle` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`adminonly` tinyint(1) unsigned NOT NULL,
`multiplechoiceoptions` int(2) DEFAULT NULL,
`createdby_ckey` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
`dontshow` tinyint(1) unsigned NOT NULL,
`minimum_playtime` int(4) NOT NULL,
`allow_revoting` tinyint(1) unsigned NOT NULL,
`deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_pquest_question_time_ckey` (`question`,`starttime`,`endtime`,`createdby_ckey`),
KEY `idx_pquest_time_deleted_id` (`starttime`,`endtime`, `deleted`, `id`),
KEY `idx_pquest_id_time_type_admin` (`id`,`starttime`,`endtime`,`polltype`,`adminonly`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Table structure for table `poll_option`
--
DROP TABLE IF EXISTS `poll_option`;
CREATE TABLE IF NOT EXISTS `poll_option` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`pollid` int(11) NOT NULL,
`text` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`minval` int(3) DEFAULT NULL,
`maxval` int(3) DEFAULT NULL,
`descmin` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`descmid` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`descmax` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`default_percentage_calc` tinyint(1) unsigned NOT NULL DEFAULT '1',
`deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_pop_pollid` (`pollid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Table structure for table `poll_textreply`
--
DROP TABLE IF EXISTS `poll_textreply`;
CREATE TABLE IF NOT EXISTS `poll_textreply` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`datetime` datetime NOT NULL,
`pollid` int(11) NOT NULL,
`ckey` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
`replytext` varchar(2048) COLLATE utf8mb4_unicode_ci NOT NULL,
`adminrank` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
`deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_ptext_pollid_ckey` (`pollid`,`ckey`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

--
-- Table structure for table `poll_vote`
--
DROP TABLE IF EXISTS `poll_vote`;
CREATE TABLE IF NOT EXISTS `poll_vote` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`datetime` datetime NOT NULL,
`pollid` int(11) NOT NULL,
`optionid` int(11) NOT NULL,
`ckey` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
`adminrank` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
`rating` int(2) DEFAULT NULL,
`deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_pvote_pollid_ckey` (`pollid`,`ckey`),
KEY `idx_pvote_optionid_ckey` (`optionid`,`ckey`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

DELIMITER $$
DROP PROCEDURE IF EXISTS `set_poll_deleted`;
CREATE PROCEDURE `set_poll_deleted`(
IN `poll_id` INT
)
SQL SECURITY INVOKER
BEGIN
UPDATE `poll_question` SET deleted = 1 WHERE id = poll_id;
UPDATE `poll_option` SET deleted = 1 WHERE pollid = poll_id;
UPDATE `poll_vote` SET deleted = 1 WHERE pollid = poll_id;
UPDATE `poll_textreply` SET deleted = 1 WHERE pollid = poll_id;
END
$$
DELIMITER ;
2 changes: 1 addition & 1 deletion code/__DEFINES/misc.dm
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@
#define EXPLOSION_BLOCK_PROC -1

// The SQL version required by this version of the code
#define SQL_VERSION 33
#define SQL_VERSION 34

// Vending machine stuff
#define CAT_NORMAL 1
Expand Down
25 changes: 25 additions & 0 deletions code/__DEFINES/polls.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

//unmagic-strings for types of polls, used by SQL don't change these
#define POLLTYPE_OPTION "Single Option"
#define POLLTYPE_TEXT "Text Reply"
#define POLLTYPE_RATING "Rating"
#define POLLTYPE_MULTI "Multiple Choice"

#define POLL_SECOND "SECOND"
#define POLL_MINUTE "MINUTE"
#define POLL_HOUR "HOUR"
#define POLL_DAY "DAY"
#define POLL_WEEK "WEEK"
#define POLL_MONTH "MONTH"
#define POLL_YEAR "YEAR"

///The message sent when you sign up to a poll.
#define POLL_RESPONSE_SIGNUP "signup"
///The message sent when you've already signed up for a poll and are trying to sign up again.
#define POLL_RESPONSE_ALREADY_SIGNED "already_signed"
///The message sent when you are not signed up for a poll.
#define POLL_RESPONSE_NOT_SIGNED "not_signed"
///The message sent when you are too late to unregister from a poll.
#define POLL_RESPONSE_TOO_LATE_TO_UNREGISTER "failed_unregister"
///The message sent when you successfully unregister from a poll.
#define POLL_RESPONSE_UNREGISTERED "unregistered"
11 changes: 11 additions & 0 deletions code/_globalvars/misc.dm
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,14 @@ GLOBAL_VAR_INIT(pacifism_after_gt, FALSE)

GLOBAL_VAR_INIT(communications_blackout, FALSE)

///All currently running polls held as datums
GLOBAL_LIST_EMPTY(polls)
GLOBAL_PROTECT(polls)
///Active polls
GLOBAL_LIST_EMPTY(active_polls)
GLOBAL_PROTECT(active_polls)


///All poll option datums of running polls
GLOBAL_LIST_EMPTY(poll_options)
GLOBAL_PROTECT(poll_options)
67 changes: 67 additions & 0 deletions code/controllers/subsystem/dbcore.dm
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,73 @@ SUBSYSTEM_DEF(dbcore)
return FALSE
return new /datum/db_query(connection, sql_query, arguments)

/*
* Takes a list of rows (each row being an associated list of column => value) and inserts them via a single mass query.
* Rows missing columns present in other rows will resolve to SQL NULL
* You are expected to do your own escaping of the data, and expected to provide your own quotes for strings.
* The duplicate_key arg can be true to automatically generate this part of the query
* or set to a string that is appended to the end of the query
* Ignore_errors instructes mysql to continue inserting rows if some of them have errors.
* the erroneous row(s) aren't inserted and there isn't really any way to know why or why errored
*/
/datum/controller/subsystem/dbcore/proc/MassInsert(table, list/rows, duplicate_key = FALSE, ignore_errors = FALSE, warn = FALSE, async = TRUE, special_columns = null)
if (!table || !rows || !istype(rows))
return

// Prepare column list
var/list/columns = list()
var/list/has_question_mark = list()
for (var/list/row in rows)
for (var/column in row)
columns[column] = "?"
has_question_mark[column] = TRUE
for (var/column in special_columns)
columns[column] = special_columns[column]
has_question_mark[column] = findtext(special_columns[column], "?")

// Prepare SQL query full of placeholders
var/list/query_parts = list("INSERT")
if (ignore_errors)
query_parts += " IGNORE"
query_parts += " INTO "
query_parts += table
query_parts += "\n([columns.Join(", ")])\nVALUES"

var/list/arguments = list()
var/has_row = FALSE
for (var/list/row in rows)
if (has_row)
query_parts += ","
query_parts += "\n ("
var/has_col = FALSE
for (var/column in columns)
if (has_col)
query_parts += ", "
if (has_question_mark[column])
var/name = "p[arguments.len]"
query_parts += replacetext(columns[column], "?", ":[name]")
arguments[name] = row[column]
else
query_parts += columns[column]
has_col = TRUE
query_parts += ")"
has_row = TRUE

if (duplicate_key == TRUE)
var/list/column_list = list()
for (var/column in columns)
column_list += "[column] = VALUES([column])"
query_parts += "\nON DUPLICATE KEY UPDATE [column_list.Join(", ")]"
else if (duplicate_key != FALSE)
query_parts += duplicate_key

var/datum/db_query/Query = NewQuery(query_parts.Join(), arguments)
if (warn)
. = Query.warn_execute(async)
else
. = Query.Execute(async)
qdel(Query)

/**
* Handler to allow many queries to be executed en masse
*
Expand Down
1 change: 1 addition & 0 deletions code/controllers/subsystem/non-firing/titlescreen.dm
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ SUBSYSTEM_DEF(title)
<a class="menu_button" href='byond://?src=[player.UID()];game_preferences=1'>Настройки игры</a>
<hr>
<a class="menu_button" href='byond://?src=[player.UID()];sound_options=1'>Настройки громкости</a>
<a class="menu_button" href='byond://?src=[player.UID()];poll_panel=1'>Открыть голосование</a>
"}
// html += "<a class="menu_button" href='byond://?src=[player.UID()];swap_server=1'>Сменить сервер</a>" // TODO: add this after regis merge
if(!viewer.prefs.discord_id || (viewer.prefs.discord_id && length(viewer.prefs.discord_id) == 32))
Expand Down
1 change: 1 addition & 0 deletions code/game/world.dm
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ GLOBAL_DATUM(test_runner, /datum/test_runner)
// Right off the bat, load up the DB
SSdbcore.CheckSchemaVersion() // This doesnt just check the schema version, it also connects to the db! This needs to happen super early! I cannot stress this enough!
SSdbcore.SetRoundID() // Set the round ID here
load_poll_data()

// Setup all log paths and stamp them with startups, including round IDs
SetupLogs()
Expand Down
2 changes: 2 additions & 0 deletions code/modules/admin/admin_verbs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ GLOBAL_LIST_INIT(admin_verbs_server, list(
/datum/admins/proc/toggleaban,
/datum/admins/proc/toggleenter, /*toggles whether people can join the current game*/
/datum/admins/proc/toggleguests, /*toggles whether guests can join the current game*/
/datum/admins/proc/open_poll_list,
/client/proc/select_next_map,
/client/proc/toggle_log_hrefs,
/client/proc/toggle_twitch_censor,
Expand Down Expand Up @@ -165,6 +166,7 @@ GLOBAL_LIST_INIT(admin_verbs_debug, list(
/client/proc/admin_serialize,
/client/proc/jump_to_ruin,
/client/proc/clear_dynamic_transit,
/client/proc/cmd_reload_polls,
/client/proc/toggle_medal_disable,
/client/proc/uid_log,
/client/proc/visualise_active_turfs,
Expand Down
Loading

0 comments on commit 65724f5

Please sign in to comment.