diff --git a/community.go b/community.go index c90d63e..a7a0518 100644 --- a/community.go +++ b/community.go @@ -10,6 +10,8 @@ package main import ( + "errors" + "fmt" "net/http" "strings" @@ -62,6 +64,7 @@ func ShowCommunity(ctxt ui.AmContext) (string, any, error) { } ctxt.VarMap().Set("commName", comm.Name) + ctxt.VarMap().Set("commAlias", comm.Alias) if ci.PhotoURL != nil && *ci.PhotoURL != "" { ctxt.VarMap().Set("logoURL", *ci.PhotoURL) } else { @@ -132,3 +135,166 @@ func ShowCommunity(ctxt ui.AmContext) (string, any, error) { ctxt.VarMap().Set("amsterdam_pageTitle", "Community Profile: "+comm.Name) return "framed_template", "comprofile.jet", nil } + +/* JoinCommunity joins a public community, or starts the process of joining a private one. + * 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 JoinCommunity(ctxt ui.AmContext) (string, any, error) { + me := ctxt.CurrentUser() + err := ctxt.SetCommunityContext(ctxt.URLParam("cid")) + ctxt.SetLeftMenu("community") + if err != nil { + ctxt.SetRC(http.StatusNotFound) + return ui.ErrorPage(ctxt, err) + } + comm := ctxt.CurrentCommunity() + mbr, _, _, err := comm.Membership(me) + if err != nil { + return ui.ErrorPage(ctxt, err) + } + if mbr { + // already member, this is a no-op + return "redirect", fmt.Sprintf("/comm/%s/profile", comm.Alias), nil + } + if comm.TestPermission("Community.Join", me.BaseLevel) { + if comm.JoinKey != nil && *comm.JoinKey != "" { + dlg, err := ui.AmLoadDialog("join") + if err != nil { + return ui.ErrorPage(ctxt, err) + } + dlg.SetCommunity(comm) + dlg.Field("cc").Value = comm.Alias + 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()) + if err != nil { + return ui.ErrorPage(ctxt, err) + } + } else { + return ui.ErrorPage(ctxt, errors.New("you are not permitted to join this community")) + } + return "redirect", fmt.Sprintf("/comm/%s/profile", comm.Alias), nil +} + +/* JoinCommunityWithKey joins a private community with a properly specified join key. + * 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 JoinCommunityWithKey(ctxt ui.AmContext) (string, any, error) { + me := ctxt.CurrentUser() + err := ctxt.SetCommunityContext(ctxt.FormField("cc")) + if err != nil { + ctxt.SetRC(http.StatusNotFound) + return ui.ErrorPage(ctxt, err) + } + comm := ctxt.CurrentCommunity() + ctxt.SetLeftMenu("community") + mbr, _, _, err := comm.Membership(me) + if err != nil { + return ui.ErrorPage(ctxt, err) + } + if mbr { + // already member, this is a no-op + return "redirect", fmt.Sprintf("/comm/%s/profile", comm.Alias), nil + } + if comm.TestPermission("Community.Join", me.BaseLevel) { + dlg, err := ui.AmLoadDialog("join") + if err != nil { + return ui.ErrorPage(ctxt, err) + } + dlg.SetCommunity(comm) + dlg.LoadFromForm(ctxt) + action := dlg.WhichButton(ctxt) + if action == "cancel" { + return "redirect", fmt.Sprintf("/comm/%s/profile", comm.Alias), nil + } + if action == "join_now" { + key := dlg.Field("key").Value + dlg.Field("key").Value = "" // clear it in case we redisplay the form + if key == "" { + return dlg.RenderError(ctxt, "No join key specified. Please try again.") + } + 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()) + if err != nil { + return dlg.RenderError(ctxt, fmt.Sprintf("Error joining: %v", err)) + } + return "redirect", fmt.Sprintf("/comm/%s/profile", comm.Alias), nil + } + return dlg.RenderError(ctxt, "Unknown button pressed on join form.") + } + return "redirect", fmt.Sprintf("/comm/%s/profile", comm.Alias), nil +} + +func UnjoinCommunity(ctxt ui.AmContext) (string, any, error) { + me := ctxt.CurrentUser() + err := ctxt.SetCommunityContext(ctxt.URLParam("cid")) + if err != nil { + ctxt.SetRC(http.StatusNotFound) + return ui.ErrorPage(ctxt, err) + } + comm := ctxt.CurrentCommunity() + ctxt.SetLeftMenu("community") + mbr, lock, _, err := comm.Membership(me) + if err != nil { + return ui.ErrorPage(ctxt, err) + } + if !mbr { + // not a member, just redirect to profile + return "redirect", fmt.Sprintf("/comm/%s/profile", comm.Alias), nil + } + if lock { + ctxt.SetRC(http.StatusForbidden) + return ui.ErrorPage(ctxt, errors.New("you are not permitted to unjoin this community")) + } + ctxt.VarMap().Set("comm", comm) + ctxt.VarMap().Set("amsterdam_pageTitle", "Unjoin Community") + return "framed_template", "unjoin.jet", nil +} + +func UnjoinCommunityConfirm(ctxt ui.AmContext) (string, any, error) { + me := ctxt.CurrentUser() + err := ctxt.SetCommunityContext(ctxt.URLParam("cid")) + if err != nil { + ctxt.SetRC(http.StatusNotFound) + return ui.ErrorPage(ctxt, err) + } + comm := ctxt.CurrentCommunity() + ctxt.SetLeftMenu("community") + mbr, lock, _, err := comm.Membership(me) + if err != nil { + return ui.ErrorPage(ctxt, err) + } + if !mbr { + // not a member, just redirect to profile + return "redirect", fmt.Sprintf("/comm/%s/profile", comm.Alias), nil + } + if lock { + ctxt.SetRC(http.StatusForbidden) + return ui.ErrorPage(ctxt, errors.New("you are not permitted to unjoin this community")) + } + if ctxt.FormFieldIsSet("cancel") { + return "redirect", fmt.Sprintf("/comm/%s/profile", comm.Alias), nil + } + if ctxt.FormFieldIsSet("unjoin") { + err = comm.SetMembership(me, 0, false, me.Uid, ctxt.RemoteIP()) + if err != nil { + return ui.ErrorPage(ctxt, err) + } + ctxt.ClearCommunityContext() + return "redirect", fmt.Sprintf("/comm/%s/profile", comm.Alias), nil + } + return ui.ErrorPage(ctxt, errors.New("unknown button pressed to confirm unjoin")) +} diff --git a/database/community.go b/database/community.go index 9f8c997..5b68ff0 100644 --- a/database/community.go +++ b/database/community.go @@ -273,7 +273,7 @@ func (c *Community) SetMembership(u *User, level uint16, locked bool, personUID stuffMembership(c.Id, u.Uid, true, locked, level) } } else { - _, err := amdb.Exec("INSERT INTO commmember (comm_id, uid, granted_lvl, locked) VALUES (?, ?, ?, ?)", + _, err := amdb.Exec("INSERT INTO commmember (commid, uid, granted_lvl, locked) VALUES (?, ?, ?, ?)", c.Id, u.Uid, level, locked) if err != nil { return err diff --git a/main.go b/main.go index 8f3f609..fb77601 100644 --- a/main.go +++ b/main.go @@ -72,6 +72,10 @@ func setupEcho() *echo.Echo { e.GET("/create_comm", ui.AmWrap(CreateCommunityForm)) e.POST("/create_comm", ui.AmWrap(CreateCommunity)) e.GET("/comm/:cid/profile", ui.AmWrap(ShowCommunity)) + e.GET("/comm/:cid/join", ui.AmWrap(JoinCommunity)) + e.POST("/comm/:cid/join", ui.AmWrap(JoinCommunityWithKey)) + e.GET("/comm/:cid/unjoin", ui.AmWrap(UnjoinCommunity)) + e.POST("/comm/:cid/unjoin", ui.AmWrap(UnjoinCommunityConfirm)) e.GET("/comm/:cid/admin", ui.AmWrap(CommunityAdminMenu)) e.GET("/comm/:cid/admin/profile", ui.AmWrap(CommunityProfileForm)) e.POST("/comm/:cid/admin/profile", ui.AmWrap(EditCommunityProfile)) diff --git a/ui/dialogs/join.yaml b/ui/dialogs/join.yaml new file mode 100644 index 0000000..400cf77 --- /dev/null +++ b/ui/dialogs/join.yaml @@ -0,0 +1,34 @@ +# +# Amsterdam Web Communities System +# Copyright (c) 2025 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/. +# +name: "join" +formName: "joinkeyform" +menuSelector: "community" +title: "Join Community" +subtitle: "[CNAME]" +action: "/comm/[CID]/join" +instructions: > + You must specify a join key before you can join this community. You might have received the join key from + the community's host, or via an invitation e-mail message. Please enter it in the box below. +fields: + - type: "hidden" + name: "cc" + value: "" + - type: "text" + name: "key" + caption: "Join Key" + size: 32 + maxLength: 64 + - type: "button" + name: "join_now" + caption: "Join Now" + param: "blue" + - type: "button" + name: "cancel" + caption: "Cancel" + param: "red" diff --git a/ui/views/comprofile.jet b/ui/views/comprofile.jet index 6eda762..07cbce9 100644 --- a/ui/views/comprofile.jet +++ b/ui/views/comprofile.jet @@ -33,7 +33,7 @@ {{ if isset(canJoin) }}
+ You are about to unjoin the {{ comm.Name }} community. + {{ if comm.MembersOnly }} + You will lose access to all content of the community, as well as the ability to interact with + the community as a whole. + {{ else }} + You will lose the ability to interact with the community's members and content. + {{ end }} +
+Please confirm if you wish to proceed.
+