diff --git a/conference.go b/conference.go index dac1910..ba120e8 100644 --- a/conference.go +++ b/conference.go @@ -405,6 +405,15 @@ func templateAttachmentInfo(args jet.Arguments) reflect.Value { return reflect.ValueOf(rc) } +// templateBozo returns true if the post's creator user has been filtered by the current one. +func templateBozo(args jet.Arguments) reflect.Value { + post := args.Get(0).Convert(reflect.TypeFor[*database.PostHeader]()).Interface().(*database.PostHeader) + topic := args.Get(1).Convert(reflect.TypeFor[*database.Topic]()).Interface().(*database.Topic) + ctxt := args.Get(2).Convert(reflect.TypeFor[ui.AmContext]()).Interface().(ui.AmContext) + rc, _ := topic.IsBozo(ctxt.Ctx(), ctxt.CurrentUser(), post.CreatorUid) + return reflect.ValueOf(rc) +} + /* ReadPosts displays posts in a topic. * Parameters: * ctxt - The AmContext for the request. @@ -450,6 +459,7 @@ func ReadPosts(ctxt ui.AmContext) (string, any, error) { conf := ctxt.GetScratch("currentConference").(*database.Conference) myLevel := ctxt.GetScratch("levelInConference").(uint16) topic := ctxt.GetScratch("currentTopic").(*database.Topic) + ctxt.VarMap().Set("post_topic", 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. lastRead, err := topic.GetLastRead(ctxt.Ctx(), ctxt.CurrentUser()) @@ -544,6 +554,13 @@ func ReadPosts(ctxt ui.AmContext) (string, any, error) { // Set advanced controls. advancedControls := ctxt.HasParameter("ac") && (len(posts) == 1) if advancedControls { + nbozo := ctxt.QueryParamInt("bozo", -1) + if nbozo >= 0 { + err = topic.SetBozo(ctxt.Ctx(), ctxt.CurrentUser(), posts[0].CreatorUid, nbozo != 0) + if err != nil { + return ui.ErrorPage(ctxt, err) + } + } isMyPost := (posts[0].CreatorUid == ctxt.CurrentUserId()) && !ctxt.CurrentUser().IsAnon isScribbled := posts[0].IsScribbled() canHide := !isScribbled && (isMyPost || confHidePerm) @@ -560,9 +577,6 @@ func ReadPosts(ctxt ui.AmContext) (string, any, error) { } } ctxt.VarMap().Set("canPublish", canPublish) - if !canHide && !canScribble && !confNukePerm && !canPublish { - advancedControls = false - } } ctxt.VarMap().Set("advancedControls", advancedControls) @@ -588,6 +602,7 @@ func ReadPosts(ctxt ui.AmContext) (string, any, error) { ctxt.VarMap().SetFunc("post_getText", templatePostText) ctxt.VarMap().SetFunc("post_getUserName", templateExtractUserName) ctxt.VarMap().SetFunc("post_getAttachmentInfo", templateAttachmentInfo) + ctxt.VarMap().SetFunc("post_isBozo", templateBozo) ctxt.VarMap().Set("post_stem", fmt.Sprintf("%s/r/%d", urlStem, topic.Number)) ctxt.VarMap().Set("post_max", topic.TopMessage) ctxt.VarMap().Set("posts", posts) @@ -605,12 +620,21 @@ func ReadPosts(ctxt ui.AmContext) (string, any, error) { return "framed_template", "posts.jet", nil } +/* PostInTopic adds a new post to a topic. + * Parameters: + * ctxt - The AmContext for the request. + * Returns: + * Command string dictating what to be rendered. + * Data as a parameter for the command string. + * Standard Go error status. + */ func PostInTopic(ctxt ui.AmContext) (string, any, error) { // Locate community, conference, and topic. comm := ctxt.CurrentCommunity() conf := ctxt.GetScratch("currentConference").(*database.Conference) level := ctxt.GetScratch("levelInConference").(uint16) topic := ctxt.GetScratch("currentTopic").(*database.Topic) + ctxt.VarMap().Set("post_topic", topic) urlStem := fmt.Sprintf("/comm/%s/conf/%s/r/%d", comm.Alias, ctxt.GetScratch("currentAlias"), topic.Number) if ctxt.FormFieldIsSet("cancel") { @@ -711,6 +735,7 @@ func PostInTopic(ctxt ui.AmContext) (string, any, error) { ctxt.VarMap().SetFunc("post_getText", templatePostText) ctxt.VarMap().SetFunc("post_getUserName", templateExtractUserName) ctxt.VarMap().SetFunc("post_getAttachmentInfo", templateAttachmentInfo) + ctxt.VarMap().SetFunc("post_isBozo", templateBozo) ctxt.VarMap().Set("post_stem", fmt.Sprintf("/comm/%s/conf/%s/r/%d", comm.Alias, ctxt.GetScratch("currentAlias"), topic.Number)) ctxt.VarMap().Set("post_max", topic.TopMessage) ctxt.VarMap().Set("posts", posts) diff --git a/database/topic.go b/database/topic.go index 10b8a80..f9a839a 100644 --- a/database/topic.go +++ b/database/topic.go @@ -129,6 +129,74 @@ func (t *Topic) SetHidden(ctx context.Context, u *User, hidden bool) error { return err } +// IsBozo returns true if the specified test UID is filtered for the specified user. +func (t *Topic) IsBozo(ctx context.Context, u *User, testUid int32) (bool, error) { + if u.IsAnon { + return false, nil + } + row := amdb.QueryRowContext(ctx, "SELECT bozo_uid FROM topicbozo WHERE topicid = ? AND uid = ? AND bozo_uid = ?", t.TopicId, u.Uid, testUid) + var tmp int32 + err := row.Scan(&tmp) + switch err { + case nil: + return true, nil + case sql.ErrNoRows: + return false, nil + } + return false, err +} + +// SetBozo adds or removes a filter of a subject UID for the specified user. +func (t *Topic) SetBozo(ctx context.Context, u *User, subjectUid int32, bozo bool) error { + var err error = nil + if !u.IsAnon { + if bozo { // Flipping the bozo bit! + row := amdb.QueryRowContext(ctx, "SELECT bozo_uid FROM topicbozo WHERE topicid = ? AND uid = ? AND bozo_uid = ?", t.TopicId, u.Uid, subjectUid) + var tmp int32 + err = row.Scan(&tmp) + switch err { + case nil: + return nil + case sql.ErrNoRows: + _, err = amdb.ExecContext(ctx, "INSERT INTO topicbozo (topicid, uid, bozo_uid) VALUES (?, ?, ?)", t.TopicId, u.Uid, subjectUid) + } + } else { + _, err = amdb.ExecContext(ctx, "DELETE FROM topicbozo WHERE topicid = ? AND uid = ? AND bozo_uid = ?", t.TopicId, u.Uid, subjectUid) + } + } + return err +} + +// TopicBozo is a structure that returns all information about a filtered user. +type TopicBozo struct { + Uid int32 + Username string + GivenName string + FamilyName string +} + +// GetBozos returns all filtered users for a given user on the topic. +func (t *Topic) GetBozos(ctx context.Context, u *User) ([]TopicBozo, error) { + if u.IsAnon { + return make([]TopicBozo, 0), nil + } + rs, err := amdb.QueryContext(ctx, `SELECT b.bozo_uid, u.username, c.given_name, c.family_name + FROM topicbozo b, users u, contacts c WHERE b.topicid = ? AND b.uid = ? AND b.bozo_uid = u.uid AND u.contactid = c.contactid`, t.TopicId, u.Uid) + if err != nil { + return nil, err + } + rc := make([]TopicBozo, 0) + for rs.Next() { + var tb TopicBozo + err = rs.Scan(&(tb.Uid), &(tb.Username), &(tb.GivenName), &(tb.FamilyName)) + if err != nil { + return nil, err + } + rc = append(rc, tb) + } + return rc, nil +} + // 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 diff --git a/docs/MISSINGFUNCS.md b/docs/MISSINGFUNCS.md index 122046d..a5c98a0 100644 --- a/docs/MISSINGFUNCS.md +++ b/docs/MISSINGFUNCS.md @@ -42,7 +42,7 @@ _(italicized items can be deferred)_ - Delete - ~~Post Scribble~~ - ~~Post Nuke~~ - - Post Filter User + - ~~Post Filter User~~ - Post Move - Post Publish - Manage Communities on communities sidebox diff --git a/ui/views/posts.jet b/ui/views/posts.jet index ad8ba50..5b54784 100644 --- a/ui/views/posts.jet +++ b/ui/views/posts.jet @@ -109,6 +109,7 @@ {{ post_overrideLine := "" }} {{ post_overrideLink := "" }} {{ post_attach := nil }} + {{ post_bozo := false }} {{ range i, p := posts }} {{ post_cur = p }} {{ post_userName = post_getUserName(p, .) }} @@ -116,6 +117,7 @@ {{ post_overrideLine = post_getOverrideLine(p, .) }} {{ post_overrideLink = post_getOverrideLink(p, post_topicPermalink) }} {{ post_attach = post_getAttachmentInfo(p, .) }} + {{ post_bozo = post_isBozo(p, post_topic, .) }} {{ include "singlepost.jet" }} {{ if advancedControls }}
{{ post_text | postRewrite | raw }}
+ {{ post_text | postRewrite | raw }}
+ {{ end }}
{{ end }}