mirror of
https://github.com/Eggbertx/gochan.git
synced 2025-08-03 07:36:23 -07:00
Add ability to update thread attributes from manage page
This commit is contained in:
parent
0f0c9362eb
commit
34cab06311
5 changed files with 174 additions and 70 deletions
|
@ -86,21 +86,28 @@ func GetTopPostInThread(postID int) (int, error) {
|
||||||
return id, err
|
return id, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTopPostIDsInThreadIDs(threads ...interface{}) ([]int, error) {
|
// GetTopPostIDsInThreadIDs takes a variable number of threads and returns a map[threadID]topPostID
|
||||||
|
func GetTopPostIDsInThreadIDs(threads ...interface{}) (map[interface{}]int, error) {
|
||||||
|
ids := make(map[interface{}]int)
|
||||||
|
if threads == nil {
|
||||||
|
return ids, nil
|
||||||
|
}
|
||||||
params := createArrayPlaceholder(threads)
|
params := createArrayPlaceholder(threads)
|
||||||
query := `SELECT id FROM DBPREFIXposts WHERE thread_id in ` + params
|
query := `SELECT id FROM DBPREFIXposts WHERE thread_id in ` + params + " AND is_top_post"
|
||||||
rows, err := QuerySQL(query, threads...)
|
rows, err := QuerySQL(query, threads...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
var ids []int
|
var i int
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var id int
|
var id int
|
||||||
if err = rows.Scan(&id); err != nil {
|
if err = rows.Scan(&id); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ids = append(ids, id)
|
|
||||||
|
ids[threads[i]] = id
|
||||||
|
i++
|
||||||
}
|
}
|
||||||
return ids, nil
|
return ids, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package gcsql
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -41,7 +42,7 @@ func createThread(tx *sql.Tx, boardID int, locked bool, stickied bool, anchored
|
||||||
return threadID, err
|
return threadID, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetThread returns a a thread object from the database
|
// GetThread returns a a thread object from the database, given its ID
|
||||||
func GetThread(threadID int) (*Thread, error) {
|
func GetThread(threadID int) (*Thread, error) {
|
||||||
const query = selectThreadsBaseSQL + `WHERE id = ?`
|
const query = selectThreadsBaseSQL + `WHERE id = ?`
|
||||||
thread := new(Thread)
|
thread := new(Thread)
|
||||||
|
@ -52,6 +53,17 @@ func GetThread(threadID int) (*Thread, error) {
|
||||||
return thread, err
|
return thread, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPostThread returns a thread object from the database, given the ID of a post in the thread
|
||||||
|
func GetPostThread(opID int) (*Thread, error) {
|
||||||
|
const query = selectThreadsBaseSQL + `WHERE id = (SELECT thread_id FROM DBPREFIXposts WHERE id = ? LIMIT 1)`
|
||||||
|
thread := new(Thread)
|
||||||
|
err := QueryRowSQL(query, interfaceSlice(opID), interfaceSlice(
|
||||||
|
&thread.ID, &thread.BoardID, &thread.Locked, &thread.Stickied, &thread.Anchored, &thread.Cyclical,
|
||||||
|
&thread.LastBump, &thread.DeletedAt, &thread.IsDeleted,
|
||||||
|
))
|
||||||
|
return thread, err
|
||||||
|
}
|
||||||
|
|
||||||
// GetTopPostThreadID gets the thread ID from the database, given the post ID of a top post
|
// GetTopPostThreadID gets the thread ID from the database, given the post ID of a top post
|
||||||
func GetTopPostThreadID(opID int) (int, error) {
|
func GetTopPostThreadID(opID int) (int, error) {
|
||||||
const query = `SELECT thread_id FROM DBPREFIXposts WHERE id = ? and is_top_post`
|
const query = `SELECT thread_id FROM DBPREFIXposts WHERE id = ? and is_top_post`
|
||||||
|
@ -195,6 +207,23 @@ func (t *Thread) GetUploads() ([]Upload, error) {
|
||||||
return uploads, nil
|
return uploads, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateThreadAttribute updates the given attribute (valid attribute values are "locked", "stickied, "anchored",
|
||||||
|
// or "cyclical") for the thread with the given top post ID
|
||||||
|
func UpdateThreadAttribute(opID int, attribute string, value bool) error {
|
||||||
|
updateSQL := "UPDATE DBPREFIXthreads SET "
|
||||||
|
if attribute == "locked" || attribute == "stickied" || attribute == "anchored" || attribute == "cyclical" {
|
||||||
|
updateSQL += attribute + " = ? WHERE id = ?"
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("invalid thread attribute %q", attribute)
|
||||||
|
}
|
||||||
|
threadID, err := GetTopPostThreadID(opID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = ExecSQL(updateSQL, value, threadID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// deleteThread updates the thread and sets it as deleted, as well as the posts where thread_id = threadID
|
// deleteThread updates the thread and sets it as deleted, as well as the posts where thread_id = threadID
|
||||||
func deleteThread(threadID int) error {
|
func deleteThread(threadID int) error {
|
||||||
const deletePostsSQL = `UPDATE DBPREFIXposts SET is_deleted = TRUE, deleted_at = CURRENT_TIMESTAMP WHERE thread_id = ?`
|
const deletePostsSQL = `UPDATE DBPREFIXposts SET is_deleted = TRUE, deleted_at = CURRENT_TIMESTAMP WHERE thread_id = ?`
|
||||||
|
|
|
@ -26,19 +26,25 @@ func (*logHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
|
||||||
|
|
||||||
func LogStr(key, val string, events ...*zerolog.Event) {
|
func LogStr(key, val string, events ...*zerolog.Event) {
|
||||||
for e := range events {
|
for e := range events {
|
||||||
if events[e] == nil {
|
if events[e] != nil {
|
||||||
continue
|
events[e] = events[e].Str(key, val)
|
||||||
}
|
}
|
||||||
events[e] = events[e].Str(key, val)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func LogInt(key string, i int, events ...*zerolog.Event) {
|
func LogInt(key string, i int, events ...*zerolog.Event) {
|
||||||
for e := range events {
|
for e := range events {
|
||||||
if events[e] == nil {
|
if events[e] != nil {
|
||||||
continue
|
events[e] = events[e].Int(key, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func LogBool(key string, b bool, events ...*zerolog.Event) {
|
||||||
|
for e := range events {
|
||||||
|
if events[e] != nil {
|
||||||
|
events[e] = events[e].Bool(key, b)
|
||||||
}
|
}
|
||||||
events[e] = events[e].Int(key, i)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -749,65 +750,107 @@ var actions = []Action{
|
||||||
Callback: func(writer http.ResponseWriter, request *http.Request, staff *gcsql.Staff, wantsJSON bool, infoEv, errEv *zerolog.Event) (output interface{}, err error) {
|
Callback: func(writer http.ResponseWriter, request *http.Request, staff *gcsql.Staff, wantsJSON bool, infoEv, errEv *zerolog.Event) (output interface{}, err error) {
|
||||||
boardDir := request.FormValue("board")
|
boardDir := request.FormValue("board")
|
||||||
attrBuffer := bytes.NewBufferString("")
|
attrBuffer := bytes.NewBufferString("")
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"action": "threadattrs",
|
||||||
|
"boards": gcsql.AllBoards,
|
||||||
|
}
|
||||||
if boardDir == "" {
|
if boardDir == "" {
|
||||||
if wantsJSON {
|
if wantsJSON {
|
||||||
return nil, errors.New(`missing required field "board"`)
|
return nil, errors.New(`missing required field "board"`)
|
||||||
}
|
}
|
||||||
if err = serverutil.MinifyTemplate(gctemplates.ManageThreadAttrs, map[string]interface{}{
|
if err = serverutil.MinifyTemplate(gctemplates.ManageThreadAttrs, data, attrBuffer, "text/html"); err != nil {
|
||||||
"action": "threadattrs",
|
|
||||||
"boards": gcsql.AllBoards,
|
|
||||||
}, attrBuffer, "text/html"); err != nil {
|
|
||||||
errEv.Err(err).Caller().Send()
|
errEv.Err(err).Caller().Send()
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return attrBuffer.String(), nil
|
return attrBuffer.String(), nil
|
||||||
}
|
}
|
||||||
errEv.Str("boardDir", boardDir)
|
gcutil.LogStr("board", boardDir, errEv, infoEv)
|
||||||
boardID, err := gcsql.GetBoardIDFromDir(boardDir)
|
board, err := gcsql.GetBoardFromDir(boardDir)
|
||||||
|
if err != nil {
|
||||||
|
errEv.Err(err)
|
||||||
|
}
|
||||||
|
data["board"] = board
|
||||||
|
topPostStr := request.FormValue("thread")
|
||||||
|
if topPostStr != "" {
|
||||||
|
var topPostID int
|
||||||
|
if topPostID, err = strconv.Atoi(topPostStr); err != nil {
|
||||||
|
errEv.Err(err).Str("topPostStr", topPostStr).Caller().Send()
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
gcutil.LogInt("topPostID", topPostID, errEv, infoEv)
|
||||||
|
var attr string
|
||||||
|
var newVal bool
|
||||||
|
if request.FormValue("unlock") != "" {
|
||||||
|
attr = "locked"
|
||||||
|
newVal = false
|
||||||
|
} else if request.FormValue("lock") != "" {
|
||||||
|
attr = "locked"
|
||||||
|
newVal = true
|
||||||
|
} else if request.FormValue("unsticky") != "" {
|
||||||
|
attr = "stickied"
|
||||||
|
newVal = false
|
||||||
|
} else if request.FormValue("sticky") != "" {
|
||||||
|
attr = "stickied"
|
||||||
|
newVal = true
|
||||||
|
} else if request.FormValue("unanchor") != "" {
|
||||||
|
attr = "anchored"
|
||||||
|
newVal = false
|
||||||
|
} else if request.FormValue("anchor") != "" {
|
||||||
|
attr = "anchored"
|
||||||
|
newVal = true
|
||||||
|
} else if request.FormValue("uncyclical") != "" {
|
||||||
|
attr = "cyclical"
|
||||||
|
newVal = false
|
||||||
|
} else if request.FormValue("cyclical") != "" {
|
||||||
|
attr = "cyclical"
|
||||||
|
newVal = true
|
||||||
|
}
|
||||||
|
if attr != "" {
|
||||||
|
gcutil.LogStr("attribute", attr, errEv, infoEv)
|
||||||
|
gcutil.LogBool("attrVal", newVal, errEv, infoEv)
|
||||||
|
if err = gcsql.UpdateThreadAttribute(topPostID, attr, newVal); err != nil {
|
||||||
|
errEv.Err(err).Caller().Send()
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thread, err := gcsql.GetPostThread(topPostID)
|
||||||
|
if err != nil {
|
||||||
|
errEv.Err(err).Caller().Send()
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
data["thread"] = thread
|
||||||
|
}
|
||||||
|
|
||||||
|
threads, err := gcsql.GetThreadsWithBoardID(board.ID, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errEv.Err(err).Caller().Send()
|
errEv.Err(err).Caller().Send()
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
data["threads"] = threads
|
||||||
var updateID int
|
|
||||||
for name, val := range request.Form {
|
|
||||||
if len(val) > 0 && val[0] == "Update attributes" {
|
|
||||||
if _, err = fmt.Sscanf(name, "update-%d", &updateID); err != nil {
|
|
||||||
return "", fmt.Errorf("invalid input name %q: %s", name, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
threads, err := gcsql.GetThreadsWithBoardID(boardID, true)
|
|
||||||
var threadIDs []interface{}
|
var threadIDs []interface{}
|
||||||
for _, thread := range threads {
|
for _, thread := range threads {
|
||||||
threadIDs = append(threadIDs, thread.ID)
|
threadIDs = append(threadIDs, thread.ID)
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
errEv.Err(err).Caller().
|
|
||||||
Int("boardID", boardID).Send()
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if wantsJSON {
|
if wantsJSON {
|
||||||
return threads, nil
|
return threads, nil
|
||||||
}
|
}
|
||||||
board := gcsql.Board{
|
|
||||||
ID: boardID,
|
|
||||||
Dir: boardDir,
|
|
||||||
}
|
|
||||||
|
|
||||||
opIDs, err := gcsql.GetTopPostIDsInThreadIDs(threadIDs...)
|
opMap, err := gcsql.GetTopPostIDsInThreadIDs(threadIDs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errEv.Err(err).Caller().Send()
|
errEv.Err(err).Caller().Send()
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if err = serverutil.MinifyTemplate(gctemplates.ManageThreadAttrs, map[string]interface{}{
|
data["opMap"] = opMap
|
||||||
"action": "threadattrs",
|
var formURL url.URL
|
||||||
"boards": gcsql.AllBoards,
|
formURL.Path = config.WebPath("/manage/threadattrs")
|
||||||
"board": board,
|
vals := formURL.Query()
|
||||||
"threads": threads,
|
vals.Set("board", boardDir)
|
||||||
"opIDs": opIDs,
|
if topPostStr != "" {
|
||||||
}, attrBuffer, "text/html"); err != nil {
|
vals.Set("thread", topPostStr)
|
||||||
|
}
|
||||||
|
formURL.RawQuery = vals.Encode()
|
||||||
|
data["formURL"] = formURL.String()
|
||||||
|
if err = serverutil.MinifyTemplate(gctemplates.ManageThreadAttrs, data, attrBuffer, "text/html"); err != nil {
|
||||||
errEv.Err(err).Caller().Send()
|
errEv.Err(err).Caller().Send()
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<form method="GET" action="{{webPath "manage" $.action}}" class="staff-form">
|
<form method="GET" action="{{webPath "manage" $.action}}" class="staff-form">
|
||||||
|
<h3>Select a board</h3>
|
||||||
{{with $.boards -}}
|
{{with $.boards -}}
|
||||||
<select name="board">
|
<select name="board">
|
||||||
{{range $_, $board := $.boards}}
|
{{range $_, $board := $.boards}}
|
||||||
|
@ -7,33 +8,51 @@
|
||||||
<i>No boards</i>
|
<i>No boards</i>
|
||||||
{{end}}
|
{{end}}
|
||||||
</select>
|
</select>
|
||||||
<input type="submit" name="show-threads" value="Show threads" /><br />
|
<input type="submit" value="Show threads" /><br />
|
||||||
|
</form>
|
||||||
{{else}}
|
{{else}}
|
||||||
<i>No boards</i>
|
<i>No boards</i>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{with $.board}}
|
{{with $.board}}
|
||||||
Threads on /{{.Dir}}/ (board ID is {{.ID}})<br/>
|
<h3>Select a thread</h3>
|
||||||
{{if (lt (len $.threads) 1)}}
|
{{if (lt (len $.threads) 1)}}
|
||||||
<i>No threads on </i>
|
<i>No threads on </i>
|
||||||
{{else -}}
|
{{else -}}
|
||||||
<table>
|
<form action="{{$.formURL}}" method="GET">
|
||||||
<tr>
|
<input type="hidden" name="board" value="{{$.board.Dir}}">
|
||||||
<th>Thread ID</th><th>OP</th><th>Locked</th><th>Stickied</th><th>Anchored</th><th>Cyclical</th>
|
<select name="thread">
|
||||||
</tr>
|
{{- range $_, $thread := $.threads}}
|
||||||
{{- range $t, $thread := $.threads}}
|
{{with $opIDstr := (print (index $.opMap $thread.ID)) -}}
|
||||||
<tr>
|
<option value="{{$opIDstr}}" {{with $.thread}}{{if eq (index $.opMap $thread.ID) (index $.opMap $.thread.ID)}}selected="selected"{{end}}{{end}}>>>/{{$.board.Dir}}/{{$opIDstr}}</option>
|
||||||
{{- with $opIDstr := (print (index $.opIDs $t)) -}}
|
|
||||||
<td>{{$thread.ID}}</td>
|
|
||||||
<td><a href="{{webPath $.board.Dir "res" $opIDstr}}.html">>>/{{$.board.Dir}}/{{$opIDstr}}</a></td>
|
|
||||||
<td><input type="checkbox" name="locked" {{if $thread.Locked}}checked="checked"{{end}} />{{$thread.Locked}}</td>
|
|
||||||
<td><input type="checkbox" name="stickied" {{if $thread.Stickied}}checked="checked"{{end}} />{{$thread.Stickied}}</td>
|
|
||||||
<td><input type="checkbox" name="anchored" {{if $thread.Anchored}}checked="checked"{{end}} />{{$thread.Anchored}}</td>
|
|
||||||
<td><input type="checkbox" name="cyclical" {{if $thread.Cyclical}}checked="checked"{{end}} />{{$thread.Cyclical}}</td>
|
|
||||||
<td><input type="submit" name="update-{{$thread.ID}}" value="Update attributes"></td>
|
|
||||||
{{- end}}
|
|
||||||
</tr>
|
|
||||||
{{end -}}
|
{{end -}}
|
||||||
</table>
|
{{end -}}
|
||||||
{{- end}}
|
</select>
|
||||||
{{end}}
|
<input type="submit" value="Show attributes">
|
||||||
</form>
|
</form>
|
||||||
|
{{with $.thread}}
|
||||||
|
<form action="{{$.formURL}}" method="POST">
|
||||||
|
<input type="hidden" name="board" value="{{$.board.Dir}}">
|
||||||
|
<input type="hidden" name="thread" value="{{$.thread.ID}}">
|
||||||
|
<h3>Thread attributes (click to toggle)</h3>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Locked</th>
|
||||||
|
<td><input type="submit" name="{{if $.thread.Locked}}unlock{{else}}lock{{end}}" value="{{if $.thread.Locked}}Locked{{else}}Not locked{{end}}" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Stickied</th>
|
||||||
|
<td><input type="submit" name="{{if $.thread.Stickied}}unsticky{{else}}sticky{{end}}" value="{{if $.thread.Stickied}}Stickied{{else}}Not stickied{{end}}" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Anchored</th>
|
||||||
|
<td><input type="submit" name="{{if $.thread.Anchored}}unanchor{{else}}anchor{{end}}" value="{{if $.thread.Anchored}}Anchored{{else}}Not anchored{{end}}" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Cyclical</th>
|
||||||
|
<td><input type="submit" name="{{if $.thread.Cyclical}}uncyclical{{else}}cyclical{{end}}" value="{{if $.thread.Cyclical}}Cyclical{{else}}Not cyclical{{end}}" /></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
{{- end}}
|
||||||
|
{{- end}}
|
||||||
|
{{- end}}
|
Loading…
Add table
Add a link
Reference in a new issue