From 5c8bb8dd5e50350612ddf6cb2c35c593be859ee0 Mon Sep 17 00:00:00 2001 From: Amy Gale Ruth Bowersox Date: Sat, 20 Dec 2025 22:29:26 -0700 Subject: [PATCH] all database operations now take a context.Context, which is propagated through from sources --- community.go | 26 ++--- communityadmin.go | 40 +++---- conference.go | 54 ++++----- database/audit.go | 7 +- database/category.go | 47 ++++---- database/community.go | 197 ++++++++++++++++++--------------- database/conference.go | 75 +++++++------ database/contactinfo.go | 34 +++--- database/emailban.go | 7 +- database/globals.go | 27 +++-- database/imagestore.go | 26 +++-- database/ipban.go | 7 +- database/post.go | 18 +-- database/post_link.go | 7 +- database/services.go | 61 +++++----- database/sidebox.go | 17 ++- database/topic.go | 59 +++++----- database/user.go | 165 ++++++++++++++------------- email/sender.go | 7 +- find.go | 14 +-- htmlcheck/checker.go | 10 +- htmlcheck/dictionary.go | 4 +- htmlcheck/emoticon_rewriter.go | 7 +- htmlcheck/rewriter.go | 18 +-- htmlcheck/url_rewriter.go | 4 +- login.go | 26 ++--- main.go | 6 +- top.go | 18 +-- ui/amcontext.go | 16 +-- ui/images.go | 2 +- ui/menus.go | 6 +- ui/middleware.go | 12 +- ui/render_wrap.go | 2 +- ui/session_mgr.go | 13 ++- ui/templates.go | 14 ++- ui/views/find.jet | 2 +- ui/views/menu_left_comm.jet | 2 +- ui/views/posts.jet | 4 +- userdata.go | 48 ++++---- 39 files changed, 605 insertions(+), 504 deletions(-) diff --git a/community.go b/community.go index c699f70..eced81c 100644 --- a/community.go +++ b/community.go @@ -32,22 +32,22 @@ import ( */ func ShowCommunity(ctxt ui.AmContext) (string, any, error) { me := ctxt.CurrentUser() - prefs, err := me.Prefs() + prefs, err := me.Prefs(ctxt.Ctx()) if err != nil { return ui.ErrorPage(ctxt, err) } comm := ctxt.CurrentCommunity() // set by middleware - ci, err := comm.ContactInfo() + ci, err := comm.ContactInfo(ctxt.Ctx()) if err != nil { return ui.ErrorPage(ctxt, err) } - host, err := comm.Host() + host, err := comm.Host(ctxt.Ctx()) if err != nil { return ui.ErrorPage(ctxt, err) } var cats []*database.Category if !ctxt.GlobalFlags().Get(database.GlobalFlagNoCategories) { - cats, err = database.AmGetCategoryHierarchy(comm.CategoryId) + cats, err = database.AmGetCategoryHierarchy(ctxt.Ctx(), comm.CategoryId) if err != nil { return ui.ErrorPage(ctxt, err) } @@ -142,7 +142,7 @@ func ShowCommunity(ctxt ui.AmContext) (string, any, error) { func JoinCommunity(ctxt ui.AmContext) (string, any, error) { me := ctxt.CurrentUser() comm := ctxt.CurrentCommunity() // set by middleware - mbr, _, _, err := comm.Membership(me) + mbr, _, _, err := comm.Membership(ctxt.Ctx(), me) if err != nil { return ui.ErrorPage(ctxt, err) } @@ -161,7 +161,7 @@ func JoinCommunity(ctxt ui.AmContext) (string, any, error) { return dlg.Render(ctxt) } // if get here, this is a public community, and we can join - err = comm.SetMembership(me, database.AmDefaultRole("Community.NewUser").Level(), false, me.Uid, ctxt.RemoteIP()) + err = comm.SetMembership(ctxt.Ctx(), me, database.AmDefaultRole("Community.NewUser").Level(), false, me.Uid, ctxt.RemoteIP()) if err != nil { return ui.ErrorPage(ctxt, err) } @@ -182,7 +182,7 @@ func JoinCommunity(ctxt ui.AmContext) (string, any, error) { func JoinCommunityWithKey(ctxt ui.AmContext) (string, any, error) { me := ctxt.CurrentUser() comm := ctxt.CurrentCommunity() // set by middleware - mbr, _, _, err := comm.Membership(me) + mbr, _, _, err := comm.Membership(ctxt.Ctx(), me) if err != nil { return ui.ErrorPage(ctxt, err) } @@ -210,7 +210,7 @@ func JoinCommunityWithKey(ctxt ui.AmContext) (string, any, error) { if comm.JoinKey != nil && key != *comm.JoinKey { return dlg.RenderError(ctxt, "The join key does not match the community. Please try again.") } - err = comm.SetMembership(me, database.AmDefaultRole("Community.NewUser").Level(), false, me.Uid, ctxt.RemoteIP()) + err = comm.SetMembership(ctxt.Ctx(), me, database.AmDefaultRole("Community.NewUser").Level(), false, me.Uid, ctxt.RemoteIP()) if err != nil { return dlg.RenderError(ctxt, fmt.Sprintf("Error joining: %v", err)) } @@ -232,7 +232,7 @@ func JoinCommunityWithKey(ctxt ui.AmContext) (string, any, error) { func UnjoinCommunity(ctxt ui.AmContext) (string, any, error) { me := ctxt.CurrentUser() comm := ctxt.CurrentCommunity() // set by middleware - mbr, lock, _, err := comm.Membership(me) + mbr, lock, _, err := comm.Membership(ctxt.Ctx(), me) if err != nil { return ui.ErrorPage(ctxt, err) } @@ -260,7 +260,7 @@ func UnjoinCommunity(ctxt ui.AmContext) (string, any, error) { func UnjoinCommunityConfirm(ctxt ui.AmContext) (string, any, error) { me := ctxt.CurrentUser() comm := ctxt.CurrentCommunity() // set by middleware - mbr, lock, _, err := comm.Membership(me) + mbr, lock, _, err := comm.Membership(ctxt.Ctx(), me) if err != nil { return ui.ErrorPage(ctxt, err) } @@ -276,7 +276,7 @@ func UnjoinCommunityConfirm(ctxt ui.AmContext) (string, any, error) { return "redirect", fmt.Sprintf("/comm/%s/profile", comm.Alias), nil } if ctxt.FormFieldIsSet("unjoin") { - err = comm.SetMembership(me, 0, false, me.Uid, ctxt.RemoteIP()) + err = comm.SetMembership(ctxt.Ctx(), me, 0, false, me.Uid, ctxt.RemoteIP()) if err != nil { return ui.ErrorPage(ctxt, err) } @@ -314,7 +314,7 @@ func MemberList(ctxt ui.AmContext) (string, any, error) { ctxt.VarMap().Set("ofs", ofs) ctxt.VarMap().Set("amsterdam_pageTitle", "List Members") listMax := int(ctxt.Globals().MaxCommunityMemberPage) - results, total, err := comm.ListMembers(database.ListMembersFieldNone, database.ListMembersOperNone, "", ofs*listMax, listMax, showHidden) + results, total, err := comm.ListMembers(ctxt.Ctx(), database.ListMembersFieldNone, database.ListMembersOperNone, "", ofs*listMax, listMax, showHidden) if err != nil { return ui.ErrorPage(ctxt, err) } @@ -385,7 +385,7 @@ func MemberSearch(ctxt ui.AmContext) (string, any, error) { return "framed_template", "memberlist.jet", nil } listMax := int(ctxt.Globals().MaxCommunityMemberPage) - results, total, err := comm.ListMembers(iField, iOper, term, ofs*listMax, listMax, showHidden) + results, total, err := comm.ListMembers(ctxt.Ctx(), iField, iOper, term, ofs*listMax, listMax, showHidden) if err != nil { return ui.ErrorPage(ctxt, err) } diff --git a/communityadmin.go b/communityadmin.go index 4d3f962..5348f59 100644 --- a/communityadmin.go +++ b/communityadmin.go @@ -88,11 +88,11 @@ func CommunityProfileForm(ctxt ui.AmContext) (string, any, error) { return ui.ErrorPage(ctxt, errors.New("you are not permitted to access this page")) } var ci *database.ContactInfo - ci, err := comm.ContactInfo() + ci, err := comm.ContactInfo(ctxt.Ctx()) if err != nil { return ui.ErrorPage(ctxt, err) } - flags, err := comm.Flags() + flags, err := comm.Flags(ctxt.Ctx()) if err != nil { return ui.ErrorPage(ctxt, err) } @@ -184,12 +184,12 @@ func EditCommunityProfile(ctxt ui.AmContext) (string, any, error) { return dlg.RenderError(ctxt, err.Error()) } var ci *database.ContactInfo - ci, err = comm.ContactInfo() + ci, err = comm.ContactInfo(ctxt.Ctx()) if err != nil { return dlg.RenderError(ctxt, err.Error()) } var flags *util.OptionSet - flags, err = comm.Flags() + flags, err = comm.Flags(ctxt.Ctx()) if err != nil { return ui.ErrorPage(ctxt, err) } @@ -202,7 +202,7 @@ func EditCommunityProfile(ctxt ui.AmContext) (string, any, error) { nci.Region = dlg.Field("reg").ValPtr() nci.PostalCode = dlg.Field("pcode").ValPtr() nci.Country = dlg.Field("country").ValPtr() - _, err = nci.Save() + _, err = nci.Save(ctxt.Ctx()) ci = nci if err == nil { var joinkey *string = nil @@ -221,17 +221,17 @@ func EditCommunityProfile(ctxt ui.AmContext) (string, any, error) { hidedir = true hidesearch = true } - err = comm.SetProfileData(dlg.Field("name").Value, dlg.Field("alias").Value, dlg.Field("synopsis").ValPtr(), + err = comm.SetProfileData(ctxt.Ctx(), dlg.Field("name").Value, dlg.Field("alias").Value, dlg.Field("synopsis").ValPtr(), dlg.Field("rules").ValPtr(), dlg.Field("language").ValPtr(), joinkey, dlg.Field("membersonly").IsChecked(), hidedir, hidesearch, dlg.Field("read_lvl").GetLevel(), dlg.Field("write_lvl").GetLevel(), dlg.Field("create_lvl").GetLevel(), dlg.Field("delete_lvl").GetLevel(), dlg.Field("join_lvl").GetLevel()) } if err == nil { flags.Set(database.CommunityFlagPicturesInPosts, dlg.Field("pic_in_post").IsChecked()) - err = comm.SaveFlags(flags) + err = comm.SaveFlags(ctxt.Ctx(), flags) } if err == nil { - err = comm.TouchUpdate() + err = comm.TouchUpdate(ctxt.Ctx()) } if err != nil { ctxt.ClearCommunityContext() @@ -259,7 +259,7 @@ func CommunityLogoForm(ctxt ui.AmContext) (string, any, error) { ctxt.SetRC(http.StatusForbidden) return ui.ErrorPage(ctxt, errors.New("you are not permitted to access this page")) } - ci, err := comm.ContactInfo() + ci, err := comm.ContactInfo(ctxt.Ctx()) if err == nil { ctxt.VarMap().Set("commName", comm.Name) ctxt.VarMap().Set("commAlias", comm.Alias) @@ -284,7 +284,7 @@ func EditCommunityLogo(ctxt ui.AmContext) (string, any, error) { ctxt.SetRC(http.StatusForbidden) return ui.ErrorPage(ctxt, errors.New("you are not permitted to access this page")) } - ci, err := comm.ContactInfo() + ci, err := comm.ContactInfo(ctxt.Ctx()) if err != nil { return ui.ErrorPage(ctxt, err) } @@ -300,13 +300,13 @@ func EditCommunityLogo(ctxt ui.AmContext) (string, any, error) { ui.CommunityLogoMaxBytes) if err == nil { var img *database.ImageStore - img, err = database.AmStoreImage(database.ImageTypeCommunityLogo, comm.Id, mimeType, imageData) + img, err = database.AmStoreImage(ctxt.Ctx(), database.ImageTypeCommunityLogo, comm.Id, mimeType, imageData) if err == nil { photourl := fmt.Sprintf("/img/store/%d", img.ImgId) ci.PhotoURL = &photourl - _, err = ci.Save() + _, err = ci.Save(ctxt.Ctx()) if err == nil { - err = comm.TouchUpdate() + err = comm.TouchUpdate(ctxt.Ctx()) } if err == nil { return "redirect", "/comm/" + comm.Alias + "/admin/profile", nil @@ -336,7 +336,7 @@ func EditCommunityLogo(ctxt ui.AmContext) (string, any, error) { defer func() { if happy { ampool.Submit(func(context.Context) { - err := database.AmDeleteImage(int32(id)) + err := database.AmDeleteImage(ctxt.Ctx(), int32(id)) if err != nil { log.Errorf("unable to delete image ID %d: %v", id, err) } @@ -345,7 +345,7 @@ func EditCommunityLogo(ctxt ui.AmContext) (string, any, error) { }() } ci.PhotoURL = nil - _, err := ci.Save() + _, err := ci.Save(ctxt.Ctx()) if err != nil { return ui.ErrorPage(ctxt, err) } @@ -409,7 +409,7 @@ func CreateCommunity(ctxt ui.AmContext) (string, any, error) { return dlg.RenderError(ctxt, err.Error()) } var testcomm *database.Community - testcomm, err = database.AmGetCommunityByAlias(dlg.Field("alias").Value) + testcomm, err = database.AmGetCommunityByAlias(ctxt.Ctx(), dlg.Field("alias").Value) if err != nil { return dlg.RenderError(ctxt, err.Error()) } @@ -429,7 +429,7 @@ func CreateCommunity(ctxt ui.AmContext) (string, any, error) { hideSearch = true } var comm *database.Community - comm, err = database.AmCreateCommunity(dlg.Field("name").Value, dlg.Field("alias").Value, user.Uid, + comm, err = database.AmCreateCommunity(ctxt.Ctx(), dlg.Field("name").Value, dlg.Field("alias").Value, user.Uid, dlg.Field("language").ValPtr(), dlg.Field("synopsis").ValPtr(), dlg.Field("rules").ValPtr(), dlg.Field("joinkey").ValPtr(), hideDir, hideSearch, ctxt.RemoteIP()) if err != nil { @@ -440,12 +440,12 @@ func CreateCommunity(ctxt ui.AmContext) (string, any, error) { ci.Region = dlg.Field("reg").ValPtr() ci.PostalCode = dlg.Field("pcode").ValPtr() ci.Country = dlg.Field("country").ValPtr() - _, err = ci.Save() + _, err = ci.Save(ctxt.Ctx()) if err == nil { - err = comm.SetContactID(ci.ContactId) + err = comm.SetContactID(ctxt.Ctx(), ci.ContactId) } if err == nil { - err = comm.TouchUpdate() + err = comm.TouchUpdate(ctxt.Ctx()) } if err != nil { return dlg.RenderError(ctxt, err.Error()) diff --git a/conference.go b/conference.go index f2ac48b..02427a7 100644 --- a/conference.go +++ b/conference.go @@ -41,7 +41,7 @@ func Conferences(ctxt ui.AmContext) (string, any, error) { ctxt.VarMap().Set("commName", comm.Name) ctxt.VarMap().Set("commAlias", comm.Alias) ctxt.VarMap().Set("amsterdam_pageTitle", "Conference Listing: "+comm.Name) - clist, err := database.AmGetCommunityConferences(comm.Id, + clist, err := database.AmGetCommunityConferences(ctxt.Ctx(), comm.Id, comm.TestPermission("Community.ShowHiddenObjects", ctxt.EffectiveLevel())) if err != nil { return ui.ErrorPage(ctxt, err) @@ -59,7 +59,7 @@ func Conferences(ctxt ui.AmContext) (string, any, error) { * Standard Go error status. */ func Topics(ctxt ui.AmContext) (string, any, error) { - prefs, err := ctxt.CurrentUser().Prefs() + prefs, err := ctxt.CurrentUser().Prefs(ctxt.Ctx()) if err != nil { return ui.ErrorPage(ctxt, err) } @@ -94,7 +94,7 @@ func Topics(ctxt ui.AmContext) (string, any, error) { sort = ctxt.QueryParamInt("sort", sort) ctxt.SetSession("topic.sort", sort) - topics, err := database.AmListTopics(conf.ConfId, ctxt.CurrentUserId(), view, sort, false) + topics, err := database.AmListTopics(ctxt.Ctx(), conf.ConfId, ctxt.CurrentUserId(), view, sort, false) if err != nil { return ui.ErrorPage(ctxt, err) } @@ -137,12 +137,12 @@ func NewTopicForm(ctxt ui.AmContext) (string, any, error) { ctxt.VarMap().Set("conferenceName", conf.Name) ctxt.VarMap().Set("urlStem", fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, ctxt.URLParam("confid"))) ctxt.VarMap().Set("topicName", "") - cs, err := conf.Settings(ctxt.CurrentUser()) + cs, err := conf.Settings(ctxt.Ctx(), ctxt.CurrentUser()) if err != nil { return ui.ErrorPage(ctxt, err) } if cs == nil || cs.DefaultPseud == nil { - ci, err := ctxt.CurrentUser().ContactInfo() + ci, err := ctxt.CurrentUser().ContactInfo(ctxt.Ctx()) if err != nil { return ui.ErrorPage(ctxt, err) } @@ -178,7 +178,7 @@ func NewTopic(ctxt ui.AmContext) (string, any, error) { } if ctxt.FormFieldIsSet("preview") { // start by escaping the title - checker, err := htmlcheck.AmNewHTMLChecker("escaper") + checker, err := htmlcheck.AmNewHTMLChecker(ctxt.Ctx(), "escaper") if err != nil { return ui.ErrorPage(ctxt, err) } @@ -203,7 +203,7 @@ func NewTopic(ctxt ui.AmContext) (string, any, error) { ctxt.VarMap().Set("pb", v) // run the preview - checker, err = htmlcheck.AmNewHTMLChecker("preview") + checker, err = htmlcheck.AmNewHTMLChecker(ctxt.Ctx(), "preview") if err != nil { return ui.ErrorPage(ctxt, err) } @@ -225,7 +225,7 @@ func NewTopic(ctxt ui.AmContext) (string, any, error) { } if ctxt.FormFieldIsSet("post1") { // start by checking the title and pseud - checker, err := htmlcheck.AmNewHTMLChecker("post-pseud") + checker, err := htmlcheck.AmNewHTMLChecker(ctxt.Ctx(), "post-pseud") if err != nil { return ui.ErrorPage(ctxt, err) } @@ -238,7 +238,7 @@ func NewTopic(ctxt ui.AmContext) (string, any, error) { zeroPostPseud, _ := checker.Value() // now check the post data itself - checker, err = htmlcheck.AmNewHTMLChecker("post-body") + checker, err = htmlcheck.AmNewHTMLChecker(ctxt.Ctx(), "post-body") if err != nil { return ui.ErrorPage(ctxt, err) } @@ -249,7 +249,7 @@ func NewTopic(ctxt ui.AmContext) (string, any, error) { lines, _ := checker.Lines() // Add the topic! - topic, err := database.AmNewTopic(conf, ctxt.CurrentUser(), topicName, zeroPostPseud, zeroPost, int32(lines), ctxt.RemoteIP()) + topic, err := database.AmNewTopic(ctxt.Ctx(), conf, ctxt.CurrentUser(), topicName, zeroPostPseud, zeroPost, int32(lines), ctxt.RemoteIP()) if err != nil { return ui.ErrorPage(ctxt, err) } @@ -258,7 +258,7 @@ func NewTopic(ctxt ui.AmContext) (string, any, error) { return "redirect", urlStem, nil // no attachment - just redisplay topic list } - post, err := topic.GetPost(0) // get the initial post in the new topic + post, err := topic.GetPost(ctxt.Ctx(), 0) // get the initial post in the new topic if err != nil { return ui.ErrorPage(ctxt, err) } @@ -303,12 +303,12 @@ func AttachmentUpload(ctxt ui.AmContext) (string, any, error) { if err == nil { if file.Size <= (1024 * 1024) { // 1 Mb var post *database.PostHeader - post, err = database.AmGetPost(postId) + post, err = database.AmGetPost(ctxt.Ctx(), postId) if err == nil { var data []byte data, err = slurpFile(file) if err == nil { - err = post.SetAttachment(file.Filename, file.Header.Get("Content-Type"), int32(file.Size), data) + err = post.SetAttachment(ctxt.Ctx(), file.Filename, file.Header.Get("Content-Type"), int32(file.Size), data) if err == nil { return "redirect", target, nil } @@ -370,7 +370,8 @@ func breakRange(topic *database.Topic, into []int32, param string, sep string) e func templateExtractUserName(args jet.Arguments) reflect.Value { rc := "<>" post := args.Get(0).Convert(reflect.TypeFor[*database.PostHeader]()).Interface().(*database.PostHeader) - user, err := database.AmGetUser(post.CreatorUid) + ctxt := args.Get(1).Convert(reflect.TypeFor[ui.AmContext]()).Interface().(ui.AmContext) + user, err := database.AmGetUser(ctxt.Ctx(), post.CreatorUid) if err == nil { rc = user.Username } else { @@ -381,7 +382,8 @@ func templateExtractUserName(args jet.Arguments) reflect.Value { func templatePostText(args jet.Arguments) reflect.Value { post := args.Get(0).Convert(reflect.TypeFor[*database.PostHeader]()).Interface().(*database.PostHeader) - rc, err := post.Text() + ctxt := args.Get(1).Convert(reflect.TypeFor[ui.AmContext]()).Interface().(ui.AmContext) + rc, err := post.Text(ctxt.Ctx()) if err != nil { log.Errorf("templatePostText could not get post text from post #%d: %v", post.PostId, err) rc = "" @@ -395,10 +397,10 @@ func templateOverrideLine(args jet.Arguments) reflect.Value { rc := "" if post.IsScribbled() { scr_date := "" - scr_user, err := database.AmGetUser(*post.ScribbleUid) + scr_user, err := database.AmGetUser(ctxt.Ctx(), *post.ScribbleUid) if err == nil { var p *database.UserPrefs - p, err = ctxt.CurrentUser().Prefs() + p, err = ctxt.CurrentUser().Prefs(ctxt.Ctx()) if err == nil { scr_date = p.Localizer().Strftime("%b %e, %Y %r", *post.ScribbleDate) } @@ -430,20 +432,20 @@ func ReadPosts(ctxt ui.AmContext) (string, any, error) { rst := strings.Split(ctxt.Parameter("rst"), ",") if len(rst) >= 2 { user := ctxt.CurrentUser() - ampool.Submit(func(context.Context) { + ampool.Submit(func(ctx context.Context) { topicId, e1 := strconv.ParseInt(rst[0], 10, 32) lastRead, e2 := strconv.ParseInt(rst[1], 10, 32) if e1 == nil && e2 == nil { - topic, _ := database.AmGetTopic(int32(topicId)) + topic, _ := database.AmGetTopic(ctx, int32(topicId)) if topic != nil { - topic.SetLastRead(user, int32(lastRead)) + topic.SetLastRead(ctx, user, int32(lastRead)) } } }) } } // Get user prefs. - prefs, err := ctxt.CurrentUser().Prefs() + prefs, err := ctxt.CurrentUser().Prefs(ctxt.Ctx()) if err != nil { return ui.ErrorPage(ctxt, err) } @@ -452,7 +454,7 @@ func ReadPosts(ctxt ui.AmContext) (string, any, error) { 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)) + topic, err = database.AmGetTopicByNumber(ctxt.Ctx(), conf, int16(rawTopic)) } if topic == nil { ctxt.SetRC(http.StatusNotFound) @@ -460,7 +462,7 @@ func ReadPosts(ctxt ui.AmContext) (string, any, error) { } // 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.CurrentUser()) + lastRead, err := topic.GetLastRead(ctxt.Ctx(), 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)) @@ -495,7 +497,7 @@ func ReadPosts(ctxt ui.AmContext) (string, any, error) { } // Load the actual posts. - posts, err := database.AmGetPostRange(topic, postRange[0], postRange[1]) + posts, err := database.AmGetPostRange(ctxt.Ctx(), topic, postRange[0], postRange[1]) if err != nil { return ui.ErrorPage(ctxt, fmt.Errorf("internal error getting posts <%d:%d-%d> - %v", topic.Number, postRange[0], postRange[1], err)) } @@ -533,8 +535,8 @@ func ReadPosts(ctxt ui.AmContext) (string, any, error) { ctxt.VarMap().Set("amsterdam_pageTitle", fmt.Sprintf("%s: %s", topic.Name, summaryLine)) if resetLastRead { user := ctxt.CurrentUser() - ampool.Submit(func(context.Context) { - topic.SetLastRead(user, topic.TopMessage) + ampool.Submit(func(ctx context.Context) { + topic.SetLastRead(ctx, user, topic.TopMessage) }) } return "framed_template", "posts.jet", nil diff --git a/database/audit.go b/database/audit.go index 92b49bd..a3341b0 100644 --- a/database/audit.go +++ b/database/audit.go @@ -10,6 +10,7 @@ package database import ( + "context" "fmt" "time" @@ -113,12 +114,12 @@ func AmNewAudit(rectype int32, uid int32, ip string, data ...string) *AuditRecor } // Store stores the audit record in the database. -func (ar *AuditRecord) Store() error { +func (ar *AuditRecord) Store(ctx context.Context) error { if ar.Record > 0 { return fmt.Errorf("audit record %d already stored", ar.Record) } moment := time.Now().UTC() - rs, err := amdb.Exec(`INSERT INTO audit (on_date, event, uid, commid, ip, data1, data2, data3, data4) + rs, err := amdb.ExecContext(ctx, `INSERT INTO audit (on_date, event, uid, commid, ip, data1, data2, data3, data4) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);`, moment, ar.Event, ar.Uid, ar.CommId, ar.IP, ar.Data1, ar.Data2, ar.Data3, ar.Data4) if err != nil { @@ -132,7 +133,7 @@ func (ar *AuditRecord) Store() error { // auditWriter is the routine that stores audit records in trhe background. func auditWriter(workChan chan *AuditRecord, doneChan chan bool) { for ar := range workChan { - err := ar.Store() + err := ar.Store(context.Background()) if err != nil { log.Errorf("dropped audit record (%+v) on the floor: %v", *ar, err) } diff --git a/database/category.go b/database/category.go index 1d60c13..99d679c 100644 --- a/database/category.go +++ b/database/category.go @@ -10,6 +10,7 @@ package database import ( + "context" "errors" "slices" "strings" @@ -45,12 +46,12 @@ var categoryIdMap map[int32]*Category = make(map[int32]*Category) var categoryMutex sync.Mutex // isCatEnabled determines if category features are enabled. -func isCatEnabled() (bool, error) { - g, err := AmGlobals() +func isCatEnabled(ctx context.Context) (bool, error) { + g, err := AmGlobals(ctx) if err != nil { return false, err } - set, err := g.Flags() + set, err := g.Flags(ctx) if err != nil { return false, err } @@ -58,11 +59,11 @@ func isCatEnabled() (bool, error) { } // loadCategories loads the categories list from the database. -func loadCategories() error { +func loadCategories(ctx context.Context) error { categoryMutex.Lock() defer categoryMutex.Unlock() if allCategories == nil { - rs, err := amdb.Query("SELECT COUNT(*) FROM refcategory") + rs, err := amdb.QueryContext(ctx, "SELECT COUNT(*) FROM refcategory") if err != nil { return err } @@ -72,7 +73,7 @@ func loadCategories() error { var ncats int32 rs.Scan(&ncats) allCategories = make([]Category, 0, ncats) - err = amdb.Select(&allCategories, "SELECT * FROM refcategory ORDER BY parent, name") + err = amdb.SelectContext(ctx, &allCategories, "SELECT * FROM refcategory ORDER BY parent, name") if err != nil { return err } @@ -85,20 +86,21 @@ func loadCategories() error { /* AmGetCategory returns the category for the given name. * Parameters: + * ctx - Standard Go context value. * catid - The ID of the category to get. * Returns: * Pointer to the appropriate Category, or nil. * Standard Go error status. */ -func AmGetCategory(catid int32) (*Category, error) { - ok, err := isCatEnabled() +func AmGetCategory(ctx context.Context, catid int32) (*Category, error) { + ok, err := isCatEnabled(ctx) if err != nil { return nil, err } if !ok { return nil, errors.New("category feature not supported") } - err = loadCategories() + err = loadCategories(ctx) if err != nil { return nil, err } @@ -116,20 +118,21 @@ func AmGetCategory(catid int32) (*Category, error) { /* AmGetCategoryHierarchy returns the category hierarchy for the given ID. * Parameters: + * ctx - Standard Go context value. * catid - The ID of the category to get. * Returns: * Array of pointers to the categories in hierarchical order, or nil. * Standard Go error status. */ -func AmGetCategoryHierarchy(catid int32) ([]*Category, error) { - ok, err := isCatEnabled() +func AmGetCategoryHierarchy(ctx context.Context, catid int32) ([]*Category, error) { + ok, err := isCatEnabled(ctx) if err != nil { return nil, err } if !ok { return nil, errors.New("category feature not supported") } - err = loadCategories() + err = loadCategories(ctx) if err != nil { return nil, err } @@ -154,20 +157,21 @@ func AmGetCategoryHierarchy(catid int32) ([]*Category, error) { /* AmGetSubCategories returns a list of all subcategories of the given category ID. * Parameters: + * ctx - Standard Go context value. * catid - The parent category ID to use. May be -1 to return all "top level" categories. * Returns: * List of subcategories of this category. * Standard Go error status. */ -func AmGetSubCategories(catid int32) ([]*Category, error) { - ok, err := isCatEnabled() +func AmGetSubCategories(ctx context.Context, catid int32) ([]*Category, error) { + ok, err := isCatEnabled(ctx) if err != nil { return nil, err } if !ok { return nil, errors.New("category feature not supported") } - err = loadCategories() + err = loadCategories(ctx) if err != nil { return nil, err } @@ -185,6 +189,7 @@ func AmGetSubCategories(catid int32) ([]*Category, error) { /* AmSearchCategories searches for categories matching certain criteria. * Parameters: + * ctx - Standard Go context value. * oper - The operation to perform on the category name: * SearchCatOperPrefix - The category name has the string "term" as a prefix. * SearchCatOperSubstring - The category name contains the string "term". @@ -197,8 +202,8 @@ func AmGetSubCategories(catid int32) ([]*Category, error) { * The total number of categories matching this query (could be greater than max) * Standard Go error status. */ -func AmSearchCategories(oper int, term string, offset int, max int, showAll bool, searchAll bool) ([]*Category, int, error) { - ok, err := isCatEnabled() +func AmSearchCategories(ctx context.Context, oper int, term string, offset int, max int, showAll bool, searchAll bool) ([]*Category, int, error) { + ok, err := isCatEnabled(ctx) if err != nil { return nil, -1, err } @@ -230,7 +235,7 @@ func AmSearchCategories(oper int, term string, offset int, max int, showAll bool queryString.WriteString(" AND hide_search = 0") } q := queryString.String() - rs, err := amdb.Query("SELECT COUNT(*) FROM refcategory WHERE " + q) + rs, err := amdb.QueryContext(ctx, "SELECT COUNT(*) FROM refcategory WHERE "+q) if err != nil { return nil, -1, err } @@ -243,9 +248,9 @@ func AmSearchCategories(oper int, term string, offset int, max int, showAll bool return make([]*Category, 0), 0, nil } if offset > 0 { - rs, err = amdb.Query("SELECT catid FROM refcategory WHERE "+q+" ORDER BY parent, name LIMIT ? OFFSET ?", max, offset) + rs, err = amdb.QueryContext(ctx, "SELECT catid FROM refcategory WHERE "+q+" ORDER BY parent, name LIMIT ? OFFSET ?", max, offset) } else { - rs, err = amdb.Query("SELECT catid FROM refcategory WHERE "+q+" ORDER BY parent, name LIMIT ?", max) + rs, err = amdb.QueryContext(ctx, "SELECT catid FROM refcategory WHERE "+q+" ORDER BY parent, name LIMIT ?", max) } if err != nil { return nil, total, err @@ -254,7 +259,7 @@ func AmSearchCategories(oper int, term string, offset int, max int, showAll bool for rs.Next() { var catid int32 rs.Scan(&catid) - c, err := AmGetCategory(catid) + c, err := AmGetCategory(ctx, catid) if err == nil { rc = append(rc, c) } diff --git a/database/community.go b/database/community.go index 086999b..492c95c 100644 --- a/database/community.go +++ b/database/community.go @@ -10,6 +10,7 @@ package database import ( + "context" "database/sql" "errors" "fmt" @@ -151,27 +152,27 @@ func (c *Community) Public() bool { } // ContactInfo returns the contact info structure for the community. -func (c *Community) ContactInfo() (*ContactInfo, error) { +func (c *Community) ContactInfo(ctx context.Context) (*ContactInfo, error) { if c.ContactId < 0 { return nil, nil } - return AmGetContactInfo(c.ContactId) + return AmGetContactInfo(ctx, c.ContactId) } // Host returns the reference to the host of the community. -func (c *Community) Host() (*User, error) { +func (c *Community) Host(ctx context.Context) (*User, error) { if c.HostUid == nil { return nil, nil } - return AmGetUser(*c.HostUid) + return AmGetUser(ctx, *c.HostUid) } // HostQ returns the reference to the community's host, quietly. -func (c *Community) HostQ() *User { +func (c *Community) HostQ(ctx context.Context) *User { if c.HostUid == nil { return nil } - u, err := AmGetUser(*c.HostUid) + u, err := AmGetUser(ctx, *c.HostUid) if err != nil { return nil } @@ -192,6 +193,7 @@ func (c *Community) LanguageTag() (*language.Tag, error) { /* Membership returns the details of the specified user's membership in the community. * Parameters: + * ctxt - Standard Go context value. * u - The user to check the membership of. * Returns: * true if the user is a member, false if not. @@ -199,7 +201,7 @@ func (c *Community) LanguageTag() (*language.Tag, error) { * User's access level in the community, or 0 if the user is not a member. * Standard Go error status. */ -func (c *Community) Membership(u *User) (bool, bool, uint16, error) { +func (c *Community) Membership(ctx context.Context, u *User) (bool, bool, uint16, error) { key := fmt.Sprintf("%d:%d", c.Id, u.Uid) memberMutex.Lock() defer memberMutex.Unlock() @@ -212,7 +214,7 @@ func (c *Community) Membership(u *User) (bool, bool, uint16, error) { // "no join required" - they are effectively a member, but don't cache that return true, false, u.BaseLevel, nil } - rs, err := amdb.Query("SELECT locked, granted_lvl FROM commmember WHERE commid = ? AND uid = ?", c.Id, u.Uid) + rs, err := amdb.QueryContext(ctx, "SELECT locked, granted_lvl FROM commmember WHERE commid = ? AND uid = ?", c.Id, u.Uid) if err == nil { if rs.Next() { var locked bool @@ -227,13 +229,13 @@ func (c *Community) Membership(u *User) (bool, bool, uint16, error) { } // MemberCount returns the number of members in the community. -func (c *Community) MemberCount(hidden bool) (int, error) { +func (c *Community) MemberCount(ctx context.Context, hidden bool) (int, error) { var rs *sql.Rows var err error if hidden { - rs, err = amdb.Query("SELECT COUNT(*) FROM commmember WHERE commid = ?", c.Id) + rs, err = amdb.QueryContext(ctx, "SELECT COUNT(*) FROM commmember WHERE commid = ?", c.Id) } else { - rs, err = amdb.Query("SELECT COUNT(*) FROM commmember WHERE commid = ? AND hidden = 0", c.Id) + rs, err = amdb.QueryContext(ctx, "SELECT COUNT(*) FROM commmember WHERE commid = ? AND hidden = 0", c.Id) } if err != nil { return -1, err @@ -248,6 +250,7 @@ func (c *Community) MemberCount(hidden bool) (int, error) { /* ListMembers lists or searches for community members matching certain criteria. * Parameters: + * ctx - Standard Go context value. * field - A value indicating which field to search: * ListMembersFieldNone - Do not search, just return all community members. * ListMembersFieldName - The user name. @@ -267,7 +270,7 @@ func (c *Community) MemberCount(hidden bool) (int, error) { * The total number of members matching this query (could be greater than max) * Standard Go error status. */ -func (c *Community) ListMembers(field int, oper int, term string, offset int, max int, showHidden bool) ([]*User, int, error) { +func (c *Community) ListMembers(ctx context.Context, field int, oper int, term string, offset int, max int, showHidden bool) ([]*User, int, error) { var query strings.Builder if field != ListMembersFieldNone && oper != ListMembersOperNone { query.WriteString(" AND ") @@ -304,7 +307,7 @@ func (c *Community) ListMembers(field int, oper int, term string, offset int, ma query.WriteString(" AND m.hidden = 0") } q := query.String() - rs, err := amdb.Query(`SELECT COUNT(*) FROM commmember m, users u, contacts c WHERE m.commid = ? AND m.uid = u.uid + rs, err := amdb.QueryContext(ctx, `SELECT COUNT(*) FROM commmember m, users u, contacts c WHERE m.commid = ? AND m.uid = u.uid AND u.contactid = c.contactid`+q, c.Id) if err != nil { return nil, -1, err @@ -315,10 +318,10 @@ func (c *Community) ListMembers(field int, oper int, term string, offset int, ma var total int rs.Scan(&total) if offset > 0 { - rs, err = amdb.Query(`SELECT m.uid FROM commmember m, users u, contacts c WHERE m.commid = ? AND m.uid = u.uid + rs, err = amdb.QueryContext(ctx, `SELECT m.uid FROM commmember m, users u, contacts c WHERE m.commid = ? AND m.uid = u.uid AND u.contactid = c.contactid`+q+" ORDER BY u.username LIMIT ? OFFSET ?", c.Id, max, offset) } else { - rs, err = amdb.Query(`SELECT m.uid FROM commmember m, users u, contacts c WHERE m.commid = ? AND m.uid = u.uid + rs, err = amdb.QueryContext(ctx, `SELECT m.uid FROM commmember m, users u, contacts c WHERE m.commid = ? AND m.uid = u.uid AND u.contactid = c.contactid`+q+" ORDER BY u.username LIMIT ?", c.Id, max) } if err != nil { @@ -328,7 +331,7 @@ func (c *Community) ListMembers(field int, oper int, term string, offset int, ma for rs.Next() { var uid int32 rs.Scan(&uid) - u, err := AmGetUser(uid) + u, err := AmGetUser(ctx, uid) if err == nil { rc = append(rc, u) } @@ -338,6 +341,7 @@ func (c *Community) ListMembers(field int, oper int, term string, offset int, ma /* SetMembership sets a user's membership status within the community. * Parameters: + * ctx - Standard Go context value. * u - The user to change the membership status of. * level - Their membership level. If this is 0, they are removed from membership. * locked - Whether they can unjoin the community themselves. Ignored if removing them. @@ -346,7 +350,7 @@ func (c *Community) ListMembers(field int, oper int, term string, offset int, ma * Returns: * Standard Go error status. */ -func (c *Community) SetMembership(u *User, level uint16, locked bool, personUID int32, ipaddr string) error { +func (c *Community) SetMembership(ctx context.Context, u *User, level uint16, locked bool, personUID int32, ipaddr string) error { success := false tx := amdb.MustBegin() defer func() { @@ -355,20 +359,20 @@ func (c *Community) SetMembership(u *User, level uint16, locked bool, personUID } }() if level == 0 { - res, err := tx.Exec("DELETE FROM commmember WHERE commid = ? AND uid = ?", c.Id, u.Uid) + res, err := tx.ExecContext(ctx, "DELETE FROM commmember WHERE commid = ? AND uid = ?", c.Id, u.Uid) if err != nil { return err } stuffMembership(c.Id, u.Uid, false, false, 0) ra, err := res.RowsAffected() if err == nil && ra > 0 { - err = AmOnUserLeaveCommunityServices(tx, c, u) + err = AmOnUserLeaveCommunityServices(ctx, tx, c, u) if err != nil { return err } } } else { - rs, err := tx.Query("SELECT granted_lvl, locked FROM commmember WHERE commid = ? AND uid = ?", c.Id, u.Uid) + rs, err := tx.QueryContext(ctx, "SELECT granted_lvl, locked FROM commmember WHERE commid = ? AND uid = ?", c.Id, u.Uid) if err != nil { return err } @@ -377,7 +381,7 @@ func (c *Community) SetMembership(u *User, level uint16, locked bool, personUID var lockStatus bool rs.Scan(&oldLevel, &lockStatus) if level != oldLevel || lockStatus != locked { - _, err := tx.Exec("UPDATE commmember SET granted_lvl = ?, locked = ? WHERE commid = ? AND uid = ?", + _, err := tx.ExecContext(ctx, "UPDATE commmember SET granted_lvl = ?, locked = ? WHERE commid = ? AND uid = ?", level, locked, c.Id, u.Uid) if err != nil { return err @@ -385,19 +389,19 @@ func (c *Community) SetMembership(u *User, level uint16, locked bool, personUID stuffMembership(c.Id, u.Uid, true, locked, level) } } else { - _, err := tx.Exec("INSERT INTO commmember (commid, uid, granted_lvl, locked) VALUES (?, ?, ?, ?)", + _, err := tx.ExecContext(ctx, "INSERT INTO commmember (commid, uid, granted_lvl, locked) VALUES (?, ?, ?, ?)", c.Id, u.Uid, level, locked) if err != nil { return err } stuffMembership(c.Id, u.Uid, true, locked, level) - err = AmOnUserJoinCommunityServices(tx, c, u) + err = AmOnUserJoinCommunityServices(ctx, tx, c, u) if err != nil { return err } } } - err := c.TouchUpdateTx(tx) + err := c.TouchUpdateTx(ctx, tx) if err == nil { ar := AmNewAudit(AuditCommunitySetMembership, personUID, ipaddr, fmt.Sprintf("cid=%d", c.Id), fmt.Sprintf("uid=%d", u.Uid), fmt.Sprintf("level=%d", level)) @@ -450,11 +454,11 @@ func (c *Community) PermissionLevel(perm string) uint16 { } // GetFlags retrieves the flags from the properties. -func (c *Community) Flags() (*util.OptionSet, error) { +func (c *Community) Flags(ctx context.Context) (*util.OptionSet, error) { c.Mutex.Lock() defer c.Mutex.Unlock() if c.flags == nil { - s, err := AmGetCommunityProperty(c.Id, CommunityPropFlags) + s, err := AmGetCommunityProperty(ctx, c.Id, CommunityPropFlags) if err != nil { return nil, err } @@ -468,11 +472,11 @@ func (c *Community) Flags() (*util.OptionSet, error) { } // SaveFlags writes the flags to the database and stores them. -func (c *Community) SaveFlags(f *util.OptionSet) error { +func (c *Community) SaveFlags(ctx context.Context, f *util.OptionSet) error { s := f.AsString() c.Mutex.Lock() defer c.Mutex.Unlock() - err := AmSetCommunityProperty(c.Id, CommunityPropFlags, &s) + err := AmSetCommunityProperty(ctx, c.Id, CommunityPropFlags, &s) if err == nil { c.flags = f } @@ -480,12 +484,12 @@ func (c *Community) SaveFlags(f *util.OptionSet) error { } // SetProfileData sets all the "settable" profile data -func (c *Community) SetProfileData(name string, alias string, synopsis *string, rules *string, language *string, +func (c *Community) SetProfileData(ctx context.Context, name string, alias string, synopsis *string, rules *string, language *string, joinkey *string, membersonly bool, hideDirectory bool, hideSearch bool, read_lvl uint16, write_lvl uint16, create_lvl uint16, delete_lvl uint16, join_lvl uint16) error { c.Mutex.Lock() defer c.Mutex.Unlock() - _, err := amdb.Exec(`UPDATE communities SET commname = ?, alias = ?, synopsis = ?, rules = ?, language = ?, + _, err := amdb.ExecContext(ctx, `UPDATE communities SET commname = ?, alias = ?, synopsis = ?, rules = ?, language = ?, joinkey = ?, membersonly = ?, hide_dir = ?, hide_search = ?, read_lvl = ?, write_lvl = ?, create_lvl = ?, delete_lvl = ?, join_lvl = ?, lastupdate = NOW() WHERE commid = ?`, name, alias, synopsis, rules, language, joinkey, membersonly, hideDirectory, hideSearch, read_lvl, write_lvl, @@ -505,7 +509,7 @@ func (c *Community) SetProfileData(name string, alias string, synopsis *string, c.CreateLevel = create_lvl c.DeleteLevel = delete_lvl c.JoinLevel = join_lvl - rs, err2 := amdb.Query("SELECT lastupdate FROM communities WHERE commid = ?", c.Id) + rs, err2 := amdb.QueryContext(ctx, "SELECT lastupdate FROM communities WHERE commid = ?", c.Id) if err2 != nil { rs.Next() rs.Scan(&c.LastUpdate) @@ -515,10 +519,10 @@ func (c *Community) SetProfileData(name string, alias string, synopsis *string, } // SetContactID sets the contact ID for the community. -func (c *Community) SetContactID(cid int32) error { +func (c *Community) SetContactID(ctx context.Context, cid int32) error { c.Mutex.Lock() defer c.Mutex.Unlock() - if _, err := amdb.Exec("UPDATE communities SET contactid = ? WHERE commid = ?", cid, c.Id); err != nil { + if _, err := amdb.ExecContext(ctx, "UPDATE communities SET contactid = ? WHERE commid = ?", cid, c.Id); err != nil { return err } c.ContactId = cid @@ -526,12 +530,12 @@ func (c *Community) SetContactID(cid int32) error { } // Touch updates the last access time of the community. -func (c *Community) Touch() error { +func (c *Community) Touch(ctx context.Context) error { c.Mutex.Lock() defer c.Mutex.Unlock() - _, err := amdb.Exec("UPDATE communities SET lastaccess = NOW() WHERE commid = ?", c.Id) + _, err := amdb.ExecContext(ctx, "UPDATE communities SET lastaccess = NOW() WHERE commid = ?", c.Id) if err == nil { - rs, err := amdb.Query("SELECT lastaccess FROM communities WHERE commid = ?", c.Id) + rs, err := amdb.QueryContext(ctx, "SELECT lastaccess FROM communities WHERE commid = ?", c.Id) if err == nil { rs.Next() var na time.Time @@ -543,12 +547,12 @@ func (c *Community) Touch() error { } // TouchUpdateTx updates the last access and last update times of the community. -func (c *Community) TouchUpdateTx(tx *sqlx.Tx) error { +func (c *Community) TouchUpdateTx(ctx context.Context, tx *sqlx.Tx) error { c.Mutex.Lock() defer c.Mutex.Unlock() - _, err := tx.Exec("UPDATE communities SET lastaccess = NOW(), lastupdate = NOW() WHERE commid = ?", c.Id) + _, err := tx.ExecContext(ctx, "UPDATE communities SET lastaccess = NOW(), lastupdate = NOW() WHERE commid = ?", c.Id) if err == nil { - rs, err := tx.Query("SELECT lastaccess, lastupdate FROM communities WHERE commid = ?", c.Id) + rs, err := tx.QueryContext(ctx, "SELECT lastaccess, lastupdate FROM communities WHERE commid = ?", c.Id) if err != nil { rs.Next() var na, nu time.Time @@ -561,9 +565,9 @@ func (c *Community) TouchUpdateTx(tx *sqlx.Tx) error { } // TouchUpdateTx updates the last access and last update times of the community. -func (c *Community) TouchUpdate() error { +func (c *Community) TouchUpdate(ctx context.Context) error { tx := amdb.MustBegin() - err := c.TouchUpdateTx(tx) + err := c.TouchUpdateTx(ctx, tx) if err != nil { err = tx.Commit() } @@ -575,18 +579,19 @@ func (c *Community) TouchUpdate() error { /* AmGetCommunity returns a reference to the specified community. * Parameters: + * ctx - Standard Go context value. * id - The ID of the community. * Returns: * Pointer to Community containing community data, or nil * Standard Go error status */ -func AmGetCommunity(id int32) (*Community, error) { +func AmGetCommunity(ctx context.Context, id int32) (*Community, error) { getCommunityMutex.Lock() defer getCommunityMutex.Unlock() rc, ok := communityCache.Get(id) if !ok { var dbdata []Community - err := amdb.Select(&dbdata, "SELECT * from communities WHERE commid = ?", id) + err := amdb.SelectContext(ctx, &dbdata, "SELECT * from communities WHERE commid = ?", id) if err != nil { return nil, err } @@ -603,19 +608,20 @@ func AmGetCommunity(id int32) (*Community, error) { /* AmGetCommunityTx returns a reference to the specified community, in a transaction. * Parameters: + * ctx - Standard Go context value. * tx - The transaction to use. * id - The ID of the community. * Returns: * Pointer to Community containing community data, or nil * Standard Go error status */ -func AmGetCommunityTx(tx *sqlx.Tx, id int32) (*Community, error) { +func AmGetCommunityTx(ctx context.Context, tx *sqlx.Tx, id int32) (*Community, error) { getCommunityMutex.Lock() defer getCommunityMutex.Unlock() rc, ok := communityCache.Get(id) if !ok { var dbdata []Community - err := tx.Select(&dbdata, "SELECT * from communities WHERE commid = ?", id) + err := tx.SelectContext(ctx, &dbdata, "SELECT * from communities WHERE commid = ?", id) if err != nil { return nil, err } @@ -632,18 +638,19 @@ func AmGetCommunityTx(tx *sqlx.Tx, id int32) (*Community, error) { /* AmGetCommunityByAlias returns a reference to the specified community. * Parameters: + * ctx - Standard Go context value. * alias - The alias for the community. * Returns: * Pointer to Community containing community data, or nil * Standard Go error status (nil if community not found) */ -func AmGetCommunityByAlias(alias string) (*Community, error) { - rs, err := amdb.Query("SELECT commid FROM communities WHERE alias = ?", alias) +func AmGetCommunityByAlias(ctx context.Context, alias string) (*Community, error) { + rs, err := amdb.QueryContext(ctx, "SELECT commid FROM communities WHERE alias = ?", alias) if err == nil { if rs.Next() { var cid int32 rs.Scan(&cid) - return AmGetCommunity(cid) + return AmGetCommunity(ctx, cid) } else { return nil, nil } @@ -653,19 +660,20 @@ func AmGetCommunityByAlias(alias string) (*Community, error) { /* AmGetCommunityByAliasTx returns a reference to the specified community, within a transaction. * Parameters: + * ctx - Standard Go context value. * tx - The transaction to use. * alias - The alias for the community. * Returns: * Pointer to Community containing community data, or nil * Standard Go error status (nil if community not found) */ -func AmGetCommunityByAliasTx(tx *sqlx.Tx, alias string) (*Community, error) { - rs, err := tx.Query("SELECT commid FROM communities WHERE alias = ?", alias) +func AmGetCommunityByAliasTx(ctx context.Context, tx *sqlx.Tx, alias string) (*Community, error) { + rs, err := tx.QueryContext(ctx, "SELECT commid FROM communities WHERE alias = ?", alias) if err == nil { if rs.Next() { var cid int32 rs.Scan(&cid) - return AmGetCommunityTx(tx, cid) + return AmGetCommunityTx(ctx, tx, cid) } else { return nil, nil } @@ -677,21 +685,22 @@ func AmGetCommunityByAliasTx(tx *sqlx.Tx, alias string) (*Community, error) { * If the parameter is numeric, it's interpreted as a community ID. Otherwise, it's interpreted * as a community alias. * Parameters: + * ctx - Standard Go context value. * id - The ID of the community. * Returns: * Pointer to Community containing community data, or nil * Standard Go error status */ -func AmGetCommunityFromParam(param string) (*Community, error) { +func AmGetCommunityFromParam(ctx context.Context, param string) (*Community, error) { if util.IsNumeric(param) { v, _ := strconv.Atoi(param) - c, err := AmGetCommunity(int32(v)) + c, err := AmGetCommunity(ctx, int32(v)) if err == nil { return c, nil } // else fall through to trying as alias } - rc, err := AmGetCommunityByAlias(param) + rc, err := AmGetCommunityByAlias(ctx, param) if err == nil { if rc == nil { return nil, fmt.Errorf("community with alias \"%s\" not found", param) @@ -702,18 +711,19 @@ func AmGetCommunityFromParam(param string) (*Community, error) { /* AmGetCommunitiesForUser returns a list of communities the user is a member of. * Parameters: + * ctx - Standard Go context value. * uid - The ID of the user. * Returns: * Array of pointers to communities for the user * Standard Go error status */ -func AmGetCommunitiesForUser(uid int32) ([]*Community, error) { +func AmGetCommunitiesForUser(ctx context.Context, uid int32) ([]*Community, error) { var rc []*Community = make([]*Community, 0) var ids []int32 = make([]int32, 0) - err := amdb.Select(&ids, "SELECT commid FROM commmember WHERE uid = ?", uid) + err := amdb.SelectContext(ctx, &ids, "SELECT commid FROM commmember WHERE uid = ?", uid) if err == nil { for _, id := range ids { - c, err := AmGetCommunity(id) + c, err := AmGetCommunity(ctx, id) if err == nil { rc = append(rc, c) } else { @@ -727,15 +737,16 @@ func AmGetCommunitiesForUser(uid int32) ([]*Community, error) { /* AmGetCommunityAccessLevel returns the access level of the specified user with respect to the community. * This may reflect the user's admin status as well as their status within the community. * Parameters: + * ctx - Standard Go context value. * uid - The UID of the user. * commid - The ID of the community. * Returns: * Access level within the community, or 0 if the user is not a member. * Standard Go error status. */ -func AmGetCommunityAccessLevel(uid int32, commid int32) (uint16, error) { +func AmGetCommunityAccessLevel(ctx context.Context, uid int32, commid int32) (uint16, error) { var rc uint16 = 0 - rows, err := amdb.Queryx(`SELECT GREATEST(m.granted_lvl, u.base_lvl) AS level FROM users u, commmember m + rows, err := amdb.QueryxContext(ctx, `SELECT GREATEST(m.granted_lvl, u.base_lvl) AS level FROM users u, commmember m WHERE u.uid = m.uid AND m.uid = ? AND m.commid = ?`, uid, commid) if err == nil { defer rows.Close() @@ -748,21 +759,22 @@ func AmGetCommunityAccessLevel(uid int32, commid int32) (uint16, error) { /* AmAutoJoinCommunities joins the specified user to any communities they're not yet a part of. * Parameters: + * ctx - Standard Go context value. * tx - The current transaction to be used for database access. * user - The user to be auto-joined to communities. * Returns: * Standard Go error status. */ -func AmAutoJoinCommunities(tx *sqlx.Tx, user *User) error { +func AmAutoJoinCommunities(ctx context.Context, tx *sqlx.Tx, user *User) error { // get list of current communities var current []int32 = make([]int32, 0) - err := tx.Select(¤t, "SELECT commid FROM commmember WHERE uid = ?", user.Uid) + err := tx.SelectContext(ctx, ¤t, "SELECT commid FROM commmember WHERE uid = ?", user.Uid) if err != nil { return err } // look for candidate communities - rows, err := tx.Queryx(`SELECT m.commid, m.locked FROM users u, communities c, commmember m + rows, err := tx.QueryxContext(ctx, `SELECT m.commid, m.locked FROM users u, communities c, commmember m WHERE m.uid = u.uid AND m.commid = c.commid AND u.is_anon = 1 AND c.join_lvl <= ?`, user.BaseLevel) if err == nil { defer rows.Close() @@ -772,7 +784,7 @@ func AmAutoJoinCommunities(tx *sqlx.Tx, user *User) error { var lock bool rows.Scan(&cid, &lock) if !slices.Contains(current, cid) { - _, err = tx.Exec("INSERT INTO commmember (commid, uid, granted_lvl, locked) VALUES (?, ?, ?, ?)", + _, err = tx.ExecContext(ctx, "INSERT INTO commmember (commid, uid, granted_lvl, locked) VALUES (?, ?, ?, ?)", cid, user.Uid, grantLevel, lock) if err != nil { break @@ -785,7 +797,7 @@ func AmAutoJoinCommunities(tx *sqlx.Tx, user *User) error { } // internalGetProp is a helper used by the property functions. -func internalGetCommProp(cid int32, ndx int32) (*CommunityProperties, error) { +func internalGetCommProp(ctx context.Context, cid int32, ndx int32) (*CommunityProperties, error) { var err error = nil key := fmt.Sprintf("%d:%d", cid, ndx) getCommunityPropMutex.Lock() @@ -793,7 +805,7 @@ func internalGetCommProp(cid int32, ndx int32) (*CommunityProperties, error) { rc, ok := communityPropCache.Get(key) if !ok { var dbdata []CommunityProperties - err = amdb.Select(&dbdata, "SELECT * from propcomm WHERE cid = ? AND ndx = ?", cid, ndx) + err = amdb.SelectContext(ctx, &dbdata, "SELECT * from propcomm WHERE cid = ? AND ndx = ?", cid, ndx) if err != nil { return nil, err } @@ -811,14 +823,15 @@ func internalGetCommProp(cid int32, ndx int32) (*CommunityProperties, error) { /* AmGetCommunityProperty retrieves the value of a user property. * Parameters: + * ctx - Standard Go context value. * cid - The ID of the community to get the property for. * ndx - The index of the property to retrieve. * Returns: * Value of the property string. * Standard Go error status. */ -func AmGetCommunityProperty(cid int32, ndx int32) (*string, error) { - p, err := internalGetCommProp(cid, ndx) +func AmGetCommunityProperty(ctx context.Context, cid int32, ndx int32) (*string, error) { + p, err := internalGetCommProp(ctx, cid, ndx) if err != nil { return nil, err } else if p == nil { @@ -829,27 +842,28 @@ func AmGetCommunityProperty(cid int32, ndx int32) (*string, error) { /* AmSetCommunityProperty sets the value of a community property. * Parameters: + * ctx - Standard Go context value. * cid - The ID of the community to set the property for. * ndx - The index of the property to set. * val - The new value of the property. * Returns: * Standard Go error status. */ -func AmSetCommunityProperty(cid int32, ndx int32, val *string) error { - p, err := internalGetCommProp(cid, ndx) +func AmSetCommunityProperty(ctx context.Context, cid int32, ndx int32, val *string) error { + p, err := internalGetCommProp(ctx, cid, ndx) if err != nil { return err } getCommunityPropMutex.Lock() defer getCommunityPropMutex.Unlock() if p != nil { - _, err = amdb.Exec("UPDATE propcomm SET data = ? WHERE cid = ? AND ndx = ?", val, cid, ndx) + _, err = amdb.ExecContext(ctx, "UPDATE propcomm SET data = ? WHERE cid = ? AND ndx = ?", val, cid, ndx) if err == nil { p.Data = val } } else { prop := CommunityProperties{Cid: cid, Index: ndx, Data: val} - _, err := amdb.NamedExec("INSERT INTO propcomm (cid, ndx, data) VALUES(:cid, :ndx, :data)", prop) + _, err := amdb.NamedExecContext(ctx, "INSERT INTO propcomm (cid, ndx, data) VALUES(:cid, :ndx, :data)", prop) if err == nil { communityPropCache.Add(fmt.Sprintf("%d:%d", cid, ndx), prop) } @@ -859,6 +873,7 @@ func AmSetCommunityProperty(cid int32, ndx int32, val *string) error { /* AmCreateCommunity creates a new community. * Parameters: + * ctx - Standard Go context value. * name - The name for the new community. * alias - The alias for the new community. Must be unique. * hostUid - The UID of the creator and new host of the community. @@ -873,7 +888,7 @@ func AmSetCommunityProperty(cid int32, ndx int32, val *string) error { * Pointer to new Community record, or nil. * Standard Go error status. */ -func AmCreateCommunity(name string, alias string, hostUid int32, language *string, synopsis *string, +func AmCreateCommunity(ctx context.Context, name string, alias string, hostUid int32, language *string, synopsis *string, rules *string, joinkey *string, hideDirectory bool, hideSearch bool, remoteIP string) (*Community, error) { var ar *AuditRecord = nil defer func() { @@ -888,7 +903,7 @@ func AmCreateCommunity(name string, alias string, hostUid int32, language *strin }() // validate alias does not already exist - rs, err := tx.Query("SELECT commid FROM communities WHERE alias = ?", alias) + rs, err := tx.QueryContext(ctx, "SELECT commid FROM communities WHERE alias = ?", alias) if err != nil { return nil, err } @@ -897,7 +912,7 @@ func AmCreateCommunity(name string, alias string, hostUid int32, language *strin } // establish the community record - _, err = tx.Exec(`INSERT INTO communities (createdate, lastaccess, lastupdate, read_lvl, write_lvl, + _, err = tx.ExecContext(ctx, `INSERT INTO communities (createdate, lastaccess, lastupdate, read_lvl, write_lvl, create_lvl, delete_lvl, join_lvl, host_uid, hide_dir, hide_search, commname, language, synopsis, rules, joinkey, alias) VALUES (NOW(), NOW(), NOW(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, AmRoleList("Community.Read").Default().Level(), AmRoleList("Community.Write").Default().Level(), @@ -909,7 +924,7 @@ func AmCreateCommunity(name string, alias string, hostUid int32, language *strin } // Read back the community, which also puts it in the cache. - comm, err := AmGetCommunityByAliasTx(tx, alias) + comm, err := AmGetCommunityByAliasTx(ctx, tx, alias) if err != nil { return nil, err } else if comm == nil { @@ -918,7 +933,7 @@ func AmCreateCommunity(name string, alias string, hostUid int32, language *strin // Ensure the new host has host privileges in the community. The host's membership is "locked" so they // can't unjoin and leave the community hostless. - _, err = tx.Exec("INSERT INTO commmember (commid, uid, granted_lvl, locked) VALUES (?, ?, ?, 1)", comm.Id, hostUid, + _, err = tx.ExecContext(ctx, "INSERT INTO commmember (commid, uid, granted_lvl, locked) VALUES (?, ?, ?, 1)", comm.Id, hostUid, AmDefaultRole("Community.Creator").Level()) if err != nil { return nil, err @@ -926,7 +941,7 @@ func AmCreateCommunity(name string, alias string, hostUid int32, language *strin stuffMembership(comm.Id, hostUid, true, true, AmDefaultRole("Community.Creator").Level()) // Establish the community services. - err = AmEstablishCommunityServices(tx, comm) + err = AmEstablishCommunityServices(ctx, tx, comm) if err != nil { return nil, err } @@ -945,6 +960,7 @@ func AmCreateCommunity(name string, alias string, hostUid int32, language *strin /* AmGetCommunitiesForCategory returns a list of communities for the specified category. * Parameters: + * ctx - Standard Go context value. * catid - Category ID to search for. * offset - Number of communities to skip at beginning of list. * max - Maximum number of communities to return. @@ -954,13 +970,13 @@ func AmCreateCommunity(name string, alias string, hostUid int32, language *strin * The total number of communities matching this query (could be greater than max) * Standard Go error status. */ -func AmGetCommunitiesForCategory(catid int32, offset int, max int, showAll bool) ([]*Community, int, error) { +func AmGetCommunitiesForCategory(ctx context.Context, catid int32, offset int, max int, showAll bool) ([]*Community, int, error) { var rs *sql.Rows var err error if showAll { - rs, err = amdb.Query("SELECT COUNT(*) FROM communities WHERE catid = ?", catid) + rs, err = amdb.QueryContext(ctx, "SELECT COUNT(*) FROM communities WHERE catid = ?", catid) } else { - rs, err = amdb.Query("SELECT COUNT(*) FROM communities WHERE catid = ? AND hide_dir = 0", catid) + rs, err = amdb.QueryContext(ctx, "SELECT COUNT(*) FROM communities WHERE catid = ? AND hide_dir = 0", catid) } if err != nil { return nil, -1, err @@ -975,17 +991,17 @@ func AmGetCommunitiesForCategory(catid int32, offset int, max int, showAll bool) } if showAll { if offset > 0 { - rs, err = amdb.Query("SELECT commid FROM communities WHERE catid = ? ORDER BY commname LIMIT ? OFFSET ?", + rs, err = amdb.QueryContext(ctx, "SELECT commid FROM communities WHERE catid = ? ORDER BY commname LIMIT ? OFFSET ?", catid, max, offset) } else { - rs, err = amdb.Query("SELECT commid FROM communities WHERE catid = ? ORDER BY commname LIMIT ?", catid, max) + rs, err = amdb.QueryContext(ctx, "SELECT commid FROM communities WHERE catid = ? ORDER BY commname LIMIT ?", catid, max) } } else { if offset > 0 { - rs, err = amdb.Query("SELECT commid FROM communities WHERE catid = ? AND hide_dir = 0 ORDER BY commname LIMIT ? OFFSET ?", + rs, err = amdb.QueryContext(ctx, "SELECT commid FROM communities WHERE catid = ? AND hide_dir = 0 ORDER BY commname LIMIT ? OFFSET ?", catid, max, offset) } else { - rs, err = amdb.Query("SELECT commid FROM communities WHERE catid = ? AND hide_dir = 0 ORDER BY commname LIMIT ?", catid, max) + rs, err = amdb.QueryContext(ctx, "SELECT commid FROM communities WHERE catid = ? AND hide_dir = 0 ORDER BY commname LIMIT ?", catid, max) } } if err != nil { @@ -995,7 +1011,7 @@ func AmGetCommunitiesForCategory(catid int32, offset int, max int, showAll bool) for rs.Next() { var commid int32 rs.Scan(&commid) - c, err := AmGetCommunity(commid) + c, err := AmGetCommunity(ctx, commid) if err == nil { rc = append(rc, c) } @@ -1005,6 +1021,7 @@ func AmGetCommunitiesForCategory(catid int32, offset int, max int, showAll bool) /* AmSearchCommunities searches for communities matching certain criteria. * Parameters: + * ctx - Standard Go context value. * field - A value indicating which field to search: * SearchCommFieldName - The community name. * SearchCommFieldSynopsis - The communty synopsis. @@ -1021,7 +1038,7 @@ func AmGetCommunitiesForCategory(catid int32, offset int, max int, showAll bool) * The total number of communities matching this query (could be greater than max) * Standard Go error status. */ -func AmSearchCommunities(field int, oper int, term string, offset int, max int, showAll bool) ([]*Community, int, error) { +func AmSearchCommunities(ctx context.Context, field int, oper int, term string, offset int, max int, showAll bool) ([]*Community, int, error) { var queryPortion strings.Builder queryPortion.WriteString("WHERE ") switch field { @@ -1052,7 +1069,7 @@ func AmSearchCommunities(field int, oper int, term string, offset int, max int, queryPortion.WriteString(" AND hide_search = 0") } q := queryPortion.String() - rs, err := amdb.Query("SELECT COUNT(*) FROM communities " + q) + rs, err := amdb.QueryContext(ctx, "SELECT COUNT(*) FROM communities "+q) if err != nil { return nil, -1, err } @@ -1065,9 +1082,9 @@ func AmSearchCommunities(field int, oper int, term string, offset int, max int, return make([]*Community, 0), 0, nil // short-circuit return } if offset > 0 { - rs, err = amdb.Query("SELECT commid FROM communities "+q+" ORDER BY commname LIMIT ? OFFSET ?", max, offset) + rs, err = amdb.QueryContext(ctx, "SELECT commid FROM communities "+q+" ORDER BY commname LIMIT ? OFFSET ?", max, offset) } else { - rs, err = amdb.Query("SELECT commid FROM communities "+q+" ORDER BY commname LIMIT ?", max) + rs, err = amdb.QueryContext(ctx, "SELECT commid FROM communities "+q+" ORDER BY commname LIMIT ?", max) } if err != nil { return nil, total, err @@ -1076,7 +1093,7 @@ func AmSearchCommunities(field int, oper int, term string, offset int, max int, for rs.Next() { var commid int32 rs.Scan(&commid) - c, err := AmGetCommunity(commid) + c, err := AmGetCommunity(ctx, commid) if err == nil { rc = append(rc, c) } diff --git a/database/conference.go b/database/conference.go index 8de4521..d19c7f9 100644 --- a/database/conference.go +++ b/database/conference.go @@ -10,6 +10,7 @@ package database import ( + "context" "errors" "fmt" "sync" @@ -66,8 +67,8 @@ func init() { } // Aliases returns the list of aliases for this conference. -func (c *Conference) Aliases() ([]string, error) { - rs, err := amdb.Query("SELECT alias FROM confalias WHERE confid = ? ORDER BY alias", c.ConfId) +func (c *Conference) Aliases(ctx context.Context) ([]string, error) { + rs, err := amdb.QueryContext(ctx, "SELECT alias FROM confalias WHERE confid = ? ORDER BY alias", c.ConfId) if err != nil { return nil, err } @@ -81,14 +82,14 @@ func (c *Conference) Aliases() ([]string, error) { } // AliasesQ returns the list of aliases for this conference, quietly. -func (c *Conference) AliasesQ() []string { - rc, _ := c.Aliases() +func (c *Conference) AliasesQ(ctx context.Context) []string { + rc, _ := c.Aliases(ctx) return rc } // Hosts returns the list of users that host this conference. -func (c *Conference) Hosts() ([]*User, error) { - rs, err := amdb.Query("SELECT uid FROM confmember WHERE confid = ? AND granted_lvl = ?", +func (c *Conference) Hosts(ctx context.Context) ([]*User, error) { + rs, err := amdb.QueryContext(ctx, "SELECT uid FROM confmember WHERE confid = ? AND granted_lvl = ?", c.ConfId, AmRole("Conference.Host").Level()) if err != nil { return nil, err @@ -97,7 +98,7 @@ func (c *Conference) Hosts() ([]*User, error) { for rs.Next() { var uid int32 rs.Scan(&uid) - u, err := AmGetUser(uid) + u, err := AmGetUser(ctx, uid) if err == nil { rc = append(rc, u) } @@ -106,14 +107,14 @@ func (c *Conference) Hosts() ([]*User, error) { } // Hosts returns the list of users that host this conference, quietly. -func (c *Conference) HostsQ() []*User { - rc, _ := c.Hosts() +func (c *Conference) HostsQ(ctx context.Context) []*User { + rc, _ := c.Hosts(ctx) return rc } // Membership returns a membership flag and granted level for the user in this conference. -func (c *Conference) Membership(u *User) (bool, uint16, error) { - rs, err := amdb.Query("SELECT granted_lvl FROM confmember WHERE confid = ? AND uid = ?", c.ConfId, u.Uid) +func (c *Conference) Membership(ctx context.Context, u *User) (bool, uint16, error) { + rs, err := amdb.QueryContext(ctx, "SELECT granted_lvl FROM confmember WHERE confid = ? AND uid = ?", c.ConfId, u.Uid) if err != nil { return false, 0, err } @@ -155,9 +156,9 @@ func (c *Conference) TestPermission(perm string, level uint16) bool { } // Settings returns the settings for a user. -func (c *Conference) Settings(u *User) (*ConferenceSettings, error) { +func (c *Conference) Settings(ctx context.Context, u *User) (*ConferenceSettings, error) { var dbdata []ConferenceSettings - err := amdb.Select(&dbdata, "SELECT * FROM confsettings WHERE confid = ? AND uid = ?", c.ConfId, u.Uid) + err := amdb.SelectContext(ctx, &dbdata, "SELECT * FROM confsettings WHERE confid = ? AND uid = ?", c.ConfId, u.Uid) if err != nil { return nil, err } @@ -171,24 +172,24 @@ func (c *Conference) Settings(u *User) (*ConferenceSettings, error) { } // TouchRead updates the "last posted" date/time in the conference for the user. -func (c *Conference) TouchRead(tx *sqlx.Tx, u *User) (*ConferenceSettings, error) { - cs, err := c.Settings(u) +func (c *Conference) TouchRead(ctx context.Context, tx *sqlx.Tx, u *User) (*ConferenceSettings, error) { + cs, err := c.Settings(ctx, u) if err != nil { return nil, err } if !u.IsAnon { // anon user can't update squat if cs == nil { - ci, cerr := u.ContactInfo() + ci, cerr := u.ContactInfo(ctx) if cerr != nil { return nil, cerr } - _, err = tx.Exec("INSERT INTO confsettings (confid, uid, default_pseud, last_read) VALUES (?, ?, ?, NOW())", + _, err = tx.ExecContext(ctx, "INSERT INTO confsettings (confid, uid, default_pseud, last_read) VALUES (?, ?, ?, NOW())", c.ConfId, u.Uid, ci.FullName(false)) } else { - _, err = tx.Exec("UPDATE confsettings SET last_read = NOW() WHERE confid = ? AND uid = ?", c.ConfId, u.Uid) + _, err = tx.ExecContext(ctx, "UPDATE confsettings SET last_read = NOW() WHERE confid = ? AND uid = ?", c.ConfId, u.Uid) } if err == nil { - cs, err = c.Settings(u) // reread to get updated or inserted values + cs, err = c.Settings(ctx, u) // reread to get updated or inserted values } if err != nil { return nil, err @@ -198,14 +199,14 @@ func (c *Conference) TouchRead(tx *sqlx.Tx, u *User) (*ConferenceSettings, error } // TouchPost updates the "last posted" date/time in the conference for the user. -func (c *Conference) TouchPost(tx *sqlx.Tx, u *User, lastPost time.Time) (*ConferenceSettings, error) { - cs, err := c.Settings(u) +func (c *Conference) TouchPost(ctx context.Context, tx *sqlx.Tx, u *User, lastPost time.Time) (*ConferenceSettings, error) { + cs, err := c.Settings(ctx, u) if err != nil { return nil, err } if !u.IsAnon { // anon user can't update squat if cs == nil { - ci, cerr := u.ContactInfo() + ci, cerr := u.ContactInfo(ctx) if cerr != nil { return nil, cerr } @@ -217,10 +218,10 @@ func (c *Conference) TouchPost(tx *sqlx.Tx, u *User, lastPost time.Time) (*Confe LastRead: &lastPost, LastPost: &lastPost, } - _, err = tx.Exec("INSERT INTO confsettings (confid, uid, default_pseud, last_read, last_post) VALUES (?, ?, ?, ?, ?)", + _, err = tx.ExecContext(ctx, "INSERT INTO confsettings (confid, uid, default_pseud, last_read, last_post) VALUES (?, ?, ?, ?, ?)", c.ConfId, u.Uid, defaultPseud, lastPost, lastPost) } else { - _, err = tx.Exec("UPDATE confsettings SET last_post = ? WHERE confid = ? AND uid = ?", lastPost, c.ConfId, u.Uid) + _, err = tx.ExecContext(ctx, "UPDATE confsettings SET last_post = ? WHERE confid = ? AND uid = ?", lastPost, c.ConfId, u.Uid) cs.LastPost = &lastPost } if err != nil { @@ -232,19 +233,20 @@ func (c *Conference) TouchPost(tx *sqlx.Tx, u *User, lastPost time.Time) (*Confe /* AmGetConference returns a conference given its ID. * Parameters: + * ctx - Standard Go context value. * id - The ID of the conference. * Returns: * Pointer to the conference, or nil. * Standard Go error status. */ -func AmGetConference(id int32) (*Conference, error) { +func AmGetConference(ctx context.Context, id int32) (*Conference, error) { var err error = nil getConferenceMutex.Lock() defer getConferenceMutex.Unlock() rc, ok := conferenceCache.Get(id) if !ok { var dbdata []Conference - err = amdb.Select(&dbdata, "SELECT * from confs where confid = ?", id) + err = amdb.SelectContext(ctx, &dbdata, "SELECT * from confs where confid = ?", id) if err != nil { return nil, err } @@ -261,18 +263,19 @@ func AmGetConference(id int32) (*Conference, error) { /* AmGetConferenceByAlias returns a conference given its alias. * Parameters: + * ctx - Standard Go context value. * alias - The alias to look up. * Returns: * Pointer to the conference, or nil. * Standard Go error status. */ -func AmGetConferenceByAlias(alias string) (*Conference, error) { +func AmGetConferenceByAlias(ctx context.Context, alias string) (*Conference, error) { var confid int32 xconf, ok := conferenceAliasMap.Load(alias) if ok { confid = xconf.(int32) } else { - rs, err := amdb.Query("SELECT confid FROM confalias WHERE alias = ?", alias) + rs, err := amdb.QueryContext(ctx, "SELECT confid FROM confalias WHERE alias = ?", alias) if err != nil { return nil, err } @@ -282,19 +285,20 @@ func AmGetConferenceByAlias(alias string) (*Conference, error) { rs.Scan(&confid) conferenceAliasMap.Store(alias, confid) } - return AmGetConference(confid) + return AmGetConference(ctx, confid) } /* AmGetConferenceByAliasInCommunity returns a conference in a community given its alias. * Parameters: + * ctx - Standard Go context value. * cid - The community to look inside. * alias - The alias to look up. * Returns: * Pointer to the conference, or nil. * Standard Go error status. */ -func AmGetConferenceByAliasInCommunity(cid int32, alias string) (*Conference, error) { - rs, err := amdb.Query(`SELECT c.confid FROM commtoconf c, confalias a WHERE c.confid = a.confid +func AmGetConferenceByAliasInCommunity(ctx context.Context, cid int32, alias string) (*Conference, error) { + rs, err := amdb.QueryContext(ctx, `SELECT c.confid FROM commtoconf c, confalias a WHERE c.confid = a.confid AND c.commid = ? AND a.alias = ?`, cid, alias) if err != nil { return nil, err @@ -304,23 +308,24 @@ func AmGetConferenceByAliasInCommunity(cid int32, alias string) (*Conference, er } var confid int32 rs.Scan(&confid) - return AmGetConference(confid) + return AmGetConference(ctx, confid) } /* AmGetCommunityConferences returns all conferences for a given community. * Parameters: + * ctx - Standard Go context value. * cid - Community ID to get conferences for. * showHidden - true to show hidden conferences. * Returns: * Array containing the COnference pointers, or nil. * Stanbard Go error status. */ -func AmGetCommunityConferences(cid int32, showHidden bool) ([]*Conference, error) { +func AmGetCommunityConferences(ctx context.Context, cid int32, showHidden bool) ([]*Conference, error) { q := "" if !showHidden { q = " AND x.hide_list = 0" } - rs, err := amdb.Query(`SELECT x.confid FROM commtoconf x, confs c WHERE x.confid = c.confid + rs, err := amdb.QueryContext(ctx, `SELECT x.confid FROM commtoconf x, confs c WHERE x.confid = c.confid AND x.commid = ?`+q+" ORDER BY x.sequence, c.name", cid) if err != nil { return nil, err @@ -329,7 +334,7 @@ func AmGetCommunityConferences(cid int32, showHidden bool) ([]*Conference, error for rs.Next() { var confid int32 rs.Scan(&confid) - conf, err := AmGetConference(confid) + conf, err := AmGetConference(ctx, confid) if err == nil { rc = append(rc, conf) } diff --git a/database/contactinfo.go b/database/contactinfo.go index be31dc7..05f36ec 100644 --- a/database/contactinfo.go +++ b/database/contactinfo.go @@ -10,6 +10,7 @@ package database import ( + "context" "errors" "fmt" "strings" @@ -51,9 +52,9 @@ type ContactInfo struct { } // lookupCommunityContact looks up the ID of a contact for a community. -func lookupCommunityContact(id int32) (int32, error) { +func lookupCommunityContact(ctx context.Context, id int32) (int32, error) { var rc int32 = -1 - rs, err := amdb.Query("SELECT contactid FROM contacts WHERE owner_commid = ?", id) + rs, err := amdb.QueryContext(ctx, "SELECT contactid FROM contacts WHERE owner_commid = ?", id) if err == nil { if rs.Next() { rs.Scan(&rc) @@ -63,9 +64,9 @@ func lookupCommunityContact(id int32) (int32, error) { } // lookupUserContact looks up the ID of a contact for a user. -func lookupUserContact(uid int32) (int32, error) { +func lookupUserContact(ctx context.Context, uid int32) (int32, error) { var rc int32 = -1 - rs, err := amdb.Query("SELECT contactid FROM contacts WHERE owner_uid = ? AND owner_commid = -1", uid) + rs, err := amdb.QueryContext(ctx, "SELECT contactid FROM contacts WHERE owner_uid = ? AND owner_commid = -1", uid) if err == nil { if rs.Next() { rs.Scan(&rc) @@ -114,11 +115,13 @@ func (ci *ContactInfo) FullName(ps bool) string { } /* Save saves the contact info to the database. + * Parameters: + * ctx - Standard Go context value. * Returns: * true if the E-mail address on this account has been changed, false if not. * Standard Go error status. */ -func (ci *ContactInfo) Save() (bool, error) { +func (ci *ContactInfo) Save(ctx context.Context) (bool, error) { ci.Mutex.Lock() defer ci.Mutex.Unlock() @@ -129,9 +132,9 @@ func (ci *ContactInfo) Save() (bool, error) { var nx int32 var err error if ci.OwnerCommId > 0 { - nx, err = lookupCommunityContact(ci.OwnerCommId) + nx, err = lookupCommunityContact(ctx, ci.OwnerCommId) } else { - nx, err = lookupUserContact(ci.OwnerUid) + nx, err = lookupUserContact(ctx, ci.OwnerUid) } if err != nil { return false, err @@ -147,7 +150,7 @@ func (ci *ContactInfo) Save() (bool, error) { } if !emailChange { // we don't THINK the E-mail address is changing, but we could be wrong... - rs, err := amdb.Query("SELECT contactid FROM contacts WHERE contactid = ? AND email = ?", ci.ContactId, ci.Email) + rs, err := amdb.QueryContext(ctx, "SELECT contactid FROM contacts WHERE contactid = ? AND email = ?", ci.ContactId, ci.Email) if err != nil { return false, err } @@ -157,7 +160,7 @@ func (ci *ContactInfo) Save() (bool, error) { } // Handle the database heavy lifting. if updateMode { - _, err := amdb.NamedExec(`UPDATE contacts SET given_name = :given_name, family_name = :family_name, middle_init = :middle_init, + _, err := amdb.NamedExecContext(ctx, `UPDATE contacts SET given_name = :given_name, family_name = :family_name, middle_init = :middle_init, prefix = :prefix, suffix = :suffix, company = :company, addr1 = :addr1, addr2 = :addr2, locality = :locality, region = :region, pcode = :pcode, country = :country, phone = :phone, fax = :fax, mobile = :mobile, email = :email, pvt_addr = :pvt_addr, pvt_phone = :pvt_phone, pvt_fax = :pvt_fax, pvt_email = :pvt_email, photo_url = :photo_url, url = :url, lastupdate = NOW() @@ -167,7 +170,7 @@ func (ci *ContactInfo) Save() (bool, error) { } contactCache.Add(ci.ContactId, ci) } else { - res, err := amdb.NamedExec(`INSERT INTO contacts (given_name, family_name, middle_init, prefix, suffix, company, addr1, + res, err := amdb.NamedExecContext(ctx, `INSERT INTO contacts (given_name, family_name, middle_init, prefix, suffix, company, addr1, addr2, locality, region, pcode, country, phone, fax, mobile, email, pvt_addr, pvt_phone, pvt_fax, pvt_email, owner_uid, owner_commid, photo_url, url, lastupdate) VALUES (:given_name, :family_name, :middle_init, :prefix, :suffix, :company, :addr1, :addr2, :locality, @@ -181,7 +184,7 @@ func (ci *ContactInfo) Save() (bool, error) { contactCache.Add(ci.ContactId, ci) } // Refresh the last update date. - rs, err := amdb.Query("SELECT lastupdate FROM contacts WHERE contactid = ?", ci.ContactId) + rs, err := amdb.QueryContext(ctx, "SELECT lastupdate FROM contacts WHERE contactid = ?", ci.ContactId) if err != nil { return false, err } @@ -241,9 +244,9 @@ func init() { } // internalContactInfo retrieves the contact info from the database. -func internalContactInfo(id int32) (*ContactInfo, error) { +func internalContactInfo(ctx context.Context, id int32) (*ContactInfo, error) { var dbdata []ContactInfo - err := amdb.Select(&dbdata, "SELECT * from contacts WHERE contactid = ?", id) + err := amdb.SelectContext(ctx, &dbdata, "SELECT * from contacts WHERE contactid = ?", id) if err == nil { if len(dbdata) > 1 { err = fmt.Errorf("internalContactInfo(%d): Too many responses (%d)", id, len(dbdata)) @@ -258,19 +261,20 @@ func internalContactInfo(id int32) (*ContactInfo, error) { /* AmGetContactInfo retrieves the contact info for a given identifier. * Parameters: + * ctx - Standard Go context value. * id - The contact info ID top retrieve. * Returns: * ContactInfo retrieved, or nil. * Standard Go error status. */ -func AmGetContactInfo(id int32) (*ContactInfo, error) { +func AmGetContactInfo(ctx context.Context, id int32) (*ContactInfo, error) { getContactMutex.Lock() defer getContactMutex.Unlock() rc, ok := contactCache.Get(id) if ok { return rc.(*ContactInfo), nil } - rc2, err := internalContactInfo(id) + rc2, err := internalContactInfo(ctx, id) if err == nil { if rc2 != nil { contactCache.Add(id, rc2) diff --git a/database/emailban.go b/database/emailban.go index bf03779..909ceca 100644 --- a/database/emailban.go +++ b/database/emailban.go @@ -9,15 +9,18 @@ // The database package contains database management and storage logic. package database +import "context" + /* AmIsEmailAddressBanned returns true if the given E-mail address is on the "banned" list. * Parameters: + * ctx - Standard Go context value. * address - The E-mail address to be checked. * Returns: * true if the address is banned, false if not. * Standard Go error status. */ -func AmIsEmailAddressBanned(address string) (bool, error) { - rs, err := amdb.Query("SELECT by_uid FROM emailban WHERE address = ?", address) +func AmIsEmailAddressBanned(ctx context.Context, address string) (bool, error) { + rs, err := amdb.QueryContext(ctx, "SELECT by_uid FROM emailban WHERE address = ?", address) if err != nil { return false, err } diff --git a/database/globals.go b/database/globals.go index 5687efb..a78daf3 100644 --- a/database/globals.go +++ b/database/globals.go @@ -10,6 +10,7 @@ package database import ( + "context" "errors" "sync" @@ -60,11 +61,11 @@ var globalProps map[int32]string = make(map[int32]string) var globalPropMutex sync.Mutex // Flags returns the global flags. -func (g *Globals) Flags() (*util.OptionSet, error) { +func (g *Globals) Flags(ctx context.Context) (*util.OptionSet, error) { g.Mutex.Lock() defer g.Mutex.Unlock() if g.flags == nil { - s, err := AmGetGlobalProperty(GlobalPropFlags) + s, err := AmGetGlobalProperty(ctx, GlobalPropFlags) if err != nil { return nil, err } @@ -74,11 +75,11 @@ func (g *Globals) Flags() (*util.OptionSet, error) { } // SaveFlags saves off the global flags. -func (g *Globals) SaveFlags(f *util.OptionSet) error { +func (g *Globals) SaveFlags(ctx context.Context, f *util.OptionSet) error { s := f.AsString() g.Mutex.Lock() defer g.Mutex.Unlock() - err := AmSetGlobalProperty(GlobalPropFlags, s) + err := AmSetGlobalProperty(ctx, GlobalPropFlags, s) if err == nil { g.flags = f } @@ -86,12 +87,12 @@ func (g *Globals) SaveFlags(f *util.OptionSet) error { } // AmGlobals returns trhe pointer to the singleton Globals instance. -func AmGlobals() (*Globals, error) { +func AmGlobals(ctx context.Context) (*Globals, error) { globalsMutex.Lock() defer globalsMutex.Unlock() if theGlobals == nil { var dbdata []Globals - err := amdb.Select(&dbdata, "SELECT * FROM globals") + err := amdb.SelectContext(ctx, &dbdata, "SELECT * FROM globals") if err != nil { return nil, err } @@ -105,17 +106,18 @@ func AmGlobals() (*Globals, error) { /* AmGetGlobalProperty returns the value of a global property. * Parameters: + * ctx - Standard Go context value. * index - The index of the property to retrieve. * Returns: * Value of the property, or empty string. * Standard Go error status. */ -func AmGetGlobalProperty(index int32) (string, error) { +func AmGetGlobalProperty(ctx context.Context, index int32) (string, error) { globalPropMutex.Lock() defer globalPropMutex.Unlock() rc, ok := globalProps[index] if !ok { - rs, err := amdb.Query("SELECT data FROM propglobal WHERE ndx = ?", index) + rs, err := amdb.QueryContext(ctx, "SELECT data FROM propglobal WHERE ndx = ?", index) if err != nil { return "", err } @@ -131,17 +133,18 @@ func AmGetGlobalProperty(index int32) (string, error) { /* AmSetGlobalProperty sets the value of a global property. * Parameters: + * ctx - Standard Go context value. * index - The index of the property to set. * value - The value of the property to set. * Returns: * Standard Go error status. */ -func AmSetGlobalProperty(index int32, value string) error { +func AmSetGlobalProperty(ctx context.Context, index int32, value string) error { globalPropMutex.Lock() defer globalPropMutex.Unlock() _, updateMode := globalProps[index] if !updateMode { - rs, err := amdb.Query("SELECT data FROM propglobal WHERE ndx = ?", index) + rs, err := amdb.QueryContext(ctx, "SELECT data FROM propglobal WHERE ndx = ?", index) if err != nil { return err } @@ -149,9 +152,9 @@ func AmSetGlobalProperty(index int32, value string) error { } var err error = nil if updateMode { - _, err = amdb.Exec("UPDATE propglobal SET data = ? WHERE ndx = ?", value, index) + _, err = amdb.ExecContext(ctx, "UPDATE propglobal SET data = ? WHERE ndx = ?", value, index) } else { - _, err = amdb.Exec("INSERT INTO propglobal (ndx, data) VALUES (?, ?)", index, value) + _, err = amdb.ExecContext(ctx, "INSERT INTO propglobal (ndx, data) VALUES (?, ?)", index, value) } if err == nil { globalProps[index] = value diff --git a/database/imagestore.go b/database/imagestore.go index da6cfd3..bc6cd4f 100644 --- a/database/imagestore.go +++ b/database/imagestore.go @@ -10,6 +10,7 @@ package database import ( + "context" "database/sql" "fmt" ) @@ -31,14 +32,14 @@ const ( ) // Save persists the ImageStore record to the database. -func (img *ImageStore) Save() error { +func (img *ImageStore) Save(ctx context.Context) error { var err error if img.ImgId > 0 { - _, err = amdb.NamedExec(`UPDATE imagestore SET typecode = :typecode, ownerid = :ownerid, mimetype = :mimetype, + _, err = amdb.NamedExecContext(ctx, `UPDATE imagestore SET typecode = :typecode, ownerid = :ownerid, mimetype = :mimetype, length = :length, data = :data WHERE imgid = :imgid`, img) } else { var rs sql.Result - rs, err = amdb.NamedExec(`INSERT INTO imagestore (typecode, ownerid, mimetype, length, data) + rs, err = amdb.NamedExecContext(ctx, `INSERT INTO imagestore (typecode, ownerid, mimetype, length, data) VALUES (:typecode, :ownerid, :mimetype, :length, :data)`, img) if err == nil { var lii int64 @@ -53,14 +54,15 @@ func (img *ImageStore) Save() error { /* AmLoadImage loads an image from the database. * Parameters: + * ctx - Standard Go context value. * id - The ID of the image to be loaded. * Returns: * Pointer to ImageStore, or nil. * Standard Go error status. */ -func AmLoadImage(id int32) (*ImageStore, error) { +func AmLoadImage(ctx context.Context, id int32) (*ImageStore, error) { var dbdata []ImageStore - err := amdb.Select(&dbdata, "SELECT * FROM imagestore WHERE imgid = ?", id) + err := amdb.SelectContext(ctx, &dbdata, "SELECT * FROM imagestore WHERE imgid = ?", id) if err != nil { return nil, err } @@ -74,6 +76,7 @@ func AmLoadImage(id int32) (*ImageStore, error) { /* AmStoreImage stores an image in the database, overwriting one with the same type code and owner if it exists. * Parameters: + * ctx - Standard Go context value. * typecode - Type code for the image. * owner - Owner Id for the image (UID or community ID) * mimetype - MIME type of the image. @@ -82,8 +85,8 @@ func AmLoadImage(id int32) (*ImageStore, error) { * Pointer to ImageStore, or nil. * Standard Go error status. */ -func AmStoreImage(typecode int16, owner int32, mimetype string, data []byte) (*ImageStore, error) { - rs, err := amdb.Query("SELECT imgid FROM imagestore WHERE typecode = ? AND ownerid = ?", typecode, owner) +func AmStoreImage(ctx context.Context, typecode int16, owner int32, mimetype string, data []byte) (*ImageStore, error) { + rs, err := amdb.QueryContext(ctx, "SELECT imgid FROM imagestore WHERE typecode = ? AND ownerid = ?", typecode, owner) if err != nil { return nil, err } @@ -91,7 +94,7 @@ func AmStoreImage(typecode int16, owner int32, mimetype string, data []byte) (*I if rs.Next() { var id int32 rs.Scan(&id) - img, err = AmLoadImage(id) + img, err = AmLoadImage(ctx, id) if err != nil { return nil, err } @@ -108,7 +111,7 @@ func AmStoreImage(typecode int16, owner int32, mimetype string, data []byte) (*I Data: data, } } - err = img.Save() + err = img.Save(ctx) if err != nil { return nil, err } @@ -117,11 +120,12 @@ func AmStoreImage(typecode int16, owner int32, mimetype string, data []byte) (*I /* AmDeleteImage erases an image from the database. * Parameters: + * ctx - Standard Go context value. * id - The ID of the image to be deleted. * Returns: * Standard Go error status. */ -func AmDeleteImage(id int32) error { - _, err := amdb.Exec("DELETE FROM imagestore WHERE imgid = ?", id) +func AmDeleteImage(ctx context.Context, id int32) error { + _, err := amdb.ExecContext(ctx, "DELETE FROM imagestore WHERE imgid = ?", id) return err } diff --git a/database/ipban.go b/database/ipban.go index 334176c..aad146a 100644 --- a/database/ipban.go +++ b/database/ipban.go @@ -10,6 +10,7 @@ package database import ( + "context" "fmt" "math/big" "net" @@ -40,12 +41,13 @@ func init() { /* AmTestIPBan tests an IP address to see if it's on the banned list. * Parameters: + * ctx - Standard Go context parameter. * ip_address - The IP address to be tested. * Returns: * Ban message if the address is banned, or empty string if it isn't. * Standard Go error status. */ -func AmTestIPBan(ip_address string) (string, error) { +func AmTestIPBan(ctx context.Context, ip_address string) (string, error) { banMutex.Lock() defer banMutex.Unlock() rc := knownBans[ip_address] @@ -63,8 +65,7 @@ func AmTestIPBan(ip_address string) (string, error) { iv.SetBytes(addr) iv_lo := big.NewInt(0).And(iv, low64mask).Uint64() iv_hi := big.NewInt(0).Rsh(iv, 64).Uint64() - rows, err := amdb.Query(` - SELECT message FROM ipban WHERE (address_lo & mask_lo) = (? & mask_lo) + rows, err := amdb.QueryContext(ctx, `SELECT message FROM ipban WHERE (address_lo & mask_lo) = (? & mask_lo) AND (address_hi & mask_hi) = (? & mask_hi) AND (expire IS NULL OR expire >= ?) AND enable <> 0 ORDER BY mask_hi DESC, mask_lo DESC`, iv_lo, iv_hi, time.Now().UTC()) if err != nil { diff --git a/database/post.go b/database/post.go index 38582c6..62dc48a 100644 --- a/database/post.go +++ b/database/post.go @@ -10,6 +10,7 @@ package database import ( + "context" "errors" "fmt" "time" @@ -43,6 +44,7 @@ func (p *PostHeader) IsScribbled() bool { /* SetAttachment sets the attachment data for a post. * Parameters: + * ctx - Standard Go context value. * fileName - Name of the original attachment file. * mimeType - MIME type of the attachment data. * length - Length of the attachment data in bytes. @@ -50,16 +52,16 @@ func (p *PostHeader) IsScribbled() bool { * Returns: * Standard Go error status. */ -func (p *PostHeader) SetAttachment(fileName string, mimeType string, length int32, data []byte) error { - _, err := amdb.Exec("INSERT INTO postattach (postid, datalen, filename, mimetype, data) VALUES (?, ?, ?, ?, ?)", +func (p *PostHeader) SetAttachment(ctx context.Context, fileName string, mimeType string, length int32, data []byte) error { + _, err := amdb.ExecContext(ctx, "INSERT INTO postattach (postid, datalen, filename, mimetype, data) VALUES (?, ?, ?, ?, ?)", p.PostId, length, fileName, mimeType, data) return err } // Text returns the text associated with a post. -func (p *PostHeader) Text() (string, error) { +func (p *PostHeader) Text(ctx context.Context) (string, error) { var dbdata []PostData - err := amdb.Select(&dbdata, "SELECT * FROM postdata WHERE postid = ?", p.PostId) + err := amdb.SelectContext(ctx, &dbdata, "SELECT * FROM postdata WHERE postid = ?", p.PostId) if err != nil { return "", err } @@ -72,9 +74,9 @@ func (p *PostHeader) Text() (string, error) { return *dbdata[0].Data, nil } -func AmGetPost(postId int64) (*PostHeader, error) { +func AmGetPost(ctx context.Context, postId int64) (*PostHeader, error) { var dbdata []PostHeader - err := amdb.Select(&dbdata, "SELECT * FROM posts WHERE postid = ?", postId) + err := amdb.SelectContext(ctx, &dbdata, "SELECT * FROM posts WHERE postid = ?", postId) if err != nil { return nil, err } @@ -87,9 +89,9 @@ func AmGetPost(postId int64) (*PostHeader, error) { return &(dbdata[0]), nil } -func AmGetPostRange(topic *Topic, first, last int32) ([]PostHeader, error) { +func AmGetPostRange(ctx context.Context, topic *Topic, first, last int32) ([]PostHeader, error) { var rc []PostHeader - err := amdb.Select(&rc, "SELECT * FROM posts WHERE topicid = ? AND num >= ? AND num <= ? ORDER BY num", topic.TopicId, first, last) + err := amdb.SelectContext(ctx, &rc, "SELECT * FROM posts WHERE topicid = ? AND num >= ? AND num <= ? ORDER BY num", topic.TopicId, first, last) if err != nil { return nil, err } diff --git a/database/post_link.go b/database/post_link.go index 5fd8ba9..f503f61 100644 --- a/database/post_link.go +++ b/database/post_link.go @@ -10,6 +10,7 @@ package database import ( + "context" "errors" "fmt" "math" @@ -32,9 +33,9 @@ func (d *PostLinkData) NeedsDBVerification() bool { } // VerifyNames verifies the post link data against the database. -func (d *PostLinkData) VerifyNames() error { +func (d *PostLinkData) VerifyNames(ctx context.Context) error { if d.Community != "" { - comm, err := AmGetCommunityByAlias(d.Community) + comm, err := AmGetCommunityByAlias(ctx, d.Community) if err != nil { return err } @@ -43,7 +44,7 @@ func (d *PostLinkData) VerifyNames() error { } } if d.Conference != "" { - conf, err := AmGetConferenceByAlias(d.Conference) + conf, err := AmGetConferenceByAlias(ctx, d.Conference) if err != nil { return err } diff --git a/database/services.go b/database/services.go index 2b48f7b..a6bd44d 100644 --- a/database/services.go +++ b/database/services.go @@ -10,6 +10,7 @@ package database import ( + "context" _ "embed" "slices" "sync" @@ -21,28 +22,28 @@ import ( // ServiceVTable is a serioes of functions called for services on specific events. type ServiceVTable interface { - OnNewCommunity(*sqlx.Tx, *Community) error - OnDeleteCommunity(*sqlx.Tx, int32) error - OnUserJoinCommunity(*sqlx.Tx, *Community, *User) error - OnUserLeaveCommunity(*sqlx.Tx, *Community, *User) error + OnNewCommunity(context.Context, *sqlx.Tx, *Community) error + OnDeleteCommunity(context.Context, *sqlx.Tx, int32) error + OnUserJoinCommunity(context.Context, *sqlx.Tx, *Community, *User) error + OnUserLeaveCommunity(context.Context, *sqlx.Tx, *Community, *User) error } // emptyServiceVTable is a default ServiceVTable that does nothing. type emptyServiceVTable struct{} -func (*emptyServiceVTable) OnNewCommunity(*sqlx.Tx, *Community) error { +func (*emptyServiceVTable) OnNewCommunity(context.Context, *sqlx.Tx, *Community) error { return nil } -func (*emptyServiceVTable) OnDeleteCommunity(*sqlx.Tx, int32) error { +func (*emptyServiceVTable) OnDeleteCommunity(context.Context, *sqlx.Tx, int32) error { return nil } -func (*emptyServiceVTable) OnUserJoinCommunity(*sqlx.Tx, *Community, *User) error { +func (*emptyServiceVTable) OnUserJoinCommunity(context.Context, *sqlx.Tx, *Community, *User) error { return nil } -func (*emptyServiceVTable) OnUserLeaveCommunity(*sqlx.Tx, *Community, *User) error { +func (*emptyServiceVTable) OnUserLeaveCommunity(context.Context, *sqlx.Tx, *Community, *User) error { return nil } @@ -124,17 +125,18 @@ func init() { /* AmGetCommunityServices returns all the community service definitions for a community. * Parameters: + * ctx - Standard Go context value. * cid - Community ID to get services for. * Returns: * Array of ServiceDef pointers for the community's services. * Standard Go error status. */ -func AmGetCommunityServices(cid int32) ([]*ServiceDef, error) { +func AmGetCommunityServices(ctx context.Context, cid int32) ([]*ServiceDef, error) { servicesCacheMutex.Lock() defer servicesCacheMutex.Unlock() rc, ok := servicesCache.Get(cid) if !ok { - rs, err := amdb.Query("SELECT ftr_code FROM commftrs WHERE commid = ?", cid) + rs, err := amdb.QueryContext(ctx, "SELECT ftr_code FROM commftrs WHERE commid = ?", cid) if err != nil { return nil, err } @@ -153,18 +155,19 @@ func AmGetCommunityServices(cid int32) ([]*ServiceDef, error) { /* AmGetCommunityServices returns all the community service definitions for a community, using a transaction. * Parameters: + * ctx - Standard Go context value. * tx - Transaction to be used. * cid - Community ID to get services for. * Returns: * Array of ServiceDef pointers for the community's services. * Standard Go error status. */ -func AmGetCommunityServicesTx(tx *sqlx.Tx, cid int32) ([]*ServiceDef, error) { +func AmGetCommunityServicesTx(ctx context.Context, tx *sqlx.Tx, cid int32) ([]*ServiceDef, error) { servicesCacheMutex.Lock() defer servicesCacheMutex.Unlock() rc, ok := servicesCache.Get(cid) if !ok { - rs, err := tx.Query("SELECT ftr_code FROM commftrs WHERE commid = ?", cid) + rs, err := tx.QueryContext(ctx, "SELECT ftr_code FROM commftrs WHERE commid = ?", cid) if err != nil { return nil, err } @@ -184,17 +187,18 @@ func AmGetCommunityServicesTx(tx *sqlx.Tx, cid int32) ([]*ServiceDef, error) { /* AmEstablishCommunityServices establishes the service (feature) records for a new community, * and allows the services to establish themselves. * Parameters: + * ctx - Standard Go context value. * tx - The transaction to use. * c - The new community. * Returns: * Standard Go error status. */ -func AmEstablishCommunityServices(tx *sqlx.Tx, c *Community) error { +func AmEstablishCommunityServices(ctx context.Context, tx *sqlx.Tx, c *Community) error { dom := serviceRoot.byName["community"] a := make([]*ServiceDef, 0, len(dom.Services)) for i, svc := range dom.Services { if svc.Default { - _, err := tx.Exec("INSERT INTO commftrs (commid, ftr_code) VALUES (?, ?)", c.Id, svc.Index) + _, err := tx.ExecContext(ctx, "INSERT INTO commftrs (commid, ftr_code) VALUES (?, ?)", c.Id, svc.Index) if err != nil { return err } @@ -205,7 +209,7 @@ func AmEstablishCommunityServices(tx *sqlx.Tx, c *Community) error { servicesCache.Add(c.Id, a) servicesCacheMutex.Unlock() for _, svc := range a { - err := svc.vtable.OnNewCommunity(tx, c) + err := svc.vtable.OnNewCommunity(ctx, tx, c) if err != nil { return err } @@ -216,23 +220,24 @@ func AmEstablishCommunityServices(tx *sqlx.Tx, c *Community) error { /* AmDeleteCommunityServices cleans up all services associated with a community that has gone away, * and then cleans up the service records. * Parameters: + * ctx - Standard Go context value. * tx - The transaction to use. * cid - The ID of the departing community. * Returns: * Standard Go error status. */ -func AmDeleteCommunityServices(tx *sqlx.Tx, cid int32) error { - arr, err := AmGetCommunityServices(cid) +func AmDeleteCommunityServices(ctx context.Context, tx *sqlx.Tx, cid int32) error { + arr, err := AmGetCommunityServices(ctx, cid) if err == nil { for _, svc := range arr { - err = svc.vtable.OnDeleteCommunity(tx, cid) + err = svc.vtable.OnDeleteCommunity(ctx, tx, cid) if err != nil { break } } } if err == nil { - _, err = tx.Exec("DELETE FROM commftrs WHERE commid = ?", cid) + _, err = tx.ExecContext(ctx, "DELETE FROM commftrs WHERE commid = ?", cid) servicesCacheMutex.Lock() servicesCache.Remove(cid) servicesCacheMutex.Unlock() @@ -242,17 +247,18 @@ func AmDeleteCommunityServices(tx *sqlx.Tx, cid int32) error { /* AmOnUserJoinCommunityServices gives services a chance to update themselves when a user joins a community. * Parameters: + * ctx - Standard Go context value. * tx - The current database transaction. * c - The community that is being joined. * u - The user leaving that community. * Returns: * Standard Go error status. */ -func AmOnUserJoinCommunityServices(tx *sqlx.Tx, c *Community, u *User) error { - arr, err := AmGetCommunityServicesTx(tx, c.Id) +func AmOnUserJoinCommunityServices(ctx context.Context, tx *sqlx.Tx, c *Community, u *User) error { + arr, err := AmGetCommunityServicesTx(ctx, tx, c.Id) if err == nil { for _, svc := range arr { - err = svc.vtable.OnUserJoinCommunity(tx, c, u) + err = svc.vtable.OnUserJoinCommunity(ctx, tx, c, u) if err != nil { break } @@ -263,17 +269,18 @@ func AmOnUserJoinCommunityServices(tx *sqlx.Tx, c *Community, u *User) error { /* AmOnUserLeaveCommunityServices gives services a chance to update themselves when a user leaves a community. * Parameters: + * ctx - Standard Go context value. * tx - The current database transaction. * c - The community that is being left. * u - The user leaving that community. * Returns: * Standard Go error status. */ -func AmOnUserLeaveCommunityServices(tx *sqlx.Tx, c *Community, u *User) error { - arr, err := AmGetCommunityServicesTx(tx, c.Id) +func AmOnUserLeaveCommunityServices(ctx context.Context, tx *sqlx.Tx, c *Community, u *User) error { + arr, err := AmGetCommunityServicesTx(ctx, tx, c.Id) if err == nil { for _, svc := range arr { - err = svc.vtable.OnUserLeaveCommunity(tx, c, u) + err = svc.vtable.OnUserLeaveCommunity(ctx, tx, c, u) if err != nil { break } @@ -282,8 +289,8 @@ func AmOnUserLeaveCommunityServices(tx *sqlx.Tx, c *Community, u *User) error { return err } -func AmTestService(c *Community, serviceId string) (bool, error) { - arr, err := AmGetCommunityServices(c.Id) +func AmTestService(ctx context.Context, c *Community, serviceId string) (bool, error) { + arr, err := AmGetCommunityServices(ctx, c.Id) if err == nil { for _, svc := range arr { if svc.Id == serviceId { diff --git a/database/sidebox.go b/database/sidebox.go index b058519..6e73747 100644 --- a/database/sidebox.go +++ b/database/sidebox.go @@ -9,7 +9,11 @@ // The database package contains database management and storage logic. package database -import "github.com/jmoiron/sqlx" +import ( + "context" + + "github.com/jmoiron/sqlx" +) type Sidebox struct { Uid int32 `db:"uid"` @@ -19,12 +23,12 @@ type Sidebox struct { } // copySideboxes copies sideboxes from one user to another. -func copySideboxes(tx *sqlx.Tx, toUid int32, fromUid int32) error { +func copySideboxes(ctx context.Context, tx *sqlx.Tx, toUid int32, fromUid int32) error { sbox := make([]Sidebox, 0, 3) - err := tx.Select(&sbox, "SELECT * from sideboxes WHERE uid = ?", fromUid) + err := tx.SelectContext(ctx, &sbox, "SELECT * from sideboxes WHERE uid = ?", fromUid) if err == nil { for _, sb := range sbox { - _, err := tx.Exec("INSERT INTO sideboxes (uid, boxid, sequence, param) VALUES (?, ?, ?, ?)", toUid, sb.Boxid, sb.Sequence, sb.Param) + _, err := tx.ExecContext(ctx, "INSERT INTO sideboxes (uid, boxid, sequence, param) VALUES (?, ?, ?, ?)", toUid, sb.Boxid, sb.Sequence, sb.Param) if err != nil { break } @@ -35,13 +39,14 @@ func copySideboxes(tx *sqlx.Tx, toUid int32, fromUid int32) error { /* AmGetSideboxes returns all the configured sideboxes for a user. * Parameters: + * ctx = Standard Go context value. * uid = The ID of the user to retrieve sideboxes for. * Returns: * Array of Sidebox structures for the user, or nil * Standard Go error status */ -func AmGetSideboxes(uid int32) ([]*Sidebox, error) { +func AmGetSideboxes(ctx context.Context, uid int32) ([]*Sidebox, error) { sboxes := make([]*Sidebox, 0, 3) - err := amdb.Select(&sboxes, "SELECT * FROM sideboxes WHERE uid = ? ORDER BY SEQUENCE", uid) + err := amdb.SelectContext(ctx, &sboxes, "SELECT * FROM sideboxes WHERE uid = ? ORDER BY SEQUENCE", uid) return sboxes, err } diff --git a/database/topic.go b/database/topic.go index 0e9cfe0..b2df68f 100644 --- a/database/topic.go +++ b/database/topic.go @@ -10,6 +10,7 @@ package database import ( + "context" "errors" "fmt" "strings" @@ -34,12 +35,12 @@ type Topic struct { } // GetPost returns a post in the topic by number. -func (t *Topic) GetPost(num int32) (*PostHeader, error) { +func (t *Topic) GetPost(ctx context.Context, num int32) (*PostHeader, error) { if num > t.TopMessage { return nil, fmt.Errorf("no post %d in topic %d", num, t.TopicId) } var dbdata []PostHeader - err := amdb.Select(&dbdata, "SELECT * FROM posts WHERE topicid = ? AND num = ?", t.TopicId, num) + err := amdb.SelectContext(ctx, &dbdata, "SELECT * FROM posts WHERE topicid = ? AND num = ?", t.TopicId, num) if err == nil { if len(dbdata) == 0 { err = fmt.Errorf("no post %d in topic %d", num, t.TopicId) @@ -53,8 +54,8 @@ func (t *Topic) GetPost(num int32) (*PostHeader, error) { } // 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) +func (t *Topic) GetLastRead(ctx context.Context, u *User) (int32, error) { + rs, err := amdb.QueryContext(ctx, "SELECT last_message FROM topicsettings WHERE topicid = ? AND uid = ?", t.TopicId, u.Uid) if err != nil { return -1, err } @@ -66,12 +67,14 @@ func (t *Topic) GetLastRead(u *User) (int32, error) { } // 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) +func (t *Topic) SetLastRead(ctx context.Context, u *User, postNum int32) error { + rs, err := amdb.ExecContext(ctx, "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) + _, err = amdb.ExecContext(ctx, "INSERT INTO topicsettings (topicid, uid, last_message, last_read, last_post) VALUES (?, ?, ?, NOW(), NULL)", + t.TopicId, u.Uid, postNum) } } return err @@ -104,14 +107,15 @@ type TopicSummary struct { /* AmGetTopic retrieves a topic by ID. * Parameters: + * ctx - Standard Go context value. * topicId - ID of the topic to retrieve. * Returns: * The topic pointer, or nil. * Standard Go error status. */ -func AmGetTopic(topicId int32) (*Topic, error) { +func AmGetTopic(ctx context.Context, topicId int32) (*Topic, error) { var dbdata []Topic - err := amdb.Select(&dbdata, "SELECT * FROM topics WHERE topicid = ?", topicId) + err := amdb.SelectContext(ctx, &dbdata, "SELECT * FROM topics WHERE topicid = ?", topicId) if err != nil { return nil, err } @@ -126,15 +130,16 @@ func AmGetTopic(topicId int32) (*Topic, error) { /* AmGetTopicTx retrieves a topic by ID, in a transaction. * Parameters: + * ctx - Standard Go context value. * tx - The transaction to use. * topicId - ID of the topic to retrieve. * Returns: * The topic pointer, or nil. * Standard Go error status. */ -func AmGetTopicTx(tx *sqlx.Tx, topicId int32) (*Topic, error) { +func AmGetTopicTx(ctx context.Context, tx *sqlx.Tx, topicId int32) (*Topic, error) { var dbdata []Topic - err := tx.Select(&dbdata, "SELECT * FROM topics WHERE topicid = ?", topicId) + err := tx.SelectContext(ctx, &dbdata, "SELECT * FROM topics WHERE topicid = ?", topicId) if err != nil { return nil, err } @@ -149,15 +154,16 @@ func AmGetTopicTx(tx *sqlx.Tx, topicId int32) (*Topic, error) { /* AmGetTopicByNumber retrieves a topic by conference and sequence number. * Parameters: + * ctx - Standard Go context value. * 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) { +func AmGetTopicByNumber(ctx context.Context, conf *Conference, topicNum int16) (*Topic, error) { var dbdata []Topic - err := amdb.Select(&dbdata, "SELECT * FROM topics WHERE confid = ? AND num = ?", conf.ConfId, topicNum) + err := amdb.SelectContext(ctx, &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) @@ -189,6 +195,7 @@ const ( /* AmListTopics produces a list of topic summary information according to specific options. * Parameters: + * ctx - Standard Go context value. * confid - The ID of the conference to list topics in. * uid - The UID of the user to consider the settings of. * viewOption - One of the following constants: @@ -210,7 +217,7 @@ const ( * List of TopicSummary pointers. * Standard Go error status. */ -func AmListTopics(confid int32, uid int32, viewOption int, sortOption int, ignoreSticky bool) ([]*TopicSummary, error) { +func AmListTopics(ctx context.Context, confid int32, uid int32, viewOption int, sortOption int, ignoreSticky bool) ([]*TopicSummary, error) { // Decode the viewOption into a WHERE clause. var whereClause string switch viewOption { @@ -296,7 +303,7 @@ func AmListTopics(confid int32, uid int32, viewOption int, sortOption int, ignor fullStatement.WriteString(orderByClause) // Execute and capture results - rs, err := amdb.Query(fullStatement.String(), uid, confid) + rs, err := amdb.QueryContext(ctx, fullStatement.String(), uid, confid) if err != nil { return nil, err } @@ -310,7 +317,7 @@ func AmListTopics(confid int32, uid int32, viewOption int, sortOption int, ignor return rc, nil } -func AmNewTopic(conf *Conference, user *User, title string, zeroPostPseud string, zeroPost string, +func AmNewTopic(ctx context.Context, conf *Conference, user *User, title string, zeroPostPseud string, zeroPost string, zeroPostLines int32, ipaddr string) (*Topic, error) { var ar *AuditRecord = nil defer func() { @@ -325,16 +332,16 @@ func AmNewTopic(conf *Conference, user *User, title string, zeroPostPseud string }() unlock := true - tx.Exec("LOCK TABLES confs WRITE, topics WRITE, topicsettings WRITE, posts WRITE, postdata WRITE;") + tx.ExecContext(ctx, "LOCK TABLES confs WRITE, topics WRITE, topicsettings WRITE, posts WRITE, postdata WRITE;") defer func() { if unlock { - tx.Exec("UNLOCK TABLES;") + tx.ExecContext(ctx, "UNLOCK TABLES;") } }() // Insert the new topic into the database. conf.Mutex.Lock() - rs, err := tx.Exec("INSERT INTO topics (confid, num, creator_uid, createdate, lastupdate, name) VALUES (?, ?, ?, NOW(), NOW(), ?)", + rs, err := tx.ExecContext(ctx, "INSERT INTO topics (confid, num, creator_uid, createdate, lastupdate, name) VALUES (?, ?, ?, NOW(), NOW(), ?)", conf.ConfId, conf.TopTopic+1, user.Uid, title) if err != nil { conf.Mutex.Unlock() @@ -347,14 +354,14 @@ func AmNewTopic(conf *Conference, user *User, title string, zeroPostPseud string return nil, err } // Get the topic. - topic, err := AmGetTopicTx(tx, int32(xid)) + topic, err := AmGetTopicTx(ctx, tx, int32(xid)) if err != nil { conf.Mutex.Unlock() return nil, err } // Update the conference to set the last update and top topic. - _, err = tx.Exec("UPDATE confs SET lastupdate = ?, top_topic = ? WHERE confid = ?", topic.CreateDate, conf.TopTopic+1, conf.ConfId) + _, err = tx.ExecContext(ctx, "UPDATE confs SET lastupdate = ?, top_topic = ? WHERE confid = ?", topic.CreateDate, conf.TopTopic+1, conf.ConfId) if err != nil { conf.Mutex.Unlock() return nil, err @@ -364,7 +371,7 @@ func AmNewTopic(conf *Conference, user *User, title string, zeroPostPseud string conf.Mutex.Unlock() // Add the "header record" for the first post. - rs, err = tx.Exec("INSERT INTO posts (topicid, num, linecount, creator_uid, posted, pseud) VALUES (?, 0, ?, ?, ?, ?)", + rs, err = tx.ExecContext(ctx, "INSERT INTO posts (topicid, num, linecount, creator_uid, posted, pseud) VALUES (?, 0, ?, ?, ?, ?)", topic.TopicId, zeroPostLines, user.Uid, topic.CreateDate, zeroPostPseud) if err != nil { return nil, err @@ -374,23 +381,23 @@ func AmNewTopic(conf *Conference, user *User, title string, zeroPostPseud string return nil, err } // Add the post data. - _, err = tx.Exec("INSERT INTO postdata (postid, data) VALUES (?, ?)", int32(xid), zeroPost) + _, err = tx.ExecContext(ctx, "INSERT INTO postdata (postid, data) VALUES (?, ?)", int32(xid), zeroPost) if err != nil { return nil, err } // Add a new topic settings record for the user, too. - _, err = tx.Exec("INSERT INTO topicsettings (topicid, uid, last_post) VALUES (?, ?, ?)", + _, err = tx.ExecContext(ctx, "INSERT INTO topicsettings (topicid, uid, last_post) VALUES (?, ?, ?)", topic.TopicId, user.Uid, topic.CreateDate) if err != nil { return nil, err } - tx.Exec("UNLOCK TABLES;") + tx.ExecContext(ctx, "UNLOCK TABLES;") unlock = false // update the "last posted" date in the conference settings - _, err = conf.TouchPost(tx, user, topic.CreateDate) + _, err = conf.TouchPost(ctx, tx, user, topic.CreateDate) if err != nil { return nil, err } diff --git a/database/user.go b/database/user.go index 37e62a3..f9b5a07 100644 --- a/database/user.go +++ b/database/user.go @@ -10,6 +10,7 @@ package database import ( + "context" "crypto/sha1" "encoding/hex" "errors" @@ -53,11 +54,11 @@ func (p *UserPrefs) Clone() *UserPrefs { } // Save saves off the user preferences, replacing the prefs on the user if necessary. -func (p *UserPrefs) Save(u *User) error { +func (p *UserPrefs) Save(ctx context.Context, u *User) error { if u != nil && u.Uid != p.Uid { return errors.New("internal mismatch of IDs") } - _, err := amdb.NamedExec("UPDATE userprefs SET localeid = :localeid, tzid = :tzid WHERE uid = :uid", p) + _, err := amdb.NamedExecContext(ctx, "UPDATE userprefs SET localeid = :localeid, tzid = :tzid WHERE uid = :uid", p) if err == nil && u != nil { u.prefs = p } @@ -181,27 +182,27 @@ func init() { } // ContactInfo returns the contact info structure for the user. -func (u *User) ContactInfo() (*ContactInfo, error) { +func (u *User) ContactInfo(ctx context.Context) (*ContactInfo, error) { if u.ContactID < 0 { return nil, nil } - return AmGetContactInfo(u.ContactID) + return AmGetContactInfo(ctx, u.ContactID) } // ContactInfo returns the contact info structure for the user, quietly. -func (u *User) ContactInfoQ() *ContactInfo { +func (u *User) ContactInfoQ(ctx context.Context) *ContactInfo { if u.ContactID < 0 { return nil } - ci, _ := AmGetContactInfo(u.ContactID) + ci, _ := AmGetContactInfo(ctx, u.ContactID) return ci } // SetContactID sets the contact ID of a user. -func (u *User) SetContactID(cid int32) error { +func (u *User) SetContactID(ctx context.Context, cid int32) error { u.Mutex.Lock() defer u.Mutex.Unlock() - if _, err := amdb.Exec("UPDATE users SET contactid = ? WHERE uid = ?", cid, u.Uid); err != nil { + if _, err := amdb.ExecContext(ctx, "UPDATE users SET contactid = ? WHERE uid = ?", cid, u.Uid); err != nil { return err } u.ContactID = cid @@ -213,14 +214,14 @@ func (u *User) SetContactID(cid int32) error { * Authentication token value * Standard Go error status. */ -func (u *User) NewAuthToken() (string, error) { +func (u *User) NewAuthToken(ctx context.Context) (string, error) { if u.IsAnon { return "", errors.New("cannot generate token for anonymous user") } u.Mutex.Lock() defer u.Mutex.Unlock() newToken := util.GenerateRandomAuthString() - if _, err := amdb.Exec("UPDATE users SET tokenauth = ? WHERE uid = ?", newToken, u.Uid); err != nil { + if _, err := amdb.ExecContext(ctx, "UPDATE users SET tokenauth = ? WHERE uid = ?", newToken, u.Uid); err != nil { return "", err } u.Tokenauth = &newToken @@ -230,12 +231,13 @@ func (u *User) NewAuthToken() (string, error) { /* ConfirmEMailAddress checks the E-mail confirmation number and sets "verified" status if it's OK. * Parameters: + * ctx - Standard Go context value. * confnum - The entered confirmation number. * remoteIP - The remote IP address for audit messages. * Returns: * Standard Go error status. */ -func (u *User) ConfirmEMailAddress(confnum int32, remoteIP string) error { +func (u *User) ConfirmEMailAddress(ctx context.Context, confnum int32, remoteIP string) error { var ar *AuditRecord = nil defer func() { AmStoreAudit(ar) @@ -260,12 +262,12 @@ func (u *User) ConfirmEMailAddress(confnum int32, remoteIP string) error { ar = AmNewAudit(AuditVerifyEmailFail, u.Uid, remoteIP, "Invalid confirmation number") return errors.New("confirmation number is incorrect. Please try again") } - _, err := tx.Exec("UPDATE users SET verify_email = 1, base_lvl = ? WHERE uid = ?", + _, err := tx.ExecContext(ctx, "UPDATE users SET verify_email = 1, base_lvl = ? WHERE uid = ?", AmDefaultRole("Global.AfterVerify").Level(), u.Uid) if err == nil { u.VerifyEMail = true u.BaseLevel = AmDefaultRole("Global.AfterVerify").Level() - err = AmAutoJoinCommunities(tx, u) + err = AmAutoJoinCommunities(ctx, tx, u) if err == nil { err = tx.Commit() if err == nil { @@ -278,11 +280,11 @@ func (u *User) ConfirmEMailAddress(confnum int32, remoteIP string) error { } // NewEmailConfirmationNumber creates a new confirmation number for a user and saves it off. -func (u *User) NewEmailConfirmationNumber() error { +func (u *User) NewEmailConfirmationNumber(ctx context.Context) error { u.Mutex.Lock() defer u.Mutex.Unlock() newnum := util.GenerateRandomConfirmationNumber() - _, err := amdb.Exec("UPDATE users SET email_confnum = ? WHERE uid = ?", newnum, u.Uid) + _, err := amdb.ExecContext(ctx, "UPDATE users SET email_confnum = ? WHERE uid = ?", newnum, u.Uid) if err != nil { u.EmailConfNum = newnum } @@ -290,7 +292,7 @@ func (u *User) NewEmailConfirmationNumber() error { } // ChangePassword resets a user's password. -func (u *User) ChangePassword(password string, remoteIP string) error { +func (u *User) ChangePassword(ctx context.Context, password string, remoteIP string) error { var ar *AuditRecord = nil defer func() { AmStoreAudit(ar) @@ -299,7 +301,7 @@ func (u *User) ChangePassword(password string, remoteIP string) error { u.Mutex.Lock() defer u.Mutex.Unlock() pval := hashPassword(password) - _, err := amdb.Exec("UPDATE users SET passhash = ? WHERE uid = ?", pval, u.Uid) + _, err := amdb.ExecContext(ctx, "UPDATE users SET passhash = ? WHERE uid = ?", pval, u.Uid) if err == nil { u.Passhash = pval ar = AmNewAudit(AuditChangePassword, u.Uid, remoteIP, "via password change request") @@ -308,11 +310,11 @@ func (u *User) ChangePassword(password string, remoteIP string) error { } // GetFlags retrieves the flags from the properties. -func (u *User) Flags() (*util.OptionSet, error) { +func (u *User) Flags(ctx context.Context) (*util.OptionSet, error) { u.Mutex.Lock() defer u.Mutex.Unlock() if u.flags == nil { - s, err := AmGetUserProperty(u.Uid, UserPropFlags) + s, err := AmGetUserProperty(ctx, u.Uid, UserPropFlags) if err != nil { return nil, err } @@ -325,11 +327,11 @@ func (u *User) Flags() (*util.OptionSet, error) { } // SaveFlags writes the flags to the database and stores them. -func (u *User) SaveFlags(f *util.OptionSet) error { +func (u *User) SaveFlags(ctx context.Context, f *util.OptionSet) error { s := f.AsString() u.Mutex.Lock() defer u.Mutex.Unlock() - err := AmSetUserProperty(u.Uid, UserPropFlags, &s) + err := AmSetUserProperty(ctx, u.Uid, UserPropFlags, &s) if err == nil { u.flags = f } @@ -337,8 +339,8 @@ func (u *User) SaveFlags(f *util.OptionSet) error { } // FlagValue returns the boolean value of one of the user flags. -func (u *User) FlagValue(ndx uint) bool { - f, err := u.Flags() +func (u *User) FlagValue(ctx context.Context, ndx uint) bool { + f, err := u.Flags(ctx) if err != nil { log.Errorf("flag retrieval error for user %d: %v", u.Uid, err) return false @@ -347,12 +349,12 @@ func (u *User) FlagValue(ndx uint) bool { } // Prefs returns the user's preferences record. -func (u *User) Prefs() (*UserPrefs, error) { +func (u *User) Prefs(ctx context.Context) (*UserPrefs, error) { u.Mutex.Lock() defer u.Mutex.Unlock() if u.prefs == nil { var dbdata []UserPrefs - err := amdb.Select(&dbdata, "SELECT * FROM userprefs WHERE uid = ?", u.Uid) + err := amdb.SelectContext(ctx, &dbdata, "SELECT * FROM userprefs WHERE uid = ?", u.Uid) if err != nil { return nil, err } @@ -366,13 +368,14 @@ func (u *User) Prefs() (*UserPrefs, error) { /* SetProfileData sets the "profile" variables for this user. * Parameters: + * ctx - Standard Go context value. * reminder - Password reminder string. * dob - Date of birth field. * descr - Description string. * Returns: * Standard Go error status. */ -func (u *User) SetProfileData(reminder string, dob *time.Time, descr *string) error { +func (u *User) SetProfileData(ctx context.Context, reminder string, dob *time.Time, descr *string) error { u.Mutex.Lock() defer u.Mutex.Unlock() _, err := amdb.Exec("UPDATE users SET passreminder = ?, dob = ?, description = ? WHERE uid = ?", reminder, dob, descr, u.Uid) @@ -386,19 +389,20 @@ func (u *User) SetProfileData(reminder string, dob *time.Time, descr *string) er /* AmGetUser returns a reference to the specified user. * Parameters: + * ctx - Standard Go context value. * uid - The UID of the user. * Returns: * Pointer to User containing user data, or nil * Standard Go error status */ -func AmGetUser(uid int32) (*User, error) { +func AmGetUser(ctx context.Context, uid int32) (*User, error) { var err error = nil getUserMutex.Lock() defer getUserMutex.Unlock() rc, ok := userCache.Get(uid) if !ok { var dbdata []User - err = amdb.Select(&dbdata, "SELECT * from users WHERE uid = ?", uid) + err = amdb.SelectContext(ctx, &dbdata, "SELECT * from users WHERE uid = ?", uid) if err != nil { return nil, err } @@ -413,20 +417,21 @@ func AmGetUser(uid int32) (*User, error) { /* AmGetUserTx returns a reference to the specified user inside a transaction. * Parameters: + * ctxt - Standard Go context value. * tx - The transaction we're in. * uid - The UID of the user. * Returns: * Pointer to User containing user data, or nil * Standard Go error status */ -func AmGetUserTx(tx *sqlx.Tx, uid int32) (*User, error) { +func AmGetUserTx(ctx context.Context, tx *sqlx.Tx, uid int32) (*User, error) { var err error = nil getUserMutex.Lock() defer getUserMutex.Unlock() rc, ok := userCache.Get(uid) if !ok { var dbdata []User - err = tx.Select(&dbdata, "SELECT * from users WHERE uid = ?", uid) + err = tx.SelectContext(ctx, &dbdata, "SELECT * from users WHERE uid = ?", uid) if err != nil { return nil, err } @@ -441,19 +446,20 @@ func AmGetUserTx(tx *sqlx.Tx, uid int32) (*User, error) { /* AmGetUserByName returns a reference to the specified user. * Parameters: + * ctx - Standard Go context value. * name - The username of the user. * tx - If this is not nil, use this transaction. * Returns: * Pointer to User containing user data, or nil * Standard Go error status */ -func AmGetUserByName(name string, tx *sqlx.Tx) (*User, error) { +func AmGetUserByName(ctx context.Context, name string, tx *sqlx.Tx) (*User, error) { var dbdata []User var err error if tx != nil { - err = tx.Select(&dbdata, "SELECT * FROM users WHERE username = ?", name) + err = tx.SelectContext(ctx, &dbdata, "SELECT * FROM users WHERE username = ?", name) } else { - err = amdb.Select(&dbdata, "SELECT * FROM users WHERE username = ?", name) + err = amdb.SelectContext(ctx, &dbdata, "SELECT * FROM users WHERE username = ?", name) } if err != nil { return nil, err @@ -472,9 +478,9 @@ func AmGetUserByName(name string, tx *sqlx.Tx) (*User, error) { } // getAnonUserID retrieves the UID of the "anonymous" user from the database. -func getAnonUserID() (int32, error) { +func getAnonUserID(ctx context.Context) (int32, error) { if anonUid < 0 { - rows, err := amdb.Query("SELECT uid FROM users WHERE is_anon = 1") + rows, err := amdb.QueryContext(ctx, "SELECT uid FROM users WHERE is_anon = 1") if err == nil { defer rows.Close() if rows.Next() { @@ -495,26 +501,29 @@ func getAnonUserID() (int32, error) { /* AmIsUserAnon returns true if the specified user ID is the anonymous one. * Parameters: + * ctx = Standard Go context value. * uid = The user ID to test. * Returns: * true if the user is anonymous, false if not * Standard Go error status */ -func AmIsUserAnon(uid int32) (bool, error) { - auid, err := getAnonUserID() +func AmIsUserAnon(ctx context.Context, uid int32) (bool, error) { + auid, err := getAnonUserID(ctx) return (uid == auid), err } /* AmGetAnonUser returns a reference to the anonymous user. + * Parameters: + * ctx = Standard Go context value. * Returns: * Pointer to User containing anonymous user data, or nil * Standard Go error status */ -func AmGetAnonUser() (*User, error) { +func AmGetAnonUser(ctx context.Context) (*User, error) { var rc *User = nil - auid, err := getAnonUserID() + auid, err := getAnonUserID(ctx) if err == nil { - rc, err = AmGetUser(auid) + rc, err = AmGetUser(ctx, auid) } return rc, err } @@ -531,16 +540,17 @@ func hashPassword(password string) string { } // touchUser updates the last access time for the user. -func touchUser(tx *sqlx.Tx, user *User) { +func touchUser(ctx context.Context, tx *sqlx.Tx, user *User) { user.Mutex.Lock() defer user.Mutex.Unlock() moment := time.Now().UTC() - tx.Exec("UPDATE user SET lastaccess = ? WHERE uid = ?", moment, user.Uid) + tx.ExecContext(ctx, "UPDATE user SET lastaccess = ? WHERE uid = ?", moment, user.Uid) user.LastAccess = &moment } /* AmAuthenticateUser authenticates a user by name and password. * Parameters: + * ctx - Standard Go context parameter. * name - The user name to try. * password - The password to try. * remote_ip - The remote IP address, for audit records. @@ -548,8 +558,8 @@ func touchUser(tx *sqlx.Tx, user *User) { * The User pointer if authenticated, or nil if not. * Standard Go error status. */ -func AmAuthenticateUser(name string, password string, remoteIP string) (*User, error) { - log.Debugf("AmAuthenicate() authenticating user %s...", name) +func AmAuthenticateUser(ctx context.Context, name string, password string, remoteIP string) (*User, error) { + log.Debugf("AmAuthenticateUser() authenticating user %s...", name) var ar *AuditRecord = nil defer func() { AmStoreAudit(ar) @@ -562,7 +572,7 @@ func AmAuthenticateUser(name string, password string, remoteIP string) (*User, e } }() - user, err := AmGetUserByName(name, tx) + user, err := AmGetUserByName(ctx, name, tx) if err != nil { log.Error("...user not found") ar = AmNewAudit(AuditLoginFail, 0, remoteIP, fmt.Sprintf("Bad username: %s", name)) @@ -585,7 +595,7 @@ func AmAuthenticateUser(name string, password string, remoteIP string) (*User, e return nil, errors.New("the password you have specified is incorrect; please try again") } log.Debug("...authenticated") - touchUser(tx, user) + touchUser(ctx, tx, user) err = tx.Commit() if err != nil { return nil, err @@ -621,13 +631,14 @@ func crackAuthString(authString string) (int32, string, error) { /* AmAuthenticateUserByToken authenticates a user via the stored cookie authentication string. * Parameters: + * ctx - Standard Go context value. * authString - The stored cookie authentication string. * remoteIP - The remote IP address wheter trhe user is logging in from. * Returns: * Pointer to the authenticated User, or nil. * Standard Go error status. */ -func AmAuthenticateUserByToken(authString string, remoteIP string) (*User, error) { +func AmAuthenticateUserByToken(ctx context.Context, authString string, remoteIP string) (*User, error) { var ar *AuditRecord = nil defer func() { AmStoreAudit(ar) @@ -645,7 +656,7 @@ func AmAuthenticateUserByToken(authString string, remoteIP string) (*User, error return nil, fmt.Errorf("authString not valid, ignored: %v", err) } var user *User - user, err = AmGetUserTx(tx, uid) + user, err = AmGetUserTx(ctx, tx, uid) if err != nil { log.Error("...user not found") ar = AmNewAudit(AuditLoginFail, 0, remoteIP, fmt.Sprintf("Bad uid: %d", uid)) @@ -668,7 +679,7 @@ func AmAuthenticateUserByToken(authString string, remoteIP string) (*User, error return nil, errors.New("token mismatch") } log.Debug("...authenticated") - touchUser(tx, user) + touchUser(ctx, tx, user) err = tx.Commit() if err != nil { return nil, err @@ -680,6 +691,7 @@ func AmAuthenticateUserByToken(authString string, remoteIP string) (*User, error /* AmCreateNewUser creates a new user record in the database. * Parameters: + * ctx - Standard Go context value. * username - New user name. * password - New password. * reminder - Password reminder string. @@ -689,12 +701,12 @@ func AmAuthenticateUserByToken(authString string, remoteIP string) (*User, error * Pointer to new user record. * Standard Go error status. */ -func AmCreateNewUser(username string, password string, reminder string, dob *time.Time, remoteIP string) (*User, error) { +func AmCreateNewUser(ctx context.Context, username string, password string, reminder string, dob *time.Time, remoteIP string) (*User, error) { var ar *AuditRecord = nil defer func() { AmStoreAudit(ar) }() - anon, _ := getAnonUserID() + anon, _ := getAnonUserID(ctx) success := false tx := amdb.MustBegin() defer func() { @@ -703,15 +715,15 @@ func AmCreateNewUser(username string, password string, reminder string, dob *tim } }() unlock := true - tx.Exec("LOCK TABLES users WRITE, userprefs WRITE, propuser WRITE, commmember WRITE, sideboxes WRITE, confhotlist WRITE;") + tx.ExecContext(ctx, "LOCK TABLES users WRITE, userprefs WRITE, propuser WRITE, commmember WRITE, sideboxes WRITE, confhotlist WRITE;") defer func() { if unlock { - tx.Exec("UNLOCK TABLES;") + tx.ExecContext(ctx, "UNLOCK TABLES;") } }() // Test if the user name is already taken. - rs, err := tx.Query("SELECT uid FROM users WHERE username = ?", username) + rs, err := tx.QueryContext(ctx, "SELECT uid FROM users WHERE username = ?", username) if err != nil { return nil, err } else if rs.Next() { @@ -720,7 +732,7 @@ func AmCreateNewUser(username string, password string, reminder string, dob *tim } // Insert the user record. - _, err2 := tx.Exec(`INSERT INTO users (username, passhash, verify_email, lockout, email_confnum, + _, err2 := tx.ExecContext(ctx, `INSERT INTO users (username, passhash, verify_email, lockout, email_confnum, base_lvl, created, lastaccess, passreminder, description, dob) VALUES (?, ?, 0, 0, ?, ?, NOW(), NOW(), ?, '', ?)`, username, hashPassword(password), util.GenerateRandomConfirmationNumber(), AmDefaultRole("Global.NewUser").Level(), reminder, dob) @@ -728,42 +740,42 @@ func AmCreateNewUser(username string, password string, reminder string, dob *tim return nil, err2 } // Read back the user, which also puts it in the cache. - user, err3 := AmGetUserByName(username, tx) + user, err3 := AmGetUserByName(ctx, username, tx) if err3 != nil { return nil, err3 } log.Debugf("...created new user \"%s\" with UID %d", username, user.Uid) // add user preferences - _, err = tx.Exec("INSERT INTO userprefs (uid) VALUES (?)", user.Uid) + _, err = tx.ExecContext(ctx, "INSERT INTO userprefs (uid) VALUES (?)", user.Uid) if err != nil { return nil, err } // add user properties props := make([]UserProperties, 0) - err = tx.Select(&props, "SELECT * FROM propuser WHERE uid = ?", anon) + err = tx.SelectContext(ctx, &props, "SELECT * FROM propuser WHERE uid = ?", anon) if err != nil { return nil, err } for _, p := range props { - _, err := tx.Exec("INSERT INTO propuser (uid, ndx, data) VALUES (?, ?, ?)", user.Uid, p.Index, p.Data) + _, err := tx.ExecContext(ctx, "INSERT INTO propuser (uid, ndx, data) VALUES (?, ?, ?)", user.Uid, p.Index, p.Data) if err != nil { return nil, err } } // add user sideboxes - err = copySideboxes(tx, user.Uid, anon) + err = copySideboxes(ctx, tx, user.Uid, anon) if err != nil { return nil, err } - tx.Exec("UNLOCK TABLES;") + tx.ExecContext(ctx, "UNLOCK TABLES;") unlock = false // auto-join communities - err = AmAutoJoinCommunities(tx, user) + err = AmAutoJoinCommunities(ctx, tx, user) if err != nil { return nil, err } @@ -782,7 +794,7 @@ func AmCreateNewUser(username string, password string, reminder string, dob *tim } // internalGetProp is a helper used by the property functions. -func internalGetProp(uid int32, ndx int32) (*UserProperties, error) { +func internalGetProp(ctx context.Context, uid int32, ndx int32) (*UserProperties, error) { var err error = nil key := fmt.Sprintf("%d:%d", uid, ndx) getUserPropMutex.Lock() @@ -790,7 +802,7 @@ func internalGetProp(uid int32, ndx int32) (*UserProperties, error) { rc, ok := userPropCache.Get(key) if !ok { var dbdata []UserProperties - err = amdb.Select(&dbdata, "SELECT * from propuser WHERE uid = ? AND ndx = ?", uid, ndx) + err = amdb.SelectContext(ctx, &dbdata, "SELECT * from propuser WHERE uid = ? AND ndx = ?", uid, ndx) if err != nil { return nil, err } @@ -808,14 +820,15 @@ func internalGetProp(uid int32, ndx int32) (*UserProperties, error) { /* AmGetUserProperty retrieves the value of a user property. * Parameters: + * ctx - Standard Go context value. * uid - The UID of the user to get the property for. * ndx - The index of the property to retrieve. * Returns: * Value of the property string. * Standard Go error status. */ -func AmGetUserProperty(uid int32, ndx int32) (*string, error) { - p, err := internalGetProp(uid, ndx) +func AmGetUserProperty(ctx context.Context, uid int32, ndx int32) (*string, error) { + p, err := internalGetProp(ctx, uid, ndx) if err != nil { return nil, err } @@ -824,27 +837,28 @@ func AmGetUserProperty(uid int32, ndx int32) (*string, error) { /* AmSetUserProperty sets the value of a user property. * Parameters: + * ctx - Standard Go context value. * uid - The UID of the user to set the property for. * ndx - The index of the property to set. * val - The new value of the property. * Returns: * Standard Go error status. */ -func AmSetUserProperty(uid int32, ndx int32, val *string) error { - p, err := internalGetProp(uid, ndx) +func AmSetUserProperty(ctx context.Context, uid int32, ndx int32, val *string) error { + p, err := internalGetProp(ctx, uid, ndx) if err != nil { return err } getUserPropMutex.Lock() defer getUserPropMutex.Unlock() if p != nil { - _, err = amdb.Exec("UPDATE propuser SET data = ? WHERE uid = ? AND ndx = ?", val, uid, ndx) + _, err = amdb.ExecContext(ctx, "UPDATE propuser SET data = ? WHERE uid = ? AND ndx = ?", val, uid, ndx) if err == nil { p.Data = val } } else { prop := UserProperties{Uid: uid, Index: ndx, Data: val} - _, err := amdb.NamedExec("INSERT INTO propuser (uid, ndx, data) VALUES(:uid, :ndx, :data)", prop) + _, err := amdb.NamedExecContext(ctx, "INSERT INTO propuser (uid, ndx, data) VALUES(:uid, :ndx, :data)", prop) if err == nil { userPropCache.Add(fmt.Sprintf("%d:%d", uid, ndx), prop) } @@ -854,6 +868,7 @@ func AmSetUserProperty(uid int32, ndx int32, val *string) error { /* AmSearchUsers searches for users matching certain criteria. * Parameters: + * ctx - Standard Go context value. * field - A value indicating which field to search: * SearchUserFieldName - The user name. * SearchUserFieldDescription - The user description. @@ -871,7 +886,7 @@ func AmSetUserProperty(uid int32, ndx int32, val *string) error { * The total number of users matching this query (could be greater than max) * Standard Go error status. */ -func AmSearchUsers(field int, oper int, term string, offset int, max int) ([]*User, int, error) { +func AmSearchUsers(ctx context.Context, field int, oper int, term string, offset int, max int) ([]*User, int, error) { var queryPortion strings.Builder switch field { case SearchUserFieldName: @@ -902,7 +917,7 @@ func AmSearchUsers(field int, oper int, term string, offset int, max int) ([]*Us return nil, -1, errors.New("invalid operator selector") } q := queryPortion.String() - rs, err := amdb.Query("SELECT COUNT(*) FROM users u, contacts c WHERE u.contactid = c.contactid AND u.is_anon = 0 AND " + q) + rs, err := amdb.QueryContext(ctx, "SELECT COUNT(*) FROM users u, contacts c WHERE u.contactid = c.contactid AND u.is_anon = 0 AND "+q) if err != nil { return nil, -1, err } @@ -915,10 +930,10 @@ func AmSearchUsers(field int, oper int, term string, offset int, max int) ([]*Us return make([]*User, 0), 0, nil } if offset > 0 { - rs, err = amdb.Query("SELECT u.uid FROM users u, contacts c WHERE u.contactid = c.contactid AND u.is_anon = 0 AND "+q+ + rs, err = amdb.QueryContext(ctx, "SELECT u.uid FROM users u, contacts c WHERE u.contactid = c.contactid AND u.is_anon = 0 AND "+q+ " ORDER BY u.username LIMIT ? OFFSET ?", max, offset) } else { - rs, err = amdb.Query("SELECT u.uid FROM users u, contacts c WHERE u.contactid = c.contactid AND u.is_anon = 0 AND "+q+ + rs, err = amdb.QueryContext(ctx, "SELECT u.uid FROM users u, contacts c WHERE u.contactid = c.contactid AND u.is_anon = 0 AND "+q+ " ORDER BY u.username LIMIT ?", max) } if err != nil { @@ -928,7 +943,7 @@ func AmSearchUsers(field int, oper int, term string, offset int, max int) ([]*Us for rs.Next() { var uid int32 rs.Scan(&uid) - u, err := AmGetUser(uid) + u, err := AmGetUser(ctx, uid) if err == nil { rc = append(rc, u) } diff --git a/email/sender.go b/email/sender.go index ec3bb56..6367f62 100644 --- a/email/sender.go +++ b/email/sender.go @@ -12,6 +12,7 @@ package email import ( "bytes" + "context" "embed" "fmt" "io" @@ -47,7 +48,7 @@ var mailHost string var auth smtp.Auth // formatMessage takes a message and turns it into serialized bytes for sending. -func formatMessage(m *amMessage) ([]byte, error) { +func formatMessage(ctx context.Context, m *amMessage) ([]byte, error) { if m.template != "" { // Render the template for the message, which may reset Subject. templ, err := emailRenderer.GetTemplate(m.template) @@ -62,7 +63,7 @@ func formatMessage(m *amMessage) ([]byte, error) { return make([]byte, 0), err } } - user, err := database.AmGetUser(m.uid) + user, err := database.AmGetUser(ctx, m.uid) if err == nil { // Build the final headers. hdrs := make(map[string]string) @@ -177,7 +178,7 @@ func transmitMessage(m *amMessage, body []byte) { // senderLoop collects E-mail messages from the channel and pushes them out. func senderLoop(sent chan *amMessage, done chan bool) { for m := range sent { - body, err := formatMessage(m) + body, err := formatMessage(context.Background(), m) if err == nil { transmitMessage(m, body) } else { diff --git a/find.go b/find.go index d90ca0a..e153520 100644 --- a/find.go +++ b/find.go @@ -38,7 +38,7 @@ func loadCategoryInformation(ctxt ui.AmContext, offset int) error { } } if catid > -1 { - cat, err := database.AmGetCategory(catid) // this step also resolves symlinks + cat, err := database.AmGetCategory(ctxt.Ctx(), catid) // this step also resolves symlinks if err != nil { return err } @@ -48,12 +48,12 @@ func loadCategoryInformation(ctxt ui.AmContext, offset int) error { ctxt.VarMap().Set("catid", catid) showHidden := database.AmTestPermission("Global.ShowHiddenCategories", u.BaseLevel) ctxt.VarMap().Set("showHiddenCat", showHidden) - hier, err := database.AmGetCategoryHierarchy(catid) + hier, err := database.AmGetCategoryHierarchy(ctxt.Ctx(), catid) if err != nil { return err } ctxt.VarMap().Set("catHierarchy", hier) - subs, err := database.AmGetSubCategories(catid) + subs, err := database.AmGetSubCategories(ctxt.Ctx(), catid) if err != nil { return err } @@ -63,7 +63,7 @@ func loadCategoryInformation(ctxt ui.AmContext, offset int) error { if catid > -1 { // search for communities in this category listMax := int(ctxt.Globals().MaxSearchPage) - commList, numComm, err := database.AmGetCommunitiesForCategory(catid, offset*listMax, listMax, showHidden) + commList, numComm, err := database.AmGetCommunitiesForCategory(ctxt.Ctx(), catid, offset*listMax, listMax, showHidden) if err != nil { return err } @@ -207,7 +207,7 @@ func Find(ctxt ui.AmContext) (string, any, error) { return "framed_template", "find.jet", nil } var clist []*database.Community - clist, total, err = database.AmSearchCommunities(iField, iOper, term, ofs*listMax, listMax, + clist, total, err = database.AmSearchCommunities(ctxt.Ctx(), iField, iOper, term, ofs*listMax, listMax, ctxt.TestPermission("Global.SearchHiddenCommunities")) if err == nil { if clist == nil { @@ -244,7 +244,7 @@ func Find(ctxt ui.AmContext) (string, any, error) { return "framed_template", "find.jet", nil } var ulist []*database.User - ulist, total, err = database.AmSearchUsers(iField, iOper, term, ofs*listMax, listMax) + ulist, total, err = database.AmSearchUsers(ctxt.Ctx(), iField, iOper, term, ofs*listMax, listMax) if err == nil { if ulist == nil { numResults = 0 @@ -268,7 +268,7 @@ func Find(ctxt ui.AmContext) (string, any, error) { return "framed_template", "find.jet", nil } var catlist []*database.Category - catlist, total, err = database.AmSearchCategories(iOper, term, ofs*listMax, listMax, + catlist, total, err = database.AmSearchCategories(ctxt.Ctx(), iOper, term, ofs*listMax, listMax, ctxt.TestPermission("Global.ShowHiddenCategories"), ctxt.TestPermission("Global.SearchHiddenCategories")) if err == nil { if catlist == nil { diff --git a/htmlcheck/checker.go b/htmlcheck/checker.go index e9cacf8..99cc807 100644 --- a/htmlcheck/checker.go +++ b/htmlcheck/checker.go @@ -10,6 +10,7 @@ package htmlcheck import ( + "context" "errors" "fmt" "net/url" @@ -81,6 +82,7 @@ const hyphApos = "-'" // htmlCheckerImpl is the implementation of the HTML checker. type htmlCheckerImpl struct { + ctx context.Context // request context, as instances are generally per-request config *HTMLCheckerConfig // pointer to configuration started bool // has checker been started? finished bool // has checker been finished? @@ -147,13 +149,14 @@ func (ht *htmlCheckerImpl) copyOutputFilters(dest []outputFilter, source []strin */ /* AmNewHTMLChecker creates a new HTML Checker object. - * Parametrers: + * Parameters: + * ctx - Standard Go context value. * configName - Name of the configuration to use. * Returns: * New HTML checker reference. * Standard Go error status. */ -func AmNewHTMLChecker(configName string) (HTMLChecker, error) { +func AmNewHTMLChecker(ctx context.Context, configName string) (HTMLChecker, error) { config, ok := configsRegistry[configName] if !ok { return nil, fmt.Errorf("configuration %s not found", configName) @@ -166,6 +169,7 @@ func AmNewHTMLChecker(configName string) (HTMLChecker, error) { } } rc := htmlCheckerImpl{ + ctx: ctx, config: config, started: false, finished: false, @@ -486,7 +490,7 @@ func (ht *htmlCheckerImpl) emitFromStartOfTempBuffer(nrunes int) { // attemptRewrite attempts to apply a list of rewriters on the text, returning the first one that matches. func (ht *htmlCheckerImpl) attemptRewrite(rewriters []rewriter, data string) *markupData { for _, r := range rewriters { - rc := r.Rewrite(data, ht) + rc := r.Rewrite(ht.ctx, data, ht) if rc != nil { return rc } diff --git a/htmlcheck/dictionary.go b/htmlcheck/dictionary.go index 07d95e6..ee5c56f 100644 --- a/htmlcheck/dictionary.go +++ b/htmlcheck/dictionary.go @@ -10,6 +10,7 @@ package htmlcheck import ( + "context" _ "embed" "os" @@ -76,12 +77,13 @@ func (rw *spellingRewriter) Name() string { /* Rewrite rewrites the given string data and adds markup before and after if needed. * Parameters: + * ctx - Standard Go error status. * data - The data to be rewritten. * svc - Services interface we can use. * Returns: * Pointer to markup data, or nil. */ -func (rw *spellingRewriter) Rewrite(data string, svc rewriterServices) *markupData { +func (rw *spellingRewriter) Rewrite(ctx context.Context, data string, svc rewriterServices) *markupData { if rw.dict.CheckWord(data) { return nil } diff --git a/htmlcheck/emoticon_rewriter.go b/htmlcheck/emoticon_rewriter.go index 5752e49..abefb68 100644 --- a/htmlcheck/emoticon_rewriter.go +++ b/htmlcheck/emoticon_rewriter.go @@ -10,6 +10,7 @@ package htmlcheck import ( + "context" _ "embed" "math" "regexp" @@ -90,12 +91,13 @@ func (rw *emoticonRewriter) Name() string { /* Rewrite rewrites the given string data and adds markup before and after if needed. * Parameters: + * ctx - Standard Go context value. * data - The data to be rewritten. * svc - Services interface we can use. * Returns: * Pointer to markup data, or nil. */ -func (rw *emoticonRewriter) Rewrite(data string, svc rewriterServices) *markupData { +func (rw *emoticonRewriter) Rewrite(ctx context.Context, data string, svc rewriterServices) *markupData { pos := math.MaxInt for _, c := range rw.prefixChars { foo := strings.IndexByte(data, c) @@ -163,12 +165,13 @@ func (rw *emoticonTagRewriter) Name() string { /* Rewrite rewrites the given string data and adds markup before and after if needed. * Parameters: + * ctx - Standard Go context value. * data - The data to be rewritten. * svc - Services interface we can use. * Returns: * Pointer to markup data, or nil. */ -func (rw *emoticonTagRewriter) Rewrite(data string, svc rewriterServices) *markupData { +func (rw *emoticonTagRewriter) Rewrite(ctx context.Context, data string, svc rewriterServices) *markupData { m := rw.re.FindStringSubmatch(data) if m == nil { return nil diff --git a/htmlcheck/rewriter.go b/htmlcheck/rewriter.go index 69edeb0..77dfdf6 100644 --- a/htmlcheck/rewriter.go +++ b/htmlcheck/rewriter.go @@ -10,6 +10,7 @@ package htmlcheck import ( + "context" "fmt" "net/mail" "net/url" @@ -41,7 +42,7 @@ type rewriterServices interface { // rewriter is the interface for components that rewrite source text and place markup around it. type rewriter interface { Name() string - Rewrite(string, rewriterServices) *markupData + Rewrite(context.Context, string, rewriterServices) *markupData } // rewriterRegistry contains a list of all rewriters. @@ -72,7 +73,7 @@ func (rw *emailRewriter) Name() string { * Returns: * Pointer to markup data, or nil. */ -func (rw *emailRewriter) Rewrite(data string, svc rewriterServices) *markupData { +func (rw *emailRewriter) Rewrite(ctx context.Context, data string, svc rewriterServices) *markupData { _, err := mail.ParseAddress(data) if err != nil { return nil @@ -154,7 +155,7 @@ func buildPostLink(decoded, context *database.PostLinkData) string { * Returns: * Pointer to markup data, or nil. */ -func (rw *postLinkRewriter) Rewrite(data string, svc rewriterServices) *markupData { +func (rw *postLinkRewriter) Rewrite(ctx context.Context, data string, svc rewriterServices) *markupData { q := svc.rewriterContextValue("PostLinkDecoderContext") if q == nil { return nil @@ -165,7 +166,7 @@ func (rw *postLinkRewriter) Rewrite(data string, svc rewriterServices) *markupDa if err != nil { return nil } - err = mydata.VerifyNames() + err = mydata.VerifyNames(ctx) if err != nil { return nil } @@ -205,17 +206,18 @@ func (rw *userLinkRewriter) Name() string { /* Rewrite rewrites the given string data and adds markup before and after if needed. * Parameters: + * ctx - Standard Go context value. * data - The data to be rewritten. * svc - Services interface we can use. * Returns: * Pointer to markup data, or nil. */ -func (rw *userLinkRewriter) Rewrite(data string, svc rewriterServices) *markupData { +func (rw *userLinkRewriter) Rewrite(ctx context.Context, data string, svc rewriterServices) *markupData { if data == "" || len(data) > 64 || !database.AmIsValidAmsterdamID(data) { return nil } - user, err := database.AmGetUserByName(data, nil) + user, err := database.AmGetUserByName(ctx, data, nil) if err != nil || user == nil { return nil } @@ -258,8 +260,8 @@ func (rw *countingRewriter) Name() string { * Returns: * Pointer to markup data, or nil. */ -func (rw *countingRewriter) Rewrite(data string, svc rewriterServices) *markupData { - rc := rw.inner.Rewrite(data, svc) +func (rw *countingRewriter) Rewrite(ctx context.Context, data string, svc rewriterServices) *markupData { + rc := rw.inner.Rewrite(ctx, data, svc) if rc != nil && !rc.rescan { rw.count++ } diff --git a/htmlcheck/url_rewriter.go b/htmlcheck/url_rewriter.go index 37cfdbf..f190d25 100644 --- a/htmlcheck/url_rewriter.go +++ b/htmlcheck/url_rewriter.go @@ -10,6 +10,7 @@ package htmlcheck import ( + "context" "net/url" "regexp" "strings" @@ -72,12 +73,13 @@ func (rw *urlRewriter) Name() string { /* Rewrite rewrites the given string data and adds markup before and after if needed. * Parameters: + * ctx - Standard Go context value. * data - The data to be rewritten. * svc - Services interface we can use. * Returns: * Pointer to markup data, or nil. */ -func (rw *urlRewriter) Rewrite(data string, svc rewriterServices) *markupData { +func (rw *urlRewriter) Rewrite(ctx context.Context, data string, svc rewriterServices) *markupData { for _, ue := range urlElements { s := ue.eval(data) if s != "" { diff --git a/login.go b/login.go index 135d9c5..cf06981 100644 --- a/login.go +++ b/login.go @@ -80,10 +80,10 @@ func Login(ctxt ui.AmContext) (string, any, error) { return dlg.RenderError(ctxt, "User name not specified.") } if action == "remind" { // Password Reminder button pressed - user, uerr := database.AmGetUserByName(username, nil) + user, uerr := database.AmGetUserByName(ctxt.Ctx(), username, nil) if uerr == nil { var ci *database.ContactInfo - ci, uerr = user.ContactInfo() + ci, uerr = user.ContactInfo(ctxt.Ctx()) if uerr == nil { if ci != nil && ci.Email != nil && *ci.Email != "" { pchange := database.AmNewPasswordChangeRequest(user.Uid, user.Username, *ci.Email) @@ -109,14 +109,14 @@ func Login(ctxt ui.AmContext) (string, any, error) { } if action == "login" { // Login button pressed // authenticate the user - user, uerr := database.AmAuthenticateUser(username, dlg.Field("pass").Value, ctxt.RemoteIP()) + user, uerr := database.AmAuthenticateUser(ctxt.Ctx(), username, dlg.Field("pass").Value, ctxt.RemoteIP()) if uerr != nil { return dlg.RenderError(ctxt, uerr.Error()) } ctxt.ReplaceUser(user) if dlg.Field("saveme").IsChecked() { // create and save an authentication token - authString, cerr := user.NewAuthToken() + authString, cerr := user.NewAuthToken(ctxt.Ctx()) if cerr == nil { ctxt.SetLoginCookie(authString) @@ -240,9 +240,9 @@ func VerifyEMail(ctxt ui.AmContext) (string, any, error) { } if action == "sendagain" { var ci *database.ContactInfo - ci, err = user.ContactInfo() + ci, err = user.ContactInfo(ctxt.Ctx()) if err == nil { - err = user.NewEmailConfirmationNumber() + err = user.NewEmailConfirmationNumber(ctxt.Ctx()) if err == nil { err = sendEmailConfirmationEmail(user, ci, ctxt.RemoteIP()) } @@ -257,7 +257,7 @@ func VerifyEMail(ctxt ui.AmContext) (string, any, error) { err = dlg.Validate() if err == nil { cn, _ := dlg.Field("num").ValueInt() - err = user.ConfirmEMailAddress(int32(cn), ctxt.RemoteIP()) + err = user.ConfirmEMailAddress(ctxt.Ctx(), int32(cn), ctxt.RemoteIP()) if err == nil { return "redirect", target, nil } @@ -358,14 +358,14 @@ func NewAccount(ctxt ui.AmContext) (string, any, error) { return dlg.RenderError(ctxt, "The typed passwords do not match.") } var banned bool - banned, err = database.AmIsEmailAddressBanned(dlg.Field("email").Value) + banned, err = database.AmIsEmailAddressBanned(ctxt.Ctx(), dlg.Field("email").Value) if err == nil { if banned { return dlg.RenderError(ctxt, "This E-mail address may not register a new account.") } // Create new user account var user *database.User - user, err = database.AmCreateNewUser(dlg.Field("user").Value, dlg.Field("pass1").Value, + user, err = database.AmCreateNewUser(ctxt.Ctx(), dlg.Field("user").Value, dlg.Field("pass1").Value, dlg.Field("remind").Value, dlg.Field("dob").AsDate(), ctxt.RemoteIP()) if err == nil { // create and save contact info @@ -384,9 +384,9 @@ func NewAccount(ctxt ui.AmContext) (string, any, error) { ci.PostalCode = dlg.Field("pcode").ValPtr() ci.Country = dlg.Field("country").ValPtr() ci.Email = dlg.Field("email").ValPtr() - _, err = ci.Save() + _, err = ci.Save(ctxt.Ctx()) if err == nil { - err = user.SetContactID(ci.ContactId) + err = user.SetContactID(ctxt.Ctx(), ci.ContactId) } if err == nil { err = sendEmailConfirmationEmail(user, ci, ctxt.RemoteIP()) @@ -435,10 +435,10 @@ func PasswordRecovery(ctxt ui.AmContext) (string, any, error) { } if err == nil { - user, err := database.AmGetUser(int32(uid)) + user, err := database.AmGetUser(ctxt.Ctx(), int32(uid)) if err == nil { newpass := util.GenerateRandomPassword() - err = user.ChangePassword(newpass, ctxt.RemoteIP()) + err = user.ChangePassword(ctxt.Ctx(), newpass, ctxt.RemoteIP()) if err == nil { // send the password change message msg := email.AmNewEmailMessage(user.Uid, ctxt.RemoteIP()) diff --git a/main.go b/main.go index 8ff8fc7..464f366 100644 --- a/main.go +++ b/main.go @@ -124,9 +124,6 @@ func main() { closer = ui.SetupAmContext() defer closer() - // Set up Echo. - e := setupEcho() - // Set up to trap SIGINT and shut down gracefully ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() @@ -138,6 +135,9 @@ func main() { ampool.Shutdown() }() + // Set up Echo. + e := setupEcho() + // Start server go func() { if err := e.Start(":1323"); err != nil && err != http.ErrServerClosed { diff --git a/top.go b/top.go index 1add63f..8cc66e6 100644 --- a/top.go +++ b/top.go @@ -48,11 +48,11 @@ type RenderedSidebox struct { * Returns: * Standard Go error status. */ -func buildCommunitiesSidebox(uid int32, out *RenderedSidebox, in *database.Sidebox) error { - user, err := database.AmGetUser(uid) +func buildCommunitiesSidebox(ctxt ui.AmContext, uid int32, out *RenderedSidebox, in *database.Sidebox) error { + user, err := database.AmGetUser(ctxt.Ctx(), uid) if err == nil { var g *database.Globals - g, err = database.AmGlobals() + g, err = database.AmGlobals(ctxt.Ctx()) if err == nil { if user.IsAnon { out.Title = "Featured Communities" @@ -60,7 +60,7 @@ func buildCommunitiesSidebox(uid int32, out *RenderedSidebox, in *database.Sideb out.Title = "Your Communities" } var l []*database.Community - l, err = database.AmGetCommunitiesForUser(uid) + l, err = database.AmGetCommunitiesForUser(ctxt.Ctx(), uid) if err == nil { out.Items = make([]RenderedSideboxItem, len(l)) for i, c := range l { @@ -69,7 +69,7 @@ func buildCommunitiesSidebox(uid int32, out *RenderedSidebox, in *database.Sideb out.Items[i].Link = &lk out.Items[i].Flags = make(map[string]bool) var level uint16 - level, err = database.AmGetCommunityAccessLevel(uid, c.Id) + level, err = database.AmGetCommunityAccessLevel(ctxt.Ctx(), uid, c.Id) if err == nil && database.AmTestPermission("Community.ShowAdmin", level) { out.Items[i].Flags["admin"] = true } @@ -150,10 +150,10 @@ func buildUsersOnline(uid int32, out *RenderedSidebox, in *database.Sidebox) err * Returns: * Standard Go error status. */ -func buildRenderedSidebox(uid int32, out *RenderedSidebox, in *database.Sidebox) error { +func buildRenderedSidebox(ctxt ui.AmContext, uid int32, out *RenderedSidebox, in *database.Sidebox) error { switch in.Boxid { case 1: - return buildCommunitiesSidebox(uid, out, in) + return buildCommunitiesSidebox(ctxt, uid, out, in) case 2: return buildFeaturedConferences(uid, out, in) case 3: @@ -177,14 +177,14 @@ func TopPage(ctxt ui.AmContext) (string, any, error) { // Retrieve the sideboxes and create the data to be presented. uid := ctxt.CurrentUserId() - sboxes, err := database.AmGetSideboxes(uid) + sboxes, err := database.AmGetSideboxes(ctxt.Ctx(), uid) if err != nil { return "string", "Unable to retrieve sideboxes", err } rc := make([]RenderedSidebox, len(sboxes)) for i, sb := range sboxes { - err = buildRenderedSidebox(uid, &(rc[i]), sb) + err = buildRenderedSidebox(ctxt, uid, &(rc[i]), sb) if err != nil { return "string", "Unable to render sideboxes", err } diff --git a/ui/amcontext.go b/ui/amcontext.go index 34f044a..386a36b 100644 --- a/ui/amcontext.go +++ b/ui/amcontext.go @@ -120,7 +120,7 @@ func (c *amContext) ClearLoginCookie() { // ClearSession clears the current session. func (c *amContext) ClearSession() { - AmResetSession(c.session) + AmResetSession(c.echoContext.Request().Context(), c.session) c.user = nil c.effectiveLevel = 0 } @@ -144,7 +144,7 @@ func (c *amContext) CurrentCommunity() *database.Community { // CurrentUser returns the current user from the session. func (c *amContext) CurrentUser() *database.User { if c.user == nil { - u, err := database.AmGetUser(AmSessionUid(c.session)) + u, err := database.AmGetUser(c.echoContext.Request().Context(), AmSessionUid(c.session)) if err != nil { log.Errorf("unable to retrieve current user") } @@ -327,12 +327,12 @@ func (c *amContext) SubRender(name string) ([]byte, error) { * Standard Go error status. */ func (c *amContext) SetCommunityContext(param string) error { - comm, err := database.AmGetCommunityFromParam(param) + comm, err := database.AmGetCommunityFromParam(c.echoContext.Request().Context(), param) if err != nil { return err } if c.community == nil || c.community.Id != comm.Id { - mbr, lock, level, err := comm.Membership(c.CurrentUser()) + mbr, lock, level, err := comm.Membership(c.echoContext.Request().Context(), c.CurrentUser()) if err != nil { return err } @@ -459,11 +459,11 @@ func newContext(ctxt echo.Context) (*amContext, error) { } var err error - if rc.globals, err = database.AmGlobals(); err != nil { + if rc.globals, err = database.AmGlobals(ctxt.Request().Context()); err != nil { amContextRecycleBin <- rc return nil, err } - if rc.globalFlags, err = rc.globals.Flags(); err != nil { + if rc.globalFlags, err = rc.globals.Flags(ctxt.Request().Context()); err != nil { amContextRecycleBin <- rc return nil, err } @@ -475,12 +475,12 @@ func newContext(ctxt echo.Context) (*amContext, error) { rc.session = sess sess.Options = defoptions if sess.IsNew { - AmSessionFirstTime(sess) + AmSessionFirstTime(ctxt.Request().Context(), sess) } else { AmHitSession(sess) } } - rc.user, err = database.AmGetUser(AmSessionUid(sess)) + rc.user, err = database.AmGetUser(ctxt.Request().Context(), AmSessionUid(sess)) if err == nil { rc.effectiveLevel = rc.user.BaseLevel } else { diff --git a/ui/images.go b/ui/images.go index 7aa20d8..6907199 100644 --- a/ui/images.go +++ b/ui/images.go @@ -78,7 +78,7 @@ func AmServeImage(ctxt AmContext) (string, any, error) { id, err = strconv.Atoi(components[3]) if err == nil { var img *database.ImageStore - img, err = database.AmLoadImage(int32(id)) + img, err = database.AmLoadImage(ctxt.Ctx(), int32(id)) if err == nil { ctxt.SetOutputType(img.MimeType) return "bytes", img.Data, nil diff --git a/ui/menus.go b/ui/menus.go index 1c88196..47e7e7e 100644 --- a/ui/menus.go +++ b/ui/menus.go @@ -10,6 +10,7 @@ package ui import ( + "context" _ "embed" "fmt" "slices" @@ -141,19 +142,20 @@ func AmMenu(name string) *MenuDefinition { /* AmBuildCommunityMenu buids a community menu for the specified community. * Parameters: + * ctx - Standard Go context value. * comm - The community to build the menu for. * Returns: * The new menu definition. * Standard Go error status. */ -func AmBuildCommunityMenu(comm *database.Community) (*MenuDefinition, error) { +func AmBuildCommunityMenu(ctx context.Context, comm *database.Community) (*MenuDefinition, error) { menuCacheMutex.Lock() defer menuCacheMutex.Unlock() m, ok := menuCache.Get(comm.Id) if ok { return m.(*MenuDefinition), nil } - sdef, err := database.AmGetCommunityServices(comm.Id) + sdef, err := database.AmGetCommunityServices(ctx, comm.Id) if err != nil { return nil, err } diff --git a/ui/middleware.go b/ui/middleware.go index 603434e..91f0176 100644 --- a/ui/middleware.go +++ b/ui/middleware.go @@ -31,7 +31,7 @@ func middlewareErrorPage(c echo.Context, ctxt AmContext, err error) error { func IPBanTest(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { // Check IP banning. - banmsg, banerr := database.AmTestIPBan(c.RealIP()) + banmsg, banerr := database.AmTestIPBan(c.Request().Context(), c.RealIP()) if banerr != nil { c.Logger().Warnf("address %s could not be tested: %v", c.RealIP(), banerr) // but let the request pass anyway @@ -55,12 +55,12 @@ func CookieLoginTest(next echo.HandlerFunc) echo.HandlerFunc { cookie, err := c.Cookie(config.GlobalConfig.Site.LoginCookieName) if err == nil { var user *database.User - user, err = database.AmAuthenticateUserByToken(cookie.Value, c.RealIP()) + user, err = database.AmAuthenticateUserByToken(c.Request().Context(), cookie.Value, c.RealIP()) if err == nil { // log the user in and rotate login cookie amctxt.ReplaceUser(user) var newToken string - if newToken, err = user.NewAuthToken(); err == nil { + if newToken, err = user.NewAuthToken(c.Request().Context()); err == nil { amctxt.SetLoginCookie(newToken) } else { log.Warnf("unable to rotate login cookie: %v", err) @@ -99,7 +99,7 @@ func ValidateConference(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { ctxt := AmContextFromEchoContext(c) comm := ctxt.CurrentCommunity() // set by middleware - b, err := database.AmTestService(comm, "Conference") + b, err := database.AmTestService(c.Request().Context(), comm, "Conference") if err != nil { return middlewareErrorPage(c, ctxt, err) } @@ -122,11 +122,11 @@ func ValidateConference(next echo.HandlerFunc) echo.HandlerFunc { func SetConference(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { ctxt := AmContextFromEchoContext(c) - conf, err := database.AmGetConferenceByAliasInCommunity(ctxt.CurrentCommunity().Id, ctxt.URLParam("confid")) + conf, err := database.AmGetConferenceByAliasInCommunity(ctxt.Ctx(), ctxt.CurrentCommunity().Id, ctxt.URLParam("confid")) if err != nil { return middlewareErrorPage(c, ctxt, err) } - m, lvl, err := conf.Membership(ctxt.CurrentUser()) + m, lvl, err := conf.Membership(ctxt.Ctx(), ctxt.CurrentUser()) if err != nil { return middlewareErrorPage(c, ctxt, err) } diff --git a/ui/render_wrap.go b/ui/render_wrap.go index 3e6d3da..80bb673 100644 --- a/ui/render_wrap.go +++ b/ui/render_wrap.go @@ -51,7 +51,7 @@ func AmSendPageData(ctxt echo.Context, amctxt AmContext, command string, data an case "top": menus[0] = AmMenu("top") case "community": - md, err := AmBuildCommunityMenu(amctxt.CurrentCommunity()) + md, err := AmBuildCommunityMenu(ctxt.Request().Context(), amctxt.CurrentCommunity()) if err != nil { return err } diff --git a/ui/session_mgr.go b/ui/session_mgr.go index 29adf8c..594c7b1 100644 --- a/ui/session_mgr.go +++ b/ui/session_mgr.go @@ -11,6 +11,7 @@ package ui import ( + "context" "crypto/rand" "encoding/gob" "encoding/hex" @@ -220,8 +221,8 @@ func AmSetSessionUser(session *sessions.Session, user *database.User) { } // setSessionAnon sets the user for the session to the anonymous user. -func setSessionAnon(session *sessions.Session) { - u, err := database.AmGetAnonUser() +func setSessionAnon(ctx context.Context, session *sessions.Session) { + u, err := database.AmGetAnonUser(ctx) if err == nil { AmSetSessionUser(session, u) } else { @@ -232,20 +233,20 @@ func setSessionAnon(session *sessions.Session) { var lastHitMutex sync.Mutex // AmSessionFirstTime initializes the session after it's first created. -func AmSessionFirstTime(session *sessions.Session) { +func AmSessionFirstTime(ctx context.Context, session *sessions.Session) { lastHitMutex.Lock() - setSessionAnon(session) + setSessionAnon(ctx, session) session.Values["lasthit"] = time.Now() lastHitMutex.Unlock() } // AmResetSession clears the specified session. -func AmResetSession(session *sessions.Session) { +func AmResetSession(ctx context.Context, session *sessions.Session) { lastHitMutex.Lock() for k := range session.Values { delete(session.Values, k) } - setSessionAnon(session) + setSessionAnon(ctx, session) session.Values["lasthit"] = time.Now() lastHitMutex.Unlock() } diff --git a/ui/templates.go b/ui/templates.go index 3d5340d..01b1899 100644 --- a/ui/templates.go +++ b/ui/templates.go @@ -112,7 +112,8 @@ func immediateIf(a jet.Arguments) reflect.Value { func extractCommunityLogo(a jet.Arguments) reflect.Value { rc := "/img/builtin/default-community.jpg" comm := a.Get(0).Convert(reflect.TypeFor[*database.Community]()).Interface().(*database.Community) - ci, err := comm.ContactInfo() + ctxt := a.Get(1).Convert(reflect.TypeFor[AmContext]()).Interface().(AmContext) + ci, err := comm.ContactInfo(ctxt.Ctx()) if err == nil { if ci.PhotoURL != nil && *ci.PhotoURL != "" { rc = *ci.PhotoURL @@ -125,7 +126,7 @@ func extractCommunityLogo(a jet.Arguments) reflect.Value { func displayDateTime(a jet.Arguments) reflect.Value { timeval := a.Get(0).Convert(reflect.TypeFor[time.Time]()).Interface().(time.Time) ctxt := a.Get(1).Convert(reflect.TypeFor[AmContext]()).Interface().(AmContext) - prefs, err := ctxt.CurrentUser().Prefs() + prefs, err := ctxt.CurrentUser().Prefs(ctxt.Ctx()) if err == nil { loc := prefs.Localizer() return reflect.ValueOf(loc.Strftime("%b %e, %Y %r", timeval)) @@ -137,7 +138,7 @@ func displayDateTime(a jet.Arguments) reflect.Value { func displayActivity(a jet.Arguments) reflect.Value { timeval := a.Get(0).Convert(reflect.TypeFor[*time.Time]()).Interface().(*time.Time) ctxt := a.Get(1).Convert(reflect.TypeFor[AmContext]()).Interface().(AmContext) - prefs, err := ctxt.CurrentUser().Prefs() + prefs, err := ctxt.CurrentUser().Prefs(ctxt.Ctx()) if err == nil { return reflect.ValueOf(util.AmActivityString(timeval, prefs.Localizer())) } @@ -150,14 +151,14 @@ func displayMemberCount(a jet.Arguments) reflect.Value { comm := a.Get(0).Convert(reflect.TypeFor[*database.Community]()).Interface().(*database.Community) ctxt := a.Get(1).Convert(reflect.TypeFor[AmContext]()).Interface().(AmContext) level := ctxt.CurrentUser().BaseLevel - mbr, _, clevel, err := comm.Membership(ctxt.CurrentUser()) + mbr, _, clevel, err := comm.Membership(ctxt.Ctx(), ctxt.CurrentUser()) if err == nil { if mbr && clevel > level { level = clevel } showHidden = comm.TestPermission("Community.ShowHiddenMembers", level) } - count, err := comm.MemberCount(showHidden) + count, err := comm.MemberCount(ctxt.Ctx(), showHidden) if err != nil { return reflect.ValueOf(-1) } @@ -194,7 +195,8 @@ func displayFullName(a jet.Arguments) reflect.Value { // displayExpandCat displays a category expanded into a hierarchy. func displayExpandCat(a jet.Arguments) reflect.Value { cat := a.Get(0).Convert(reflect.TypeFor[*database.Category]()).Interface().(*database.Category) - hier, _ := database.AmGetCategoryHierarchy(cat.CatId) + ctxt := a.Get(1).Convert(reflect.TypeFor[AmContext]()).Interface().(AmContext) + hier, _ := database.AmGetCategoryHierarchy(ctxt.Ctx(), cat.CatId) var rc strings.Builder for i, c := range hier { if i > 0 { diff --git a/ui/views/find.jet b/ui/views/find.jet index 106f54b..3959a62 100644 --- a/ui/views/find.jet +++ b/ui/views/find.jet @@ -257,7 +257,7 @@ 🟣
{{ DisplayExpandCat(rx) }} + class="text-blue-700 hover:text-blue-900 font-bold text-base">{{ DisplayExpandCat(rx, .) }}
{{ else if mode == "PST" }} diff --git a/ui/views/menu_left_comm.jet b/ui/views/menu_left_comm.jet index 5abc6cd..f0ca74a 100644 --- a/ui/views/menu_left_comm.jet +++ b/ui/views/menu_left_comm.jet @@ -10,7 +10,7 @@ {{ comm := .CurrentCommunity() }}
- {{ comm.Name }} + {{ comm.Name }}
{{ menu.Title }}
{{ ctxt := . }} diff --git a/ui/views/posts.jet b/ui/views/posts.jet index a4d307d..0440842 100644 --- a/ui/views/posts.jet +++ b/ui/views/posts.jet @@ -79,8 +79,8 @@ {{ post_overrideLine := "" }} {{ post_overrideLink := "" }} {{ range i, post_cur := posts }} - {{ post_userName = post_getUserName(post_cur) }} - {{ post_text = post_getText(post_cur) }} + {{ post_userName = post_getUserName(post_cur, .) }} + {{ post_text = post_getText(post_cur, .) }} {{ post_overrideLine = post_getOverrideLine(post_cur, .) }} {{ post_overrideLink = post_getOverrideLink(post_cur, post_topicPermalink) }} {{ .SubRender("singlepost.jet") | raw }} diff --git a/userdata.go b/userdata.go index 6890daa..f932c04 100644 --- a/userdata.go +++ b/userdata.go @@ -57,10 +57,10 @@ func EditProfileForm(ctxt ui.AmContext) (string, any, error) { dlg.Field("tgt").Value = target ctxt.VarMap().Set("target", target) var ci *database.ContactInfo - ci, err = u.ContactInfo() + ci, err = u.ContactInfo(ctxt.Ctx()) if err == nil { var prefs *database.UserPrefs - prefs, err = u.Prefs() + prefs, err = u.Prefs(ctxt.Ctx()) if err == nil { dlg.Field("remind").Value = u.PassReminder dlg.Field("prefix").SetVal(ci.Prefix) @@ -87,8 +87,8 @@ func EditProfileForm(ctxt ui.AmContext) (string, any, error) { dlg.Field("dob").SetDate(u.DOB) dlg.Field("descr").SetVal(u.Description) dlg.Field("photo").Value = userPhotoURL(ci) - dlg.Field("pic_in_post").SetChecked(u.FlagValue(database.UserFlagPicturesInPosts)) - dlg.Field("no_mass_mail").SetChecked(u.FlagValue(database.UserFlagMassMailOptOut)) + dlg.Field("pic_in_post").SetChecked(u.FlagValue(ctxt.Ctx(), database.UserFlagPicturesInPosts)) + dlg.Field("no_mass_mail").SetChecked(u.FlagValue(ctxt.Ctx(), database.UserFlagMassMailOptOut)) dlg.Field("locale").Value = prefs.ReadLocale() dlg.Field("tz").Value = prefs.TimeZoneID return dlg.Render(ctxt) @@ -130,15 +130,15 @@ func EditProfile(ctxt ui.AmContext) (string, any, error) { return dlg.RenderError(ctxt, err.Error()) } var ci *database.ContactInfo - ci, err = u.ContactInfo() + ci, err = u.ContactInfo(ctxt.Ctx()) if err == nil { var prefs *database.UserPrefs emailChange := false - prefs, err = u.Prefs() + prefs, err = u.Prefs(ctxt.Ctx()) if err == nil && !(dlg.Field("pass1").IsEmpty() && dlg.Field("pass2").IsEmpty()) { p1 := dlg.Field("pass1").Value if p1 == dlg.Field("pass2").Value { - err = u.ChangePassword(p1, ctxt.RemoteIP()) + err = u.ChangePassword(ctxt.Ctx(), p1, ctxt.RemoteIP()) } else { err = errors.New("passwords do not match") } @@ -166,27 +166,27 @@ func EditProfile(ctxt ui.AmContext) (string, any, error) { nci.Email = dlg.Field("email").ValPtr() nci.PrivateEmail = dlg.Field("pvt_email").IsChecked() nci.URL = dlg.Field("url").ValPtr() - emailChange, err = nci.Save() + emailChange, err = nci.Save(ctxt.Ctx()) ci = nci } if err == nil { nprefs := prefs.Clone() nprefs.WriteLocale(dlg.Field("locale").Value) nprefs.TimeZoneID = dlg.Field("tz").Value - err = nprefs.Save(u) + err = nprefs.Save(ctxt.Ctx(), u) } if err == nil { var f *util.OptionSet - f, err = u.Flags() + f, err = u.Flags(ctxt.Ctx()) if err == nil { nf := f.Clone() nf.Set(database.UserFlagPicturesInPosts, dlg.Field("pic_in_post").IsChecked()) nf.Set(database.UserFlagMassMailOptOut, dlg.Field("no_mass_mail").IsChecked()) - err = u.SaveFlags(nf) + err = u.SaveFlags(ctxt.Ctx(), nf) } } if err == nil { - err = u.SetProfileData(dlg.Field("remind").Value, dlg.Field("dob").AsDate(), dlg.Field("descr").ValPtr()) + err = u.SetProfileData(ctxt.Ctx(), dlg.Field("remind").Value, dlg.Field("dob").AsDate(), dlg.Field("descr").ValPtr()) } if err == nil { if emailChange { @@ -223,7 +223,7 @@ func ProfilePhotoForm(ctxt ui.AmContext) (string, any, error) { if u.IsAnon { return ui.ErrorPage(ctxt, errors.New("you are not logged in")) } - ci, err := u.ContactInfo() + ci, err := u.ContactInfo(ctxt.Ctx()) if err == nil { ctxt.VarMap().Set("target", target) ctxt.VarMap().Set("photo_url", userPhotoURL(ci)) @@ -247,7 +247,7 @@ func ProfilePhoto(ctxt ui.AmContext) (string, any, error) { if u.IsAnon { return ui.ErrorPage(ctxt, errors.New("you are not logged in")) } - ci, err := u.ContactInfo() + ci, err := u.ContactInfo(ctxt.Ctx()) if err != nil { return ui.ErrorPage(ctxt, err) } @@ -267,11 +267,11 @@ func ProfilePhoto(ctxt ui.AmContext) (string, any, error) { ui.UserPhotoMaxBytes) if err == nil { var img *database.ImageStore - img, err = database.AmStoreImage(database.ImageTypeUserPhoto, u.Uid, mimeType, imageData) + img, err = database.AmStoreImage(ctxt.Ctx(), database.ImageTypeUserPhoto, u.Uid, mimeType, imageData) if err == nil { photourl := fmt.Sprintf("/img/store/%d", img.ImgId) ci.PhotoURL = &photourl - _, err = ci.Save() + _, err = ci.Save(ctxt.Ctx()) if err == nil { return "redirect", "/profile?tgt=" + url.QueryEscape(target), nil } @@ -300,7 +300,7 @@ func ProfilePhoto(ctxt ui.AmContext) (string, any, error) { defer func() { if happy { ampool.Submit(func(context.Context) { - err := database.AmDeleteImage(int32(id)) + err := database.AmDeleteImage(ctxt.Ctx(), int32(id)) if err != nil { log.Errorf("unable to delete image ID %d: %v", id, err) } @@ -309,7 +309,7 @@ func ProfilePhoto(ctxt ui.AmContext) (string, any, error) { }() } ci.PhotoURL = nil - _, err := ci.Save() + _, err := ci.Save(ctxt.Ctx()) if err != nil { return ui.ErrorPage(ctxt, err) } @@ -329,18 +329,18 @@ func ProfilePhoto(ctxt ui.AmContext) (string, any, error) { */ func ShowProfile(ctxt ui.AmContext) (string, any, error) { me := ctxt.CurrentUser() - prefs, err := me.Prefs() + prefs, err := me.Prefs(ctxt.Ctx()) if err != nil { return ui.ErrorPage(ctxt, err) } // Gather the info on the current user. - user, err := database.AmGetUserByName(ctxt.URLParam("uname"), nil) + user, err := database.AmGetUserByName(ctxt.Ctx(), ctxt.URLParam("uname"), nil) if err != nil { ctxt.SetRC(http.StatusNotFound) return ui.ErrorPage(ctxt, err) } - ci, err := user.ContactInfo() + ci, err := user.ContactInfo(ctxt.Ctx()) if err != nil { return ui.ErrorPage(ctxt, err) } @@ -449,7 +449,7 @@ func QuickEMail(ctxt ui.AmContext) (string, any, error) { if me.IsAnon { return ui.ErrorPage(ctxt, errors.New("you are not logged in")) } - myCI, err := me.ContactInfo() + myCI, err := me.ContactInfo(ctxt.Ctx()) if err != nil { return ui.ErrorPage(ctxt, err) } @@ -457,14 +457,14 @@ func QuickEMail(ctxt ui.AmContext) (string, any, error) { if err != nil { return ui.ErrorPage(ctxt, err) } - user, err := database.AmGetUser(int32(toUid)) + user, err := database.AmGetUser(ctxt.Ctx(), int32(toUid)) if err != nil { return ui.ErrorPage(ctxt, err) } if user.IsAnon { return ui.ErrorPage(ctxt, errors.New("cannot send quick E-mail to anonymous user")) } - ci, err := user.ContactInfo() + ci, err := user.ContactInfo(ctxt.Ctx()) if err != nil { return ui.ErrorPage(ctxt, err) }