partial implementation of "read posts"
This commit is contained in:
@@ -17,6 +17,7 @@ import (
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.erbosoft.com/amy/amsterdam/database"
|
||||
"git.erbosoft.com/amy/amsterdam/htmlcheck"
|
||||
@@ -322,3 +323,88 @@ func AttachmentUpload(ctxt ui.AmContext) (string, any, error) {
|
||||
}
|
||||
return ui.ErrorPage(ctxt, errors.New("invalid button clicked on form"))
|
||||
}
|
||||
|
||||
func ReadPosts(ctxt ui.AmContext) (string, any, error) {
|
||||
// If we need to reset a topic's last read count (as with "Next & Keep New"), spin the task off into a goroutine.
|
||||
if ctxt.HasParameter("rst") {
|
||||
rst := strings.Split(ctxt.Parameter("rst"), ",")
|
||||
if len(rst) >= 2 {
|
||||
topicId, e1 := strconv.ParseInt(rst[0], 10, 32)
|
||||
lastRead, e2 := strconv.ParseInt(rst[1], 10, 32)
|
||||
if e1 == nil && e2 == nil {
|
||||
user := ctxt.CurrentUser()
|
||||
go func() {
|
||||
topic, _ := database.AmGetTopic(int32(topicId))
|
||||
if topic != nil {
|
||||
topic.SetLastRead(user, int32(lastRead))
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
// Locate community, conference, and topic.
|
||||
comm := ctxt.CurrentCommunity()
|
||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||
var topic *database.Topic = nil
|
||||
if rawTopic, err := strconv.ParseInt(ctxt.URLParam("topic"), 10, 16); err == nil {
|
||||
topic, err = database.AmGetTopicByNumber(conf, int16(rawTopic))
|
||||
}
|
||||
if topic == nil {
|
||||
ctxt.SetRC(http.StatusNotFound)
|
||||
return ui.ErrorPage(ctxt, fmt.Errorf("topic not found: %s", ctxt.URLParam("topic")))
|
||||
}
|
||||
|
||||
// Determine the range of posts to display. The "pin" is the post number after which we display the horizontal line separating old and new posts.
|
||||
postRange := make([]int32, 2)
|
||||
var pin int32 = -1
|
||||
if ctxt.HasParameter("r") {
|
||||
rstr := strings.Split(ctxt.Parameter("r"), ",")
|
||||
if len(rstr) == 0 {
|
||||
ctxt.SetRC(http.StatusNotFound)
|
||||
return ui.ErrorPage(ctxt, fmt.Errorf("posts not found: %s in topic %d", ctxt.Parameter("r"), topic.Number))
|
||||
}
|
||||
v, err := strconv.ParseInt(rstr[0], 10, 32)
|
||||
if err != nil {
|
||||
ctxt.SetRC(http.StatusNotFound)
|
||||
return ui.ErrorPage(ctxt, fmt.Errorf("posts not found: %s in topic %d", ctxt.Parameter("r"), topic.Number))
|
||||
}
|
||||
postRange[0] = int32(v)
|
||||
if len(rstr) > 1 {
|
||||
v, err = strconv.ParseInt(rstr[1], 10, 32)
|
||||
if err != nil {
|
||||
ctxt.SetRC(http.StatusNotFound)
|
||||
return ui.ErrorPage(ctxt, fmt.Errorf("posts not found: %s in topic %d", ctxt.Parameter("r"), topic.Number))
|
||||
}
|
||||
postRange[1] = int32(v)
|
||||
} else {
|
||||
postRange[1] = postRange[0]
|
||||
}
|
||||
if postRange[1] < 0 {
|
||||
postRange[1] = topic.TopMessage
|
||||
}
|
||||
if postRange[0] > postRange[1] {
|
||||
t := postRange[0]
|
||||
postRange[0] = postRange[1]
|
||||
postRange[1] = t
|
||||
}
|
||||
} else {
|
||||
lastRead, err := topic.GetLastRead(ctxt.CurrentUser())
|
||||
if err != nil {
|
||||
ctxt.SetRC(http.StatusNotFound)
|
||||
return ui.ErrorPage(ctxt, fmt.Errorf("posts not found in topic %d - %v", topic.Number, err))
|
||||
}
|
||||
postRange[0] = lastRead + 1
|
||||
postRange[1] = topic.TopMessage
|
||||
count := postRange[1] - postRange[0] + 1
|
||||
if count > ctxt.Globals().PostsPerPage {
|
||||
postRange[0] = postRange[1] - ctxt.Globals().PostsPerPage + 1
|
||||
} else if count < ctxt.Globals().PostsPerPage {
|
||||
pin = postRange[0] - 1
|
||||
postRange[0] -= ctxt.Globals().OldPostsAtTop
|
||||
postRange[0] = max(0, postRange[0])
|
||||
if pin < postRange[0] {
|
||||
pin = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+49
-1
@@ -52,6 +52,31 @@ func (t *Topic) GetPost(num int32) (*PostHeader, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// GetLastRead returns the "last read" message for a user on a topic.
|
||||
func (t *Topic) GetLastRead(u *User) (int32, error) {
|
||||
rs, err := amdb.Query("SELECT last_message FROM topicsettings WHERE topicid = ? AND uid = ?", t.TopicId, u.Uid)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
var rc int32 = -1
|
||||
if rs.Next() {
|
||||
rs.Scan(&rc)
|
||||
}
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
// SetLastRead sets the "last read" message for a user on a topic.
|
||||
func (t *Topic) SetLastRead(u *User, postNum int32) error {
|
||||
rs, err := amdb.Exec("UPDATE topicsettings SET last_message = ?, last_read = NOW() WHERE topicid = ? AND uid = ?", postNum, t.TopicId, u.Uid)
|
||||
if err == nil {
|
||||
nrow, _ := rs.RowsAffected()
|
||||
if nrow == 0 {
|
||||
_, err = amdb.Exec("INSERT INTO topicsettings (topicid, uid, last_message, last_read, last_post) VALUES (?, ?, ?, NOW(), NULL)", t.TopicId, u.Uid, postNum)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// TopicSettings contains per-user settings for topics, including the "last read" message pointer.
|
||||
type TopicSettings struct {
|
||||
TopicId int32 `db:"topicid"` // unique ID of the topic
|
||||
@@ -99,7 +124,7 @@ func AmGetTopic(topicId int32) (*Topic, error) {
|
||||
return &(dbdata[0]), nil
|
||||
}
|
||||
|
||||
/* AmGetTopic retrieves a topic by ID, in a transaction.
|
||||
/* AmGetTopicTx retrieves a topic by ID, in a transaction.
|
||||
* Parameters:
|
||||
* tx - The transaction to use.
|
||||
* topicId - ID of the topic to retrieve.
|
||||
@@ -122,6 +147,29 @@ func AmGetTopicTx(tx *sqlx.Tx, topicId int32) (*Topic, error) {
|
||||
return &(dbdata[0]), nil
|
||||
}
|
||||
|
||||
/* AmGetTopicByNumber retrieves a topic by conference and sequence number.
|
||||
* Parameters:
|
||||
* conf - The conference to look in.
|
||||
* topicNum - The topic number within that conference.
|
||||
* Returns:
|
||||
* Pointer to the Topic, or nil.
|
||||
* Standard Go error status.
|
||||
*/
|
||||
func AmGetTopicByNumber(conf *Conference, topicNum int16) (*Topic, error) {
|
||||
var dbdata []Topic
|
||||
err := amdb.Select(&dbdata, "SELECT * FROM topics WHERE confid = ? AND num = ?", conf.ConfId, topicNum)
|
||||
if err == nil {
|
||||
if len(dbdata) == 0 {
|
||||
err = fmt.Errorf("no topic numbered %d in conference %s (#%d)", topicNum, conf.Name, conf.ConfId)
|
||||
} else if len(dbdata) > 1 {
|
||||
err = fmt.Errorf("AmGetTopicByNumber: too many entries (%d) for topic #%d in conference %s (#%d)", len(dbdata), topicNum, conf.Name, conf.ConfId)
|
||||
} else {
|
||||
return &(dbdata[0]), nil
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// View and sort constants for AmListTopics.
|
||||
const (
|
||||
TopicViewAll = 0 // list all topics
|
||||
|
||||
@@ -97,6 +97,7 @@ func setupEcho() *echo.Echo {
|
||||
confGroup.GET("", ui.AmWrap(Topics))
|
||||
confGroup.GET("/new_topic", ui.AmWrap(NewTopicForm))
|
||||
confGroup.POST("/new_topic", ui.AmWrap(NewTopic))
|
||||
confGroup.GET("/r/:topic", ui.AmWrap(ReadPosts))
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ type AmContext interface {
|
||||
FormFile(string) (*multipart.FileHeader, error)
|
||||
Globals() *database.Globals
|
||||
GlobalFlags() *util.OptionSet
|
||||
HasParameter(string) bool
|
||||
IsMember() bool
|
||||
IsMemberLocked() bool
|
||||
LeftMenu() string
|
||||
@@ -206,6 +207,21 @@ func (c *amContext) GlobalFlags() *util.OptionSet {
|
||||
return c.globalFlags
|
||||
}
|
||||
|
||||
// HasParameter tests to see if we have a parameter.
|
||||
func (c *amContext) HasParameter(name string) bool {
|
||||
s := c.echoContext.QueryParam(name)
|
||||
if s != "" {
|
||||
return true
|
||||
}
|
||||
if c.echoContext.Request().Method == "POST" {
|
||||
s = c.echoContext.FormValue(name)
|
||||
if s != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsMember returns true if the user is a member of the current community.
|
||||
func (c *amContext) IsMember() bool {
|
||||
return c.isMember
|
||||
|
||||
Reference in New Issue
Block a user