mirror of
https://github.com/Eggbertx/gochan.git
synced 2025-09-16 07:56:24 -07:00
Replace JSON struct wrappers with struct tags
move (My)SQL initialization stuff to sql script to be run every time
This commit is contained in:
parent
bb9cb5a96c
commit
fa72398c0c
16 changed files with 438 additions and 694 deletions
24
ROADMAP.md
24
ROADMAP.md
|
@ -1,24 +0,0 @@
|
|||
# Gochan Development Roadmap
|
||||
|
||||
This is a rough roadmap to map out what I plan to focus on with the versions
|
||||
|
||||
|
||||
1.x
|
||||
----
|
||||
* Improve posting stability (done)
|
||||
* Add management functions to make things simpler
|
||||
* Add some kind of database schema to handle any possible changes in the database structure with new versions (if there are any)
|
||||
* Add PostgreSQL (and possibly SQLite) support
|
||||
* Add functionality to aid new admins in transferring boards from other imageboard systems (TinyBoard, Kusaba, etc) to Gochan. This would largely be a database issue (see above)
|
||||
* Add a manage function to modify the configuration without having to directly modify gochan.json directly (mostly done)
|
||||
|
||||
2.x
|
||||
----
|
||||
* Add functionality to go above and beyond what you would expect from an imageboard, without worrying about feature creep. Go's speed would allow us to do this without causing Gochan to slow down
|
||||
* Add a plugin system, likely using Lua to make it lightweight but still powerful. Go's package system would make this pretty easy and straightforward
|
||||
* Add a mange function to make it easier to update gochan. Not just the database schema but with the system in general.
|
||||
|
||||
3.x
|
||||
----
|
||||
* ???
|
||||
* I'll figure this out when we get to 2.x but feel free to put suggestions in the [issue page](https://github.com/Eggbertx/gochan/issues), or make a post at http://gochan.org/
|
|
@ -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.6.1</address>
|
||||
<hr><address>http://gochan.org powered by Gochan v2.7.0</address>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -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.6.1</address>
|
||||
<hr><address>http://gochan.org powered by Gochan v2.7.0</address>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
-- Initial setup file for Gochan
|
||||
|
||||
-- Turn off warnings in case tables are already there.
|
||||
SET sql_notes=0;
|
||||
-- Gochan MySQL startup/update script
|
||||
-- DO NOT DELETE
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `DBPREFIXannouncements` (
|
||||
`id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
@ -42,6 +40,18 @@ CREATE TABLE IF NOT EXISTS `DBPREFIXbanlist` (
|
|||
`can_appeal` TINYINT(1) NOT NULL DEFAULT '1',
|
||||
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 `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',
|
||||
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',
|
||||
DROP COLUMN IF EXISTS `message`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `DBPREFIXbannedhashes` (
|
||||
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
@ -62,7 +72,6 @@ CREATE TABLE IF NOT EXISTS `DBPREFIXboards` (
|
|||
`section` VARCHAR(45) NOT NULL,
|
||||
`max_image_size` INT UNSIGNED NOT NULL DEFAULT 4718592,
|
||||
`max_pages` TINYINT UNSIGNED NOT NULL DEFAULT 11,
|
||||
`locale` VARCHAR(10) NOT NULL DEFAULT 'en-us',
|
||||
`default_style` VARCHAR(45) NOT NULL,
|
||||
`locked` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0,
|
||||
`created_on` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
@ -79,6 +88,8 @@ CREATE TABLE IF NOT EXISTS `DBPREFIXboards` (
|
|||
PRIMARY KEY (`id`),
|
||||
UNIQUE (`dir`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=0;
|
||||
ALTER TABLE `DBPREFIXboards`
|
||||
DROP COLUMN IF EXISTS `locale`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `DBPREFIXembeds` (
|
||||
`id` TINYINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
@ -163,7 +174,6 @@ CREATE TABLE IF NOT EXISTS `DBPREFIXposts` (
|
|||
`tag` VARCHAR(5) NOT NULL DEFAULT '',
|
||||
`timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`autosage` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0,
|
||||
`poster_authority` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0,
|
||||
`deleted_timestamp` TIMESTAMP,
|
||||
`bumped` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`stickied` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0,
|
||||
|
@ -176,6 +186,9 @@ CREATE TABLE IF NOT EXISTS `DBPREFIXposts` (
|
|||
KEY `file_checksum` (`file_checksum`),
|
||||
KEY `stickied` (`stickied`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1;
|
||||
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,
|
||||
|
@ -219,12 +232,6 @@ CREATE TABLE IF NOT EXISTS `DBPREFIXstaff` (
|
|||
UNIQUE (`username`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- create a temp table with the same columns as the posts table to be stored in memory
|
||||
-- This is currently not used, and commented out.
|
||||
-- CREATE TABLE IF NOT EXISTS `DBPREFIXtempposts` SELECT * FROM DBPREFIXposts;
|
||||
-- ALTER TABLE `DBPREFIXtempposts` CHANGE `message` `message` VARCHAR(1024);
|
||||
-- ALTER TABLE `DBPREFIXtempposts` ENGINE=MEMORY;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `DBPREFIXwordfilters` (
|
||||
`id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`from` VARCHAR(75) NOT NULL,
|
45
src/api_test.go
Normal file
45
src/api_test.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Color struct {
|
||||
Red int `json:"red"`
|
||||
Green int `json:"green"`
|
||||
Blue int `json:"blue"`
|
||||
}
|
||||
|
||||
func TestAPI(t *testing.T) {
|
||||
var api string
|
||||
var err error
|
||||
|
||||
if api, err = marshalAPI("colorsSlice", []Color{
|
||||
Color{255, 0, 0},
|
||||
Color{0, 255, 0},
|
||||
Color{0, 0, 255},
|
||||
}, true); err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
fmt.Println("API slice: " + api)
|
||||
|
||||
if api, err = marshalAPI("colorsMap", map[string]Color{
|
||||
"red": Color{255, 0, 0},
|
||||
"green": Color{0, 255, 0},
|
||||
"blue": Color{0, 0, 255},
|
||||
}, true); err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
fmt.Println("API map: " + api)
|
||||
|
||||
if api, err = marshalAPI("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 {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
fmt.Println("API string: " + api)
|
||||
}
|
180
src/building.go
180
src/building.go
|
@ -19,7 +19,7 @@ func buildFrontPage() (html string) {
|
|||
|
||||
os.Remove(path.Join(config.DocumentRoot, "index.html"))
|
||||
front_file, err := os.OpenFile(path.Join(config.DocumentRoot, "index.html"), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777)
|
||||
defer closeFile(front_file)
|
||||
defer closeHandle(front_file)
|
||||
if err != nil {
|
||||
return handleError(1, "Failed opening front page for writing: "+err.Error()) + "<br />\n"
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ func buildFrontPage() (html string) {
|
|||
"ORDER BY `timestamp` DESC LIMIT ?"
|
||||
|
||||
rows, err := querySQL(recentQueryStr, nilTimestamp, config.MaxRecentPosts)
|
||||
defer closeRows(rows)
|
||||
defer closeHandle(rows)
|
||||
if err != nil {
|
||||
return handleError(1, err.Error())
|
||||
}
|
||||
|
@ -55,8 +55,7 @@ func buildFrontPage() (html string) {
|
|||
recentPostsArr = append(recentPostsArr, recentPost)
|
||||
}
|
||||
|
||||
for i := range allBoards {
|
||||
board := allBoards[i].(BoardsTable)
|
||||
for _, board := range allBoards {
|
||||
if board.Section == 0 {
|
||||
board.Section = 1
|
||||
}
|
||||
|
@ -75,29 +74,24 @@ 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 closeFile(board_list_file)
|
||||
defer closeHandle(board_list_file)
|
||||
if err != nil {
|
||||
return handleError(1, "Failed opening board.json for writing: "+err.Error()) + "<br />\n"
|
||||
}
|
||||
|
||||
board_list_wrapper := new(BoardJSONWrapper)
|
||||
|
||||
// Our cooldowns are site-wide currently.
|
||||
cooldowns_obj := BoardCooldowns{NewThread: config.NewThreadDelay, Reply: config.ReplyDelay, ImageReply: config.ReplyDelay}
|
||||
|
||||
for _, board_int := range allBoards {
|
||||
board := board_int.(BoardsTable)
|
||||
board_obj := BoardJSON{BoardName: board.Dir, Title: board.Title, WorkSafeBoard: 1,
|
||||
ThreadsPerPage: config.ThreadsPerPage, Pages: board.MaxPages, MaxFilesize: board.MaxImageSize,
|
||||
MaxMessageLength: board.MaxMessageLength, BumpLimit: 200, ImageLimit: board.NoImagesAfter,
|
||||
Cooldowns: cooldowns_obj, Description: board.Description, IsArchived: 0}
|
||||
if board.EnableNSFW {
|
||||
board_obj.WorkSafeBoard = 0
|
||||
}
|
||||
board_list_wrapper.Boards = append(board_list_wrapper.Boards, board_obj)
|
||||
boardsMap := map[string][]Board{
|
||||
"boards": []Board{},
|
||||
}
|
||||
|
||||
boardJSON, err := json.Marshal(board_list_wrapper)
|
||||
// Our cooldowns are site-wide currently.
|
||||
cooldowns := BoardCooldowns{NewThread: config.NewThreadDelay, Reply: config.ReplyDelay, ImageReply: config.ReplyDelay}
|
||||
|
||||
for _, board := range allBoards {
|
||||
board.Cooldowns = cooldowns
|
||||
boardsMap["boards"] = append(boardsMap["boards"], board)
|
||||
}
|
||||
|
||||
boardJSON, err := json.Marshal(boardsMap)
|
||||
if err != nil {
|
||||
return handleError(1, "Failed marshal to JSON: "+err.Error()) + "<br />\n"
|
||||
}
|
||||
|
@ -108,9 +102,9 @@ func buildBoardListJSON() (html string) {
|
|||
}
|
||||
|
||||
// buildBoardPages builds the pages for the board archive.
|
||||
// `board` is a BoardsTable object representing the board to build archive pages for.
|
||||
// `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 *BoardsTable) (html string) {
|
||||
func buildBoardPages(board *Board) (html string) {
|
||||
start_time := benchmarkTimer("buildBoard"+strconv.Itoa(board.ID), time.Now(), true)
|
||||
var current_page_file *os.File
|
||||
var threads []interface{}
|
||||
|
@ -148,7 +142,7 @@ func buildBoardPages(board *BoardsTable) (html string) {
|
|||
// For each top level post, start building a Thread struct
|
||||
for _, op := range op_posts {
|
||||
var thread Thread
|
||||
var posts_in_thread []PostTable
|
||||
var posts_in_thread []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` = ?",
|
||||
|
@ -188,7 +182,7 @@ func buildBoardPages(board *BoardsTable) (html string) {
|
|||
html += err.Error() + "<br />"
|
||||
}
|
||||
|
||||
var reversedPosts []PostTable
|
||||
var reversedPosts []Post
|
||||
for i := len(posts_in_thread); i > 0; i-- {
|
||||
reversedPosts = append(reversedPosts, posts_in_thread[i-1])
|
||||
}
|
||||
|
@ -254,10 +248,10 @@ func buildBoardPages(board *BoardsTable) (html string) {
|
|||
board.NumPages = len(thread_pages) - 1
|
||||
|
||||
// Create array of page wrapper objects, and open the file.
|
||||
var pages_obj []BoardPageJSON
|
||||
pagesArr := make([]map[string]interface{}, board.NumPages)
|
||||
|
||||
catalog_json_file, err := os.OpenFile(path.Join(config.DocumentRoot, board.Dir, "catalog.json"), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777)
|
||||
defer closeFile(catalog_json_file)
|
||||
defer closeHandle(catalog_json_file)
|
||||
if err != nil {
|
||||
html += handleError(1, "Failed opening /"+board.Dir+"/catalog.json: "+err.Error())
|
||||
return
|
||||
|
@ -270,7 +264,7 @@ func buildBoardPages(board *BoardsTable) (html string) {
|
|||
pageFilename := strconv.Itoa(board.CurrentPage) + ".html"
|
||||
current_page_filepath = path.Join(config.DocumentRoot, board.Dir, pageFilename)
|
||||
current_page_file, err = os.OpenFile(current_page_filepath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777)
|
||||
defer closeFile(current_page_file)
|
||||
defer closeHandle(current_page_file)
|
||||
if err != nil {
|
||||
html += handleError(1, "Failed opening board page: "+err.Error()) + "<br />"
|
||||
continue
|
||||
|
@ -284,7 +278,7 @@ func buildBoardPages(board *BoardsTable) (html string) {
|
|||
"threads": page_threads,
|
||||
"board": board,
|
||||
"posts": []interface{}{
|
||||
PostTable{BoardID: board.ID},
|
||||
Post{BoardID: board.ID},
|
||||
},
|
||||
}); err != nil {
|
||||
html += handleError(1, "Failed building /"+board.Dir+"/ boardpage: "+err.Error()) + "<br />"
|
||||
|
@ -300,38 +294,14 @@ func buildBoardPages(board *BoardsTable) (html string) {
|
|||
}
|
||||
|
||||
// Collect up threads for this page.
|
||||
var page_obj BoardPageJSON
|
||||
page_obj.Page = board.CurrentPage
|
||||
|
||||
for _, thread_int := range page_threads {
|
||||
thread := thread_int.(Thread)
|
||||
post_json := makePostJSON(thread.OP, board.Anonymous)
|
||||
var thread_json ThreadJSON
|
||||
thread_json.PostJSON = &post_json
|
||||
thread_json.Replies = thread.NumReplies
|
||||
thread_json.ImagesOnArchive = thread.NumImages
|
||||
thread_json.OmittedImages = thread.OmittedImages
|
||||
if thread.Stickied {
|
||||
if thread.NumReplies > config.StickyRepliesOnBoardPage {
|
||||
thread_json.OmittedPosts = thread.NumReplies - config.StickyRepliesOnBoardPage
|
||||
}
|
||||
thread_json.Sticky = 1
|
||||
} else {
|
||||
if thread.NumReplies > config.RepliesOnBoardPage {
|
||||
thread_json.OmittedPosts = thread.NumReplies - config.RepliesOnBoardPage
|
||||
}
|
||||
}
|
||||
if thread.OP.Locked {
|
||||
thread_json.Locked = 1
|
||||
}
|
||||
page_obj.Threads = append(page_obj.Threads, thread_json)
|
||||
}
|
||||
|
||||
pages_obj = append(pages_obj, page_obj)
|
||||
pageMap := make(map[string]interface{})
|
||||
pageMap["page"] = board.CurrentPage
|
||||
pageMap["threads"] = page_threads
|
||||
pagesArr = append(pagesArr, pageMap)
|
||||
}
|
||||
board.CurrentPage = currentBoardPage
|
||||
|
||||
catalog_json, err := json.Marshal(pages_obj)
|
||||
catalog_json, err := json.Marshal(pagesArr)
|
||||
if err != nil {
|
||||
html += handleError(1, "Failed to marshal to JSON: "+err.Error()) + "<br />"
|
||||
return
|
||||
|
@ -346,35 +316,34 @@ func buildBoardPages(board *BoardsTable) (html string) {
|
|||
return
|
||||
}
|
||||
|
||||
// buildBoards builds one or all boards.
|
||||
// If `all` == true, all boards will have their pages built and `which` is ignored
|
||||
// Otherwise, the board with the id equal to the value specified as which.
|
||||
// buildBoards builds the specified board IDs, or all boards if no arguments are passed
|
||||
// The return value is a string of HTML with debug information produced by the build process.
|
||||
// TODO: make this a variadic function (which ...int)
|
||||
func buildBoards(all bool, which int) (html string) {
|
||||
if all {
|
||||
boards, _ := getBoardArr(nil, "")
|
||||
if len(boards) == 0 {
|
||||
return html + "No boards to build.<br />\n"
|
||||
}
|
||||
for _, board := range boards {
|
||||
html += buildBoardPages(&board) + "<br />\n"
|
||||
if board.EnableCatalog {
|
||||
html += buildCatalog(board.ID) + "<br />\n"
|
||||
}
|
||||
func buildBoards(which ...int) (html string) {
|
||||
var boards []Board
|
||||
|
||||
html += buildThreads(true, board.ID, 0)
|
||||
}
|
||||
if which == nil {
|
||||
boards = allBoards
|
||||
} else {
|
||||
boardArr, _ := getBoardArr(map[string]interface{}{"id": which}, "")
|
||||
board := boardArr[0]
|
||||
html += buildBoardPages(&board) + "<br />\n"
|
||||
for _, b := range which {
|
||||
board, err := getBoardFromID(b)
|
||||
if err != nil {
|
||||
html += handleError(0, err.Error()) + "<br />\n"
|
||||
continue
|
||||
}
|
||||
boards = append(boards, *board)
|
||||
}
|
||||
}
|
||||
|
||||
if len(boards) == 0 {
|
||||
return html + "No boards to build.<br />\n"
|
||||
}
|
||||
for _, board := range boards {
|
||||
if board.EnableCatalog {
|
||||
html += buildCatalog(board.ID) + "<br />\n"
|
||||
}
|
||||
html += buildThreads(true, board.ID, 0)
|
||||
html += buildBoardPages(&board) + "<br />\n" +
|
||||
buildThreads(true, board.ID, 0) + "<br />\n"
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -417,9 +386,9 @@ func buildCatalog(which int) (html string) {
|
|||
return
|
||||
}
|
||||
|
||||
// buildThreadPages builds the pages for a thread given by a PostTable object.
|
||||
func buildThreadPages(op *PostTable) (html string) {
|
||||
var replies []PostTable
|
||||
// buildThreadPages builds the pages for a thread given by a Post object.
|
||||
func buildThreadPages(op *Post) (html string) {
|
||||
var replies []Post
|
||||
var current_page_file *os.File
|
||||
board, err := getBoardFromID(op.BoardID)
|
||||
if err != nil {
|
||||
|
@ -453,6 +422,7 @@ func buildThreadPages(op *PostTable) (html string) {
|
|||
html += handleError(1, "Failed opening "+current_page_filepath+": "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// render main page
|
||||
if err = img_threadpage_tmpl.Execute(current_page_file, map[string]interface{}{
|
||||
"config": config,
|
||||
|
@ -468,25 +438,22 @@ func buildThreadPages(op *PostTable) (html string) {
|
|||
|
||||
// Put together the thread JSON
|
||||
threadJSONFile, err := os.OpenFile(path.Join(config.DocumentRoot, board.Dir, "res", strconv.Itoa(op.ID)+".json"), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777)
|
||||
defer closeFile(threadJSONFile)
|
||||
defer closeHandle(threadJSONFile)
|
||||
if err != nil {
|
||||
html += handleError(1, "Failed opening /%s/res/%d.json: %s", board.Dir, op.ID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Create the wrapper object
|
||||
thread_json_wrapper := new(ThreadJSONWrapper)
|
||||
threadMap := make(map[string][]Post)
|
||||
|
||||
// Handle the OP, of type *PostTable
|
||||
op_post_obj := makePostJSON(*op, board.Anonymous)
|
||||
thread_json_wrapper.Posts = append(thread_json_wrapper.Posts, op_post_obj)
|
||||
// Handle the OP, of type *Post
|
||||
threadMap["posts"] = []Post{*op}
|
||||
|
||||
// Iterate through each reply, which are of type PostTable
|
||||
// Iterate through each reply, which are of type Post
|
||||
for _, reply := range replies {
|
||||
postJSON := makePostJSON(reply, board.Anonymous)
|
||||
thread_json_wrapper.Posts = append(thread_json_wrapper.Posts, postJSON)
|
||||
threadMap["posts"] = append(threadMap["posts"], reply)
|
||||
}
|
||||
threadJSON, err := json.Marshal(thread_json_wrapper)
|
||||
threadJSON, err := json.Marshal(threadMap)
|
||||
if err != nil {
|
||||
html += handleError(1, "Failed to marshal to JSON: %s", err.Error()) + "<br />"
|
||||
return
|
||||
|
@ -527,27 +494,24 @@ func buildThreadPages(op *PostTable) (html string) {
|
|||
|
||||
// buildThreads builds thread(s) given a boardid, or if all = false, also given a threadid.
|
||||
// if all is set to true, ignore which, otherwise, which = build only specified boardid
|
||||
// TODO: detect which page will be built and only build that one and the board page
|
||||
// TODO: make it variadic
|
||||
func buildThreads(all bool, boardid, threadid int) (html string) {
|
||||
if !all {
|
||||
threads, _ := getPostArr(map[string]interface{}{
|
||||
"boardid": boardid,
|
||||
"id": threadid,
|
||||
"parentid": 0,
|
||||
"deleted_timestamp": nilTimestamp,
|
||||
}, "")
|
||||
thread := threads[0]
|
||||
html += buildThreadPages(&thread) + "<br />\n"
|
||||
return
|
||||
}
|
||||
var threads []Post
|
||||
var err error
|
||||
|
||||
threads, _ := getPostArr(map[string]interface{}{
|
||||
queryMap := map[string]interface{}{
|
||||
"boardid": boardid,
|
||||
"parentid": 0,
|
||||
"deleted_timestamp": nilTimestamp,
|
||||
}, "")
|
||||
}
|
||||
if !all {
|
||||
queryMap["id"] = threadid
|
||||
}
|
||||
if threads, err = getPostArr(queryMap, ""); err != nil {
|
||||
return handleError(0, err.Error()) + "<br />\n"
|
||||
}
|
||||
if len(threads) == 0 {
|
||||
return
|
||||
return "No threads to build<br />\n"
|
||||
}
|
||||
|
||||
for _, op := range threads {
|
||||
|
|
|
@ -11,11 +11,7 @@ var versionStr string
|
|||
var buildtimeString string // set in Makefile, format: YRMMDD.HHMM
|
||||
|
||||
func main() {
|
||||
defer func() {
|
||||
if db != nil {
|
||||
_ = db.Close()
|
||||
}
|
||||
}()
|
||||
defer closeHandle(db)
|
||||
initConfig()
|
||||
connectToSQLServer()
|
||||
parseCommandLine()
|
||||
|
@ -27,11 +23,6 @@ func main() {
|
|||
os.Exit(2)
|
||||
}
|
||||
|
||||
println(0, "Initializing server...")
|
||||
if _, err := db.Exec("USE `" + config.DBname + "`"); err != nil {
|
||||
handleError(0, customError(err))
|
||||
os.Exit(2)
|
||||
}
|
||||
initServer()
|
||||
}
|
||||
|
||||
|
|
110
src/manage.go
110
src/manage.go
|
@ -84,25 +84,25 @@ func getCurrentStaff(request *http.Request) (string, error) {
|
|||
return "", nil
|
||||
}
|
||||
key := sessionCookie.Value
|
||||
current_session := new(SessionsTable)
|
||||
currentSession := new(LoginSession)
|
||||
if err := queryRowSQL(
|
||||
"SELECT `data` FROM `"+config.DBprefix+"sessions` WHERE `key` = ?",
|
||||
[]interface{}{key},
|
||||
[]interface{}{¤t_session.Data},
|
||||
[]interface{}{¤tSession.Data},
|
||||
); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return current_session.Data, nil
|
||||
return currentSession.Data, nil
|
||||
}
|
||||
|
||||
func getStaff(name string) (*StaffTable, error) {
|
||||
staff_obj := new(StaffTable)
|
||||
func getStaff(name string) (*Staff, error) {
|
||||
staff := new(Staff)
|
||||
err := queryRowSQL(
|
||||
"SELECT * FROM `"+config.DBprefix+"staff` WHERE `username` = ?",
|
||||
[]interface{}{name},
|
||||
[]interface{}{&staff_obj.ID, &staff_obj.Username, &staff_obj.PasswordChecksum, &staff_obj.Salt, &staff_obj.Rank, &staff_obj.Boards, &staff_obj.AddedOn, &staff_obj.LastActive},
|
||||
[]interface{}{&staff.ID, &staff.Username, &staff.PasswordChecksum, &staff.Salt, &staff.Rank, &staff.Boards, &staff.AddedOn, &staff.LastActive},
|
||||
)
|
||||
return staff_obj, err
|
||||
return staff, err
|
||||
}
|
||||
|
||||
func getStaffRank(request *http.Request) int {
|
||||
|
@ -200,7 +200,7 @@ var manage_functions = map[string]ManageFunction{
|
|||
|
||||
html += "Optimizing all tables in database.<hr />"
|
||||
tableRows, tablesErr := querySQL("SHOW TABLES")
|
||||
defer closeRows(tableRows)
|
||||
defer closeHandle(tableRows)
|
||||
if tablesErr != nil {
|
||||
html += "<tr><td>" + tablesErr.Error() + "</td></tr></table>"
|
||||
return
|
||||
|
@ -438,7 +438,7 @@ var manage_functions = map[string]ManageFunction{
|
|||
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`")
|
||||
defer closeRows(rows)
|
||||
defer closeHandle(rows)
|
||||
if err != nil {
|
||||
html += err.Error()
|
||||
handleError(1, customError(err))
|
||||
|
@ -484,7 +484,7 @@ var manage_functions = map[string]ManageFunction{
|
|||
return
|
||||
}
|
||||
html += "<br />Everything purged, rebuilding all<br />" +
|
||||
buildBoards(true, 0) + "<hr />\n" +
|
||||
buildBoards() + "<hr />\n" +
|
||||
buildFrontPage()
|
||||
return
|
||||
}},
|
||||
|
@ -545,14 +545,14 @@ var manage_functions = map[string]ManageFunction{
|
|||
html = "<h1 class=\"manage-header\">Announcements</h1><br />"
|
||||
|
||||
rows, err := querySQL("SELECT `subject`,`message`,`poster`,`timestamp` FROM `" + config.DBprefix + "announcements` ORDER BY `id` DESC")
|
||||
defer closeRows(rows)
|
||||
defer closeHandle(rows)
|
||||
if err != nil {
|
||||
html += handleError(1, err.Error())
|
||||
return
|
||||
}
|
||||
iterations := 0
|
||||
for rows.Next() {
|
||||
announcement := new(AnnouncementsTable)
|
||||
announcement := new(Announcement)
|
||||
err = rows.Scan(&announcement.Subject, &announcement.Message, &announcement.Poster, &announcement.Timestamp)
|
||||
if err != nil {
|
||||
html += handleError(1, err.Error())
|
||||
|
@ -572,7 +572,7 @@ var manage_functions = map[string]ManageFunction{
|
|||
"bans": {
|
||||
Permissions: 1,
|
||||
Callback: func(writer http.ResponseWriter, request *http.Request) (pageHTML string) {
|
||||
var post PostTable
|
||||
var post Post
|
||||
if request.FormValue("do") == "add" {
|
||||
ip := net.ParseIP(request.FormValue("ip"))
|
||||
name := request.FormValue("name")
|
||||
|
@ -641,15 +641,15 @@ 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`")
|
||||
defer closeRows(rows)
|
||||
defer closeHandle(rows)
|
||||
if err != nil {
|
||||
pageHTML += handleError(1, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var banlist []BanlistTable
|
||||
var banlist []BanInfo
|
||||
for rows.Next() {
|
||||
var ban BanlistTable
|
||||
var ban BanInfo
|
||||
rows.Scan(&ban.IP, &ban.Name, &ban.Reason, &ban.Boards, &ban.Staff, &ban.Timestamp, &ban.Expires, &ban.Permaban, &ban.CanAppeal)
|
||||
banlist = append(banlist, ban)
|
||||
}
|
||||
|
@ -677,7 +677,7 @@ var manage_functions = map[string]ManageFunction{
|
|||
html = "nobody;0;"
|
||||
return
|
||||
}
|
||||
staff := new(StaffTable)
|
||||
staff := new(Staff)
|
||||
if err := queryRowSQL("SELECT `rank`,`boards` FROM `"+config.DBprefix+"staff` WHERE `username` = ?",
|
||||
[]interface{}{current_staff},
|
||||
[]interface{}{&staff.Rank, &staff.Boards},
|
||||
|
@ -693,7 +693,7 @@ var manage_functions = map[string]ManageFunction{
|
|||
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
|
||||
do := request.FormValue("do")
|
||||
var done bool
|
||||
board := new(BoardsTable)
|
||||
board := new(Board)
|
||||
var board_creation_status string
|
||||
var err error
|
||||
var rows *sql.Rows
|
||||
|
@ -729,9 +729,9 @@ var manage_functions = map[string]ManageFunction{
|
|||
if err != nil {
|
||||
board.Section = 0
|
||||
}
|
||||
board.MaxImageSize, err = strconv.Atoi(request.FormValue("maximagesize"))
|
||||
board.MaxFilesize, err = strconv.Atoi(request.FormValue("maximagesize"))
|
||||
if err != nil {
|
||||
board.MaxImageSize = 1024 * 4
|
||||
board.MaxFilesize = 1024 * 4
|
||||
}
|
||||
|
||||
board.MaxPages, err = strconv.Atoi(request.FormValue("maxpages"))
|
||||
|
@ -800,17 +800,17 @@ var manage_functions = map[string]ManageFunction{
|
|||
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`,`locale`,`default_style`,`locked`,`created_on`,"+
|
||||
"`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`) "+
|
||||
"VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
&board.Order, &board.Dir, &board.Type, &board.UploadType,
|
||||
&board.Title, &board.Subtitle, &board.Description, &board.Section,
|
||||
&board.MaxImageSize, &board.MaxPages, &board.Locale, &board.DefaultStyle,
|
||||
&board.Locked, &boardCreationTimestamp, &board.Anonymous,
|
||||
&board.ForcedAnon, &board.MaxAge, &board.AutosageAfter,
|
||||
&board.NoImagesAfter, &board.MaxMessageLength, &board.EmbedsAllowed,
|
||||
&board.RedirectToThread, &board.RequireFile, &board.EnableCatalog,
|
||||
"VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
board.Order, 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,
|
||||
board.ForcedAnon, board.MaxAge, board.AutosageAfter,
|
||||
board.NoImagesAfter, board.MaxMessageLength, board.EmbedsAllowed,
|
||||
board.RedirectToThread, board.RequireFile, board.EnableCatalog,
|
||||
); err != nil {
|
||||
do = ""
|
||||
board_creation_status = handleError(1, "Error creating board: "+customError(err))
|
||||
|
@ -818,7 +818,7 @@ var manage_functions = map[string]ManageFunction{
|
|||
} else {
|
||||
board_creation_status = "Board created successfully"
|
||||
println(2, board_creation_status)
|
||||
buildBoards(true, 0)
|
||||
buildBoards()
|
||||
resetBoardSectionArrays()
|
||||
println(2, "Boards rebuilt successfully")
|
||||
done = true
|
||||
|
@ -831,7 +831,7 @@ var manage_functions = map[string]ManageFunction{
|
|||
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 closeRows(rows)
|
||||
defer closeHandle(rows)
|
||||
if err != nil {
|
||||
html += handleError(1, "Error getting column names from boards table:"+err.Error())
|
||||
return
|
||||
|
@ -862,11 +862,9 @@ var manage_functions = map[string]ManageFunction{
|
|||
case "section":
|
||||
board.Section = columnDefaultInt
|
||||
case "max_image_size":
|
||||
board.MaxImageSize = columnDefaultInt
|
||||
board.MaxFilesize = columnDefaultInt
|
||||
case "max_pages":
|
||||
board.MaxPages = columnDefaultInt
|
||||
case "locale":
|
||||
board.Locale = columnDefault
|
||||
case "default_style":
|
||||
board.DefaultStyle = columnDefault
|
||||
case "locked":
|
||||
|
@ -897,7 +895,7 @@ var manage_functions = map[string]ManageFunction{
|
|||
|
||||
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`")
|
||||
defer closeRows(rows)
|
||||
defer closeHandle(rows)
|
||||
if err != nil {
|
||||
html += handleError(1, err.Error())
|
||||
return
|
||||
|
@ -976,13 +974,13 @@ var manage_functions = map[string]ManageFunction{
|
|||
resetBoardSectionArrays()
|
||||
return buildFrontPage() + "<hr />\n" +
|
||||
buildBoardListJSON() + "<hr />\n" +
|
||||
buildBoards(true, 0) + "<hr />\n"
|
||||
buildBoards() + "<hr />\n"
|
||||
}},
|
||||
"rebuildboards": {
|
||||
Permissions: 3,
|
||||
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
|
||||
initTemplates()
|
||||
return buildBoards(true, 0)
|
||||
return buildBoards()
|
||||
}},
|
||||
"reparsehtml": {
|
||||
Permissions: 3,
|
||||
|
@ -1007,7 +1005,7 @@ var manage_functions = map[string]ManageFunction{
|
|||
html += "Done reparsing HTML<hr />" +
|
||||
buildFrontPage() + "<hr />\n" +
|
||||
buildBoardListJSON() + "<hr />\n" +
|
||||
buildBoards(true, 0) + "<hr />\n"
|
||||
buildBoards() + "<hr />\n"
|
||||
return
|
||||
}},
|
||||
"recentposts": {
|
||||
|
@ -1037,7 +1035,7 @@ var manage_functions = map[string]ManageFunction{
|
|||
"ORDER BY `timestamp` DESC LIMIT ?",
|
||||
nilTimestamp, limit,
|
||||
)
|
||||
defer closeRows(rows)
|
||||
defer closeHandle(rows)
|
||||
if err != nil {
|
||||
html += "<tr><td>" + handleError(1, err.Error()) + "</td></tr></table>"
|
||||
return
|
||||
|
@ -1070,36 +1068,28 @@ var manage_functions = map[string]ManageFunction{
|
|||
"dir": boardDir,
|
||||
}, "")
|
||||
if err != nil {
|
||||
var jsonErr ErrorJSON
|
||||
jsonErr.Message = err.Error()
|
||||
jsonBytes, _ := json.Marshal(jsonErr)
|
||||
return string(jsonBytes)
|
||||
jsonErr, _ := marshalAPI("error", err.Error(), false)
|
||||
return jsonErr
|
||||
}
|
||||
if len(boards) < 1 {
|
||||
var jsonErr ErrorJSON
|
||||
jsonErr.Message = "Board doesn't exist."
|
||||
jsonBytes, _ := json.Marshal(jsonErr)
|
||||
return string(jsonBytes)
|
||||
jsonErr, _ := marshalAPI("error", "Board doesn't exist.", false)
|
||||
return jsonErr
|
||||
}
|
||||
|
||||
posts, err := getPostArr(map[string]interface{}{
|
||||
"id": request.FormValue("postid"),
|
||||
"boardid": boards[0].ID,
|
||||
}, "")
|
||||
if err != nil {
|
||||
var jsonErr ErrorJSON
|
||||
jsonErr.Message = err.Error()
|
||||
jsonBytes, _ := json.Marshal(jsonErr)
|
||||
return string(jsonBytes)
|
||||
jsonErr, _ := marshalAPI("error", err.Error(), false)
|
||||
return jsonErr
|
||||
}
|
||||
if len(posts) < 1 {
|
||||
var jsonErr ErrorJSON
|
||||
jsonErr.Message = "Post doesn't exist."
|
||||
jsonBytes, _ := json.Marshal(jsonErr)
|
||||
return string(jsonBytes)
|
||||
jsonErr, _ := marshalAPI("eror", "Post doesn't exist.", false)
|
||||
return jsonErr
|
||||
}
|
||||
jsonBytes, _ := json.Marshal(posts[0])
|
||||
|
||||
return string(jsonBytes)
|
||||
jsonStr, _ := marshalAPI("", posts[0], false)
|
||||
return jsonStr
|
||||
}},
|
||||
"staff": {
|
||||
Permissions: 3,
|
||||
|
@ -1109,7 +1099,7 @@ var manage_functions = map[string]ManageFunction{
|
|||
"<table id=\"stafftable\" border=\"1\">\n" +
|
||||
"<tr><td><b>Username</b></td><td><b>Rank</b></td><td><b>Boards</b></td><td><b>Added on</b></td><td><b>Action</b></td></tr>\n"
|
||||
rows, err := querySQL("SELECT `username`,`rank`,`boards`,`added_on` FROM `" + config.DBprefix + "staff`")
|
||||
defer closeRows(rows)
|
||||
defer closeHandle(rows)
|
||||
if err != nil {
|
||||
html += "<tr><td>" + handleError(1, err.Error()) + "</td></tr></table>"
|
||||
return
|
||||
|
@ -1117,7 +1107,7 @@ var manage_functions = map[string]ManageFunction{
|
|||
|
||||
iter := 1
|
||||
for rows.Next() {
|
||||
staff := new(StaffTable)
|
||||
staff := new(Staff)
|
||||
if err = rows.Scan(&staff.Username, &staff.Rank, &staff.Boards, &staff.AddedOn); err != nil {
|
||||
handleError(1, err.Error())
|
||||
return err.Error()
|
||||
|
|
|
@ -34,8 +34,8 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
allSections []interface{}
|
||||
allBoards []interface{}
|
||||
allSections []BoardSection
|
||||
allBoards []Board
|
||||
)
|
||||
|
||||
// bumps the given thread on the given board and returns true if there were no errors
|
||||
|
@ -47,10 +47,10 @@ func bumpThread(postID, boardID int) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Checks check poster's name/tripcode/file checksum (from PostTable post) for banned status
|
||||
// Checks check poster's name/tripcode/file checksum (from Post post) for banned status
|
||||
// returns ban table if the user is banned or errNotBanned if they aren't
|
||||
func getBannedStatus(request *http.Request) (BanlistTable, error) {
|
||||
var banEntry BanlistTable
|
||||
func getBannedStatus(request *http.Request) (BanInfo, error) {
|
||||
var banEntry BanInfo
|
||||
|
||||
formName := request.FormValue("postname")
|
||||
var tripcode string
|
||||
|
@ -103,7 +103,7 @@ func getBannedStatus(request *http.Request) (BanlistTable, error) {
|
|||
return banEntry, err
|
||||
}
|
||||
|
||||
func isBanned(ban BanlistTable, board string) bool {
|
||||
func isBanned(ban BanInfo, board string) bool {
|
||||
if ban.Boards == "" && (ban.Expires.After(time.Now()) || ban.Permaban) {
|
||||
return true
|
||||
}
|
||||
|
@ -117,7 +117,7 @@ func isBanned(ban BanlistTable, board string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func sinceLastPost(post *PostTable) int {
|
||||
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",
|
||||
[]interface{}{post.IP},
|
||||
|
@ -243,17 +243,17 @@ func parseName(name string) map[string]string {
|
|||
}
|
||||
|
||||
// inserts prepared post object into the SQL table so that it can be rendered
|
||||
func insertPost(post PostTable, bump bool) (sql.Result, error) {
|
||||
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`,`poster_authority`,`deleted_timestamp`,`bumped`,`stickied`,`locked`,`reviewed`,`sillytag`)"+
|
||||
"VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
"(`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(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
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.Tag,
|
||||
post.Timestamp, post.Autosage, post.PosterAuthority, post.DeletedTimestamp,
|
||||
post.Bumped, post.Stickied, post.Locked, post.Reviewed, post.Sillytag,
|
||||
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,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
|
@ -274,7 +274,7 @@ func insertPost(post PostTable, bump bool) (sql.Result, error) {
|
|||
func makePost(writer http.ResponseWriter, request *http.Request) {
|
||||
startTime := benchmarkTimer("makePost", time.Now(), true)
|
||||
var maxMessageLength int
|
||||
var post PostTable
|
||||
var post Post
|
||||
domain := request.Host
|
||||
var formName string
|
||||
var nameCookie string
|
||||
|
@ -343,7 +343,7 @@ func makePost(writer http.ResponseWriter, request *http.Request) {
|
|||
|
||||
post.IP = getRealIP(request)
|
||||
post.Timestamp = time.Now()
|
||||
post.PosterAuthority = getStaffRank(request)
|
||||
// post.PosterAuthority = getStaffRank(request)
|
||||
post.Bumped = time.Now()
|
||||
post.Stickied = request.FormValue("modstickied") == "on"
|
||||
post.Locked = request.FormValue("modlocked") == "on"
|
||||
|
@ -597,7 +597,7 @@ func makePost(writer http.ResponseWriter, request *http.Request) {
|
|||
post.ID = int(postid)
|
||||
|
||||
// rebuild the board page
|
||||
buildBoards(false, post.BoardID)
|
||||
buildBoards(post.BoardID)
|
||||
buildFrontPage()
|
||||
|
||||
if emailCommand == "noko" {
|
||||
|
@ -659,7 +659,7 @@ func formatMessage(message string) string {
|
|||
return strings.Join(postLines, "<br />")
|
||||
}
|
||||
|
||||
func bannedForever(ban BanlistTable) bool {
|
||||
func bannedForever(ban BanInfo) bool {
|
||||
return ban.Permaban && !ban.CanAppeal && ban.Type == 3 && ban.Boards == ""
|
||||
}
|
||||
|
||||
|
@ -694,7 +694,7 @@ func banHandler(writer http.ResponseWriter, request *http.Request) {
|
|||
|
||||
banpageBuffer.Write([]byte(""))
|
||||
if err = banpage_tmpl.Execute(&banpageBuffer, map[string]interface{}{
|
||||
"config": config, "ban": banStatus, "banBoards": banStatus.Boards, "post": PostTable{},
|
||||
"config": config, "ban": banStatus, "banBoards": banStatus.Boards, "post": Post{},
|
||||
}); err != nil {
|
||||
fmt.Fprintf(writer, handleError(1, err.Error())+"\n</body>\n</html>")
|
||||
return
|
||||
|
|
|
@ -58,6 +58,7 @@ func (s GochanServer) serveFile(writer http.ResponseWriter, request *http.Reques
|
|||
writer.Header().Add("Content-Type", "image/gif")
|
||||
writer.Header().Add("Cache-Control", "max-age=86400")
|
||||
case "jpg":
|
||||
fallthrough
|
||||
case "jpeg":
|
||||
writer.Header().Add("Content-Type", "image/jpeg")
|
||||
writer.Header().Add("Cache-Control", "max-age=86400")
|
||||
|
@ -73,8 +74,9 @@ func (s GochanServer) serveFile(writer http.ResponseWriter, request *http.Reques
|
|||
case "webm":
|
||||
writer.Header().Add("Content-Type", "video/webm")
|
||||
writer.Header().Add("Cache-Control", "max-age=86400")
|
||||
}
|
||||
if strings.HasPrefix(extension, "htm") {
|
||||
case "htm":
|
||||
fallthrough
|
||||
case "html":
|
||||
writer.Header().Add("Content-Type", "text/html")
|
||||
writer.Header().Add("Cache-Control", "max-age=5, must-revalidate")
|
||||
}
|
||||
|
@ -133,7 +135,7 @@ func initServer() {
|
|||
// Check if Akismet API key is usable at startup.
|
||||
if err = checkAkismetAPIKey(config.AkismetAPIKey); err != nil {
|
||||
config.AkismetAPIKey = ""
|
||||
handleError(0, err.Error())
|
||||
// handleError(0, err.Error())
|
||||
}
|
||||
|
||||
// Compile regex for checking referrers.
|
||||
|
@ -148,7 +150,6 @@ func initServer() {
|
|||
http.Redirect(writer, request, "https://www.youtube.com/watch?v=dQw4w9WgXcQ", http.StatusFound)
|
||||
}
|
||||
})
|
||||
|
||||
// eventually plugins will be able to register new namespaces. Or they will be restricted to something like /plugin
|
||||
|
||||
if config.UseFastCGI {
|
||||
|
@ -221,7 +222,7 @@ func utilHandler(writer http.ResponseWriter, request *http.Request) {
|
|||
}
|
||||
passwordMD5 := md5Sum(password)
|
||||
|
||||
var post PostTable
|
||||
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` = ?",
|
||||
|
@ -292,7 +293,7 @@ func utilHandler(writer http.ResponseWriter, request *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
buildBoards(false, boardid)
|
||||
buildBoards(boardid)
|
||||
if request.FormValue("parentid") == "0" {
|
||||
http.Redirect(writer, request, "/"+board.Dir+"/res/"+strconv.Itoa(postid)+".html", http.StatusFound)
|
||||
} else {
|
||||
|
@ -316,7 +317,7 @@ func utilHandler(writer http.ResponseWriter, request *http.Request) {
|
|||
for _, checkedPostID := range postsArr {
|
||||
var fileType string
|
||||
var thumbType string
|
||||
var post PostTable
|
||||
var post Post
|
||||
var err error
|
||||
post.ID, _ = strconv.Atoi(checkedPostID)
|
||||
post.BoardID, _ = strconv.Atoi(boardid)
|
||||
|
@ -423,7 +424,7 @@ func utilHandler(writer http.ResponseWriter, request *http.Request) {
|
|||
os.Remove(path.Join(config.DocumentRoot, board, "/thumb/", strings.Replace(deletedFilename, ".", "c.", -1)))
|
||||
}
|
||||
|
||||
buildBoards(false, post.BoardID)
|
||||
buildBoards(post.BoardID)
|
||||
|
||||
writer.Header().Add("refresh", "4;url="+request.Referer())
|
||||
fmt.Fprintf(writer, "%d deleted successfully\n", post.ID)
|
||||
|
|
130
src/sql.go
130
src/sql.go
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -23,66 +24,72 @@ var (
|
|||
|
||||
func connectToSQLServer() {
|
||||
var err error
|
||||
var sqlVersionStr 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 {
|
||||
handleError(0, "Failed to connect to the database: %s\n", customError(err))
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
if !checkTableExists(config.DBprefix+"info") {
|
||||
err = sql.ErrNoRows
|
||||
} else {
|
||||
err = queryRowSQL("SELECT `value` FROM `"+config.DBprefix+"info` WHERE `name` = 'version'",
|
||||
[]interface{}{}, []interface{}{&sqlVersionStr})
|
||||
if err = initDB(); err != nil {
|
||||
handleError(0, "Failed initializing DB: %s\n", customError(err))
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
var sqlVersionStr string
|
||||
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 = loadInitialSQL(); err != nil {
|
||||
handleError(0, "failed with error: %s\n", customError(err))
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
if _, err = db.Exec("INSERT INTO `" + config.DBname + "`.`" + config.DBprefix + "staff` " +
|
||||
"(`username`, `password_checksum`, `salt`, `rank`) " +
|
||||
"VALUES ('admin', '" + bcryptSum("password") + "', 'abc', 3)",
|
||||
); err != nil {
|
||||
handleError(0, "failed with error: %s\n", customError(err))
|
||||
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)
|
||||
if err != nil && !strings.Contains(err.Error(), "Duplicate entry") {
|
||||
handleError(0, "failed with error: %s\n", customError(err))
|
||||
os.Exit(2)
|
||||
}
|
||||
return
|
||||
} else if err != nil {
|
||||
handleError(0, "failed: %s\n", customError(err))
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
checkDeprecatedSchema(sqlVersionStr)
|
||||
}
|
||||
|
||||
func loadInitialSQL() error {
|
||||
var err error
|
||||
if _, err = os.Stat("initialsetupdb.sql"); err != nil {
|
||||
return errors.New("Initial setup file (initialsetupdb.sql) missing. Please reinstall gochan")
|
||||
if err != nil && !strings.Contains(err.Error(), "Duplicate entry") {
|
||||
handleError(0, "failed with error: %s\n", customError(err))
|
||||
os.Exit(2)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
initialSQLBytes, err := ioutil.ReadFile("initialsetupdb.sql")
|
||||
}
|
||||
|
||||
func initDB() 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")
|
||||
}
|
||||
|
||||
sqlBytes, err := ioutil.ReadFile("initdb.sql")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
initialSQLStr := string(initialSQLBytes)
|
||||
initialSQLStr = strings.NewReplacer("DBNAME", config.DBname, "DBPREFIX", config.DBprefix).Replace(initialSQLStr)
|
||||
initialSQLArr := strings.Split(initialSQLStr, ";")
|
||||
for _, statement := range initialSQLArr {
|
||||
if statement != "" && statement != "\n" && statement != "\r\n" && strings.Index(statement, "--") != 0 {
|
||||
if _, err := db.Exec(statement); err != nil {
|
||||
sqlStr := regexp.MustCompile("--.*\n?").ReplaceAllString(string(sqlBytes), " ")
|
||||
sqlStr = strings.NewReplacer(
|
||||
"DBNAME", config.DBname,
|
||||
"DBPREFIX", config.DBprefix,
|
||||
"\n", " ").Replace(sqlStr)
|
||||
sqlArr := strings.Split(sqlStr, ";")
|
||||
|
||||
for _, statement := range sqlArr {
|
||||
if statement != "" && statement != " " {
|
||||
if _, err := db.Exec(statement + ";"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +106,7 @@ func loadInitialSQL() error {
|
|||
*/
|
||||
func execSQL(query string, values ...interface{}) (sql.Result, error) {
|
||||
stmt, err := db.Prepare(query)
|
||||
defer closeStatement(stmt)
|
||||
defer closeHandle(stmt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -120,7 +127,7 @@ func execSQL(query string, values ...interface{}) (sql.Result, error) {
|
|||
*/
|
||||
func queryRowSQL(query string, values []interface{}, out []interface{}) error {
|
||||
stmt, err := db.Prepare(query)
|
||||
defer closeStatement(stmt)
|
||||
defer closeHandle(stmt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -143,7 +150,7 @@ func queryRowSQL(query string, values []interface{}, out []interface{}) error {
|
|||
*/
|
||||
func querySQL(query string, a ...interface{}) (*sql.Rows, error) {
|
||||
stmt, err := db.Prepare(query)
|
||||
defer closeStatement(stmt)
|
||||
defer closeHandle(stmt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -158,63 +165,8 @@ func getSpecificSQLDateTime(t time.Time) string {
|
|||
return t.Format(mysqlDatetimeFormat)
|
||||
}
|
||||
|
||||
// checkDeprecatedSchema checks the tables for outdated columns and column values
|
||||
// and causes gochan to quit with an error message specific to the needed change
|
||||
func checkDeprecatedSchema(dbVersion string) {
|
||||
var hasColumn int
|
||||
var err error
|
||||
if version.CompareString(dbVersion) < 1 {
|
||||
return
|
||||
}
|
||||
printf(0, "Database is out of date (%s) updating for compatibility\n", dbVersion)
|
||||
|
||||
execSQL("ALTER TABLE `"+config.DBprefix+"banlist` CHANGE COLUMN `id` `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT", nil)
|
||||
execSQL("ALTER TABLE `"+config.DBprefix+"banlist` CHANGE COLUMN `expires` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP", nil)
|
||||
if err = queryRowSQL(
|
||||
"SELECT COUNT(*) FROM information_schema.COlUMNS WHERE `TABLE_SCHEMA` = '"+config.DBname+"' AND TABLE_NAME = '"+config.DBprefix+"banlist' AND COLUMN_NAME = 'appeal_message'",
|
||||
[]interface{}{}, []interface{}{&hasColumn},
|
||||
); err != nil {
|
||||
println(0, "error checking for deprecated column: "+err.Error())
|
||||
os.Exit(2)
|
||||
return
|
||||
}
|
||||
if hasColumn > 0 {
|
||||
// Running them one at a time, in case we get errors from individual queries
|
||||
execSQL("ALTER TABLE `"+config.DBprefix+"banlist` CHANGE COLUMN `banned_by` `staff` VARCHAR(50) NOT NULL", nil)
|
||||
execSQL("ALTER TABLE `"+config.DBprefix+"banlist` ADD COLUMN `type` TINYINT UNSIGNED NOT NULL DEFAULT '3'", nil)
|
||||
execSQL("ALTER TABLE `"+config.DBprefix+"banlist` ADD COLUMN `name_is_regex` TINYINT(1) DEFAULT '0'", nil)
|
||||
execSQL("ALTER TABLE `"+config.DBprefix+"banlist` ADD COLUMN `filename` VARCHAR(255) NOT NULL DEFAULT ''", nil)
|
||||
execSQL("ALTER TABLE `"+config.DBprefix+"banlist` ADD COLUMN `file_checksum` VARCHAR(255) NOT NULL DEFAULT ''", nil)
|
||||
execSQL("ALTER TABLE `"+config.DBprefix+"banlist` ADD COLUMN `permaban` TINYINT(1) DEFAULT '0'", nil)
|
||||
execSQL("ALTER TABLE `"+config.DBprefix+"banlist` ADD COLUMN `can_appeal` TINYINT(1) DEFAULT '1'", nil)
|
||||
execSQL("ALTER TABLE `"+config.DBprefix+"banlist` DROP COLUMN `message`", nil)
|
||||
execSQL("ALTER TABLE `"+config.DBprefix+"boards` CHANGE COLUMN `boards` VARCHAR(255) NOT NULL DEFAULT ''", nil)
|
||||
|
||||
println(0, "The column `appeal_message` in table "+config.DBprefix+"banlist is deprecated. A new table , `"+config.DBprefix+"appeals` has been created for it, and the banlist table will be modified accordingly.")
|
||||
println(0, "Just to be safe, you may want to check both tables to make sure everything is good.")
|
||||
|
||||
rows, err := querySQL("SELECT `id`,`appeal_message` FROM `" + config.DBprefix + "banlist`")
|
||||
if err != nil {
|
||||
handleError(0, "Error updating banlist schema: %s\n", err.Error())
|
||||
os.Exit(2)
|
||||
return
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var id int
|
||||
var appealMessage string
|
||||
rows.Scan(&id, &appealMessage)
|
||||
if appealMessage != "" {
|
||||
execSQL("INSERT INTO `"+config.DBprefix+"appeals` (`ban`,`message`) VALUES(?,?)", &id, &appealMessage)
|
||||
}
|
||||
execSQL("ALTER TABLE `" + config.DBprefix + "banlist` DROP COLUMN `appeal_message`")
|
||||
}
|
||||
}
|
||||
execSQL("UPDATE `"+config.DBprefix+"info` SET `value` = ? WHERE `name` = 'version'", versionStr)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -145,7 +145,7 @@ var funcMap = template.FuncMap{
|
|||
return getThumbnailPath("catalog", img)
|
||||
},
|
||||
"getThreadID": func(post_i interface{}) (thread int) {
|
||||
post, ok := post_i.(PostTable)
|
||||
post, ok := post_i.(Post)
|
||||
if !ok {
|
||||
thread = 0
|
||||
} else if post.ParentID == 0 {
|
||||
|
@ -168,7 +168,7 @@ var funcMap = template.FuncMap{
|
|||
}
|
||||
postURL = post.GetURL(withDomain)
|
||||
} else {
|
||||
post, ok := post_i.(*PostTable)
|
||||
post, ok := post_i.(*Post)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
|
301
src/types.go
301
src/types.go
|
@ -54,26 +54,28 @@ func (p *RecentPost) GetURL(includeDomain bool) string {
|
|||
}
|
||||
|
||||
type Thread struct {
|
||||
OP PostTable
|
||||
NumReplies int
|
||||
NumImages int
|
||||
OmittedImages int
|
||||
BoardReplies []PostTable
|
||||
Stickied bool
|
||||
ThreadPage int
|
||||
OP Post `json:"-"`
|
||||
NumReplies int `json:"replies"`
|
||||
NumImages int `json:"images"`
|
||||
OmittedPosts int `json:"omitted_posts"`
|
||||
OmittedImages int `json:"omitted_images"`
|
||||
BoardReplies []Post `json:"-"`
|
||||
Sticky int `json:"sticky"`
|
||||
Locked int `json:"locked"`
|
||||
ThreadPage int `json:"-"`
|
||||
}
|
||||
|
||||
// SQL Table structs
|
||||
|
||||
type AnnouncementsTable struct {
|
||||
ID uint
|
||||
Subject string
|
||||
Message string
|
||||
Poster string
|
||||
type Announcement struct {
|
||||
ID uint `json:"no"`
|
||||
Subject string `json:"sub"`
|
||||
Message string `json:"com"`
|
||||
Poster string `json:"name"`
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
type AppealsTable struct {
|
||||
type BanAppeal struct {
|
||||
ID int
|
||||
Ban int
|
||||
Message string
|
||||
|
@ -81,8 +83,8 @@ type AppealsTable struct {
|
|||
StaffResponse string
|
||||
}
|
||||
|
||||
func (a *AppealsTable) GetBan() (BanlistTable, error) {
|
||||
var ban BanlistTable
|
||||
func (a *BanAppeal) GetBan() (BanInfo, error) {
|
||||
var ban BanInfo
|
||||
var err error
|
||||
|
||||
err = queryRowSQL("SELECT * FROM `"+config.DBprefix+"banlist` WHERE `id` = ? LIMIT 1",
|
||||
|
@ -94,7 +96,7 @@ func (a *AppealsTable) GetBan() (BanlistTable, error) {
|
|||
return ban, err
|
||||
}
|
||||
|
||||
type BanlistTable struct {
|
||||
type BanInfo struct {
|
||||
ID uint
|
||||
AllowRead bool
|
||||
IP string
|
||||
|
@ -113,48 +115,49 @@ type BanlistTable struct {
|
|||
CanAppeal bool
|
||||
}
|
||||
|
||||
type BannedHashesTable struct {
|
||||
type BannedHash struct {
|
||||
ID uint
|
||||
Checksum string
|
||||
Description string
|
||||
}
|
||||
|
||||
type BoardsTable struct {
|
||||
ID int
|
||||
CurrentPage int
|
||||
NumPages int
|
||||
Order int
|
||||
Dir string
|
||||
Type int
|
||||
UploadType int
|
||||
Title string
|
||||
Subtitle string
|
||||
Description string
|
||||
Section int
|
||||
MaxImageSize int
|
||||
MaxPages int
|
||||
Locale string
|
||||
DefaultStyle string
|
||||
Locked bool
|
||||
CreatedOn time.Time
|
||||
Anonymous string
|
||||
ForcedAnon bool
|
||||
MaxAge int
|
||||
AutosageAfter int
|
||||
NoImagesAfter int
|
||||
MaxMessageLength int
|
||||
EmbedsAllowed bool
|
||||
RedirectToThread bool
|
||||
ShowID bool
|
||||
RequireFile bool
|
||||
EnableCatalog bool
|
||||
EnableSpoileredImages bool
|
||||
EnableSpoileredThreads bool
|
||||
EnableNSFW bool
|
||||
ThreadPage int
|
||||
type Board struct {
|
||||
ID int `json:"-"`
|
||||
CurrentPage int `json:`
|
||||
NumPages int `json:"pages"`
|
||||
Order int `json:"-"`
|
||||
Dir string `json:"board"`
|
||||
Type int `json:"-"`
|
||||
UploadType int `json:"-"`
|
||||
Title string `json:"title"`
|
||||
Subtitle string `json:"meta_description"`
|
||||
Description string `json:"-"`
|
||||
Section int `json:"-"`
|
||||
MaxFilesize int `json:"max_filesize"`
|
||||
MaxPages int `json:"max_pages"`
|
||||
DefaultStyle string `json:"-"`
|
||||
Locked bool `json:"is_archived"`
|
||||
CreatedOn time.Time `json:"-"`
|
||||
Anonymous string `json:"-"`
|
||||
ForcedAnon bool `json:"-"`
|
||||
MaxAge int `json:"-"`
|
||||
AutosageAfter int `json:"bump_limit"`
|
||||
NoImagesAfter int `json:"image_limit"`
|
||||
MaxMessageLength int `json:"max_comment_chars"`
|
||||
EmbedsAllowed bool `json:"-"`
|
||||
RedirectToThread bool `json:"-"`
|
||||
ShowID bool `json:"-"`
|
||||
RequireFile bool `json:"-"`
|
||||
EnableCatalog bool `json:"-"`
|
||||
EnableSpoileredImages bool `json:"-"`
|
||||
EnableSpoileredThreads bool `json:"-"`
|
||||
Worksafe bool `json:"ws_board"`
|
||||
ThreadPage int `json:"-"`
|
||||
Cooldowns BoardCooldowns `json:"cooldowns"`
|
||||
ThreadsPerPage int `json:"per_page"`
|
||||
}
|
||||
|
||||
type BoardSectionsTable struct {
|
||||
type BoardSection struct {
|
||||
ID int
|
||||
Order int
|
||||
Hidden bool
|
||||
|
@ -162,95 +165,41 @@ type BoardSectionsTable struct {
|
|||
Abbreviation string
|
||||
}
|
||||
|
||||
// EmbedsTable represents the embedable media on different sites.
|
||||
// It's held over from Kusaba X and may be removed in the future
|
||||
type EmbedsTable struct {
|
||||
ID uint8
|
||||
Filetype string
|
||||
Name string
|
||||
URL string
|
||||
Width uint16
|
||||
Height uint16
|
||||
EmbedCode string
|
||||
// Post represents each post in the database
|
||||
type Post struct {
|
||||
ID int `json:"no"`
|
||||
ParentID int `json:"resto"`
|
||||
CurrentPage int `json:"-"`
|
||||
NumPages int `json:"-"`
|
||||
BoardID int `json:"-"`
|
||||
Name string `json:"name"`
|
||||
Tripcode string `json:"trip"`
|
||||
Email string `json:"email"`
|
||||
Subject string `json:"sub"`
|
||||
MessageHTML string `json:"com"`
|
||||
MessageText string `json:"-"`
|
||||
Password string `json:"-"`
|
||||
Filename string `json:"tim"`
|
||||
FilenameOriginal string `json:"filename"`
|
||||
FileChecksum string `json:"md5"`
|
||||
FileExt string `json:"extension"`
|
||||
Filesize int `json:"fsize"`
|
||||
ImageW int `json:"w"`
|
||||
ImageH int `json:"h"`
|
||||
ThumbW int `json:"tn_w"`
|
||||
ThumbH int `json:"tn_h"`
|
||||
IP string `json:"-"`
|
||||
Capcode string `json:"capcode"`
|
||||
Timestamp time.Time `json:"time"`
|
||||
Autosage int `json:"-"`
|
||||
DeletedTimestamp time.Time `json:"-"`
|
||||
Bumped time.Time `json:"last_modified"`
|
||||
Stickied bool `json:"-"`
|
||||
Locked bool `json:"-"`
|
||||
Reviewed bool `json:"-"`
|
||||
}
|
||||
|
||||
// FrontTable represents the information (News, rules, etc) on the front page
|
||||
type FrontTable struct {
|
||||
ID int
|
||||
Page int
|
||||
Order int
|
||||
Subject string
|
||||
Message string
|
||||
Timestamp time.Time
|
||||
Poster string
|
||||
Email string
|
||||
}
|
||||
|
||||
// FrontLinksTable is used for linking to sites that the admin likes
|
||||
type FrontLinksTable struct {
|
||||
ID uint8
|
||||
Title string
|
||||
URL string
|
||||
}
|
||||
|
||||
type LoginAttemptsTable struct {
|
||||
ID uint
|
||||
IP string
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
type ModLogTable struct {
|
||||
IP uint
|
||||
Entry string
|
||||
User string
|
||||
Category uint8
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
// PollResultsTable may or may not be used in the future for user polls
|
||||
type PollResultsTable struct {
|
||||
ID uint
|
||||
IP string
|
||||
Selection string
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
// PostTable represents each post in the database
|
||||
type PostTable struct {
|
||||
ID int
|
||||
CurrentPage int
|
||||
NumPages int
|
||||
BoardID int
|
||||
ParentID int
|
||||
Name string
|
||||
Tripcode string
|
||||
Email string
|
||||
Subject string
|
||||
MessageHTML string
|
||||
MessageText string
|
||||
Password string
|
||||
Filename string
|
||||
FilenameOriginal string
|
||||
FileChecksum string
|
||||
Filesize int
|
||||
ImageW int
|
||||
ImageH int
|
||||
ThumbW int
|
||||
ThumbH int
|
||||
IP string
|
||||
Tag string
|
||||
Timestamp time.Time
|
||||
Autosage int
|
||||
PosterAuthority int
|
||||
DeletedTimestamp time.Time
|
||||
Bumped time.Time
|
||||
Stickied bool
|
||||
Locked bool
|
||||
Reviewed bool
|
||||
Sillytag bool
|
||||
}
|
||||
|
||||
func (p *PostTable) GetURL(includeDomain bool) string {
|
||||
func (p *Post) GetURL(includeDomain bool) string {
|
||||
postURL := ""
|
||||
if includeDomain {
|
||||
postURL += config.SiteDomain
|
||||
|
@ -273,14 +222,14 @@ func (p *PostTable) GetURL(includeDomain bool) string {
|
|||
|
||||
// Sanitize escapes HTML strings in a post. This should be run immediately before
|
||||
// the post is inserted into the database
|
||||
func (p *PostTable) Sanitize() {
|
||||
func (p *Post) Sanitize() {
|
||||
p.Name = html.EscapeString(p.Name)
|
||||
p.Email = html.EscapeString(p.Email)
|
||||
p.Subject = html.EscapeString(p.Subject)
|
||||
p.Password = html.EscapeString(p.Password)
|
||||
}
|
||||
|
||||
type ReportsTable struct {
|
||||
type Report struct {
|
||||
ID uint
|
||||
Board string
|
||||
PostID uint
|
||||
|
@ -291,14 +240,14 @@ type ReportsTable struct {
|
|||
IsTemp bool
|
||||
}
|
||||
|
||||
type SessionsTable struct {
|
||||
type LoginSession struct {
|
||||
ID uint
|
||||
Data string
|
||||
Expires string
|
||||
}
|
||||
|
||||
// StaffTable represents a single staff member's info stored in the database
|
||||
type StaffTable struct {
|
||||
// Staff represents a single staff member's info stored in the database
|
||||
type Staff struct {
|
||||
ID int
|
||||
Username string
|
||||
PasswordChecksum string
|
||||
|
@ -309,7 +258,7 @@ type StaffTable struct {
|
|||
LastActive time.Time
|
||||
}
|
||||
|
||||
type WordFiltersTable struct {
|
||||
type WordFilter struct {
|
||||
ID int
|
||||
From string
|
||||
To string
|
||||
|
@ -317,76 +266,12 @@ type WordFiltersTable struct {
|
|||
RegEx bool
|
||||
}
|
||||
|
||||
// Types for the JSON files we generate as a sort of "API"
|
||||
type BoardJSONWrapper struct {
|
||||
Boards []BoardJSON `json:"boards"`
|
||||
}
|
||||
|
||||
type BoardJSON struct {
|
||||
BoardName string `json:"board"`
|
||||
Title string `json:"title"`
|
||||
WorkSafeBoard int `json:"ws_board"`
|
||||
ThreadsPerPage int `json:"per_page"`
|
||||
Pages int `json:"pages"`
|
||||
MaxFilesize int `json:"max_filesize"`
|
||||
MaxMessageLength int `json:"max_comment_chars"`
|
||||
BumpLimit int `json:"bump_limit"`
|
||||
ImageLimit int `json:"image_limit"`
|
||||
Cooldowns BoardCooldowns `json:"cooldowns"`
|
||||
Description string `json:"meta_description"`
|
||||
IsArchived int `json:"is_archived"`
|
||||
}
|
||||
|
||||
type BoardCooldowns struct {
|
||||
NewThread int `json:"threads"`
|
||||
Reply int `json:"replies"`
|
||||
ImageReply int `json:"images"`
|
||||
}
|
||||
|
||||
type ThreadJSONWrapper struct {
|
||||
Posts []PostJSON `json:"posts"`
|
||||
}
|
||||
|
||||
type PostJSON struct {
|
||||
ID int `json:"no"`
|
||||
ParentID int `json:"resto"`
|
||||
Subject string `json:"sub"`
|
||||
Message string `json:"com"`
|
||||
Name string `json:"name"`
|
||||
Tripcode string `json:"trip"`
|
||||
Timestamp int64 `json:"time"`
|
||||
Bumped int64 `json:"last_modified"`
|
||||
ThumbWidth int `json:"tn_w"`
|
||||
ThumbHeight int `json:"tn_h"`
|
||||
ImageWidth int `json:"w"`
|
||||
ImageHeight int `json:"h"`
|
||||
FileSize int `json:"fsize"`
|
||||
OrigFilename string `json:"filename"`
|
||||
Extension string `json:"ext"`
|
||||
Filename string `json:"tim"`
|
||||
FileChecksum string `json:"md5"`
|
||||
}
|
||||
|
||||
type BoardPageJSON struct {
|
||||
Threads []ThreadJSON `json:"threads"`
|
||||
Page int `json:"page"`
|
||||
}
|
||||
|
||||
type ThreadJSON struct {
|
||||
*PostJSON
|
||||
OmittedPosts int `json:"omitted_posts"`
|
||||
OmittedImages int `json:"omitted_images"`
|
||||
Replies int `json:"replies"`
|
||||
ImagesOnArchive int `json:"images"`
|
||||
Sticky int `json:"sticky"`
|
||||
Locked int `json:"locked"`
|
||||
}
|
||||
|
||||
// ErrorJSON and PostInfoJSON are mostly used for AJAX requests
|
||||
type ErrorJSON struct {
|
||||
Message string `json:"error"`
|
||||
}
|
||||
|
||||
type Style struct {
|
||||
Name string
|
||||
Filename string
|
||||
|
|
166
src/util.go
166
src/util.go
|
@ -1,10 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -14,13 +13,14 @@ import (
|
|||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nranchev/go-libGeoIP"
|
||||
libgeo "github.com/nranchev/go-libGeoIP"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
|
@ -91,21 +91,13 @@ func byteByByteReplace(input, from, to string) string {
|
|||
}
|
||||
|
||||
// for easier defer cleaning
|
||||
func closeFile(file *os.File) {
|
||||
if file != nil {
|
||||
_ = file.Close()
|
||||
}
|
||||
type Closeable interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
func closeRows(rows *sql.Rows) {
|
||||
if rows != nil {
|
||||
_ = rows.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func closeStatement(stmt *sql.Stmt) {
|
||||
if stmt != nil {
|
||||
_ = stmt.Close()
|
||||
func closeHandle(handle Closeable) {
|
||||
if handle != nil && !reflect.ValueOf(handle).IsNil() {
|
||||
handle.Close()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,57 +120,9 @@ func deleteMatchingFiles(root, match string) (filesDeleted int, err error) {
|
|||
return filesDeleted, err
|
||||
}
|
||||
|
||||
// escapeString and escapeQuotes copied from github.com/ziutek/mymysql/native/codecs.go
|
||||
func escapeString(txt string) string {
|
||||
var (
|
||||
esc string
|
||||
buf bytes.Buffer
|
||||
)
|
||||
last := 0
|
||||
for ii, bb := range txt {
|
||||
switch bb {
|
||||
case 0:
|
||||
esc = `\0`
|
||||
case '\n':
|
||||
esc = `\n`
|
||||
case '\r':
|
||||
esc = `\r`
|
||||
case '\\':
|
||||
esc = `\\`
|
||||
case '\'':
|
||||
esc = `\'`
|
||||
case '"':
|
||||
esc = `\"`
|
||||
case '\032':
|
||||
esc = `\Z`
|
||||
default:
|
||||
continue
|
||||
}
|
||||
io.WriteString(&buf, txt[last:ii])
|
||||
io.WriteString(&buf, esc)
|
||||
last = ii + 1
|
||||
}
|
||||
io.WriteString(&buf, txt[last:])
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func escapeQuotes(txt string) string {
|
||||
var buf bytes.Buffer
|
||||
last := 0
|
||||
for ii, bb := range txt {
|
||||
if bb == '\'' {
|
||||
io.WriteString(&buf, txt[last:ii])
|
||||
io.WriteString(&buf, `''`)
|
||||
last = ii + 1
|
||||
}
|
||||
}
|
||||
io.WriteString(&buf, txt[last:])
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// getBoardArr performs a query against the database, and returns an array of BoardsTables 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 []BoardsTable, err error) {
|
||||
func getBoardArr(parameterList map[string]interface{}, extra string) (boards []Board, err error) {
|
||||
queryString := "SELECT * FROM `" + config.DBprefix + "boards` "
|
||||
numKeys := len(parameterList)
|
||||
var parameterValues []interface{}
|
||||
|
@ -199,7 +143,7 @@ func getBoardArr(parameterList map[string]interface{}, extra string) (boards []B
|
|||
queryString += fmt.Sprintf(" %s ORDER BY `order`", extra)
|
||||
|
||||
rows, err := querySQL(queryString, parameterValues...)
|
||||
defer closeRows(rows)
|
||||
defer closeHandle(rows)
|
||||
if err != nil {
|
||||
handleError(0, "error getting board list: %s", customError(err))
|
||||
return
|
||||
|
@ -208,7 +152,7 @@ func getBoardArr(parameterList map[string]interface{}, extra string) (boards []B
|
|||
// For each row in the results from the database, populate a new BoardsTable instance,
|
||||
// then append it to the boards array we are going to return
|
||||
for rows.Next() {
|
||||
board := new(BoardsTable)
|
||||
board := new(Board)
|
||||
if err = rows.Scan(
|
||||
&board.ID,
|
||||
&board.Order,
|
||||
|
@ -219,9 +163,8 @@ func getBoardArr(parameterList map[string]interface{}, extra string) (boards []B
|
|||
&board.Subtitle,
|
||||
&board.Description,
|
||||
&board.Section,
|
||||
&board.MaxImageSize,
|
||||
&board.MaxFilesize,
|
||||
&board.MaxPages,
|
||||
&board.Locale,
|
||||
&board.DefaultStyle,
|
||||
&board.Locked,
|
||||
&board.CreatedOn,
|
||||
|
@ -244,30 +187,29 @@ func getBoardArr(parameterList map[string]interface{}, extra string) (boards []B
|
|||
return
|
||||
}
|
||||
|
||||
func getBoardFromID(id int) (*BoardsTable, error) {
|
||||
board := new(BoardsTable)
|
||||
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`,`locale`,`default_style`,`locked`,`created_on`,`anonymous`,`forced_anon`,`max_age`,"+
|
||||
"`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` = ?",
|
||||
[]interface{}{id},
|
||||
[]interface{}{
|
||||
&board.Order, &board.Dir, &board.Type, &board.UploadType, &board.Title,
|
||||
&board.Subtitle, &board.Description, &board.Section, &board.MaxImageSize,
|
||||
&board.MaxPages, &board.Locale, &board.DefaultStyle, &board.Locked, &board.CreatedOn,
|
||||
&board.Subtitle, &board.Description, &board.Section, &board.MaxFilesize,
|
||||
&board.MaxPages, &board.DefaultStyle, &board.Locked, &board.CreatedOn,
|
||||
&board.Anonymous, &board.ForcedAnon, &board.MaxAge, &board.AutosageAfter,
|
||||
&board.NoImagesAfter, &board.MaxMessageLength, &board.EmbedsAllowed,
|
||||
&board.RedirectToThread, &board.RequireFile, &board.EnableCatalog,
|
||||
},
|
||||
)
|
||||
|
||||
board.ID = id
|
||||
return board, err
|
||||
}
|
||||
|
||||
// if parameterList is nil, ignore it and treat extra like a whole SQL query
|
||||
func getPostArr(parameterList map[string]interface{}, extra string) (posts []PostTable, err error) {
|
||||
func getPostArr(parameterList map[string]interface{}, extra string) (posts []Post, err error) {
|
||||
queryString := "SELECT * FROM `" + config.DBprefix + "posts` "
|
||||
numKeys := len(parameterList)
|
||||
var parameterValues []interface{}
|
||||
|
@ -287,22 +229,22 @@ func getPostArr(parameterList map[string]interface{}, extra string) (posts []Pos
|
|||
|
||||
queryString += " " + extra // " ORDER BY `order`"
|
||||
rows, err := querySQL(queryString, parameterValues...)
|
||||
defer closeRows(rows)
|
||||
defer closeHandle(rows)
|
||||
if err != nil {
|
||||
handleError(1, customError(err))
|
||||
return
|
||||
}
|
||||
|
||||
// For each row in the results from the database, populate a new PostTable instance,
|
||||
// then append it to the posts array we are going to return
|
||||
// then append it to the posts array we are going to return
|
||||
for rows.Next() {
|
||||
var post PostTable
|
||||
var post Post
|
||||
|
||||
if err = rows.Scan(&post.ID, &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.Tag, &post.Timestamp,
|
||||
&post.Autosage, &post.PosterAuthority, &post.DeletedTimestamp, &post.Bumped,
|
||||
&post.Stickied, &post.Locked, &post.Reviewed, &post.Sillytag,
|
||||
&post.ImageH, &post.ThumbW, &post.ThumbH, &post.IP, &post.Capcode, &post.Timestamp,
|
||||
&post.Autosage, &post.DeletedTimestamp, &post.Bumped, &post.Stickied, &post.Locked, &post.Reviewed,
|
||||
); err != nil {
|
||||
handleError(0, customError(err))
|
||||
return
|
||||
|
@ -313,19 +255,19 @@ 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 []interface{}, err error) {
|
||||
func getSectionArr(where string) (sections []BoardSection, err error) {
|
||||
if where == "" {
|
||||
where = "1"
|
||||
}
|
||||
rows, err := querySQL("SELECT * FROM `" + config.DBprefix + "sections` WHERE " + where + " ORDER BY `order`")
|
||||
defer closeRows(rows)
|
||||
defer closeHandle(rows)
|
||||
if err != nil {
|
||||
errorLog.Print(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
section := new(BoardSectionsTable)
|
||||
var section BoardSection
|
||||
if err = rows.Scan(§ion.ID, §ion.Order, §ion.Hidden, §ion.Name, §ion.Abbreviation); err != nil {
|
||||
handleError(1, customError(err))
|
||||
return
|
||||
|
@ -392,6 +334,7 @@ func customError(err error) string {
|
|||
|
||||
func handleError(verbosity int, format string, a ...interface{}) string {
|
||||
out := fmt.Sprintf(format, a...)
|
||||
panic(out)
|
||||
println(verbosity, out)
|
||||
errorLog.Print(out)
|
||||
return out
|
||||
|
@ -566,36 +509,33 @@ func checkPostForSpam(userIP string, userAgent string, referrer string,
|
|||
return "other_failure"
|
||||
}
|
||||
|
||||
func makePostJSON(post PostTable, anonymous string) (postObj PostJSON) {
|
||||
var filename string
|
||||
var fileExt string
|
||||
var origFilename string
|
||||
|
||||
// Separate out the extension from the filenames
|
||||
if post.Filename != "deleted" && post.Filename != "" {
|
||||
extStart := strings.LastIndex(post.Filename, ".")
|
||||
fileExt = post.Filename[extStart:]
|
||||
|
||||
origExtStart := strings.LastIndex(post.FilenameOriginal, fileExt)
|
||||
origFilename = post.FilenameOriginal[:origExtStart]
|
||||
filename = post.Filename[:extStart]
|
||||
func marshalAPI(tag string, data interface{}, indent bool) (string, error) {
|
||||
var apiMap map[string]interface{}
|
||||
var apiBytes []byte
|
||||
var err error
|
||||
if indent {
|
||||
if tag == "" {
|
||||
apiBytes, err = json.MarshalIndent(data, "", " ")
|
||||
} else {
|
||||
apiMap = make(map[string]interface{})
|
||||
apiMap[tag] = data
|
||||
apiBytes, err = json.MarshalIndent(apiMap, "", " ")
|
||||
}
|
||||
} else {
|
||||
if tag == "" {
|
||||
apiBytes, err = json.Marshal(data)
|
||||
} else {
|
||||
apiMap := make(map[string]interface{})
|
||||
apiMap[tag] = data
|
||||
apiBytes, err = json.Marshal(apiMap)
|
||||
}
|
||||
}
|
||||
|
||||
postObj = PostJSON{ID: post.ID, ParentID: post.ParentID, Subject: post.Subject, Message: post.MessageHTML,
|
||||
Name: post.Name, Timestamp: post.Timestamp.Unix(), Bumped: post.Bumped.Unix(),
|
||||
ThumbWidth: post.ThumbW, ThumbHeight: post.ThumbH, ImageWidth: post.ImageW, ImageHeight: post.ImageH,
|
||||
FileSize: post.Filesize, OrigFilename: origFilename, Extension: fileExt, Filename: filename, FileChecksum: post.FileChecksum}
|
||||
|
||||
// Handle Anonymous
|
||||
if post.Name == "" {
|
||||
postObj.Name = anonymous
|
||||
if err != nil {
|
||||
apiBytes, _ = json.Marshal(map[string]string{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
// If we have a Tripcode, prepend a !
|
||||
if post.Tripcode != "" {
|
||||
postObj.Tripcode = "!" + post.Tripcode
|
||||
}
|
||||
return
|
||||
return string(apiBytes), err
|
||||
}
|
||||
|
||||
func limitArraySize(arr []string, maxSize int) []string {
|
||||
|
|
|
@ -1,24 +1,19 @@
|
|||
#!/bin/bash
|
||||
# Vagrant provision script
|
||||
# I guess I should give credit where credit is due since a fair amount
|
||||
# of this script was was originally written by Macil (as far as I know) for Ponychan
|
||||
# https://github.com/MacilPrime/ponychan
|
||||
|
||||
set -euo pipefail
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
export GOCHAN_PATH=/home/vagrant/gochan
|
||||
export GOPATH=/vagrant/lib
|
||||
|
||||
export DBTYPE=mysql
|
||||
|
||||
apt-get update
|
||||
apt-get -y update && apt-get -y upgrade
|
||||
|
||||
if [ "$DBTYPE" == "mysql" ]; then
|
||||
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 - <<EOF123 >/etc/mysql/conf.d/local.cnf
|
||||
cat << EOF >/etc/mysql/conf.d/local.cnf
|
||||
[client]
|
||||
default-character-set = utf8mb4
|
||||
|
||||
|
@ -30,7 +25,7 @@ character-set-client-handshake = FALSE
|
|||
character-set-server = utf8mb4
|
||||
collation-server = utf8mb4_unicode_ci
|
||||
default-storage-engine = innodb
|
||||
EOF123
|
||||
EOF
|
||||
|
||||
mysql -uroot -e "CREATE DATABASE IF NOT EXISTS gochan; \
|
||||
GRANT USAGE ON *.* TO gochan IDENTIFIED BY ''; \
|
||||
|
@ -38,10 +33,10 @@ GRANT ALL PRIVILEGES ON gochan.* TO gochan; \
|
|||
SET PASSWORD FOR 'gochan'@'%' = PASSWORD('gochan');
|
||||
FLUSH PRIVILEGES;"
|
||||
|
||||
cat - <<EOF123 >/etc/mysql/conf.d/open.cnf
|
||||
cat << EOF >/etc/mysql/conf.d/open.cnf
|
||||
[mysqld]
|
||||
bind-address = 0.0.0.0
|
||||
EOF123
|
||||
EOF
|
||||
elif [ "$DBTYPE" == "postgresql" ]; then
|
||||
# apt-get -y install postgresql postgresql-contrib
|
||||
# useradd gochan
|
||||
|
@ -61,7 +56,6 @@ else
|
|||
fi
|
||||
|
||||
apt-get -y install git subversion mercurial golang-1.10 nginx ffmpeg
|
||||
apt-get -y upgrade
|
||||
|
||||
rm -f /etc/nginx/sites-enabled/* /etc/nginx/sites-available/*
|
||||
ln -sf /vagrant/gochan-fastcgi.nginx /etc/nginx/sites-available/gochan.nginx
|
||||
|
@ -76,11 +70,10 @@ sed -i 's/WantedBy=multi-user.target/WantedBy=vagrant.mount/' /lib/systemd/syste
|
|||
systemctl daemon-reload
|
||||
systemctl enable nginx
|
||||
|
||||
|
||||
systemctl restart nginx mysql &
|
||||
wait
|
||||
|
||||
mkdir -p /vagrant/lib || true
|
||||
mkdir -p /vagrant/lib
|
||||
cd /vagrant
|
||||
su - vagrant
|
||||
export GOCHAN_PATH=/home/vagrant/gochan
|
||||
|
@ -98,64 +91,64 @@ function makeLink {
|
|||
ln -sf /vagrant/$1 $GOCHAN_PATH/$1
|
||||
}
|
||||
|
||||
cat - <<EOF >>/home/vagrant/.bashrc
|
||||
cat << EOF >>/home/vagrant/.bashrc
|
||||
export GOPATH=/vagrant/lib
|
||||
export GOCHAN_PATH=/home/vagrant/gochan
|
||||
EOF
|
||||
|
||||
# a couple convenience shell scripts, since they're nice to have
|
||||
cat - <<EOF >/home/vagrant/dbconnect.sh
|
||||
cat << EOF >/home/vagrant/dbconnect.sh
|
||||
#!/usr/bin/env bash
|
||||
|
||||
mysql -s -t -u gochan -D gochan -pgochan
|
||||
EOF
|
||||
|
||||
cat - <<EOF >/home/vagrant/buildgochan.sh
|
||||
cat << EOF >/home/vagrant/buildgochan.sh
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cd /vagrant && make debug && cd ~/gochan && ./gochan
|
||||
EOF
|
||||
|
||||
chmod +x /home/vagrant/dbconnect.sh
|
||||
chmod +x /home/vagrant/buildgochan.sh
|
||||
sudo chown vagrant:vagrant /home/vagrant/dbconnect.sh /home/vagrant/buildgochan.sh
|
||||
sudo chown -R vagrant:vagrant /home/vagrant/bin
|
||||
|
||||
go get github.com/disintegration/imaging
|
||||
go get github.com/nranchev/go-libGeoIP
|
||||
#go get github.com/nyarla/go-crypt
|
||||
go get github.com/go-sql-driver/mysql
|
||||
go get github.com/lib/pq
|
||||
go get golang.org/x/crypto/bcrypt
|
||||
go get github.com/frustra/bbcode
|
||||
go get \
|
||||
github.com/disintegration/imaging \
|
||||
github.com/nranchev/go-libGeoIP \
|
||||
github.com/go-sql-driver/mysql \
|
||||
github.com/lib/pq \
|
||||
golang.org/x/net/html \
|
||||
github.com/aquilax/tripcode \
|
||||
golang.org/x/crypto/bcrypt \
|
||||
github.com/frustra/bbcode
|
||||
make debug
|
||||
|
||||
rm -f $GOCHAN_PATH/gochan
|
||||
rm -f $GOCHAN_PATH/initialsetupdb.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 initialsetupdb.sql
|
||||
makeLink initdb.sql
|
||||
changePerms $GOCHAN_PATH
|
||||
|
||||
if [ ! -e "$GOCHAN_PATH/gochan.json" ]; then
|
||||
install -m 775 -o vagrant -g vagrant -T /vagrant/gochan.example.json $GOCHAN_PATH/gochan.json
|
||||
fi
|
||||
|
||||
mkdir -p /home/vagrant/.config/systemd/user/
|
||||
ln -s /vagrant/gochan.service /home/vagrant/.config/systemd/user/gochan.service
|
||||
|
||||
sed -e 's/"Port": 8080,/"Port": 9000,/' -i $GOCHAN_PATH/gochan.json
|
||||
sed -e 's/"UseFastCGI": false,/"UseFastCGI": true,/' -i /$GOCHAN_PATH/gochan.json
|
||||
sed -e 's/"DomainRegex": ".*",/"DomainRegex": "(https|http):\\\/\\\/(.*)\\\/(.*)",/' -i $GOCHAN_PATH/gochan.json
|
||||
sed -e 's/"DBpassword": ""/"DBpassword": "gochan"/' -i /home/vagrant/gochan/gochan.json
|
||||
sed -e 's/"RandomSeed": ""/"RandomSeed": "abc123"/' -i $GOCHAN_PATH/gochan.json
|
||||
sed /vagrant/gochan.example.json \
|
||||
-e 's/"Port": 8080,/"Port": 9000,/' \
|
||||
-e 's/"UseFastCGI": false,/"UseFastCGI": true,/' \
|
||||
-e 's/"DomainRegex": ".*",/"DomainRegex": "(https|http):\\\/\\\/(.*)\\\/(.*)",/' \
|
||||
-e 's/"DBpassword": ""/"DBpassword": "gochan"/' \
|
||||
-e 's/"RandomSeed": ""/"RandomSeed": "abc123"/' \
|
||||
-e 's/"Verbosity": 0/"Verbosity": 1/' \
|
||||
-e w\ $GOCHAN_PATH/gochan.json
|
||||
|
||||
echo
|
||||
echo "Server set up, please run \"vagrant ssh\" on your host machine, and"
|
||||
echo "Server set up, please run \"vagrant ssh\" on your host machine and"
|
||||
echo "(optionally) \"systemctl --user start gochan\" in the guest."
|
||||
echo "Then browse to http://172.27.0.3/manage to complete installation"
|
||||
echo "If you want gochan to run on system startup run \"systemctl --user enable gochan\""
|
||||
echo "Then browse to http://172.27.0.3/manage to complete installation."
|
||||
# echo "If you want gochan to run on system startup run \"systemctl --user enable gochan\""
|
||||
# TODO: add further instructions as default initial announcement or /manage?action=firstrun
|
||||
|
|
2
version
2
version
|
@ -1 +1 @@
|
|||
2.6.1
|
||||
2.7.0
|
Loading…
Add table
Add a link
Reference in a new issue