1
0
Fork 0
mirror of https://github.com/Eggbertx/gochan.git synced 2025-08-24 08:46:24 -07:00

Add support for PostgreSQL and SQLite

This commit is contained in:
Eggbertx 2019-06-13 16:36:02 -07:00
parent 72774c48aa
commit 317f9d4ba0
19 changed files with 928 additions and 539 deletions

View file

@ -7,6 +7,6 @@
<h1>404: File not found</h1>
<img src="/error/lol 404.gif" border="0" alt="">
<p>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 <a href="/site">here</a></p>
<hr><address>http://gochan.org powered by Gochan v2.7.0</address>
<hr><address>http://gochan.org powered by Gochan v2.8.0</address>
</body>
</html>

View file

@ -7,6 +7,6 @@
<h1>500: Internal Server error</h1>
<img src="/error/derpy server.gif" border="0" alt="">
<p>The server encountered an error while trying to serve the page, and we apologize for the inconvenience. The <a href="https://en.wikipedia.org/wiki/Idiot">system administrator</a> will try to fix things as soon has he/she/it can.</p>
<hr><address>http://gochan.org powered by Gochan v2.7.0</address>
<hr><address>http://gochan.org powered by Gochan v2.8.0</address>
</body>
</html>

View file

@ -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;

177
initdb_postgres.sql Normal file
View file

@ -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)
);

162
initdb_sqlite3.sql Normal file
View file

@ -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
);

View file

@ -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)

View file

@ -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()) + "<br />\n"
}
@ -95,7 +105,7 @@ func buildBoardListJSON() (html string) {
if err != nil {
return handleError(1, "Failed marshal to JSON: "+err.Error()) + "<br />\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()) + "<br />\n"
}
return "Board list JSON rebuilt successfully.<br />"
@ -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()) + "<br />"
html += handleError(1, "Error getting OP posts for /%s/: %s", board.Dir, err.Error()) + "<br />\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() + "<br />\n"
html += handleError(1,
"Error getting replies to /%s/%d: %s",
board.Dir, op.ID, err.Error()) + "<br />\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() + "<br />\n"
html += handleError(1,
"Error getting number of image replies to /%s/%d: %s",
board.Dir, op.ID, err.Error()) + "<br />\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() + "<br />"
html += handleError(1,
"Error getting posts in /%s/%d: %s",
board.Dir, op.ID, err.Error()) + "<br />\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()) + "<br />"
html += handleError(1,
"Failed opening /%s/board.html: %s", board.Dir, err.Error()) + "<br />"
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

View file

@ -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)
}

View file

@ -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{}{&currentSession.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() + "<br />"
} 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 = "<img src=\"/css/purge.jpg\" />"
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() + "<br />"
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()) + "<br />\n"
return
}
if _, err = execSQL("ALTER TABLE `" + config.DBprefix + "posts` AUTO_INCREMENT = 1"); err != nil {
html += err.Error() + "<br />"
handleError(1, customError(err))
html += handleError(0, err.Error()) + "<br />\n"
return
}
html += "<br />Everything purged, rebuilding all<br />" +
@ -544,7 +545,7 @@ var manage_functions = map[string]ManageFunction{
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
html = "<h1 class=\"manage-header\">Announcements</h1><br />"
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 = "<h1 class=\"manage-header\">Manage boards</h1>\n<form action=\"/manage?action=boards\" method=\"POST\">\n<input type=\"hidden\" name=\"do\" value=\"existing\" /><select name=\"boardselect\">\n<option>Select board...</option>\n"
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 += handleError(1, err.Error())
@ -908,12 +866,12 @@ var manage_functions = map[string]ManageFunction{
}
html += "</select> <input type=\"submit\" value=\"Edit\" /> <input type=\"submit\" value=\"Delete\" /></form><hr />" +
"<h2 class=\"manage-header\">Create new board</h2>\n<span id=\"board-creation-message\">" + board_creation_status + "</span><br />"
"<h2 class=\"manage-header\">Create new board</h2>\n<span id=\"board-creation-message\">" + boardCreationStatus + "</span><br />"
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": {

View file

@ -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())

View file

@ -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 {

View file

@ -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
}

View file

@ -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
}

View file

@ -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."`

View file

@ -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(&section.ID, &section.Order, &section.Hidden, &section.Name, &section.Abbreviation); err != nil {
if err = rows.Scan(&section.ID, &section.ListOrder, &section.Hidden, &section.Name, &section.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

View file

@ -3,10 +3,12 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{{with $.op.Subject}}{{if ne $.op.Subject ""}}<title>/{{$.board.Dir}}/ - {{truncateString $.op.Subject 20 true}}</title>
{{else if ne $.op.MessageHTML ""}}<title>/{{$.board.Dir}}/ - {{truncateString $.op.MessageText 20 true}}</title>
{{end}}{{else}}{{with $.recent_posts}}<title>{{$.config.SiteName}}</title>
{{else}}<title>/{{.board.Dir}}/ - {{.board.Title}}</title>{{end}}{{end}}
{{with .board}}
{{with $.op}}
{{if ne $.op.Subject ""}}<title>/{{$.board.Dir}}/ - {{truncateString $.op.Subject 20 true}}</title>
{{else if ne $.op.MessageHTML ""}}<title>/{{$.board.Dir}}/ - {{truncateString $.op.MessageText 20 true}}</title>{{end}}
{{else}}<title>/{{$.board.Dir}}/ - {{$.board.Title}}</title>{{end}}
{{else}}<title>{{.config.SiteName}}</title>{{end}}
<script type="text/javascript" src="{{$.config.SiteWebfolder}}javascript/jquery-3.3.1.min.js"></script>
<script type="text/javascript">
var styles = [{{range $ii, $style := .config.Styles}}{{if gt $ii 0}}, {{end}}{Name: "{{$style.Name}}", Filename: "{{$style.Filename}}"}{{end}}];

View file

@ -7,13 +7,13 @@
<option value="{{$section.ID}}">{{$section.Name}}</option>{{end}}
</select></td></tr>
<tr><td>Order</td><td><input type="text" name="order" value="0" /></td></tr>
<tr><td>Title</td><td><input type="text" name="title" value="" /></td></tr>
<tr><td>Subtitle</td><td><input type="text" name="subtitle" value="" /></td></tr>
<tr><td>Description</td><td><input type="text" name="description" value="" /></td></tr>
<tr><td>Max image size</td><td><input type="text" name="maximagesize" value="4718592" /></td></tr>
<tr><td>Max pages</td><td><input type="text" name="maxpages" value="11" /></td></tr>
<tr><td>Title</td><td><input type="text" name="title" value="{{$.board.Title}}" /></td></tr>
<tr><td>Subtitle</td><td><input type="text" name="subtitle" value="{{$.board.Subtitle}}" /></td></tr>
<tr><td>Description</td><td><input type="text" name="description" value="{{$.board.Description}}" /></td></tr>
<tr><td>Max image size</td><td><input type="text" name="maximagesize" value="{{$.board.MaxFilesize}}" /></td></tr>
<tr><td>Max pages</td><td><input type="text" name="maxpages" value="{{$.board.MaxPages}}" /></td></tr>
<tr><td>Default style</td><td><select name="defaultstyle">{{range $_, $style := $.config.Styles}}
<option value="{{$style}}">{{$style}}</option>{{end}}
<option value="{{$style.Filename}}">{{$style.Name}} ({{$style.Filename}})</option>{{end}}
</select></td></tr>
<tr><td>Locked</td><td><input type="checkbox" name="locked" {{if $.board.Locked}}checked{{end}}/></td></tr>
<tr><td>Forced anonymity</td><td><input type="checkbox" name="forcedanon" {{if .board.ForcedAnon}}checked{{end}}/></td></tr>

View file

@ -4,55 +4,47 @@
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
export DBTYPE=mysql
export DBTYPE=postgresql
apt-get -y update && apt-get -y upgrade
if [ "$DBTYPE" == "mysql" ]; then
# Using MySQL (stable)
apt-get -y install mariadb-server mariadb-client
# Make sure any imported database is utf8mb4
# http://mathiasbynens.be/notes/mysql-utf8mb4
# Put in /etc/mysql/conf.d/local.cnf
cat << EOF >/etc/mysql/conf.d/local.cnf
[client]
default-character-set = utf8mb4
[mysql]
default-character-set = utf8mb4
[mysqld]
character-set-client-handshake = FALSE
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
default-storage-engine = innodb
EOF
mysql -uroot -e "CREATE DATABASE IF NOT EXISTS gochan; \
GRANT USAGE ON *.* TO gochan IDENTIFIED BY ''; \
GRANT ALL PRIVILEGES ON gochan.* TO gochan; \
SET PASSWORD FOR 'gochan'@'%' = PASSWORD('gochan');
FLUSH PRIVILEGES;"
cat << EOF >/etc/mysql/conf.d/open.cnf
[mysqld]
bind-address = 0.0.0.0
EOF
systemctl enable mysql
systemctl start mysql &
wait
elif [ "$DBTYPE" == "postgresql" ]; then
# apt-get -y install postgresql postgresql-contrib
# useradd gochan
# passwd -d gochan
# sudo -u postgres createuser -d gochan
echo "PostgreSQL not supported yet"
exit 1
# using PostgreSQL (mostly stable)
apt-get -y install postgresql postgresql-contrib
sudo -u postgres psql -f - << EOF
CREATE USER gochan PASSWORD 'gochan';
CREATE DATABASE gochan;
GRANT ALL PRIVILEGES ON DATABASE gochan TO gochan;
EOF
echo "127.0.0.1:5432:gochan:gochan:gochan" > /home/vagrant/.pgpass
chown vagrant:vagrant /home/vagrant/.pgpass
chmod 0600 /home/vagrant/.pgpass
systemctl enable postgresql
systemctl start postgresql &
wait
elif [ "$DBTYPE" == "sqlite3" ]; then
# using SQLite (mostly stable)
apt-get -y install sqlite3
elif [ "$DBTYPE" == "mssql" ]; then
echo "Microsoft SQL Server not supported yet";
exit 1
elif [ "$DBTYPE" == "sqlite" ]; then
echo "SQLite not supported yet"
exit 1
# using Microsoft SQL Server (currently unsupported)
echo "Microsoft SQL Server not supported yet";
exit 1
else
echo "Invalid DB type: $DBTYPE"
exit 1
echo "Invalid DB type: $DBTYPE"
exit 1
fi
apt-get -y install git subversion mercurial golang-1.10 nginx ffmpeg
@ -69,13 +61,13 @@ systemctl disable nginx
sed -i 's/WantedBy=multi-user.target/WantedBy=vagrant.mount/' /lib/systemd/system/nginx.service
systemctl daemon-reload
systemctl enable nginx
systemctl restart nginx mysql &
systemctl restart nginx &
wait
mkdir -p /vagrant/lib
cd /vagrant
su - vagrant
export GOCHAN_PATH=/home/vagrant/gochan
export GOPATH=/vagrant/lib
mkdir /home/vagrant/bin
@ -88,19 +80,28 @@ function changePerms {
}
function makeLink {
ln -sf /vagrant/$1 $GOCHAN_PATH/$1
ln -sf /vagrant/$1 $GOCHAN_PATH/
}
cat << EOF >>/home/vagrant/.bashrc
export GOPATH=/vagrant/lib
export GOCHAN_PATH=/home/vagrant/gochan
export GOPATH=$GOPATH
export GOCHAN_PATH=$GOCHAN_PATH
export DBTYPE=$DBTYPE
EOF
# a couple convenience shell scripts, since they're nice to have
cat << EOF >/home/vagrant/dbconnect.sh
#!/usr/bin/env bash
mysql -s -t -u gochan -D gochan -pgochan
if [ "$DBTYPE" = "mysql" ] || [ -z "$DBTYPE" ]; then
mysql -stu gochan -D gochan -pgochan
elif [ "$DBTYPE" = "postgresql" ]; then
psql -U gochan -h 127.0.0.1 gochan
elif [ "$DBTYPE" = "sqlite3" ]; then
sqlite3 ~/gochan/gochan.db
else
echo "DB type '$DBTYPE' not supported"
fi
EOF
cat << EOF >/home/vagrant/buildgochan.sh
@ -120,18 +121,19 @@ go get \
golang.org/x/net/html \
github.com/aquilax/tripcode \
golang.org/x/crypto/bcrypt \
github.com/frustra/bbcode
github.com/frustra/bbcode \
github.com/mattn/go-sqlite3
make debug
rm -f $GOCHAN_PATH/gochan
rm -f $GOCHAN_PATH/initdb.sql
rm -f $GOCHAN_PATH/initdb*.sql
install -m 775 -o vagrant -g vagrant -d $GOCHAN_PATH
makeLink html
makeLink log
makeLink gochan
makeLink templates
makeLink initdb.sql
ln -sf /vagrant/initdb*.sql $GOCHAN_PATH/
changePerms $GOCHAN_PATH
mkdir -p /home/vagrant/.config/systemd/user/
@ -144,7 +146,19 @@ sed /vagrant/gochan.example.json \
-e 's/"DBpassword": ""/"DBpassword": "gochan"/' \
-e 's/"RandomSeed": ""/"RandomSeed": "abc123"/' \
-e 's/"Verbosity": 0/"Verbosity": 1/' \
-e w\ $GOCHAN_PATH/gochan.json
-e "w $GOCHAN_PATH/gochan.json"
if [ "$DBTYPE" = "postgresql" ]; then
sed \
-e 's/"DBtype": ".*",/"DBtype": "postgres",/' \
-e 's/"DBhost": ".*",/"DBhost": "127.0.0.1",/' \
-i $GOCHAN_PATH/gochan.json
elif [ "$DBTYPE" = "sqlite3" ]; then
sed \
-e 's/"DBtype": ".*",/"DBtype": "sqlite3",/' \
-e 's/"DBhost": ".*",/"DBhost": "gochan.db",/' \
-i $GOCHAN_PATH/gochan.json
fi
echo
echo "Server set up, please run \"vagrant ssh\" on your host machine and"

View file

@ -1 +1 @@
2.7.0
2.8.0