From 317f9d4ba099326cb3db109fb81b3afa0db5714d Mon Sep 17 00:00:00 2001 From: Eggbertx Date: Thu, 13 Jun 2019 16:36:02 -0700 Subject: [PATCH] Add support for PostgreSQL and SQLite --- html/error/404.html | 2 +- html/error/500.html | 2 +- initdb.sql => initdb_mysql.sql | 197 ++++++++++++++------------------- initdb_postgres.sql | 177 +++++++++++++++++++++++++++++ initdb_sqlite3.sql | 162 +++++++++++++++++++++++++++ src/api_test.go | 8 +- src/building.go | 122 ++++++++++++-------- src/gochan.go | 2 +- src/manage.go | 190 +++++++++++++------------------ src/posting.go | 63 ++++++----- src/server.go | 24 ++-- src/sql.go | 127 +++++++++++++++------ src/template.go | 131 ++++++++++++---------- src/types.go | 24 ++-- src/util.go | 108 ++++++++---------- templates/img_header.html | 10 +- templates/manage_boards.html | 12 +- vagrant/bootstrap.sh | 104 +++++++++-------- version | 2 +- 19 files changed, 928 insertions(+), 539 deletions(-) rename initdb.sql => initdb_mysql.sql (52%) create mode 100644 initdb_postgres.sql create mode 100644 initdb_sqlite3.sql diff --git a/html/error/404.html b/html/error/404.html index 9f7f85dc..47368da1 100755 --- a/html/error/404.html +++ b/html/error/404.html @@ -7,6 +7,6 @@

404: File not found

The requested file could not be found on this server. Are you just typing random stuff in the address bar? If you followed a link from this site here, then post here

-
http://gochan.org powered by Gochan v2.7.0
+
http://gochan.org powered by Gochan v2.8.0
diff --git a/html/error/500.html b/html/error/500.html index 5b7f3e43..37185a6c 100755 --- a/html/error/500.html +++ b/html/error/500.html @@ -7,6 +7,6 @@

500: Internal Server error

The server encountered an error while trying to serve the page, and we apologize for the inconvenience. The system administrator will try to fix things as soon has he/she/it can.

-
http://gochan.org powered by Gochan v2.7.0
+
http://gochan.org powered by Gochan v2.8.0
diff --git a/initdb.sql b/initdb_mysql.sql similarity index 52% rename from initdb.sql rename to initdb_mysql.sql index 740961b9..99212682 100644 --- a/initdb.sql +++ b/initdb_mysql.sql @@ -2,97 +2,94 @@ -- DO NOT DELETE CREATE TABLE IF NOT EXISTS `DBPREFIXannouncements` ( - `id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, - `subject` VARCHAR(45) NOT NULL, - `message` TEXT NOT NULL, - `poster` VARCHAR(45) NOT NULL, + `id` SERIAL, + `subject` VARCHAR(45) NOT NULL DEFAULT '', + `message` TEXT NOT NULL CHECK (message <> ''), + `poster` VARCHAR(45) NOT NULL CHECK (poster <> ''), `timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `DBPREFIXappeals` ( - `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, - `ban` INT(11) UNSIGNED NOT NULL, - `message` TEXT NOT NULL, + `id` SERIAL, + `ban` INT(11) UNSIGNED NOT NULL CHECK (ban <> 0), + `message` TEXT NOT NULL CHECK (message <> ''), `timestamp` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - `denied` TINYINT(1) NOT NULL DEFAULT '0', - `staff_response` TEXT NOT NULL, + `denied` BOOLEAN DEFAULT false, + `staff_response` TEXT NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; -CREATE TABLE IF NOT EXISTS `DBPREFIXbanlist` ( - `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, - `allow_read` TINYINT(1) DEFAULT '1', +CREATE TABLE IF NOT EXISTS DBPREFIXbanlist ( + `id` SERIAL, + `allow_read` BOOLEAN DEFAULT TRUE, `ip` VARCHAR(45) NOT NULL DEFAULT '', - `name` VARCHAR(255) NOT NULL, - `name_is_regex` TINYINT(1) DEFAULT '0', + `name` VARCHAR(255) NOT NULL DEFAULT '', + `name_is_regex` BOOLEAN DEFAULT FALSE, `filename` VARCHAR(255) NOT NULL DEFAULT '', `file_checksum` VARCHAR(255) NOT NULL DEFAULT '', - `boards` VARCHAR(255) NOT NULL DEFAULT '', - `staff` VARCHAR(50) NOT NULL, + `boards` VARCHAR(255) NOT NULL DEFAULT '*', + `staff` VARCHAR(50) NOT NULL DEFAULT '', `timestamp` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `expires` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `permaban` TINYINT(1) NOT NULL DEFAULT '1', - `reason` VARCHAR(255) NOT NULL, - `type` TINYINT UNSIGNED NOT NULL DEFAULT '3', - `staff_note` VARCHAR(255) NOT NULL, + `permaban` BOOLEAN NOT NULL DEFAULT TRUE, + `reason` VARCHAR(255) NOT NULL DEFAULT '', + `type` SMALLINT NOT NULL DEFAULT 3, + `staff_note` VARCHAR(255) NOT NULL DEFAULT '', `appeal_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `can_appeal` TINYINT(1) NOT NULL DEFAULT '1', - PRIMARY KEY (`id`) + `can_appeal` BOOLEAN NOT NULL DEFAULT true, + PRIMARY KEY (id) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; ALTER TABLE `DBPREFIXbanlist` - CHANGE IF EXISTS `banned_by` `staff` VARCHAR(50) NOT NULL, - CHANGE IF EXISTS `id` `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, + CHANGE IF EXISTS `banned_by` `staff` VARCHAR(50) NOT NULL DEFAULT '', + CHANGE IF EXISTS `id` `id` SERIAL, CHANGE IF EXISTS `expires` `expires` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - CHANGE IF EXISTS `boards` `boards` VARCHAR(255) NOT NULL DEFAULT '', - ADD COLUMN IF NOT EXISTS `type` TINYINT UNSIGNED NOT NULL DEFAULT '3', - ADD COLUMN IF NOT EXISTS `name_is_regex` TINYINT(1) DEFAULT '0', + CHANGE IF EXISTS `boards` `boards` VARCHAR(255) NOT NULL DEFAULT '*', + ADD COLUMN IF NOT EXISTS `type` TINYINT UNSIGNED NOT NULL DEFAULT 3, + ADD COLUMN IF NOT EXISTS `name_is_regex` BOOLEAN DEFAULT FALSE, ADD COLUMN IF NOT EXISTS `filename` VARCHAR(255) NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS `file_checksum` VARCHAR(255) NOT NULL DEFAULT '', - ADD COLUMN IF NOT EXISTS `permaban` TINYINT(1) DEFAULT '0', - ADD COLUMN IF NOT EXISTS `can_appeal` TINYINT(1) DEFAULT '1', + ADD COLUMN IF NOT EXISTS `permaban` BOOLEAN DEFAULT FALSE, + ADD COLUMN IF NOT EXISTS `can_appeal` BOOLEAN DEFAULT TRUE, DROP COLUMN IF EXISTS `message`; -CREATE TABLE IF NOT EXISTS `DBPREFIXbannedhashes` ( - `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, - `checksum` VARCHAR(45) NOT NULL, - `description` VARCHAR(45) NOT NULL, - PRIMARY KEY(`id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; +DROP TABLE IF EXISTS `DBPREFIXbannedhashes`; CREATE TABLE IF NOT EXISTS `DBPREFIXboards` ( - `id` int UNSIGNED NOT NULL AUTO_INCREMENT, - `order` TINYINT UNSIGNED NOT NULL DEFAULT 0, - `dir` VARCHAR(45) NOT NULL, - `type` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, - `upload_type` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, - `title` VARCHAR(45) NOT NULL, + `id` SERIAL, + `list_order` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `dir` VARCHAR(45) NOT NULL CHECK (dir <> ''), + `type` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `upload_type` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `title` VARCHAR(45) NOT NULL CHECK (title <> ''), `subtitle` VARCHAR(64) NOT NULL DEFAULT '', `description` VARCHAR(64) NOT NULL DEFAULT '', - `section` VARCHAR(45) NOT NULL, - `max_image_size` INT UNSIGNED NOT NULL DEFAULT 4718592, + `section` INT NOT NULL DEFAULT 1, + `max_file_size` INT UNSIGNED NOT NULL DEFAULT 4718592, `max_pages` TINYINT UNSIGNED NOT NULL DEFAULT 11, `default_style` VARCHAR(45) NOT NULL, - `locked` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, + `locked` BOOLEAN NOT NULL DEFAULT FALSE, `created_on` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `anonymous` VARCHAR(45) NOT NULL DEFAULT 'Anonymous', - `forced_anon` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, + `forced_anon` BOOLEAN NOT NULL DEFAULT FALSE, `max_age` INT(20) UNSIGNED NOT NULL DEFAULT 0, `autosage_after` INT(5) UNSIGNED NOT NULL DEFAULT 200, `no_images_after` INT(5) UNSIGNED NOT NULL DEFAULT 0, `max_message_length` INT(10) UNSIGNED NOT NULL DEFAULT 8192, - `embeds_allowed` TINYINT(1) NOT NULL DEFAULT 1, - `redirect_to_thread` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, - `require_file` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, - `enable_catalog` TINYINT(1) UNSIGNED NOT NULL DEFAULT 1, + `embeds_allowed` BOOLEAN NOT NULL DEFAULT TRUE, + `redirect_to_thread` BOOLEAN NOT NULL DEFAULT TRUE, + `require_file` BOOLEAN NOT NULL DEFAULT FALSE, + `enable_catalog` BOOLEAN NOT NULL DEFAULT TRUE, PRIMARY KEY (`id`), UNIQUE (`dir`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=0; +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; ALTER TABLE `DBPREFIXboards` + CHANGE COLUMN IF EXISTS `order` `list_order` INT UNSIGNED NOT NULL DEFAULT 0, + CHANGE COLUMN IF EXISTS `max_image_size` `max_file_size` INT UNSIGNED NOT NULL DEFAULT 4718592, DROP COLUMN IF EXISTS `locale`; CREATE TABLE IF NOT EXISTS `DBPREFIXembeds` ( - `id` TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, + `id` SERIAL, `filetype` VARCHAR(3) NOT NULL, `name` VARCHAR(45) NOT NULL, `video_url` VARCHAR(255) NOT NULL, @@ -102,27 +99,7 @@ CREATE TABLE IF NOT EXISTS `DBPREFIXembeds` ( PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; -CREATE TABLE IF NOT EXISTS `DBPREFIXfiletypes` ( - `id` TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, - `filetype` VARCHAR(10) NOT NULL, - `mime` VARCHAR(45) NOT NULL, - `thumb_image` VARCHAR(255) NOT NULL, - `image_w` INT UNSIGNED NOT NULL DEFAULT 0, - `image_h` INT UNSIGNED NOT NULL DEFAULT 0, - PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; - -CREATE TABLE IF NOT EXISTS `DBPREFIXfrontpage` ( - `id` SMALLINT(5) UNSIGNED NOT NULL AUTO_INCREMENT, - `page` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, - `order` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0, - `subject` VARCHAR(140) NOT NULL, - `message` TEXT NOT NULL, - `timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `poster` VARCHAR(45) NOT NULL, - `email` VARCHAR(45) NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; +DROP TABLE IF EXISTS `DBPREFIXDBPREFIXfrontpage`; CREATE TABLE IF NOT EXISTS `DBPREFIXinfo` ( `name` VARCHAR(45) NOT NULL, @@ -131,32 +108,21 @@ CREATE TABLE IF NOT EXISTS `DBPREFIXinfo` ( ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `DBPREFIXlinks` ( - `id` TINYINT NOT NULL AUTO_INCREMENT, + `id` SERIAL, `title` VARCHAR(45) NOT NULL, `url` VARCHAR(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; -CREATE TABLE IF NOT EXISTS `DBPREFIXloginattempts` ( - `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, - `ip` VARCHAR(45) NOT NULL, - `timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; - -CREATE TABLE IF NOT EXISTS `DBPREFIXpluginsettings` ( - `module` CHAR(32) NOT NULL, - `key` VARCHAR(200) NOT NULL DEFAULT '', - `value` TEXT NOT NULL, - PRIMARY KEY(`module`,`key`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; +DROP TABLE IF EXISTS DBPREFIXloginattempts; +DROP TABLE IF EXISTS DBPREFIXpluginsettings; CREATE TABLE IF NOT EXISTS `DBPREFIXposts` ( - `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `id` SERIAL, `boardid` INT NOT NULL, `parentid` INT(10) UNSIGNED NOT NULL DEFAULT '0', `name` VARCHAR(50) NOT NULL, - `tripcode` CHAR(10) NOT NULL, + `tripcode` VARCHAR(10) NOT NULL, `email` VARCHAR(50) NOT NULL, `subject` VARCHAR(100) NOT NULL, `message` TEXT NOT NULL, @@ -173,72 +139,77 @@ CREATE TABLE IF NOT EXISTS `DBPREFIXposts` ( `ip` VARCHAR(45) NOT NULL DEFAULT '', `tag` VARCHAR(5) NOT NULL DEFAULT '', `timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `autosage` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, + `autosage` BOOLEAN NOT NULL DEFAULT FALSE, `deleted_timestamp` TIMESTAMP, `bumped` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `stickied` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, - `locked` TINYINT(1) NOT NULL DEFAULT 0, - `reviewed` TINYINT(1) NOT NULL DEFAULT 0, - `sillytag` TINYINT(1) NOT NULL DEFAULT 0, + `stickied` BOOLEAN NOT NULL DEFAULT FALSE, + `locked` BOOLEAN NOT NULL DEFAULT FALSE, + `reviewed` BOOLEAN NOT NULL DEFAULT FALSE, PRIMARY KEY (`boardid`,`id`), KEY `parentid` (`parentid`), KEY `bumped` (`bumped`), KEY `file_checksum` (`file_checksum`), KEY `stickied` (`stickied`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1; +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; ALTER TABLE `DBPREFIXposts` DROP COLUMN IF EXISTS `sillytag`, DROP COLUMN IF EXISTS `poster_authority`; CREATE TABLE IF NOT EXISTS `DBPREFIXreports` ( - `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `id` SERIAL, `board` VARCHAR(45) NOT NULL, `postid` INT(10) UNSIGNED NOT NULL, `timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `ip` VARCHAR(45) NOT NULL, `reason` VARCHAR(255) NOT NULL, - `cleared` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, - `istemp` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, + `cleared` BOOLEAN NOT NULL DEFAULT FALSE, + `istemp` BOOLEAN NOT NULL DEFAULT FALSE, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; + CREATE TABLE IF NOT EXISTS `DBPREFIXsections` ( - `id` TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, - `order` TINYINT UNSIGNED NOT NULL DEFAULT 0, - `hidden` TINYINT(1) UNSIGNED NOT NULL, + `id` SERIAL, + `list_order` INT UNSIGNED NOT NULL DEFAULT 0, + `hidden` BOOLEAN NOT NULL DEFAULT FALSE, `name` VARCHAR(45) NOT NULL, `abbreviation` VARCHAR(10) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; +ALTER TABLE `DBPREFIXsections` + CHANGE COLUMN IF EXISTS `order` `list_order` INT UNSIGNED NOT NULL DEFAULT 0; CREATE TABLE IF NOT EXISTS `DBPREFIXsessions` ( - `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, - `key` CHAR(10) NOT NULL, - `data` VARCHAR(45) NOT NULL, + `id` SERIAL, + `name` CHAR(16) NOT NULL, + `sessiondata` VARCHAR(45) NOT NULL, `expires` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=MEMORY DEFAULT CHARSET=utf8mb4; +ALTER TABLE `DBPREFIXsessions` + CHANGE IF EXISTS `key` `name` CHAR(16) NOT NULL, + CHANGE IF EXISTS `data` `sessiondata` VARCHAR(45) NOT NULL; CREATE TABLE IF NOT EXISTS `DBPREFIXstaff` ( - `id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, + `id` SERIAL, `username` VARCHAR(45) NOT NULL, `password_checksum` VARCHAR(120) NOT NULL, `salt` CHAR(3) NOT NULL, - `rank` TINYINT(1) UNSIGNED NOT NULL, - `boards` VARCHAR(128) NOT NULL DEFAULT 'all', + `rank` TINYINT(1) UNSIGNED NOT NULL DEFAULT 2, + `boards` VARCHAR(128) NOT NULL DEFAULT '*', `added_on` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `last_active` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE (`username`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; +ALTER TABLE `DBPREFIXstaff` + CHANGE IF EXISTS `boards` `boards` VARCHAR(128) NOT NULL DEFAULT '*'; CREATE TABLE IF NOT EXISTS `DBPREFIXwordfilters` ( - `id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, - `from` VARCHAR(75) NOT NULL, - `to` VARCHAR(75) NOT NULL, - `boards` TEXT NOT NULL, - `regex` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, + `id` SERIAL, + `search` VARCHAR(75) NOT NULL CHECK (search <> ''), + `change_to` VARCHAR(75) NOT NULL DEFAULT '', + `boards` VARCHAR(128) NOT NULL DEFAULT '*', + `regex` BOOLEAN NOT NULL DEFAULT FALSE, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; - -SET sql_notes=1; diff --git a/initdb_postgres.sql b/initdb_postgres.sql new file mode 100644 index 00000000..e731facc --- /dev/null +++ b/initdb_postgres.sql @@ -0,0 +1,177 @@ +-- Gochan PostgreSQL/SQLite startup/update script +-- DO NOT DELETE + +CREATE TABLE IF NOT EXISTS DBPREFIXannouncements ( + id SERIAL, + subject VARCHAR(45) NOT NULL DEFAULT '', + message TEXT NOT NULL CHECK (message <> ''), + poster VARCHAR(45) NOT NULL CHECK (poster <> ''), + timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS DBPREFIXappeals ( + id SERIAL, + ban INT NOT NULL CHECK (ban <> 0), + message TEXT NOT NULL CHECK (message <> ''), + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + denied BOOLEAN DEFAULT FALSE, + staff_response TEXT NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS DBPREFIXbanlist ( + id SERIAL, + allow_read BOOLEAN DEFAULT TRUE, + ip VARCHAR(45) NOT NULL DEFAULT '', + name VARCHAR(255) NOT NULL DEFAULT '', + name_is_regex BOOLEAN DEFAULT FALSE, + filename VARCHAR(255) NOT NULL DEFAULT '', + file_checksum VARCHAR(255) NOT NULL DEFAULT '', + boards VARCHAR(255) NOT NULL DEFAULT '*', + staff VARCHAR(50) NOT NULL DEFAULT '', + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + expires TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + permaban BOOLEAN NOT NULL DEFAULT TRUE, + reason VARCHAR(255) NOT NULL DEFAULT '', + type SMALLINT NOT NULL DEFAULT 3, + staff_note VARCHAR(255) NOT NULL DEFAULT '', + appeal_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + can_appeal BOOLEAN NOT NULL DEFAULT true, + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS DBPREFIXboards ( + id SERIAL, + list_order SMALLINT NOT NULL DEFAULT 0, + dir VARCHAR(45) NOT NULL CHECK (dir <> ''), + type SMALLINT NOT NULL DEFAULT 0, + upload_type SMALLINT NOT NULL DEFAULT 0, + title VARCHAR(45) NOT NULL CHECK (title <> ''), + subtitle VARCHAR(64) NOT NULL DEFAULT '', + description VARCHAR(64) NOT NULL DEFAULT '', + section INT NOT NULL DEFAULT 1, + max_file_size INT NOT NULL DEFAULT 4718592, + max_pages SMALLINT NOT NULL DEFAULT 11, + default_style VARCHAR(45) NOT NULL, + locked BOOLEAN NOT NULL DEFAULT FALSE, + created_on TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + anonymous VARCHAR(45) NOT NULL DEFAULT 'Anonymous', + forced_anon BOOLEAN NOT NULL DEFAULT FALSE, + max_age INT NOT NULL DEFAULT 0, + autosage_after INT NOT NULL DEFAULT 200, + no_images_after INT NOT NULL DEFAULT 0, + max_message_length INT NOT NULL DEFAULT 8192, + embeds_allowed BOOLEAN NOT NULL DEFAULT TRUE, + redirect_to_thread BOOLEAN NOT NULL DEFAULT TRUE, + require_file BOOLEAN NOT NULL DEFAULT FALSE, + enable_catalog BOOLEAN NOT NULL DEFAULT TRUE, + PRIMARY KEY (id), + UNIQUE (dir) +); + +CREATE TABLE IF NOT EXISTS DBPREFIXembeds ( + id SERIAL, + filetype VARCHAR(3) NOT NULL, + name VARCHAR(45) NOT NULL, + video_url VARCHAR(255) NOT NULL, + width SMALLINT NOT NULL, + height SMALLINT NOT NULL, + embed_code TEXT NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS DBPREFIXinfo ( + name VARCHAR(45) NOT NULL, + value TEXT NOT NULL, + PRIMARY KEY (name) +); + +CREATE TABLE IF NOT EXISTS DBPREFIXlinks ( + id SERIAL, + title VARCHAR(45) NOT NULL, + url VARCHAR(255) NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS DBPREFIXposts ( + id SERIAL, + boardid INT NOT NULL, + parentid INT NOT NULL DEFAULT '0', + name VARCHAR(50) NOT NULL, + tripcode VARCHAR(10) NOT NULL, + email VARCHAR(50) NOT NULL, + subject VARCHAR(100) NOT NULL, + message TEXT NOT NULL, + message_raw TEXT NOT NULL, + password VARCHAR(45) NOT NULL, + filename VARCHAR(45) NOT NULL DEFAULT '', + filename_original VARCHAR(255) NOT NULL DEFAULT '', + file_checksum VARCHAR(45) NOT NULL DEFAULT '', + filesize INT NOT NULL DEFAULT 0, + image_w SMALLINT NOT NULL DEFAULT 0, + image_h SMALLINT NOT NULL DEFAULT 0, + thumb_w SMALLINT NOT NULL DEFAULT 0, + thumb_h SMALLINT NOT NULL DEFAULT 0, + ip VARCHAR(45) NOT NULL DEFAULT '', + tag VARCHAR(5) NOT NULL DEFAULT '', + timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + autosage BOOLEAN NOT NULL DEFAULT FALSE, + deleted_timestamp TIMESTAMP, + bumped TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + stickied BOOLEAN NOT NULL DEFAULT FALSE, + locked BOOLEAN NOT NULL DEFAULT FALSE, + reviewed BOOLEAN NOT NULL DEFAULT FALSE, + PRIMARY KEY (boardid,id) +); + +CREATE TABLE IF NOT EXISTS DBPREFIXreports ( + id SERIAL, + board VARCHAR(45) NOT NULL, + postid INT NOT NULL, + timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + ip VARCHAR(45) NOT NULL, + reason VARCHAR(255) NOT NULL, + cleared BOOLEAN NOT NULL DEFAULT FALSE, + istemp BOOLEAN NOT NULL DEFAULT FALSE, + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS DBPREFIXsections ( + id SERIAL, + list_order SMALLINT NOT NULL DEFAULT 0, + hidden SMALLINT NOT NULL, + name VARCHAR(45) NOT NULL, + abbreviation VARCHAR(10) NOT NULL, + PRIMARY KEY (id) +); + +CREATE TEMP TABLE IF NOT EXISTS DBPREFIXsessions ( + id SERIAL, + name CHAR(16) NOT NULL, + sessiondata VARCHAR(45) NOT NULL, + expires TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS DBPREFIXstaff ( + id SERIAL, + username VARCHAR(45) NOT NULL, + password_checksum VARCHAR(120) NOT NULL, + salt CHAR(3) NOT NULL, + rank SMALLINT NOT NULL, + boards VARCHAR(128) NOT NULL DEFAULT '*', + added_on TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + last_active TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + UNIQUE (username) +); + +CREATE TABLE IF NOT EXISTS DBPREFIXwordfilters ( + id SERIAL, + search VARCHAR(75) NOT NULL CHECK (search <> ''), + change_to VARCHAR(75) NOT NULL DEFAULT '', + boards VARCHAR(128) NOT NULL DEFAULT '*', + regex BOOLEAN NOT NULL DEFAULT FALSE, + PRIMARY KEY (id) +); diff --git a/initdb_sqlite3.sql b/initdb_sqlite3.sql new file mode 100644 index 00000000..c24ed7ec --- /dev/null +++ b/initdb_sqlite3.sql @@ -0,0 +1,162 @@ +-- Gochan SQLite startup/update script +-- DO NOT DELETE + +CREATE TABLE IF NOT EXISTS DBPREFIXannouncements ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + subject VARCHAR(45) NOT NULL DEFAULT '', + message TEXT NOT NULL CHECK (message <> ''), + poster VARCHAR(45) NOT NULL CHECK (poster <> ''), + timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS DBPREFIXappeals ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + ban INT NOT NULL CHECK (ban <> 0), + message TEXT NOT NULL CHECK (message <> ''), + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + denied BOOLEAN DEFAULT FALSE, + staff_response TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS DBPREFIXbanlist ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + allow_read BOOLEAN DEFAULT TRUE, + ip VARCHAR(45) NOT NULL DEFAULT '', + name VARCHAR(255) NOT NULL DEFAULT '', + name_is_regex BOOLEAN DEFAULT FALSE, + filename VARCHAR(255) NOT NULL DEFAULT '', + file_checksum VARCHAR(255) NOT NULL DEFAULT '', + boards VARCHAR(255) NOT NULL DEFAULT '*', + staff VARCHAR(50) NOT NULL DEFAULT '', + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + expires TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + permaban BOOLEAN NOT NULL DEFAULT TRUE, + reason VARCHAR(255) NOT NULL DEFAULT '', + type SMALLINT NOT NULL DEFAULT 3, + staff_note VARCHAR(255) NOT NULL DEFAULT '', + appeal_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + can_appeal BOOLEAN NOT NULL DEFAULT true +); + +CREATE TABLE IF NOT EXISTS DBPREFIXboards ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + list_order SMALLINT NOT NULL DEFAULT 0, + dir VARCHAR(45) UNIQUE NOT NULL CHECK (dir <> ''), + type SMALLINT NOT NULL DEFAULT 0, + upload_type SMALLINT NOT NULL DEFAULT 0, + title VARCHAR(45) NOT NULL CHECK (title <> ''), + subtitle VARCHAR(64) NOT NULL DEFAULT '', + description VARCHAR(64) NOT NULL DEFAULT '', + section INT NOT NULL DEFAULT 1, + max_file_size INT NOT NULL DEFAULT 4718592, + max_pages SMALLINT NOT NULL DEFAULT 11, + default_style VARCHAR(45) NOT NULL, + locked BOOLEAN NOT NULL DEFAULT FALSE, + created_on TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + anonymous VARCHAR(45) NOT NULL DEFAULT 'Anonymous', + forced_anon BOOLEAN NOT NULL DEFAULT FALSE, + max_age INT NOT NULL DEFAULT 0, + autosage_after INT NOT NULL DEFAULT 200, + no_images_after INT NOT NULL DEFAULT 0, + max_message_length INT NOT NULL DEFAULT 8192, + embeds_allowed BOOLEAN NOT NULL DEFAULT TRUE, + redirect_to_thread BOOLEAN NOT NULL DEFAULT TRUE, + require_file BOOLEAN NOT NULL DEFAULT FALSE, + enable_catalog BOOLEAN NOT NULL DEFAULT TRUE +); + +CREATE TABLE IF NOT EXISTS DBPREFIXembeds ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + filetype VARCHAR(3) NOT NULL, + name VARCHAR(45) NOT NULL, + video_url VARCHAR(255) NOT NULL, + width SMALLINT NOT NULL, + height SMALLINT NOT NULL, + embed_code TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS DBPREFIXinfo ( + name VARCHAR(45) PRIMARY KEY NOT NULL, + value TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS DBPREFIXlinks ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + title VARCHAR(45) NOT NULL, + url VARCHAR(255) NOT NULL +); + +CREATE TABLE IF NOT EXISTS DBPREFIXposts ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + boardid INT NOT NULL, + parentid INT NOT NULL DEFAULT '0', + name VARCHAR(50) NOT NULL, + tripcode VARCHAR(10) NOT NULL, + email VARCHAR(50) NOT NULL, + subject VARCHAR(100) NOT NULL, + message TEXT NOT NULL, + message_raw TEXT NOT NULL, + password VARCHAR(45) NOT NULL, + filename VARCHAR(45) NOT NULL DEFAULT '', + filename_original VARCHAR(255) NOT NULL DEFAULT '', + file_checksum VARCHAR(45) NOT NULL DEFAULT '', + filesize INT NOT NULL DEFAULT 0, + image_w SMALLINT NOT NULL DEFAULT 0, + image_h SMALLINT NOT NULL DEFAULT 0, + thumb_w SMALLINT NOT NULL DEFAULT 0, + thumb_h SMALLINT NOT NULL DEFAULT 0, + ip VARCHAR(45) NOT NULL DEFAULT '', + tag VARCHAR(5) NOT NULL DEFAULT '', + timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + autosage BOOLEAN NOT NULL DEFAULT FALSE, + deleted_timestamp TIMESTAMP, + bumped TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + stickied BOOLEAN NOT NULL DEFAULT FALSE, + locked BOOLEAN NOT NULL DEFAULT FALSE, + reviewed BOOLEAN NOT NULL DEFAULT FALSE +); + +CREATE TABLE IF NOT EXISTS DBPREFIXreports ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + board VARCHAR(45) NOT NULL, + postid INT NOT NULL, + timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + ip VARCHAR(45) NOT NULL, + reason VARCHAR(255) NOT NULL, + cleared BOOLEAN NOT NULL DEFAULT FALSE, + istemp BOOLEAN NOT NULL DEFAULT FALSE +); + +CREATE TABLE IF NOT EXISTS DBPREFIXsections ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + list_order SMALLINT NOT NULL DEFAULT 0, + hidden SMALLINT NOT NULL, + name VARCHAR(45) NOT NULL, + abbreviation VARCHAR(10) NOT NULL +); + +CREATE TEMP TABLE IF NOT EXISTS DBPREFIXsessions ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name CHAR(16) NOT NULL, + sessiondata VARCHAR(45) NOT NULL, + expires TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS gc_staff ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + username VARCHAR(45) UNIQUE NOT NULL, + password_checksum VARCHAR(120) NOT NULL, + salt CHAR(3) NOT NULL, + rank SMALLINT NOT NULL, + boards VARCHAR(128) NOT NULL DEFAULT '*', + added_on TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + last_active TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS DBPREFIXwordfilters ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + search VARCHAR(75) NOT NULL CHECK (search <> ''), + change_to VARCHAR(75) NOT NULL DEFAULT '', + boards VARCHAR(128) NOT NULL DEFAULT '*', + regex BOOLEAN NOT NULL DEFAULT FALSE +); diff --git a/src/api_test.go b/src/api_test.go index cba82099..a673b1f6 100644 --- a/src/api_test.go +++ b/src/api_test.go @@ -15,7 +15,7 @@ func TestAPI(t *testing.T) { var api string var err error - if api, err = marshalAPI("colorsSlice", []Color{ + if api, err = marshalJSON("colorsSlice", []Color{ Color{255, 0, 0}, Color{0, 255, 0}, Color{0, 0, 255}, @@ -24,7 +24,7 @@ func TestAPI(t *testing.T) { } fmt.Println("API slice: " + api) - if api, err = marshalAPI("colorsMap", map[string]Color{ + if api, err = marshalJSON("colorsMap", map[string]Color{ "red": Color{255, 0, 0}, "green": Color{0, 255, 0}, "blue": Color{0, 0, 255}, @@ -33,12 +33,12 @@ func TestAPI(t *testing.T) { } fmt.Println("API map: " + api) - if api, err = marshalAPI("color", Color{255, 0, 0}, true); err != nil { + if api, err = marshalJSON("color", Color{255, 0, 0}, true); err != nil { t.Fatal(err.Error()) } fmt.Println("API struct: " + api) - if api, err = marshalAPI("error", "Some error", false); err != nil { + if api, err = marshalJSON("error", "Some error", false); err != nil { t.Fatal(err.Error()) } fmt.Println("API string: " + api) diff --git a/src/building.go b/src/building.go index 254e7270..20e04507 100644 --- a/src/building.go +++ b/src/building.go @@ -13,8 +13,11 @@ import ( ) // build front page using templates/front.html -func buildFrontPage() (html string) { - initTemplates() +func buildFrontPage() string { + err := initTemplates("front") + if err != nil { + return err.Error() + } var recentPostsArr []interface{} os.Remove(path.Join(config.DocumentRoot, "index.html")) @@ -25,18 +28,25 @@ func buildFrontPage() (html string) { } // get recent posts - recentQueryStr := "SELECT `" + config.DBprefix + "posts`.`id`, " + - "`" + config.DBprefix + "posts`.`parentid`, " + - "`" + config.DBprefix + "boards`.`dir` AS boardname, " + - "`" + config.DBprefix + "posts`.`boardid` AS boardid, " + - "`name`, `tripcode`, `message`, `filename`, `thumb_w`, `thumb_h` " + - "FROM `" + config.DBprefix + "posts`, `" + config.DBprefix + "boards` " + - "WHERE `" + config.DBprefix + "posts`.`deleted_timestamp` = ? " + recentQueryStr := "SELECT " + + config.DBprefix + "posts.id, " + + config.DBprefix + "posts.parentid, " + + config.DBprefix + "boards.dir as boardname, " + + config.DBprefix + "posts.boardid as boardid, " + + config.DBprefix + "posts.name, " + + config.DBprefix + "posts.tripcode, " + + config.DBprefix + "posts.message, " + + config.DBprefix + "posts.filename, " + + config.DBprefix + "posts.thumb_w, " + + config.DBprefix + "posts.thumb_h " + + "FROM " + config.DBprefix + "posts, " + config.DBprefix + "boards " + + "WHERE " + config.DBprefix + "posts.deleted_timestamp = ? " + if !config.RecentPostsWithNoFile { - recentQueryStr += "AND `" + config.DBprefix + "posts`.`filename` != '' AND `" + config.DBprefix + "posts`.filename != 'deleted' " + recentQueryStr += "AND " + config.DBprefix + "posts.filename != '' AND " + config.DBprefix + "posts.filename != 'deleted' " } - recentQueryStr += "AND `boardid` = `" + config.DBprefix + "boards`.`id` " + - "ORDER BY `timestamp` DESC LIMIT ?" + recentQueryStr += "AND boardid = " + config.DBprefix + "boards.id " + + "ORDER BY timestamp DESC LIMIT ?" rows, err := querySQL(recentQueryStr, nilTimestamp, config.MaxRecentPosts) defer closeHandle(rows) @@ -73,8 +83,8 @@ func buildFrontPage() (html string) { } func buildBoardListJSON() (html string) { - board_list_file, err := os.OpenFile(path.Join(config.DocumentRoot, "boards.json"), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777) - defer closeHandle(board_list_file) + boardListFile, err := os.OpenFile(path.Join(config.DocumentRoot, "boards.json"), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777) + defer closeHandle(boardListFile) if err != nil { return handleError(1, "Failed opening board.json for writing: "+err.Error()) + "
\n" } @@ -95,7 +105,7 @@ func buildBoardListJSON() (html string) { if err != nil { return handleError(1, "Failed marshal to JSON: "+err.Error()) + "
\n" } - if _, err = board_list_file.Write(boardJSON); err != nil { + if _, err = boardListFile.Write(boardJSON); err != nil { return handleError(1, "Failed writing boards.json file: "+err.Error()) + "
\n" } return "Board list JSON rebuilt successfully.
" @@ -105,6 +115,10 @@ func buildBoardListJSON() (html string) { // `board` is a Board object representing the board to build archive pages for. // The return value is a string of HTML with debug information from the build process. func buildBoardPages(board *Board) (html string) { + err := initTemplates("boardpage") + if err != nil { + return err.Error() + } start_time := benchmarkTimer("buildBoard"+strconv.Itoa(board.ID), time.Now(), true) var current_page_file *os.File var threads []interface{} @@ -132,9 +146,9 @@ func buildBoardPages(board *Board) (html string) { "boardid": board.ID, "parentid": 0, "deleted_timestamp": nilTimestamp, - }, " ORDER BY `bumped` DESC") + }, " ORDER BY bumped DESC") if err != nil { - html += handleError(1, err.Error()) + "
" + html += handleError(1, "Error getting OP posts for /%s/: %s", board.Dir, err.Error()) + "
\n" op_posts = nil return } @@ -142,22 +156,29 @@ func buildBoardPages(board *Board) (html string) { // For each top level post, start building a Thread struct for _, op := range op_posts { var thread Thread - var posts_in_thread []Post + var postsInThread []Post // Get the number of replies to this thread. - if err = queryRowSQL("SELECT COUNT(*) FROM `"+config.DBprefix+"posts` WHERE `boardid` = ? AND `parentid` = ? AND `deleted_timestamp` = ?", + queryStr := "SELECT COUNT(*) FROM " + config.DBprefix + "posts WHERE boardid = ? AND parentid = ? AND deleted_timestamp = ?" + + if err = queryRowSQL(queryStr, []interface{}{board.ID, op.ID, nilTimestamp}, []interface{}{&thread.NumReplies}, ); err != nil { - html += err.Error() + "
\n" + html += handleError(1, + "Error getting replies to /%s/%d: %s", + board.Dir, op.ID, err.Error()) + "
\n" } // Get the number of image replies in this thread - if err = queryRowSQL("SELECT COUNT(*) FROM `"+config.DBprefix+"posts` WHERE `boardid` = ? AND `parentid` = ? AND `deleted_timestamp` = ? AND `filesize` <> 0", - []interface{}{board.ID, op.ID, nilTimestamp}, + queryStr += " AND filesize <> 0" + if err = queryRowSQL(queryStr, + []interface{}{board.ID, op.ID, op.DeletedTimestamp}, []interface{}{&thread.NumImages}, ); err != nil { - html += err.Error() + "
\n" + html += handleError(1, + "Error getting number of image replies to /%s/%d: %s", + board.Dir, op.ID, err.Error()) + "
\n" } thread.OP = op @@ -173,28 +194,30 @@ func buildBoardPages(board *Board) (html string) { numRepliesOnBoardPage = config.RepliesOnBoardPage } - posts_in_thread, err = getPostArr(map[string]interface{}{ + postsInThread, err = getPostArr(map[string]interface{}{ "boardid": board.ID, "parentid": op.ID, "deleted_timestamp": nilTimestamp, - }, fmt.Sprintf(" ORDER BY `id` DESC LIMIT %d", numRepliesOnBoardPage)) + }, fmt.Sprintf(" ORDER BY id DESC LIMIT %d", numRepliesOnBoardPage)) if err != nil { - html += err.Error() + "
" + html += handleError(1, + "Error getting posts in /%s/%d: %s", + board.Dir, op.ID, err.Error()) + "
\n" } var reversedPosts []Post - for i := len(posts_in_thread); i > 0; i-- { - reversedPosts = append(reversedPosts, posts_in_thread[i-1]) + for i := len(postsInThread); i > 0; i-- { + reversedPosts = append(reversedPosts, postsInThread[i-1]) } - if len(posts_in_thread) > 0 { + if len(postsInThread) > 0 { // Store the posts to show on board page - //thread.BoardReplies = posts_in_thread + //thread.BoardReplies = postsInThread thread.BoardReplies = reversedPosts // Count number of images on board page image_count := 0 - for _, reply := range posts_in_thread { + for _, reply := range postsInThread { if reply.Filesize != 0 { image_count++ } @@ -222,7 +245,8 @@ func buildBoardPages(board *Board) (html string) { // Open board.html for writing to the first page. board_page_file, err := os.OpenFile(path.Join(config.DocumentRoot, board.Dir, "board.html"), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777) if err != nil { - html += handleError(1, "Failed opening /"+board.Dir+"/board.html: "+err.Error()) + "
" + html += handleError(1, + "Failed opening /%s/board.html: %s", board.Dir, err.Error()) + "
" return } @@ -347,31 +371,34 @@ func buildBoards(which ...int) (html string) { return } -func buildCatalog(which int) (html string) { +func buildCatalog(which int) string { + err := initTemplates("catalog") + if err != nil { + return err.Error() + } board, err := getBoardFromID(which) if err != nil { - html += handleError(1, err.Error()) + return handleError(1, err.Error()) } catalogPath := path.Join(config.DocumentRoot, board.Dir, "catalog.html") catalogFile, err := os.OpenFile(catalogPath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777) if err != nil { - html += handleError(1, "Failed opening /"+board.Dir+"/catalog.html: "+err.Error()) - return + return handleError(1, "Failed opening /%s/catalog.html: %s", board.Dir, err.Error()) } threadOPs, err := getPostArr(map[string]interface{}{ "boardid": which, "parentid": 0, "deleted_timestamp": nilTimestamp, - }, "ORDER BY `bumped` ASC") + }, "ORDER BY bumped ASC") if err != nil { - html += handleError(1, "Error building catalog for /%s/: %s", board.Dir, err.Error()) - return + return handleError(1, "Error building catalog for /%s/: %s", board.Dir, err.Error()) } var threadInterfaces []interface{} for _, thread := range threadOPs { threadInterfaces = append(threadInterfaces, thread) } threadPages := paginate(config.PostsPerThreadPage, threadInterfaces) + if err = catalog_tmpl.Execute(catalogFile, map[string]interface{}{ "boards": allBoards, "config": config, @@ -379,19 +406,22 @@ func buildCatalog(which int) (html string) { "sections": allSections, "threadPages": threadPages, }); err != nil { - html += handleError(1, "Error building catalog for /%s/: %s", board.Dir, err.Error()) - return + return handleError(1, "Error building catalog for /%s/: %s", board.Dir, err.Error()) } - html += fmt.Sprintf("Built catalog for /%s/ successfully", board.Dir) - return + return fmt.Sprintf("Built catalog for /%s/ successfully", board.Dir) } // buildThreadPages builds the pages for a thread given by a Post object. func buildThreadPages(op *Post) (html string) { + err := initTemplates("threadpage") + if err != nil { + return err.Error() + } + var replies []Post var current_page_file *os.File - board, err := getBoardFromID(op.BoardID) - if err != nil { + var board *Board + if board, err = getBoardFromID(op.BoardID); err != nil { html += handleError(1, err.Error()) } @@ -399,7 +429,7 @@ func buildThreadPages(op *Post) (html string) { "boardid": op.BoardID, "parentid": op.ID, "deleted_timestamp": nilTimestamp, - }, "ORDER BY `id` ASC") + }, "ORDER BY id ASC") if err != nil { html += handleError(1, "Error building thread "+strconv.Itoa(op.ID)+":"+err.Error()) return diff --git a/src/gochan.go b/src/gochan.go index cacf4d58..7b340c0f 100755 --- a/src/gochan.go +++ b/src/gochan.go @@ -18,7 +18,7 @@ func main() { printf(0, "Starting gochan v%s.%s, using verbosity level %d\n", versionStr, buildtimeString, config.Verbosity) println(0, "Loading and parsing templates...") - if err := initTemplates(); err != nil { + if err := initTemplates("all"); err != nil { handleError(0, customError(err)) os.Exit(2) } diff --git a/src/manage.go b/src/manage.go index aec438cf..a7213e8d 100644 --- a/src/manage.go +++ b/src/manage.go @@ -86,7 +86,7 @@ func getCurrentStaff(request *http.Request) (string, error) { key := sessionCookie.Value currentSession := new(LoginSession) if err := queryRowSQL( - "SELECT `data` FROM `"+config.DBprefix+"sessions` WHERE `key` = ?", + "SELECT sessiondata FROM "+config.DBprefix+"sessions WHERE name = ?", []interface{}{key}, []interface{}{¤tSession.Data}, ); err != nil { @@ -97,8 +97,7 @@ func getCurrentStaff(request *http.Request) (string, error) { func getStaff(name string) (*Staff, error) { staff := new(Staff) - err := queryRowSQL( - "SELECT * FROM `"+config.DBprefix+"staff` WHERE `username` = ?", + err := queryRowSQL("SELECT * FROM "+config.DBprefix+"staff WHERE username = ?", []interface{}{name}, []interface{}{&staff.ID, &staff.Username, &staff.PasswordChecksum, &staff.Salt, &staff.Rank, &staff.Boards, &staff.AddedOn, &staff.LastActive}, ) @@ -124,13 +123,13 @@ func getStaffRank(request *http.Request) int { } func newStaff(username string, password string, rank int) error { - _, err := execSQL("INSERT INTO `"+config.DBprefix+"staff` (`username`, `password_checksum`, `rank`) VALUES(?,?,?)", + _, err := execSQL("INSERT INTO `"+config.DBprefix+"staff` (username, password_checksum, rank) VALUES(?,?,?)", &username, bcryptSum(password), &rank) return err } func deleteStaff(username string) error { - _, err := execSQL("DELETE FROM `"+config.DBprefix+"staff` WHERE `username` = ?", username) + _, err := execSQL("DELETE FROM "+config.DBprefix+"staff WHERE username = ?", username) return err } @@ -164,16 +163,16 @@ func createSession(key string, username string, password string, request *http.R Domain: domain, MaxAge: 60 * 60 * 24 * 7, }) + if _, err = execSQL( - "INSERT INTO `"+config.DBprefix+"sessions` (`key`, `data`, `expires`) VALUES(?,?,?)", - key, username, getSpecificSQLDateTime(time.Now().Add(time.Duration(time.Hour*730))), - ); err != nil { - handleError(1, customError(err)) + "INSERT INTO "+config.DBprefix+"sessions (name,sessiondata,expires) VALUES(?,?,?)", + key, username, getSpecificSQLDateTime(time.Now().Add(time.Duration(time.Hour*730)))); err != nil { + handleError(0, customError(err)) return 2 } if _, err = execSQL( - "UPDATE `"+config.DBprefix+"staff` SET `last_active` = ? WHERE `username` = ?", getSQLDateTime(), username, + "UPDATE "+config.DBprefix+"staff SET last_active = ? WHERE username = ?", getSQLDateTime(), username, ); err != nil { handleError(1, customError(err)) } @@ -381,8 +380,8 @@ var manage_functions = map[string]ManageFunction{ config.EnableQuickReply = (request.PostFormValue("EnableQuickReply") == "on") config.DateTimeFormat = request.PostFormValue("DateTimeFormat") AkismetAPIKey := request.PostFormValue("AkismetAPIKey") - err = checkAkismetAPIKey(AkismetAPIKey) - if err != nil { + + if err = checkAkismetAPIKey(AkismetAPIKey); err != nil { status += err.Error() + "
" } else { config.AkismetAPIKey = AkismetAPIKey @@ -437,7 +436,7 @@ var manage_functions = map[string]ManageFunction{ Permissions: 3, Callback: func(writer http.ResponseWriter, request *http.Request) (html string) { html = "" - rows, err := querySQL("SELECT `dir` FROM `" + config.DBprefix + "boards`") + rows, err := querySQL("SELECT dir FROM " + config.DBprefix + "boards") defer closeHandle(rows) if err != nil { html += err.Error() @@ -472,15 +471,17 @@ var manage_functions = map[string]ManageFunction{ return } } - if _, err = execSQL("TRUNCATE `" + config.DBprefix + "posts`"); err != nil { - html += err.Error() + "
" - handleError(1, customError(err)) + truncateSQL := "TRUNCATE " + config.DBprefix + "posts" + if config.DBtype == "postgres" { + truncateSQL += " RESTART IDENTITY" + } + if _, err = execSQL(truncateSQL); err != nil { + html += handleError(0, err.Error()) + "
\n" return } if _, err = execSQL("ALTER TABLE `" + config.DBprefix + "posts` AUTO_INCREMENT = 1"); err != nil { - html += err.Error() + "
" - handleError(1, customError(err)) + html += handleError(0, err.Error()) + "
\n" return } html += "
Everything purged, rebuilding all
" + @@ -544,7 +545,7 @@ var manage_functions = map[string]ManageFunction{ Callback: func(writer http.ResponseWriter, request *http.Request) (html string) { html = "

Announcements


" - rows, err := querySQL("SELECT `subject`,`message`,`poster`,`timestamp` FROM `" + config.DBprefix + "announcements` ORDER BY `id` DESC") + rows, err := querySQL("SELECT subject,message,poster,timestamp FROM " + config.DBprefix + "announcements ORDER BY id DESC") defer closeHandle(rows) if err != nil { html += handleError(1, err.Error()) @@ -605,9 +606,16 @@ var manage_functions = map[string]ManageFunction{ reason := html.EscapeString(request.FormValue("reason")) staffNote := html.EscapeString(request.FormValue("staffnote")) currentStaff, _ := getCurrentStaff(request) - if _, err := execSQL("INSERT INTO `"+config.DBprefix+"banlist`"+ - "(`ip`,`name`,`name_is_regex`,`filename`,`file_checksum`,`boards`,`staff`,`expires`,`permaban`,`reason`,`type`,`staff_note`)"+ - "VALUES(?,?,?,?,?,?,?,?,?,?,?,?)", + sqlStr := "INSERT INTO " + config.DBprefix + "banlist (ip,name,name_is_regex,filename,file_checksum,boards,staff,expires,permaban,reason,type,staff_note) VALUES(" + for i := 1; i <= 12; i++ { + sqlStr += "?" + if i < 12 { + sqlStr += "," + } + } + sqlStr += ")" + println(1, sqlStr) + if _, err := execSQL(sqlStr, ip.String(), name, nameIsRegex, filename, checksum, boards, currentStaff, expires, permaban, reason, bantype, staffNote, ); err != nil { pageHTML += err.Error() @@ -640,7 +648,7 @@ var manage_functions = map[string]ManageFunction{ } post = posts[0] } - rows, err := querySQL("SELECT `ip`,`name`,`reason`,`boards`,`staff`,`timestamp`,`expires`,`permaban`,`can_appeal` FROM `" + config.DBprefix + "banlist`") + rows, err := querySQL("SELECT ip,name,reason,boards,staff,timestamp,expires,permaban,can_appeal FROM " + config.DBprefix + "banlist") defer closeHandle(rows) if err != nil { pageHTML += handleError(1, err.Error()) @@ -678,7 +686,7 @@ var manage_functions = map[string]ManageFunction{ return } staff := new(Staff) - if err := queryRowSQL("SELECT `rank`,`boards` FROM `"+config.DBprefix+"staff` WHERE `username` = ?", + if err := queryRowSQL("SELECT rank,boards FROM "+config.DBprefix+"staff WHERE username = ?", []interface{}{current_staff}, []interface{}{&staff.Rank, &staff.Boards}, ); err != nil { @@ -694,7 +702,7 @@ var manage_functions = map[string]ManageFunction{ do := request.FormValue("do") var done bool board := new(Board) - var board_creation_status string + var boardCreationStatus string var err error var rows *sql.Rows for !done { @@ -702,30 +710,30 @@ var manage_functions = map[string]ManageFunction{ case do == "add": board.Dir = request.FormValue("dir") if board.Dir == "" { - board_creation_status = "Error: \"Directory\" cannot be blank" + boardCreationStatus = "Error: \"Directory\" cannot be blank" do = "" continue } - order_str := request.FormValue("order") - board.Order, err = strconv.Atoi(order_str) + orderStr := request.FormValue("order") + board.ListOrder, err = strconv.Atoi(orderStr) if err != nil { - board.Order = 0 + board.ListOrder = 0 } board.Title = request.FormValue("title") if board.Title == "" { - board_creation_status = "Error: \"Title\" cannot be blank" + boardCreationStatus = "Error: \"Title\" cannot be blank" do = "" continue } board.Subtitle = request.FormValue("subtitle") board.Description = request.FormValue("description") - section_str := request.FormValue("section") - if section_str == "none" { - section_str = "0" + sectionStr := request.FormValue("section") + if sectionStr == "none" { + sectionStr = "0" } board.CreatedOn = time.Now() - board.Section, err = strconv.Atoi(section_str) + board.Section, err = strconv.Atoi(sectionStr) if err != nil { board.Section = 0 } @@ -776,35 +784,35 @@ var manage_functions = map[string]ManageFunction{ //actually start generating stuff if err = os.Mkdir(path.Join(config.DocumentRoot, board.Dir), 0666); err != nil { do = "" - board_creation_status = handleError(1, "ERROR: directory /"+config.DocumentRoot+"/"+board.Dir+"/ already exists!") + boardCreationStatus = handleError(1, "ERROR: directory /"+config.DocumentRoot+"/"+board.Dir+"/ already exists!") break } if err = os.Mkdir(path.Join(config.DocumentRoot, board.Dir, "res"), 0666); err != nil { do = "" - board_creation_status = handleError(1, "ERROR: directory /"+config.DocumentRoot+"/"+board.Dir+"/res/ already exists!") + boardCreationStatus = handleError(1, "ERROR: directory /"+config.DocumentRoot+"/"+board.Dir+"/res/ already exists!") break } if err = os.Mkdir(path.Join(config.DocumentRoot, board.Dir, "thumb"), 0666); err != nil { do = "" - board_creation_status = handleError(1, "ERROR: directory /"+config.DocumentRoot+"/"+board.Dir+"/thumb/ already exists!") + boardCreationStatus = handleError(1, "ERROR: directory /"+config.DocumentRoot+"/"+board.Dir+"/thumb/ already exists!") break } if err = os.Mkdir(path.Join(config.DocumentRoot, board.Dir, "src"), 0666); err != nil { do = "" - board_creation_status = handleError(1, "ERROR: directory /"+config.DocumentRoot+"/"+board.Dir+"/src/ already exists!") + boardCreationStatus = handleError(1, "ERROR: directory /"+config.DocumentRoot+"/"+board.Dir+"/src/ already exists!") break } boardCreationTimestamp := getSpecificSQLDateTime(board.CreatedOn) if _, err := execSQL( - "INSERT INTO `"+config.DBprefix+"boards` (`order`,`dir`,`type`,`upload_type`,`title`,`subtitle`,"+ - "`description`,`section`,`max_image_size`,`max_pages`,`default_style`,`locked`,`created_on`,"+ - "`anonymous`,`forced_anon`,`max_age`,`autosage_after`,`no_images_after`,`max_message_length`,`embeds_allowed`,"+ - "`redirect_to_thread`,`require_file`,`enable_catalog`) "+ + "INSERT INTO "+config.DBprefix+"boards (list_order,dir,type,upload_type,title,subtitle,"+ + "description,section,max_file_size,max_pages,default_style,locked,created_on,"+ + "anonymous,forced_anon,max_age,autosage_after,no_images_after,max_message_length,embeds_allowed,"+ + "redirect_to_thread,require_file,enable_catalog) "+ "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", - board.Order, board.Dir, board.Type, board.UploadType, + board.ListOrder, board.Dir, board.Type, board.UploadType, board.Title, board.Subtitle, board.Description, board.Section, board.MaxFilesize, board.MaxPages, board.DefaultStyle, board.Locked, boardCreationTimestamp, board.Anonymous, @@ -813,11 +821,11 @@ var manage_functions = map[string]ManageFunction{ board.RedirectToThread, board.RequireFile, board.EnableCatalog, ); err != nil { do = "" - board_creation_status = handleError(1, "Error creating board: "+customError(err)) + boardCreationStatus = handleError(1, "Error creating board: "+customError(err)) break } else { - board_creation_status = "Board created successfully" - println(2, board_creation_status) + boardCreationStatus = "Board created successfully" + println(2, boardCreationStatus) buildBoards() resetBoardSectionArrays() println(2, "Boards rebuilt successfully") @@ -830,71 +838,21 @@ var manage_functions = map[string]ManageFunction{ // resetBoardSectionArrays() default: // put the default column values in the text boxes - rows, err = querySQL("SELECT `column_name`,`column_default` FROM `information_schema`.`columns` WHERE `table_name` = '" + config.DBprefix + "boards'") - defer closeHandle(rows) - if err != nil { - html += handleError(1, "Error getting column names from boards table:"+err.Error()) - return - } - for rows.Next() { - var columnName string - var columnDefault string - rows.Scan(&columnName, &columnDefault) - columnDefaultInt, _ := strconv.Atoi(columnDefault) - columnDefaultBool := (columnDefaultInt == 1) - switch columnName { - case "id": - board.ID = columnDefaultInt - case "order": - board.Order = columnDefaultInt - case "dir": - board.Dir = columnDefault - case "type": - board.Type = columnDefaultInt - case "upload_type": - board.UploadType = columnDefaultInt - case "title": - board.Title = columnDefault - case "subtitle": - board.Subtitle = columnDefault - case "description": - board.Description = columnDefault - case "section": - board.Section = columnDefaultInt - case "max_image_size": - board.MaxFilesize = columnDefaultInt - case "max_pages": - board.MaxPages = columnDefaultInt - case "default_style": - board.DefaultStyle = columnDefault - case "locked": - board.Locked = columnDefaultBool - case "anonymous": - board.Anonymous = columnDefault - case "forced_anon": - board.ForcedAnon = columnDefaultBool - case "max_age": - board.MaxAge = columnDefaultInt - case "autosage_after": - board.AutosageAfter = columnDefaultInt - case "no_images_after": - board.NoImagesAfter = columnDefaultInt - case "max_message_length": - board.MaxMessageLength = columnDefaultInt - case "embeds_allowed": - board.EmbedsAllowed = columnDefaultBool - case "redirect_to_thread": - board.RedirectToThread = columnDefaultBool - case "require_file": - board.RequireFile = columnDefaultBool - case "enable_catalog": - board.EnableCatalog = columnDefaultBool - } - } + board.Section = 1 + board.MaxFilesize = 4718592 + board.MaxPages = 11 + board.DefaultStyle = "pipes.css" + board.Anonymous = "Anonymous" + board.AutosageAfter = 200 + board.MaxMessageLength = 8192 + board.EmbedsAllowed = true + board.EnableCatalog = true + board.Worksafe = true + board.ThreadsPerPage = config.ThreadsPerPage } html = "

Manage boards

\n
\n

" + - "

Create new board

\n" + board_creation_status + "
" + "

Create new board

\n" + boardCreationStatus + "
" manageBoardsBuffer := bytes.NewBufferString("") allSections, _ = getSectionArr("") if len(allSections) == 0 { - execSQL("INSERT INTO `" + config.DBprefix + "sections` (`hidden`,`name`,`abbreviation`) VALUES(0,'Main','main')") + execSQL("INSERT INTO " + config.DBprefix + "sections (hidden,name,abbreviation) VALUES(0,'Main','main')") } allSections, _ = getSectionArr("") @@ -1027,7 +985,7 @@ var manage_functions = map[string]ManageFunction{ "`"+config.DBprefix+"posts`. "+ "`ip` AS ip, "+ "`"+config.DBprefix+"posts`. "+ - "`timestamp` AS timestamp "+ + "`timestamp` AS timestamp "+ "FROM `"+config.DBprefix+"posts`, `"+config.DBprefix+"boards` "+ "WHERE `reviewed` = 0 "+ "AND `"+config.DBprefix+"posts`.`deleted_timestamp` = ? "+ @@ -1068,11 +1026,11 @@ var manage_functions = map[string]ManageFunction{ "dir": boardDir, }, "") if err != nil { - jsonErr, _ := marshalAPI("error", err.Error(), false) + jsonErr, _ := marshalJSON("error", err.Error(), false) return jsonErr } if len(boards) < 1 { - jsonErr, _ := marshalAPI("error", "Board doesn't exist.", false) + jsonErr, _ := marshalJSON("error", "Board doesn't exist.", false) return jsonErr } @@ -1081,14 +1039,14 @@ var manage_functions = map[string]ManageFunction{ "boardid": boards[0].ID, }, "") if err != nil { - jsonErr, _ := marshalAPI("error", err.Error(), false) + jsonErr, _ := marshalJSON("error", err.Error(), false) return jsonErr } if len(posts) < 1 { - jsonErr, _ := marshalAPI("eror", "Post doesn't exist.", false) + jsonErr, _ := marshalJSON("eror", "Post doesn't exist.", false) return jsonErr } - jsonStr, _ := marshalAPI("", posts[0], false) + jsonStr, _ := marshalJSON("", posts[0], false) return jsonStr }}, "staff": { diff --git a/src/posting.go b/src/posting.go index fc64f357..279d222e 100755 --- a/src/posting.go +++ b/src/posting.go @@ -40,7 +40,7 @@ var ( // bumps the given thread on the given board and returns true if there were no errors func bumpThread(postID, boardID int) error { - _, err := execSQL("UPDATE `"+config.DBprefix+"posts` SET `bumped` = ? WHERE `id` = ? AND `boardid` = ?", + _, err := execSQL("UPDATE "+config.DBprefix+"posts SET bumped = ? WHERE id = ? AND boardid = ?", time.Now(), postID, boardID, ) @@ -79,21 +79,21 @@ func getBannedStatus(request *http.Request) (BanInfo, error) { } in := []interface{}{ip} - query := "SELECT `id`,`ip`,`name`,`boards`,`timestamp`,`expires`,`permaban`,`reason`,`type`,`appeal_at`,`can_appeal` FROM `" + config.DBprefix + "banlist` WHERE `ip` = ? " + query := "SELECT id,ip,name,boards,timestamp,expires,permaban,reason,type,appeal_at,can_appeal FROM " + config.DBprefix + "banlist WHERE ip = ? " if tripcode != "" { in = append(in, tripcode) - query += "OR `name` = ? " + query += "OR name = ? " } if filename != "" { in = append(in, filename) - query += "OR `filename` = ? " + query += "OR filename = ? " } if checksum != "" { in = append(in, checksum) - query += "OR `file_checksum` = ? " + query += "OR file_checksum = ? " } - query += " ORDER BY `id` DESC LIMIT 1" + query += " ORDER BY id DESC LIMIT 1" err = queryRowSQL(query, in, []interface{}{ &banEntry.ID, &banEntry.IP, &banEntry.Name, &banEntry.Boards, &banEntry.Timestamp, @@ -119,7 +119,7 @@ func isBanned(ban BanInfo, board string) bool { func sinceLastPost(post *Post) int { var lastPostTime time.Time - if err := queryRowSQL("SELECT `timestamp` FROM `"+config.DBprefix+"posts` WHERE `ip` = '?' ORDER BY `timestamp` DESC LIMIT 1", + if err := queryRowSQL("SELECT timestamp FROM "+config.DBprefix+"posts WHERE ip = ? ORDER BY timestamp DESC LIMIT 1", []interface{}{post.IP}, []interface{}{&lastPostTime}, ); err == sql.ErrNoRows { @@ -243,31 +243,38 @@ func parseName(name string) map[string]string { } // inserts prepared post object into the SQL table so that it can be rendered -func insertPost(post Post, bump bool) (sql.Result, error) { - result, err := execSQL( - "INSERT INTO `"+config.DBprefix+"posts` "+ - "(`boardid`,`parentid`,`name`,`tripcode`,`email`,`subject`,`message`,`message_raw`,`password`,`filename`,`filename_original`,`file_checksum`,`filesize`,`image_w`,`image_h`,`thumb_w`,`thumb_h`,`ip`,`tag`,`timestamp`,`autosage`,`deleted_timestamp`,`bumped`,`stickied`,`locked`,`reviewed`)"+ - "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", +func insertPost(post *Post, bump bool) error { + queryStr := "INSERT INTO " + config.DBprefix + "posts " + + "(boardid,parentid,name,tripcode,email,subject,message,message_raw,password,filename,filename_original,file_checksum,filesize,image_w,image_h,thumb_w,thumb_h,ip,tag,timestamp,autosage,deleted_timestamp,bumped,stickied,locked,reviewed)" + + "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" + + result, err := execSQL(queryStr, post.BoardID, post.ParentID, post.Name, post.Tripcode, post.Email, post.Subject, post.MessageHTML, post.MessageText, post.Password, post.Filename, post.FilenameOriginal, post.FileChecksum, post.Filesize, post.ImageW, post.ImageH, post.ThumbW, post.ThumbH, post.IP, post.Capcode, post.Timestamp, post.Autosage, post.DeletedTimestamp, post.Bumped, - post.Stickied, post.Locked, post.Reviewed, - ) - + post.Stickied, post.Locked, post.Reviewed) if err != nil { - return result, err + return err + } + + switch config.DBtype { + case "mysql": + var postID int64 + postID, err = result.LastInsertId() + post.ID = int(postID) + case "postgres": + err = queryRowSQL("SELECT currval(pg_get_serial_sequence('"+config.DBprefix+"posts','id'))", nil, []interface{}{&post.ID}) + case "sqlite3": + err = queryRowSQL("SELECT LAST_INSERT_ROWID()", nil, []interface{}{&post.ID}) } // Bump parent post if requested. - if post.ParentID != 0 && bump { + if err != nil && post.ParentID != 0 && bump { err = bumpThread(post.ParentID, post.BoardID) - if err != nil { - return nil, err - } } - return result, err + return err } // called when a user accesses /post. Parse form data, then insert and build @@ -310,7 +317,7 @@ func makePost(writer http.ResponseWriter, request *http.Request) { post.Subject = request.FormValue("postsubject") post.MessageText = strings.Trim(request.FormValue("postmsg"), "\r\n") - if err := queryRowSQL("SELECT `max_message_length` from `"+config.DBprefix+"boards` WHERE `id` = ?", + if err := queryRowSQL("SELECT max_message_length from "+config.DBprefix+"boards WHERE id = ?", []interface{}{post.BoardID}, []interface{}{&maxMessageLength}, ); err != nil { @@ -377,6 +384,7 @@ func makePost(writer http.ResponseWriter, request *http.Request) { // no file was uploaded post.Filename = "" accessLog.Print("Receiving post from " + post.IP + ", referred from: " + request.Referer()) + accessLog.Printf("Receiving post from %s, referred from: %s", post.IP, request.Referer()) } else { data, err := ioutil.ReadAll(file) if err != nil { @@ -411,7 +419,7 @@ func makePost(writer http.ResponseWriter, request *http.Request) { post.FileChecksum = fmt.Sprintf("%x", md5.Sum(data)) var allowsVids bool - if err = queryRowSQL("SELECT `embeds_allowed` FROM `"+config.DBprefix+"boards` WHERE `id` = ? LIMIT 1", + if err = queryRowSQL("SELECT embeds_allowed FROM "+config.DBprefix+"boards WHERE id = ? LIMIT 1", []interface{}{post.BoardID}, []interface{}{&allowsVids}, ); err != nil { @@ -588,13 +596,10 @@ func makePost(writer http.ResponseWriter, request *http.Request) { } post.Sanitize() - result, err := insertPost(post, emailCommand != "sage") - if err != nil { + if err = insertPost(&post, emailCommand != "sage"); err != nil { serveErrorPage(writer, handleError(1, err.Error())) return } - postid, _ := result.LastInsertId() - post.ID = int(postid) // rebuild the board page buildBoards(post.BoardID) @@ -628,7 +633,7 @@ func formatMessage(message string) string { var boardDir string var linkParent int - if err = queryRowSQL("SELECT `dir`,`parentid` FROM "+config.DBprefix+"posts,"+config.DBprefix+"boards WHERE "+config.DBprefix+"posts.id = ?", + if err = queryRowSQL("SELECT dir,parentid FROM "+config.DBprefix+"posts,"+config.DBprefix+"boards WHERE "+config.DBprefix+"posts.id = ?", []interface{}{word[8:]}, []interface{}{&boardDir, &linkParent}, ); err != nil { @@ -673,7 +678,7 @@ func banHandler(writer http.ResponseWriter, request *http.Request) { return } escapedMsg := html.EscapeString(appealMsg) - if _, err = execSQL("INSERT INTO `"+config.DBprefix+"appeals` (`ban`,`message`) VALUES(?,?)", + if _, err = execSQL("INSERT INTO "+config.DBprefix+"appeals (ban,message) VALUES(?,?)", banStatus.ID, escapedMsg, ); err != nil { serveErrorPage(writer, err.Error()) diff --git a/src/server.go b/src/server.go index 88a5b918..b189a575 100755 --- a/src/server.go +++ b/src/server.go @@ -225,7 +225,7 @@ func utilHandler(writer http.ResponseWriter, request *http.Request) { var post Post post.ID, _ = strconv.Atoi(postsArr[0]) post.BoardID, _ = strconv.Atoi(boardid) - if err = queryRowSQL("SELECT `parentid`,`name`,`tripcode`,`email`,`subject`,`password`,`message_raw` FROM `"+config.DBprefix+"posts` WHERE `id` = ? AND `boardid` = ? AND `deleted_timestamp` = ?", + if err = queryRowSQL("SELECT parentid,name,tripcode,email,subject,password,message_raw FROM "+config.DBprefix+"posts WHERE id = ? AND boardid = ? AND deleted_timestamp = ?", []interface{}{post.ID, post.BoardID, nilTimestamp}, []interface{}{ &post.ParentID, &post.Name, &post.Tripcode, &post.Email, &post.Subject, @@ -265,7 +265,7 @@ func utilHandler(writer http.ResponseWriter, request *http.Request) { return } - if err = queryRowSQL("SELECT `password` FROM `"+config.DBprefix+"posts` WHERE `id` = ? AND `boardid` = ?", + if err = queryRowSQL("SELECT password FROM "+config.DBprefix+"posts WHERE id = ? AND boardid = ?", []interface{}{postid, boardid}, []interface{}{&postPassword}, ); err != nil { @@ -284,8 +284,8 @@ func utilHandler(writer http.ResponseWriter, request *http.Request) { return } - if _, err = execSQL("UPDATE `"+config.DBprefix+"posts` SET "+ - "`email` = ?, `subject` = ?, `message` = ?, `message_raw` = ? WHERE `id` = ? AND `boardid` = ?", + if _, err = execSQL("UPDATE "+config.DBprefix+"posts SET "+ + "email = ?, subject = ?, message = ?, message_raw = ? WHERE id = ? AND boardid = ?", request.FormValue("editemail"), request.FormValue("editsubject"), formatMessage(request.FormValue("editmsg")), request.FormValue("editmsg"), postid, boardid, ); err != nil { @@ -323,8 +323,8 @@ func utilHandler(writer http.ResponseWriter, request *http.Request) { post.BoardID, _ = strconv.Atoi(boardid) if err = queryRowSQL( - "SELECT `parentid`, `filename`, `password` FROM `"+config.DBprefix+"posts` WHERE `id` = ? AND `boardid` = ? AND `deleted_timestamp` = ?", - []interface{}{&post.ID, &post.BoardID, nilTimestamp}, + "SELECT parentid, filename,password FROM "+config.DBprefix+"posts WHERE id = ? AND boardid = ? AND deleted_timestamp = ?", + []interface{}{post.ID, post.BoardID, nilTimestamp}, []interface{}{&post.ParentID, &post.Filename, &post.Password}, ); err == sql.ErrNoRows { //the post has already been deleted @@ -337,7 +337,7 @@ func utilHandler(writer http.ResponseWriter, request *http.Request) { } if err = queryRowSQL( - "SELECT `id` FROM `"+config.DBprefix+"boards` WHERE `dir` = ?", + "SELECT id FROM "+config.DBprefix+"boards WHERE dir = ?", []interface{}{board}, []interface{}{&post.BoardID}, ); err != nil { @@ -364,7 +364,7 @@ func utilHandler(writer http.ResponseWriter, request *http.Request) { os.Remove(path.Join(config.DocumentRoot, board, "/thumb/"+fileName+"c."+thumbType)) if _, err = execSQL( - "UPDATE `"+config.DBprefix+"posts` SET `filename` = 'deleted' WHERE `id` = ? AND `boardid` = ?", + "UPDATE "+config.DBprefix+"posts SET filename = deleted WHERE id = ? AND boardid = ?", post.ID, post.BoardID, ); err != nil { serveErrorPage(writer, err.Error()) @@ -382,7 +382,7 @@ func utilHandler(writer http.ResponseWriter, request *http.Request) { } else { // delete the post if _, err = execSQL( - "UPDATE `"+config.DBprefix+"posts` SET `deleted_timestamp` = ? WHERE `id` = ?", + "UPDATE "+config.DBprefix+"posts SET deleted_timestamp = ? WHERE id = ?", getSQLDateTime(), post.ID, ); err != nil { serveErrorPage(writer, err.Error()) @@ -395,7 +395,7 @@ func utilHandler(writer http.ResponseWriter, request *http.Request) { } // if the deleted post is actually a thread, delete its posts - if _, err = execSQL("UPDATE `"+config.DBprefix+"posts` SET `deleted_timestamp` = ? WHERE `parentID` = ?", + if _, err = execSQL("UPDATE "+config.DBprefix+"posts SET deleted_timestamp = ? WHERE parentID = ?", getSQLDateTime(), post.ID, ); err != nil { serveErrorPage(writer, err.Error()) @@ -405,7 +405,7 @@ func utilHandler(writer http.ResponseWriter, request *http.Request) { // delete the file var deletedFilename string if err = queryRowSQL( - "SELECT `filename` FROM `"+config.DBprefix+"posts` WHERE `id` = ? AND `filename` != ''", + "SELECT filename FROM "+config.DBprefix+"posts WHERE id = ? AND filename != ''", []interface{}{post.ID}, []interface{}{&deletedFilename}, ); err == nil { @@ -415,7 +415,7 @@ func utilHandler(writer http.ResponseWriter, request *http.Request) { } if err = queryRowSQL( - "SELECT `filename` FROM `"+config.DBprefix+"posts` WHERE `parentID` = ? AND `filename` != ''", + "SELECT filename FROM "+config.DBprefix+"posts WHERE parentID = ? AND filename != ''", []interface{}{post.ID}, []interface{}{&deletedFilename}, ); err == nil { diff --git a/src/sql.go b/src/sql.go index 029f55f0..9695b98d 100755 --- a/src/sql.go +++ b/src/sql.go @@ -2,7 +2,7 @@ package main import ( "database/sql" - "errors" + "fmt" "io/ioutil" "os" "regexp" @@ -10,49 +10,73 @@ import ( "time" _ "github.com/go-sql-driver/mysql" - // _ "github.com/lib/pq" + _ "github.com/lib/pq" + _ "github.com/mattn/go-sqlite3" ) const ( - nilTimestamp = "0000-00-00 00:00:00" - mysqlDatetimeFormat = "2006-01-02 15:04:05" + mysqlDatetimeFormat = "2006-01-02 15:04:05" + unsupportedSQLVersionMsg = `Received syntax error while preparing a SQL string. +This means that either there is a bug in gochan's code (hopefully not) or that you are using an unsupported My/Postgre/SQLite version. +Before reporting an error, make sure that you are using the up to date version of your selected SQL server. +Error text: %s +` ) var ( - db *sql.DB + db *sql.DB + nilTimestamp string ) func connectToSQLServer() { var err error - + var connStr string println(0, "Initializing server...") - db, err = sql.Open("mysql", config.DBusername+":"+config.DBpassword+"@"+config.DBhost+"/"+config.DBname+"?parseTime=true&collation=utf8mb4_unicode_ci") - if err != nil { + + switch config.DBtype { + case "mysql": + connStr = fmt.Sprintf("%s:%s@%s/%s?parseTime=true&collation=utf8mb4_unicode_ci", + config.DBusername, config.DBpassword, config.DBhost, config.DBname) + nilTimestamp = "0000-00-00 00:00:00" + case "postgres": + connStr = fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=verify-ca", + config.DBusername, config.DBpassword, config.DBhost, config.DBname) + nilTimestamp = "0001-01-01 00:00:00" + case "sqlite3": + connStr = config.DBhost + nilTimestamp = "0001-01-01 00:00:00+00:00" + default: + handleError(0, "Invalid DBtype '%s' in gochan.json, valid values are 'mysql' and 'postgres'", config.DBtype) + os.Exit(2) + } + + nullTime, _ = time.Parse("2006-01-02 15:04:05", nilTimestamp) + if db, err = sql.Open(config.DBtype, connStr); err != nil { handleError(0, "Failed to connect to the database: %s\n", customError(err)) os.Exit(2) } - if err = initDB(); err != nil { - handleError(0, "Failed initializing DB: %s\n", customError(err)) + if err = initDB("initdb_" + config.DBtype + ".sql"); err != nil { + println(0, "Failed initializing DB:", sqlVersionErr(err)) os.Exit(2) } var sqlVersionStr string - err = queryRowSQL("SELECT `value` FROM `"+config.DBprefix+"info` WHERE `name` = 'version'", + err = queryRowSQL("SELECT value FROM "+config.DBprefix+"info WHERE name = 'version'", []interface{}{}, []interface{}{&sqlVersionStr}) if err == sql.ErrNoRows { println(0, "\nThis looks like a new installation") - if _, err = db.Exec("INSERT INTO `" + config.DBname + "`.`" + config.DBprefix + "staff` " + - "(`username`, `password_checksum`, `salt`, `rank`) " + + if _, err = db.Exec("INSERT INTO " + config.DBprefix + "staff " + + "(username, password_checksum, salt, rank) " + "VALUES ('admin', '" + bcryptSum("password") + "', 'abc', 3)", ); err != nil { handleError(0, "Failed creating admin user with error: %s\n", customError(err)) os.Exit(2) } - _, err = execSQL("INSERT INTO `"+config.DBprefix+"info` (`name`,`value`) VALUES('version',?)", versionStr) + _, err = execSQL("INSERT INTO "+config.DBprefix+"info (name,value) VALUES('version',?)", versionStr) return } else if err != nil { handleError(0, "failed: %s\n", customError(err)) @@ -64,18 +88,18 @@ func connectToSQLServer() { } if version.CompareString(sqlVersionStr) > 0 { printf(0, "Updating version in database from %s to %s\n", sqlVersionStr, version.String()) - execSQL("UPDATE `"+config.DBprefix+"info` SET `value` = ? WHERE `name` = 'version'", versionStr) + execSQL("UPDATE "+config.DBprefix+"info SET value = ? WHERE name = 'version'", versionStr) } } -func initDB() error { +func initDB(initFile string) error { var err error - if _, err = os.Stat("initdb.sql"); err != nil { - return errors.New("SQL database initialization file (initdb.sql) missing. Please reinstall gochan") + if _, err = os.Stat(initFile); err != nil { + return fmt.Errorf("SQL database initialization file (%s) missing. Please reinstall gochan", initFile) } - sqlBytes, err := ioutil.ReadFile("initdb.sql") + sqlBytes, err := ioutil.ReadFile(initFile) if err != nil { return err } @@ -97,15 +121,62 @@ func initDB() error { return nil } +// checks to see if the given error is a syntax error (used for built-in strings) +func sqlVersionErr(err error) error { + if err == nil { + return nil + } + errText := err.Error() + switch config.DBtype { + case "mysql": + if !strings.Contains(errText, "You have an error in your SQL syntax") { + return err + } + case "postgres": + if !strings.Contains(errText, "syntax error at or near") { + return err + } + case "sqlite3": + if !strings.Contains(errText, "Error: near ") { + return err + } + } + return fmt.Errorf(unsupportedSQLVersionMsg, errText) +} + +// used for generating a prepared SQL statement formatted according to config.DBtype +func prepareSQL(query string) (*sql.Stmt, error) { + var preparedStr string + switch config.DBtype { + case "mysql": + fallthrough + case "sqlite3": + preparedStr = query + break + case "postgres": + arr := strings.Split(query, "?") + for i := range arr { + if i == len(arr)-1 { + break + } + arr[i] += fmt.Sprintf("$%d", i+1) + } + preparedStr = strings.Join(arr, "") + break + } + stmt, err := db.Prepare(preparedStr) + return stmt, sqlVersionErr(err) +} + /* * Automatically escapes the given values and caches the statement * Example: * var intVal int * var stringVal string - * result, err := execSQL("INSERT INTO `tablename` (`intval`,`stringval`) VALUES(?,?)", intVal, stringVal) + * result, err := execSQL("INSERT INTO tablename (intval,stringval) VALUES(?,?)", intVal, stringVal) */ func execSQL(query string, values ...interface{}) (sql.Result, error) { - stmt, err := db.Prepare(query) + stmt, err := prepareSQL(query) defer closeHandle(stmt) if err != nil { return nil, err @@ -120,13 +191,13 @@ func execSQL(query string, values ...interface{}) (sql.Result, error) { * id := 32 * var intVal int * var stringVal string - * err := queryRowSQL("SELECT `intval`,`stringval` FROM `table` WHERE `id` = ?", + * err := queryRowSQL("SELECT intval,stringval FROM table WHERE id = ?", * []interface{}{&id}, * []interface{}{&intVal, &stringVal} * ) */ func queryRowSQL(query string, values []interface{}, out []interface{}) error { - stmt, err := db.Prepare(query) + stmt, err := prepareSQL(query) defer closeHandle(stmt) if err != nil { return err @@ -138,7 +209,7 @@ func queryRowSQL(query string, values []interface{}, out []interface{}) error { * Gets all rows from the db with the values in values[] and fills the respective pointers in out[] * Automatically escapes the given values and caches the query * Example: - * rows, err := querySQL("SELECT * FROM `table`") + * rows, err := querySQL("SELECT * FROM table") * if err == nil { * for rows.Next() { * var intVal int @@ -149,7 +220,7 @@ func queryRowSQL(query string, values []interface{}, out []interface{}) error { * } */ func querySQL(query string, a ...interface{}) (*sql.Rows, error) { - stmt, err := db.Prepare(query) + stmt, err := prepareSQL(query) defer closeHandle(stmt) if err != nil { return nil, err @@ -164,9 +235,3 @@ func getSQLDateTime() string { func getSpecificSQLDateTime(t time.Time) string { return t.Format(mysqlDatetimeFormat) } - -func checkTableExists(tableName string) bool { - rows, err := querySQL("SELECT * FROM information_schema.tables WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? LIMIT 1", - config.DBname, tableName) - return err == nil && rows.Next() == true -} diff --git a/src/template.go b/src/template.go index 67c9babf..682be59d 100755 --- a/src/template.go +++ b/src/template.go @@ -1,7 +1,6 @@ package main import ( - "errors" "fmt" "html" "os" @@ -307,7 +306,7 @@ var ( func loadTemplate(files ...string) (*template.Template, error) { if len(files) == 0 { - return nil, errors.New("No files named in call to loadTemplate") + return nil, fmt.Errorf("No files named in call to loadTemplate") } var templates []string for i, file := range files { @@ -323,65 +322,83 @@ func loadTemplate(files ...string) (*template.Template, error) { } func templateError(name string, err error) error { - return errors.New("Failed loading template \"" + config.TemplateDir + "/" + name + ": \"" + err.Error()) + if err == nil { + return nil + } + return fmt.Errorf("Failed loading template '%s/%s': %s", config.TemplateDir, name, err.Error()) } -func initTemplates() error { +func initTemplates(which ...string) error { var err error - resetBoardSectionArrays() - banpage_tmpl, err = loadTemplate("banpage.html", "global_footer.html") - if err != nil { - return templateError("banpage.html", err) - } + buildAll := len(which) == 0 || which[0] == "all" - catalog_tmpl, err = loadTemplate("catalog.html", "img_header.html", "global_footer.html") - if err != nil { - return templateError("catalog.html", err) - } - - errorpage_tmpl, err = loadTemplate("error.html") - if err != nil { - return templateError("error.html", err) - } - - img_boardpage_tmpl, err = loadTemplate("img_boardpage.html", "img_header.html", "postbox.html", "global_footer.html") - if err != nil { - return templateError("img_boardpage.html", err) - } - - img_threadpage_tmpl, err = loadTemplate("img_threadpage.html", "img_header.html", "postbox.html", "global_footer.html") - if err != nil { - return templateError("img_threadpage.html", err) - } - - post_edit_tmpl, err = loadTemplate("post_edit.html", "img_header.html", "global_footer.html") - if err != nil { - return templateError("img_threadpage.html", err) - } - - manage_bans_tmpl, err = loadTemplate("manage_bans.html") - if err != nil { - return templateError("manage_bans.html", err) - } - - manage_boards_tmpl, err = loadTemplate("manage_boards.html") - if err != nil { - return templateError("manage_boards.html", err) - } - - manage_config_tmpl, err = loadTemplate("manage_config.html") - if err != nil { - return templateError("manage_config.html", err) - } - - manage_header_tmpl, err = loadTemplate("manage_header.html") - if err != nil { - return templateError("manage_header.html", err) - } - - front_page_tmpl, err = loadTemplate("front.html", "front_intro.html", "img_header.html", "global_footer.html") - if err != nil { - return templateError("front.html", err) + for _, t := range which { + if buildAll || t == "banpage" { + banpage_tmpl, err = loadTemplate("banpage.html", "global_footer.html") + if err != nil { + return templateError("banpage.html", err) + } + } + if buildAll || t == "catalog" { + catalog_tmpl, err = loadTemplate("catalog.html", "img_header.html", "global_footer.html") + if err != nil { + return templateError("catalog.html", err) + } + } + if buildAll || t == "error" { + errorpage_tmpl, err = loadTemplate("error.html") + if err != nil { + return templateError("error.html", err) + } + } + if buildAll || t == "front" { + front_page_tmpl, err = loadTemplate("front.html", "front_intro.html", "img_header.html", "global_footer.html") + if err != nil { + return templateError("front.html", err) + } + } + if buildAll || t == "boardpage" { + img_boardpage_tmpl, err = loadTemplate("img_boardpage.html", "img_header.html", "postbox.html", "global_footer.html") + if err != nil { + return templateError("img_boardpage.html", err) + } + } + if buildAll || t == "threadpage" { + img_threadpage_tmpl, err = loadTemplate("img_threadpage.html", "img_header.html", "postbox.html", "global_footer.html") + if err != nil { + return templateError("img_threadpage.html", err) + } + } + if buildAll || t == "postedit" { + post_edit_tmpl, err = loadTemplate("post_edit.html", "img_header.html", "global_footer.html") + if err != nil { + return templateError("img_threadpage.html", err) + } + } + if buildAll || t == "managebans" { + manage_bans_tmpl, err = loadTemplate("manage_bans.html") + if err != nil { + return templateError("manage_bans.html", err) + } + } + if buildAll || t == "manageboards" { + manage_boards_tmpl, err = loadTemplate("manage_boards.html") + if err != nil { + return templateError("manage_boards.html", err) + } + } + if buildAll || t == "manageconfig" { + manage_config_tmpl, err = loadTemplate("manage_config.html") + if err != nil { + return templateError("manage_config.html", err) + } + } + if buildAll || t == "manageheader" { + manage_header_tmpl, err = loadTemplate("manage_header.html") + if err != nil { + return templateError("manage_header.html", err) + } + } } return nil } diff --git a/src/types.go b/src/types.go index b339e530..09950edb 100644 --- a/src/types.go +++ b/src/types.go @@ -87,7 +87,7 @@ func (a *BanAppeal) GetBan() (BanInfo, error) { var ban BanInfo var err error - err = queryRowSQL("SELECT * FROM `"+config.DBprefix+"banlist` WHERE `id` = ? LIMIT 1", + err = queryRowSQL("SELECT * FROM "+config.DBprefix+"banlist WHERE id = ? LIMIT 1", []interface{}{a.ID}, []interface{}{ &ban.ID, &ban.AllowRead, &ban.IP, &ban.Name, &ban.NameIsRegex, &ban.SilentBan, &ban.Boards, &ban.Staff, &ban.Timestamp, &ban.Expires, &ban.Permaban, &ban.Reason, @@ -123,9 +123,9 @@ type BannedHash struct { type Board struct { ID int `json:"-"` - CurrentPage int `json:` + CurrentPage int `json:"-"` NumPages int `json:"pages"` - Order int `json:"-"` + ListOrder int `json:"-"` Dir string `json:"board"` Type int `json:"-"` UploadType int `json:"-"` @@ -159,7 +159,7 @@ type Board struct { type BoardSection struct { ID int - Order int + ListOrder int Hidden bool Name string Abbreviation string @@ -191,7 +191,7 @@ type Post struct { IP string `json:"-"` Capcode string `json:"capcode"` Timestamp time.Time `json:"time"` - Autosage int `json:"-"` + Autosage bool `json:"-"` DeletedTimestamp time.Time `json:"-"` Bumped time.Time `json:"last_modified"` Stickied bool `json:"-"` @@ -242,6 +242,7 @@ type Report struct { type LoginSession struct { ID uint + Name string Data string Expires string } @@ -291,13 +292,12 @@ type GochanConfig struct { TemplateDir string LogDir string - DBtype string - DBhost string - DBname string - DBusername string - DBpassword string - DBprefix string - DBkeepalive bool + DBtype string + DBhost string + DBname string + DBusername string + DBpassword string + DBprefix string Lockdown bool `description:"Disables posting." default:"unchecked"` LockdownMessage string `description:"Message displayed when someone tries to post while the site is on lockdown."` diff --git a/src/util.go b/src/util.go index acb7d54b..88f8b78e 100644 --- a/src/util.go +++ b/src/util.go @@ -25,7 +25,7 @@ import ( ) var ( - nullTime, _ = time.Parse("2006-01-02 15:04:05", "0000-00-00 00:00:00") + nullTime time.Time errEmptyDurationString = errors.New("Empty Duration string") errInvalidDurationString = errors.New("Invalid Duration string") durationRegexp = regexp.MustCompile(`^((\d+)\s?ye?a?r?s?)?\s?((\d+)\s?mon?t?h?s?)?\s?((\d+)\s?we?e?k?s?)?\s?((\d+)\s?da?y?s?)?\s?((\d+)\s?ho?u?r?s?)?\s?((\d+)\s?mi?n?u?t?e?s?)?\s?((\d+)\s?s?e?c?o?n?d?s?)?$`) @@ -91,11 +91,7 @@ func byteByByteReplace(input, from, to string) string { } // for easier defer cleaning -type Closeable interface { - Close() error -} - -func closeHandle(handle Closeable) { +func closeHandle(handle io.Closer) { if handle != nil && !reflect.ValueOf(handle).IsNil() { handle.Close() } @@ -120,10 +116,10 @@ func deleteMatchingFiles(root, match string) (filesDeleted int, err error) { return filesDeleted, err } -// getBoardArr performs a query against the database, and returns an array of BoardsTables along with an error value. +// getBoardArr performs a query against the database, and returns an array of Boards along with an error value. // If specified, the string where is added to the query, prefaced by WHERE. An example valid value is where = "id = 1". func getBoardArr(parameterList map[string]interface{}, extra string) (boards []Board, err error) { - queryString := "SELECT * FROM `" + config.DBprefix + "boards` " + queryString := "SELECT * FROM " + config.DBprefix + "boards " numKeys := len(parameterList) var parameterValues []interface{} if numKeys > 0 { @@ -131,7 +127,7 @@ func getBoardArr(parameterList map[string]interface{}, extra string) (boards []B } for key, value := range parameterList { - queryString += fmt.Sprintf("`%s` = ? AND ", key) + queryString += fmt.Sprintf("%s = ? AND ", key) parameterValues = append(parameterValues, value) } @@ -140,7 +136,7 @@ func getBoardArr(parameterList map[string]interface{}, extra string) (boards []B queryString = queryString[:len(queryString)-4] } - queryString += fmt.Sprintf(" %s ORDER BY `order`", extra) + queryString += fmt.Sprintf(" %s ORDER BY list_order", extra) rows, err := querySQL(queryString, parameterValues...) defer closeHandle(rows) @@ -149,13 +145,13 @@ func getBoardArr(parameterList map[string]interface{}, extra string) (boards []B return } - // For each row in the results from the database, populate a new BoardsTable instance, + // For each row in the results from the database, populate a new Board instance, // then append it to the boards array we are going to return for rows.Next() { board := new(Board) if err = rows.Scan( &board.ID, - &board.Order, + &board.ListOrder, &board.Dir, &board.Type, &board.UploadType, @@ -189,14 +185,13 @@ func getBoardArr(parameterList map[string]interface{}, extra string) (boards []B func getBoardFromID(id int) (*Board, error) { board := new(Board) - err := queryRowSQL( - "SELECT `order`,`dir`,`type`,`upload_type`,`title`,`subtitle`,`description`,`section`,"+ - "`max_image_size`,`max_pages`,`default_style`,`locked`,`created_on`,`anonymous`,`forced_anon`,`max_age`,"+ - "`autosage_after`,`no_images_after`,`max_message_length`,`embeds_allowed`,`redirect_to_thread`,`require_file`,"+ - "`enable_catalog` FROM `"+config.DBprefix+"boards` WHERE `id` = ?", + err := queryRowSQL("SELECT list_order,dir,type,upload_type,title,subtitle,description,section,"+ + "max_file_size,max_pages,default_style,locked,created_on,anonymous,forced_anon,max_age,"+ + "autosage_after,no_images_after,max_message_length,embeds_allowed,redirect_to_thread,require_file,"+ + "enable_catalog FROM "+config.DBprefix+"boards WHERE id = ?", []interface{}{id}, []interface{}{ - &board.Order, &board.Dir, &board.Type, &board.UploadType, &board.Title, + &board.ListOrder, &board.Dir, &board.Type, &board.UploadType, &board.Title, &board.Subtitle, &board.Description, &board.Section, &board.MaxFilesize, &board.MaxPages, &board.DefaultStyle, &board.Locked, &board.CreatedOn, &board.Anonymous, &board.ForcedAnon, &board.MaxAge, &board.AutosageAfter, @@ -210,7 +205,7 @@ func getBoardFromID(id int) (*Board, error) { // if parameterList is nil, ignore it and treat extra like a whole SQL query func getPostArr(parameterList map[string]interface{}, extra string) (posts []Post, err error) { - queryString := "SELECT * FROM `" + config.DBprefix + "posts` " + queryString := "SELECT * FROM " + config.DBprefix + "posts " numKeys := len(parameterList) var parameterValues []interface{} if numKeys > 0 { @@ -218,7 +213,7 @@ func getPostArr(parameterList map[string]interface{}, extra string) (posts []Pos } for key, value := range parameterList { - queryString += fmt.Sprintf("`%s` = ? AND ", key) + queryString += fmt.Sprintf("%s = ? AND ", key) parameterValues = append(parameterValues, value) } @@ -235,7 +230,7 @@ func getPostArr(parameterList map[string]interface{}, extra string) (posts []Pos return } - // For each row in the results from the database, populate a new PostTable instance, + // For each row in the results from the database, populate a new Post instance, // then append it to the posts array we are going to return for rows.Next() { var post Post @@ -257,18 +252,18 @@ func getPostArr(parameterList map[string]interface{}, extra string) (posts []Pos // TODO: replace where with a map[string]interface{} like getBoardsArr() func getSectionArr(where string) (sections []BoardSection, err error) { if where == "" { - where = "1" + where = "1 = 1" } - rows, err := querySQL("SELECT * FROM `" + config.DBprefix + "sections` WHERE " + where + " ORDER BY `order`") + rows, err := querySQL("SELECT * FROM " + config.DBprefix + "sections WHERE " + where + " ORDER BY list_order") defer closeHandle(rows) if err != nil { - errorLog.Print(err.Error()) + handleError(0, err.Error()) return } for rows.Next() { var section BoardSection - if err = rows.Scan(§ion.ID, §ion.Order, §ion.Hidden, §ion.Name, §ion.Abbreviation); err != nil { + if err = rows.Scan(§ion.ID, §ion.ListOrder, §ion.Hidden, §ion.Name, §ion.Abbreviation); err != nil { handleError(1, customError(err)) return } @@ -326,7 +321,7 @@ func getMetaInfo(stackOffset int) (string, int, string) { func customError(err error) string { if err != nil { - file, line, _ := getMetaInfo(1) + file, line, _ := getMetaInfo(2) return fmt.Sprintf("[ERROR] %s:%d: %s\n", file, line, err.Error()) } return "" @@ -360,14 +355,14 @@ func getThumbnailPath(thumbType string, img string) string { } // paginate returns a 2d array of a specified interface from a 1d array passed in, -// with a specified number of values per array in the 2d array. -// interface_length is the number of interfaces per array in the 2d array (e.g, threads per page) +// with a specified number of values per array in the 2d array. +// interfaceLength is the number of interfaces per array in the 2d array (e.g, threads per page) // interf is the array of interfaces to be split up. func paginate(interfaceLength int, interf []interface{}) [][]interface{} { - // paginated_interfaces = the finished interface array - // num_arrays = the current number of arrays (before remainder overflow) - // interfaces_remaining = if greater than 0, these are the remaining interfaces - // that will be added to the super-interface + // paginatedInterfaces = the finished interface array + // numArrays = the current number of arrays (before remainder overflow) + // interfacesRemaining = if greater than 0, these are the remaining interfaces + // that will be added to the super-interface var paginatedInterfaces [][]interface{} numArrays := len(interf) / interfaceLength @@ -478,15 +473,15 @@ func checkPostForSpam(userIP string, userAgent string, referrer string, req.Header.Set("User-Agent", "gochan/1.0 | Akismet/0.1") req.Header.Set("Content-Type", "application/x-www-form-urlencoded") resp, err := client.Do(req) - if err != nil { - handleError(1, err.Error()) - return "other_failure" - } defer func() { if resp != nil && resp.Body != nil { resp.Body.Close() } }() + if err != nil { + handleError(1, err.Error()) + return "other_failure" + } body, err := ioutil.ReadAll(resp.Body) if err != nil { handleError(1, err.Error()) @@ -508,33 +503,25 @@ func checkPostForSpam(userIP string, userAgent string, referrer string, return "other_failure" } -func marshalAPI(tag string, data interface{}, indent bool) (string, error) { - var apiMap map[string]interface{} - var apiBytes []byte +func marshalJSON(tag string, data interface{}, indent bool) (string, error) { + var jsonBytes []byte var err error + + if tag != "" { + data = map[string]interface{}{ + tag: data, + } + } if indent { - if tag == "" { - apiBytes, err = json.MarshalIndent(data, "", " ") - } else { - apiMap = make(map[string]interface{}) - apiMap[tag] = data - apiBytes, err = json.MarshalIndent(apiMap, "", " ") - } + jsonBytes, err = json.MarshalIndent(data, "", " ") } else { - if tag == "" { - apiBytes, err = json.Marshal(data) - } else { - apiMap := make(map[string]interface{}) - apiMap[tag] = data - apiBytes, err = json.Marshal(apiMap) - } + jsonBytes, err = json.Marshal(data) } + if err != nil { - apiBytes, _ = json.Marshal(map[string]string{ - "error": err.Error(), - }) + jsonBytes, _ = json.Marshal(map[string]string{"error": err.Error()}) } - return string(apiBytes), err + return string(jsonBytes), err } func limitArraySize(arr []string, maxSize int) []string { @@ -546,9 +533,10 @@ func limitArraySize(arr []string, maxSize int) []string { func numReplies(boardid, threadid int) int { var num int - if err := queryRowSQL("SELECT COUNT(*) FROM `"+config.DBprefix+"posts` WHERE `boardid` = ? AND `parentid` = ?", - []interface{}{boardid, threadid}, []interface{}{&num}, - ); err != nil { + + if err := queryRowSQL( + "SELECT COUNT(*) FROM "+config.DBprefix+"posts WHERE boardid = ? AND parentid = ?", + []interface{}{boardid, threadid}, []interface{}{&num}); err != nil { return 0 } return num diff --git a/templates/img_header.html b/templates/img_header.html index a9ee13a5..c244dd77 100644 --- a/templates/img_header.html +++ b/templates/img_header.html @@ -3,10 +3,12 @@ - {{with $.op.Subject}}{{if ne $.op.Subject ""}}/{{$.board.Dir}}/ - {{truncateString $.op.Subject 20 true}} - {{else if ne $.op.MessageHTML ""}}/{{$.board.Dir}}/ - {{truncateString $.op.MessageText 20 true}} - {{end}}{{else}}{{with $.recent_posts}}{{$.config.SiteName}} - {{else}}/{{.board.Dir}}/ - {{.board.Title}}{{end}}{{end}} + {{with .board}} + {{with $.op}} + {{if ne $.op.Subject ""}}/{{$.board.Dir}}/ - {{truncateString $.op.Subject 20 true}} + {{else if ne $.op.MessageHTML ""}}/{{$.board.Dir}}/ - {{truncateString $.op.MessageText 20 true}}{{end}} + {{else}}/{{$.board.Dir}}/ - {{$.board.Title}}{{end}} + {{else}}{{.config.SiteName}}{{end}}