From 2c9ceefd6b001f2a6cac2ee80224fea32894a6d8 Mon Sep 17 00:00:00 2001 From: Amy Gale Ruth Bowersox Date: Fri, 30 Jan 2026 14:44:30 -0700 Subject: [PATCH] landed E-mail invites at three levels --- database/conference.go | 14 +++++ docs/MISSINGFUNCS.md | 8 +-- email/templates/invite_private.jet | 35 +++++++++++ email/templates/invite_public.jet | 34 +++++++++++ invites.go | 98 +++++++++++++++++++++++++++++- main.go | 1 + ui/views/invite.jet | 2 +- 7 files changed, 185 insertions(+), 7 deletions(-) create mode 100644 email/templates/invite_private.jet create mode 100644 email/templates/invite_public.jet diff --git a/database/conference.go b/database/conference.go index fde7ecd..354b595 100644 --- a/database/conference.go +++ b/database/conference.go @@ -112,6 +112,20 @@ func (c *Conference) Hosts(ctx context.Context) ([]*User, error) { return rc, nil } +// InCommunity returns true if the specified conference is in the community. +func (c *Conference) InCommunity(ctx context.Context, comm *Community) (bool, error) { + row := amdb.QueryRowContext(ctx, "SELECT commid FROM commtoconf WHERE commid = ? AND confid = ?", comm.Id, c.ConfId) + var tmp int32 + err := row.Scan(&tmp) + switch err { + case nil: + return true, nil + case sql.ErrNoRows: + return false, nil + } + return false, err +} + // ContainedBy returns the communities that contain this conference. func (c *Conference) ContainedBy(ctx context.Context) ([]*Community, error) { rs, err := amdb.QueryContext(ctx, "SELECT commid FROM commtoconf WHERE confid = ?", c.ConfId) diff --git a/docs/MISSINGFUNCS.md b/docs/MISSINGFUNCS.md index 6c79a9b..dcdf17c 100644 --- a/docs/MISSINGFUNCS.md +++ b/docs/MISSINGFUNCS.md @@ -25,16 +25,16 @@ _(italicized items can be deferred)_ - E-Mail to All Members - Display Audit Records - Delete Community -- Community Profile: Invite +- ~~Community Profile: Invite~~ - _Help link atop page headers_ - _Policy page_ - Member List: Export - HTML reference for post boxes - Posts view: - Find - - Manage: + - ~~Manage:~~ - ~~Subscribe to Topic~~ - - Send invite + - ~~Send invite~~ - ~~Filtered Users (list/remove)~~ - ~~Stick/Unstick~~ - ~~Freeze/Unfreeze~~ @@ -55,7 +55,7 @@ _(italicized items can be deferred)_ - Manage: - Set pseud - Fixseen - - Send invite + - ~~Send invite~~ - Change information - Manage aliases - Manage members diff --git a/email/templates/invite_private.jet b/email/templates/invite_private.jet new file mode 100644 index 0000000..86cbf16 --- /dev/null +++ b/email/templates/invite_private.jet @@ -0,0 +1,35 @@ +{* + * Amsterdam Web Communities System + * Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + *} +{{ .SetSubject("Invitation to " + comm.Name + " Community")}} +Hi! I would like to invite you to join the "{{ comm.Name }}" community on +{{ GlobalConfig.Site.Title }}. To do so, you must register as a user, which +is absolutely free! Just point your Web browser at +<{{ GlobalConfig.Site.BaseURL }}/comm/{{ comm.Alias }}> and click the "Create Account" +link at the top of the page, or click the "Log In" link if you already have +a Amsterdam account. Once you have completed the process, click the "Join Now" +button. You will be prompted for the "password" for this community, which is +"{{ comm.JoinKey }}". You will then be able to take part in the conferences that are +going on in the community. +{{ if mode == "conference" }} +After you've joined the "{{ comm.Name }}" community, check out the +"{{ conf.Name }}" conference. To find it, after joining the community, +click "Conferences" on the left menu bar, then click on the +"{{ conf.Name }}" conference name in the conference list. +{{ else if mode == "topic" }} +After you've joined the "{{ comm.Name }}" community, check out the +"{{ topic.Name | raw }}" topic in the "{{ conf.Name }}" conference. To find it, +after joining the community, click "Conferences" on the left menu bar, then +click on the "{{ conf.Name }}" conference name in the conference list, +then click on the "{{ topic.Name | raw }}" topic name in the topic list. +{{ end }} +{{ personal }} + +Hope to see you in "{{ comm.Name }}" soon! + +-- {{ fullname }} (Amsterdam user ID: {{ username }}) diff --git a/email/templates/invite_public.jet b/email/templates/invite_public.jet new file mode 100644 index 0000000..a339d05 --- /dev/null +++ b/email/templates/invite_public.jet @@ -0,0 +1,34 @@ +{* + * Amsterdam Web Communities System + * Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + *} +{{ .SetSubject("Invitation to " + comm.Name + " Community")}} +Hi! I would like to invite you to join the "{{ comm.Name }}" community on +{{ GlobalConfig.Site.Title }}. To do so, you must register as a user, which +is absolutely free! Just point your Web browser at +<{{ GlobalConfig.Site.BaseURL }}/comm/{{ comm.Alias }}> and click the "Create Account" +link at the top of the page, or click the "Log In" link if you already have +a Amsterdam account. Once you have completed the process, click the "Join Now" +button. You will then be able to take part in the conferences that are +going on in the community. +{{ if mode == "conference" }} +After you've joined the "{{ comm.Name }}" community, check out the +"{{ conf.Name }}" conference. To find it, after joining the community, +click "Conferences" on the left menu bar, then click on the +"{{ conf.Name }}" conference name in the conference list. +{{ else if mode == "topic" }} +After you've joined the "{{ comm.Name }}" community, check out the +"{{ topic.Name | raw }}" topic in the "{{ conf.Name }}" conference. To find it, +after joining the community, click "Conferences" on the left menu bar, then +click on the "{{ conf.Name }}" conference name in the conference list, +then click on the "{{ topic.Name | raw }}" topic name in the topic list. +{{ end }} +{{ personal }} + +Hope to see you in "{{ comm.Name }}" soon! + +-- {{ fullname }} (Amsterdam user ID: {{ username }}) diff --git a/invites.go b/invites.go index 1804915..a50c0e6 100644 --- a/invites.go +++ b/invites.go @@ -10,10 +10,13 @@ package main import ( + "errors" "fmt" "net/http" + "net/mail" "git.erbosoft.com/amy/amsterdam/database" + "git.erbosoft.com/amy/amsterdam/email" "git.erbosoft.com/amy/amsterdam/ui" ) @@ -59,7 +62,7 @@ func InviteToConference(ctxt ui.AmContext) (string, any, error) { ctxt.VarMap().Set("amsterdam_pageTitle", "Send Invitation") ctxt.VarMap().Set("title", "Send Conference Invitation") ctxt.VarMap().Set("subtitle", conf.Name) - ctxt.VarMap().Set("backlink", fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, ctxt.GetScratch("currentAlias"))) + ctxt.VarMap().Set("backlink", fmt.Sprintf("/comm/%s/conf/%s/manage", comm.Alias, ctxt.GetScratch("currentAlias"))) ctxt.VarMap().Set("cid", fmt.Sprintf("%d", comm.Id)) ctxt.VarMap().Set("confid", fmt.Sprintf("%d", conf.ConfId)) return "framed_template", "invite.jet", nil @@ -85,9 +88,100 @@ func InviteToTopic(ctxt ui.AmContext) (string, any, error) { ctxt.VarMap().Set("amsterdam_pageTitle", "Send Invitation") ctxt.VarMap().Set("title", "Send Topic Invitation") ctxt.VarMap().Set("subtitle", topic.Name) - ctxt.VarMap().Set("backlink", fmt.Sprintf("/comm/%s/conf/%s/r/%d", comm.Alias, ctxt.GetScratch("currentAlias"), topic.Number)) + ctxt.VarMap().Set("backlink", fmt.Sprintf("/comm/%s/conf/%s/op/%d/manage", comm.Alias, ctxt.GetScratch("currentAlias"), topic.Number)) ctxt.VarMap().Set("cid", fmt.Sprintf("%d", comm.Id)) ctxt.VarMap().Set("confid", fmt.Sprintf("%d", conf.ConfId)) ctxt.VarMap().Set("topicid", fmt.Sprintf("%d", topic.TopicId)) return "framed_template", "invite.jet", nil } + +/* InviteSend is the back end that handles sending invitations. + * Parameters: + * ctxt - The AmContext for the request. + * Returns: + * Command string dictating what to be rendered. + * Data as a parameter for the command string. + * Standard Go error status. + */ +func InviteSend(ctxt ui.AmContext) (string, any, error) { + backlink := ctxt.FormField("backlink") + if ctxt.FormFieldIsSet("cancel") { + return "redirect", backlink, nil + } else if !ctxt.FormFieldIsSet("send") { + return ui.ErrorPage(ctxt, errors.New("invalid command")) + } + var comm *database.Community + if ctxt.FormFieldIsSet("cid") { + id, err := ctxt.FormFieldInt("cid") + if err == nil { + comm, err = database.AmGetCommunity(ctxt.Ctx(), int32(id)) + } + if err != nil { + return ui.ErrorPage(ctxt, err) + } + } else { + return ui.ErrorPage(ctxt, errors.New("no parameters specified")) + } + mode := "community" + var conf *database.Conference = nil + var topic *database.Topic = nil + if ctxt.FormFieldIsSet("confid") { + id, err := ctxt.FormFieldInt("confid") + if err == nil { + if conf, err = database.AmGetConference(ctxt.Ctx(), int32(id)); err == nil { + var f bool + if f, err = conf.InCommunity(ctxt.Ctx(), comm); err == nil { + if !f { + err = errors.New("invalid conference; not in community") + } + } + } + } + if err != nil { + return ui.ErrorPage(ctxt, err) + } + if ctxt.FormFieldIsSet("topicid") { + id, err := ctxt.FormFieldInt("topicid") + if err == nil { + topic, err = database.AmGetTopic(ctxt.Ctx(), int32(id)) + if err == nil && topic.ConfId != conf.ConfId { + err = errors.New("invalid topic; not in conference") + } + } + if err != nil { + return ui.ErrorPage(ctxt, err) + } + mode = "topic" + } else { + mode = "conference" + } + } + addr := ctxt.FormField("addr") + _, err := mail.ParseAddress(addr) + if err != nil { + return ui.ErrorPage(ctxt, err) + } + + ci, err := database.AmGetContactInfoForUser(ctxt.Ctx(), ctxt.CurrentUserId()) + if err != nil { + return ui.ErrorPage(ctxt, err) + } + + mailMessage := email.AmNewEmailMessage(ctxt.CurrentUserId(), ctxt.RemoteIP()) + if comm.Public() { + mailMessage.SetTemplate("invite_public.jet") + } else { + mailMessage.SetTemplate("invite_private.jet") + } + mailMessage.AddTo(addr, "") + mailMessage.AddVariable("comm", comm) + mailMessage.AddVariable("conf", conf) + mailMessage.AddVariable("topic", topic) + mailMessage.AddVariable("mode", mode) + mailMessage.AddVariable("personal", ctxt.FormField("msg")) + mailMessage.AddVariable("fullname", ci.FullName(true)) + mailMessage.AddVariable("username", ctxt.CurrentUser().Username) + mailMessage.Send() + + return "redirect", backlink, nil +} diff --git a/main.go b/main.go index fcccce0..add5605 100644 --- a/main.go +++ b/main.go @@ -77,6 +77,7 @@ func setupEcho() *echo.Echo { e.POST("/create_comm", ui.AmWrap(CreateCommunity)) e.POST("/attachment_upload", ui.AmWrap(AttachmentUpload)) e.GET("/attachment/:post", ui.AmWrap(AttachmentSend)) + e.POST("/__invite_send", ui.AmWrap(InviteSend)) // community group commGroup := e.Group("/comm/:cid", ui.SetCommunity) diff --git a/ui/views/invite.jet b/ui/views/invite.jet index abfd8f5..164ff18 100644 --- a/ui/views/invite.jet +++ b/ui/views/invite.jet @@ -16,7 +16,7 @@
-
+ {{ if isset(cid) }}