Compare commits
23 Commits
8c1fa9ebdc
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a2c2a1f750 | |||
| 08a10a55dd | |||
| 64161721bf | |||
| d3e89b886e | |||
| e962c4d0c5 | |||
| 53ee2281bc | |||
| d309b90953 | |||
| e05b34a866 | |||
| 6526c74ed2 | |||
| 10a347747e | |||
| 631215f6a0 | |||
| 759996b4cd | |||
| a068e17e65 | |||
| 57088e4680 | |||
| 17de55c5c2 | |||
| ea807cc55f | |||
| 14c6df9891 | |||
| 581319279a | |||
| 0e3c1a293e | |||
| 391d8ccc99 | |||
| 3deb11e0a5 | |||
| 1348d0225f | |||
| 70dcf82234 |
@@ -0,0 +1,33 @@
|
|||||||
|
name: Build and Release Amsterdam Assets
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
jobs:
|
||||||
|
build-and-upload:
|
||||||
|
runs-on: host
|
||||||
|
steps:
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.26'
|
||||||
|
- name: Build Binaries
|
||||||
|
run: |
|
||||||
|
# Build targets: Linux AMD64, Linux ARM64 (RPi), Windows AMD64, macOS ARM64 (Apple Silicon), macOS AMD64 (Intel)
|
||||||
|
GOOS=linux GOARCH=amd64 go build -o amsterdam-linux-amd64
|
||||||
|
GOOS=linux GOARCH=arm64 go build -o amsterdam-linux-arm64
|
||||||
|
GOOS=windows GOARCH=amd64 go build -o amsterdam-windows-amd64.exe
|
||||||
|
GOOS=darwin GOARCH=arm64 go build -o amsterdam-macos-arm64
|
||||||
|
GOOS=darwin GOARCH=amd64 go build -o amsterdam-macos-amd64
|
||||||
|
- name: Upload Assets to Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
amsterdam-linux-amd64
|
||||||
|
amsterdam-linux-arm64
|
||||||
|
amsterdam-windows-amd64.exe
|
||||||
|
amsterdam-macos-arm64
|
||||||
|
amsterdam-macos-amd64
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
+1
-1
@@ -6,4 +6,4 @@ __debug_bin*
|
|||||||
/test*.yaml
|
/test*.yaml
|
||||||
|
|
||||||
# Ignore mac-specific files
|
# Ignore mac-specific files
|
||||||
/.DS_Store
|
.DS_Store
|
||||||
|
|||||||
@@ -114,14 +114,9 @@ in your `PATH`. Then run `go generate` to regenerate the CSS file before you run
|
|||||||
|
|
||||||
## Installing Amsterdam
|
## Installing Amsterdam
|
||||||
|
|
||||||
You will need a MySQL database to store Amsterdam data. Create a new empty database, then, from the command line, use the command:
|
You will need a MySQL database to store Amsterdam data. Create a new empty database.
|
||||||
|
|
||||||
> `mysql -u root -p _databasename_ \< setup/mysql-database.sql`
|
Ensure a user in your database is granted all privileges to all tables in your new database.
|
||||||
|
|
||||||
(Replace _databasename_ with the name of your database. If you use a user other than `root` for administrative access to your
|
|
||||||
MySQL server, use that.)
|
|
||||||
|
|
||||||
Ensure a user in your database is granted SELECT, INSERT, UPDATE, and DELETE privileges to all tables in your new database.
|
|
||||||
This is the user that you will configure Amsterdam to use.
|
This is the user that you will configure Amsterdam to use.
|
||||||
|
|
||||||
The database may be specified to Amsterdam with the following command line options or environment variables:
|
The database may be specified to Amsterdam with the following command line options or environment variables:
|
||||||
@@ -133,6 +128,8 @@ The database may be specified to Amsterdam with the following command line optio
|
|||||||
|
|
||||||
All these options may also be specified via the configuration file (see below).
|
All these options may also be specified via the configuration file (see below).
|
||||||
|
|
||||||
|
The first time you execute Amsterdam, the necessary database tables will be created and populated.
|
||||||
|
|
||||||
Amsterdam also requires access to a local SMTP server, as it sends out E-mail messages such as account verification,
|
Amsterdam also requires access to a local SMTP server, as it sends out E-mail messages such as account verification,
|
||||||
password reminders, subscribed posts, and messages from conference or community hosts. It may be specified to Amsterdam
|
password reminders, subscribed posts, and messages from conference or community hosts. It may be specified to Amsterdam
|
||||||
with the following command line options or environment variables:
|
with the following command line options or environment variables:
|
||||||
|
|||||||
@@ -1,22 +1,10 @@
|
|||||||
# Future Directions for Amsterdam
|
# Future Directions for Amsterdam
|
||||||
|
|
||||||
After the point where it reaches feature parity with Venice circa 2006.
|
|
||||||
|
|
||||||
## Pre-Launch Polish
|
|
||||||
|
|
||||||
* ~~Policy page support.~~
|
|
||||||
* ~~User agreement in a separate file rather than directly in settings.~~
|
|
||||||
* ~~Support all customizations that were done with the EMinds instance of Venice.~~
|
|
||||||
* ~~Gitea-like status page showing Go-specific internals.~~
|
|
||||||
* ~~Build static Tailwind CSS file rather than using remote-loaded version. (Gate on debug/prod flag)~~
|
|
||||||
* ~~Rate limiter.~~
|
|
||||||
* ~~Better logging configuration.~~
|
|
||||||
|
|
||||||
## Immediate Cleanups Required
|
## Immediate Cleanups Required
|
||||||
|
|
||||||
* A better way to set up the database than `setup/database.sql`. Bring the table setup into the application somehow.
|
* <s>A better way to set up the database than `setup/database.sql`. Bring the table setup into the application somehow.
|
||||||
The [migrate](https://github.com/golang-migrate/migrate) library might be of use here.
|
The [migrate](https://github.com/golang-migrate/migrate) library might be of use here.</s>
|
||||||
* Database format migrations.
|
* ~~Database format migrations.~~
|
||||||
* Allow use of Postgres as a database.
|
* Allow use of Postgres as a database.
|
||||||
* Dockerization.
|
* Dockerization.
|
||||||
* Implement proper help and online documentation.
|
* Implement proper help and online documentation.
|
||||||
@@ -27,12 +15,9 @@ After the point where it reaches feature parity with Venice circa 2006.
|
|||||||
* Should those be community "services" instead?
|
* Should those be community "services" instead?
|
||||||
* For Chat, if it's implemented, it should use XMPP.
|
* For Chat, if it's implemented, it should use XMPP.
|
||||||
|
|
||||||
## Architectural Goofs
|
|
||||||
|
|
||||||
* Conference Aliases are effectively in a system-wide namespace. Should be per-community.
|
|
||||||
|
|
||||||
## Additional Items
|
## Additional Items
|
||||||
|
|
||||||
|
* Ensure design is responsive enough that we can use the site on mobile devices.
|
||||||
* Decouple from MySQL, introduce other database support. Postgres and SQLite are the two priorities here.
|
* Decouple from MySQL, introduce other database support. Postgres and SQLite are the two priorities here.
|
||||||
* Fix password storage. Straight SHA-1 hashes aren't gonna cut it. There are better ways.
|
* Fix password storage. Straight SHA-1 hashes aren't gonna cut it. There are better ways.
|
||||||
* Introduce OAuth authentication? (Related to above)
|
* Introduce OAuth authentication? (Related to above)
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@ import (
|
|||||||
"git.erbosoft.com/amy/amsterdam/ui"
|
"git.erbosoft.com/amy/amsterdam/ui"
|
||||||
"git.erbosoft.com/amy/amsterdam/util"
|
"git.erbosoft.com/amy/amsterdam/util"
|
||||||
"github.com/biter777/countries"
|
"github.com/biter777/countries"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ENOJOIN is an error for not being permitted to join a community.
|
// ENOJOIN is an error for not being permitted to join a community.
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ import (
|
|||||||
"git.erbosoft.com/amy/amsterdam/exports"
|
"git.erbosoft.com/amy/amsterdam/exports"
|
||||||
"git.erbosoft.com/amy/amsterdam/ui"
|
"git.erbosoft.com/amy/amsterdam/ui"
|
||||||
"git.erbosoft.com/amy/amsterdam/util"
|
"git.erbosoft.com/amy/amsterdam/util"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v5"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+26
-21
@@ -26,7 +26,7 @@ import (
|
|||||||
"git.erbosoft.com/amy/amsterdam/htmlcheck"
|
"git.erbosoft.com/amy/amsterdam/htmlcheck"
|
||||||
"git.erbosoft.com/amy/amsterdam/ui"
|
"git.erbosoft.com/amy/amsterdam/ui"
|
||||||
"github.com/CloudyKit/jet/v6"
|
"github.com/CloudyKit/jet/v6"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v5"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ func Topics(ctxt ui.AmContext) (string, any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// create the "read new" URL
|
// create the "read new" URL
|
||||||
urlStem := fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, ctxt.GetScratch("currentAlias"))
|
urlStem := ctxt.GetScratch("ConferenceLink").(string)
|
||||||
if !ctxt.CurrentUser().IsAnon {
|
if !ctxt.CurrentUser().IsAnon {
|
||||||
traverser := ui.NewTopicTraverser(topics)
|
traverser := ui.NewTopicTraverser(topics)
|
||||||
ctxt.SetSession("topic.traverser", traverser)
|
ctxt.SetSession("topic.traverser", traverser)
|
||||||
@@ -178,14 +178,13 @@ func Topics(ctxt ui.AmContext) (string, any) {
|
|||||||
* Data as a parameter for the command string.
|
* Data as a parameter for the command string.
|
||||||
*/
|
*/
|
||||||
func NewTopicForm(ctxt ui.AmContext) (string, any) {
|
func NewTopicForm(ctxt ui.AmContext) (string, any) {
|
||||||
comm := ctxt.CurrentCommunity()
|
|
||||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||||
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
||||||
if !conf.TestPermission("Conference.Create", myLevel) {
|
if !conf.TestPermission("Conference.Create", myLevel) {
|
||||||
return "error", echo.NewHTTPError(http.StatusForbidden, "you are not permitted to create topics in this conference")
|
return "error", echo.NewHTTPError(http.StatusForbidden, "you are not permitted to create topics in this conference")
|
||||||
}
|
}
|
||||||
ctxt.VarMap().Set("conferenceName", conf.Name)
|
ctxt.VarMap().Set("conferenceName", conf.Name)
|
||||||
ctxt.VarMap().Set("urlStem", fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, ctxt.GetScratch("currentAlias")))
|
ctxt.VarMap().Set("urlStem", ctxt.GetScratch("ConferenceLink").(string))
|
||||||
ctxt.VarMap().Set("topicName", "")
|
ctxt.VarMap().Set("topicName", "")
|
||||||
pseud, err := conf.DefaultPseud(ctxt.Ctx(), ctxt.CurrentUser())
|
pseud, err := conf.DefaultPseud(ctxt.Ctx(), ctxt.CurrentUser())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -212,7 +211,7 @@ func NewTopic(ctxt ui.AmContext) (string, any) {
|
|||||||
return "error", echo.NewHTTPError(http.StatusForbidden, "you are not permitted to create topics in this conference")
|
return "error", echo.NewHTTPError(http.StatusForbidden, "you are not permitted to create topics in this conference")
|
||||||
}
|
}
|
||||||
|
|
||||||
urlStem := fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, ctxt.GetScratch("currentAlias"))
|
urlStem := ctxt.GetScratch("ConferenceLink").(string)
|
||||||
if ctxt.FormFieldIsSet("cancel") {
|
if ctxt.FormFieldIsSet("cancel") {
|
||||||
return "redirect", urlStem
|
return "redirect", urlStem
|
||||||
}
|
}
|
||||||
@@ -247,7 +246,7 @@ func NewTopic(ctxt ui.AmContext) (string, any) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
checker.SetContext("PostLinkDecoderContext", database.AmCreatePostLinkContext(comm.Alias, ctxt.GetScratch("currentAlias").(string), conf.TopTopic+1))
|
checker.SetContext("PostLinkDecoderContext", database.AmCreatePostLinkContext(comm.Alias, comm.Id, ctxt.GetScratch("currentAlias").(string), conf.TopTopic+1))
|
||||||
checker.Append(postdata)
|
checker.Append(postdata)
|
||||||
checker.Finish()
|
checker.Finish()
|
||||||
v, _ = checker.Value()
|
v, _ = checker.Value()
|
||||||
@@ -282,7 +281,7 @@ func NewTopic(ctxt ui.AmContext) (string, any) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
checker.SetContext("PostLinkDecoderContext", database.AmCreatePostLinkContext(comm.Alias, ctxt.GetScratch("currentAlias").(string), conf.TopTopic+1))
|
checker.SetContext("PostLinkDecoderContext", database.AmCreatePostLinkContext(comm.Alias, comm.Id, ctxt.GetScratch("currentAlias").(string), conf.TopTopic+1))
|
||||||
checker.Append(ctxt.FormField("pb"))
|
checker.Append(ctxt.FormField("pb"))
|
||||||
checker.Finish()
|
checker.Finish()
|
||||||
zeroPost, _ := checker.Value()
|
zeroPost, _ := checker.Value()
|
||||||
@@ -385,7 +384,8 @@ func templatePostText(args jet.Arguments) reflect.Value {
|
|||||||
// templateOverrideLine creates the "override line" for a post, that is, what gets displayed in place of the post text.
|
// templateOverrideLine creates the "override line" for a post, that is, what gets displayed in place of the post text.
|
||||||
func templateOverrideLine(args jet.Arguments) reflect.Value {
|
func templateOverrideLine(args jet.Arguments) reflect.Value {
|
||||||
post := args.Get(0).Convert(reflect.TypeFor[*database.PostHeader]()).Interface().(*database.PostHeader)
|
post := args.Get(0).Convert(reflect.TypeFor[*database.PostHeader]()).Interface().(*database.PostHeader)
|
||||||
ctxt := args.Get(1).Convert(reflect.TypeFor[ui.AmContext]()).Interface().(ui.AmContext)
|
advanced := args.Get(1).Convert(reflect.TypeFor[bool]()).Bool()
|
||||||
|
ctxt := args.Get(2).Convert(reflect.TypeFor[ui.AmContext]()).Interface().(ui.AmContext)
|
||||||
rc := ""
|
rc := ""
|
||||||
if post.IsScribbled() {
|
if post.IsScribbled() {
|
||||||
scr_date := ""
|
scr_date := ""
|
||||||
@@ -402,7 +402,7 @@ func templateOverrideLine(args jet.Arguments) reflect.Value {
|
|||||||
} else {
|
} else {
|
||||||
rc = fmt.Sprintf("<<<%v>>>", err)
|
rc = fmt.Sprintf("<<<%v>>>", err)
|
||||||
}
|
}
|
||||||
} else if post.Hidden {
|
} else if post.Hidden && !advanced {
|
||||||
rc = fmt.Sprintf("(Hidden Message: %d Lines)", *post.LineCount)
|
rc = fmt.Sprintf("(Hidden Message: %d Lines)", *post.LineCount)
|
||||||
}
|
}
|
||||||
return reflect.ValueOf(rc)
|
return reflect.ValueOf(rc)
|
||||||
@@ -411,9 +411,10 @@ func templateOverrideLine(args jet.Arguments) reflect.Value {
|
|||||||
// templateOverrideLink creates the "override link" for a post, which can make the override line a hyperlink.
|
// templateOverrideLink creates the "override link" for a post, which can make the override line a hyperlink.
|
||||||
func templateOverrideLink(args jet.Arguments) reflect.Value {
|
func templateOverrideLink(args jet.Arguments) reflect.Value {
|
||||||
post := args.Get(0).Convert(reflect.TypeFor[*database.PostHeader]()).Interface().(*database.PostHeader)
|
post := args.Get(0).Convert(reflect.TypeFor[*database.PostHeader]()).Interface().(*database.PostHeader)
|
||||||
root := args.Get(1).Convert(reflect.TypeFor[string]()).String()
|
advanced := args.Get(1).Convert(reflect.TypeFor[bool]()).Bool()
|
||||||
|
root := args.Get(2).Convert(reflect.TypeFor[string]()).String()
|
||||||
rc := ""
|
rc := ""
|
||||||
if post.Hidden {
|
if post.Hidden && !advanced {
|
||||||
rc = fmt.Sprintf("%s?r=%d&ac=1", root, post.Num)
|
rc = fmt.Sprintf("%s?r=%d&ac=1", root, post.Num)
|
||||||
}
|
}
|
||||||
return reflect.ValueOf(rc)
|
return reflect.ValueOf(rc)
|
||||||
@@ -512,11 +513,11 @@ func ReadPosts(ctxt ui.AmContext) (string, any) {
|
|||||||
resetLastRead := false
|
resetLastRead := false
|
||||||
if ctxt.HasParameter("r") {
|
if ctxt.HasParameter("r") {
|
||||||
if err := breakRange(topic, postRange, ctxt.Parameter("r"), ","); err != nil {
|
if err := breakRange(topic, postRange, ctxt.Parameter("r"), ","); err != nil {
|
||||||
return "error", echo.NewHTTPError(http.StatusNotFound).SetInternal(err)
|
return "error", echo.NewHTTPError(http.StatusNotFound, err.Error()).Wrap(err)
|
||||||
}
|
}
|
||||||
} else if ctxt.HasParameter("rgo") {
|
} else if ctxt.HasParameter("rgo") {
|
||||||
if err := breakRange(topic, postRange, ctxt.Parameter("rgo"), "-"); err != nil {
|
if err := breakRange(topic, postRange, ctxt.Parameter("rgo"), "-"); err != nil {
|
||||||
return "error", echo.NewHTTPError(http.StatusNotFound).SetInternal(err)
|
return "error", echo.NewHTTPError(http.StatusNotFound, err.Error()).Wrap(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
postRange[0] = lastRead + 1
|
postRange[0] = lastRead + 1
|
||||||
@@ -560,10 +561,11 @@ func ReadPosts(ctxt ui.AmContext) (string, any) {
|
|||||||
summaryLine := fmt.Sprintf("%d Total; %d New; Last: %s", topic.TopMessage+1, topic.TopMessage-lastRead, prefs.Localizer().Strftime("%b %e, %Y %r", topic.LastUpdate))
|
summaryLine := fmt.Sprintf("%d Total; %d New; Last: %s", topic.TopMessage+1, topic.TopMessage-lastRead, prefs.Localizer().Strftime("%b %e, %Y %r", topic.LastUpdate))
|
||||||
ctxt.VarMap().Set("summaryLine", flags.String()+summaryLine)
|
ctxt.VarMap().Set("summaryLine", flags.String()+summaryLine)
|
||||||
ctxt.SetFrameTitle(fmt.Sprintf("%s: %s%s", topic.Name, flags.String(), summaryLine))
|
ctxt.SetFrameTitle(fmt.Sprintf("%s: %s%s", topic.Name, flags.String(), summaryLine))
|
||||||
plc := database.AmCreatePostLinkContext("", ctxt.GetScratch("currentAlias").(string), topic.Number)
|
plc := database.AmCreatePostLinkContext("", comm.Id, ctxt.GetScratch("currentAlias").(string), topic.Number)
|
||||||
ctxt.VarMap().Set("post_confRef", plc.AsString())
|
ctxt.VarMap().Set("post_confRef", plc.AsString())
|
||||||
plc.Community = comm.Alias
|
plc.Community = comm.Alias
|
||||||
ctxt.VarMap().Set("post_topicPermalink", fmt.Sprintf("/go/%s", plc.AsString()))
|
ctxt.VarMap().Set("post_topicPermalink", fmt.Sprintf("/go/%s", plc.AsString()))
|
||||||
|
ctxt.VarMap().Set("post_topicLink", fmt.Sprintf("%s/r/%d", ctxt.GetScratch("ConferenceLink"), topic.Number))
|
||||||
plc.FirstPost = postRange[0]
|
plc.FirstPost = postRange[0]
|
||||||
plc.LastPost = postRange[1]
|
plc.LastPost = postRange[1]
|
||||||
ctxt.VarMap().Set("postsPermalink", fmt.Sprintf("/go/%s", plc.AsString()))
|
ctxt.VarMap().Set("postsPermalink", fmt.Sprintf("/go/%s", plc.AsString()))
|
||||||
@@ -631,7 +633,7 @@ func ReadPosts(ctxt ui.AmContext) (string, any) {
|
|||||||
ctxt.VarMap().Set("advancedControls", advancedControls)
|
ctxt.VarMap().Set("advancedControls", advancedControls)
|
||||||
|
|
||||||
// Adjust the traverser and get the "next" link.
|
// Adjust the traverser and get the "next" link.
|
||||||
urlStem := fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, ctxt.GetScratch("currentAlias").(string))
|
urlStem := ctxt.GetScratch("ConferenceLink").(string)
|
||||||
if traverser != nil {
|
if traverser != nil {
|
||||||
traverser.ClearTopic(topic.Number)
|
traverser.ClearTopic(topic.Number)
|
||||||
nextTopic := traverser.NextTopic(topic.Number)
|
nextTopic := traverser.NextTopic(topic.Number)
|
||||||
@@ -686,7 +688,7 @@ func PostInTopic(ctxt ui.AmContext) (string, any) {
|
|||||||
topic := ctxt.GetScratch("currentTopic").(*database.Topic)
|
topic := ctxt.GetScratch("currentTopic").(*database.Topic)
|
||||||
ctxt.VarMap().Set("post_topic", topic)
|
ctxt.VarMap().Set("post_topic", topic)
|
||||||
|
|
||||||
urlStem := fmt.Sprintf("/comm/%s/conf/%s/r/%d", comm.Alias, ctxt.GetScratch("currentAlias"), topic.Number)
|
urlStem := fmt.Sprintf("%s/r/%d", ctxt.GetScratch("ConferenceLink"), topic.Number)
|
||||||
if ctxt.FormFieldIsSet("cancel") {
|
if ctxt.FormFieldIsSet("cancel") {
|
||||||
return "redirect", urlStem
|
return "redirect", urlStem
|
||||||
}
|
}
|
||||||
@@ -734,7 +736,7 @@ func PostInTopic(ctxt ui.AmContext) (string, any) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
checker.SetContext("PostLinkDecoderContext", database.AmCreatePostLinkContext(comm.Alias, ctxt.GetScratch("currentAlias").(string), topic.Number))
|
checker.SetContext("PostLinkDecoderContext", database.AmCreatePostLinkContext(comm.Alias, comm.Id, ctxt.GetScratch("currentAlias").(string), topic.Number))
|
||||||
checker.Append(postdata)
|
checker.Append(postdata)
|
||||||
checker.Finish()
|
checker.Finish()
|
||||||
v, _ = checker.Value()
|
v, _ = checker.Value()
|
||||||
@@ -754,7 +756,7 @@ func PostInTopic(ctxt ui.AmContext) (string, any) {
|
|||||||
} else if ctxt.FormFieldIsSet("postnext") && len(urlNextTopic) > 0 {
|
} else if ctxt.FormFieldIsSet("postnext") && len(urlNextTopic) > 0 {
|
||||||
returnURL = urlNextTopic
|
returnURL = urlNextTopic
|
||||||
} else if ctxt.FormFieldIsSet("posttopics") {
|
} else if ctxt.FormFieldIsSet("posttopics") {
|
||||||
returnURL = fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, ctxt.GetScratch("currentAlias"))
|
returnURL = ctxt.GetScratch("ConferenceLink").(string)
|
||||||
} else {
|
} else {
|
||||||
return "error", EBUTTON
|
return "error", EBUTTON
|
||||||
}
|
}
|
||||||
@@ -772,10 +774,13 @@ func PostInTopic(ctxt ui.AmContext) (string, any) {
|
|||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
|
|
||||||
plc := database.AmCreatePostLinkContext("", ctxt.GetScratch("currentAlias").(string), topic.Number)
|
plc := database.AmCreatePostLinkContext("", comm.Id, ctxt.GetScratch("currentAlias").(string), topic.Number)
|
||||||
ctxt.VarMap().Set("post_confRef", plc.AsString())
|
ctxt.VarMap().Set("post_confRef", plc.AsString())
|
||||||
plc.Community = comm.Alias
|
plc.Community = comm.Alias
|
||||||
ctxt.VarMap().Set("post_topicPermalink", fmt.Sprintf("/go/%s", plc.AsString()))
|
ctxt.VarMap().Set("post_topicPermalink", fmt.Sprintf("/go/%s", plc.AsString()))
|
||||||
|
t := fmt.Sprintf("%s/r/%d", ctxt.GetScratch("ConferenceLink"), topic.Number)
|
||||||
|
ctxt.VarMap().Set("post_topicLink", t)
|
||||||
|
ctxt.VarMap().Set("post_stem", t)
|
||||||
|
|
||||||
ctxt.VarMap().SetFunc("post_getOverrideLine", templateOverrideLine)
|
ctxt.VarMap().SetFunc("post_getOverrideLine", templateOverrideLine)
|
||||||
ctxt.VarMap().SetFunc("post_getOverrideLink", templateOverrideLink)
|
ctxt.VarMap().SetFunc("post_getOverrideLink", templateOverrideLink)
|
||||||
@@ -783,7 +788,6 @@ func PostInTopic(ctxt ui.AmContext) (string, any) {
|
|||||||
ctxt.VarMap().SetFunc("post_getUserName", templateExtractUserName)
|
ctxt.VarMap().SetFunc("post_getUserName", templateExtractUserName)
|
||||||
ctxt.VarMap().SetFunc("post_getAttachmentInfo", templateAttachmentInfo)
|
ctxt.VarMap().SetFunc("post_getAttachmentInfo", templateAttachmentInfo)
|
||||||
ctxt.VarMap().SetFunc("post_isBozo", templateBozo)
|
ctxt.VarMap().SetFunc("post_isBozo", templateBozo)
|
||||||
ctxt.VarMap().Set("post_stem", fmt.Sprintf("/comm/%s/conf/%s/r/%d", comm.Alias, ctxt.GetScratch("currentAlias"), topic.Number))
|
|
||||||
ctxt.VarMap().Set("post_max", topic.TopMessage)
|
ctxt.VarMap().Set("post_max", topic.TopMessage)
|
||||||
ctxt.VarMap().Set("posts", posts)
|
ctxt.VarMap().Set("posts", posts)
|
||||||
ctxt.VarMap().Set("topicName", topic.Name)
|
ctxt.VarMap().Set("topicName", topic.Name)
|
||||||
@@ -805,7 +809,8 @@ func PostInTopic(ctxt ui.AmContext) (string, any) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
checker.SetContext("PostLinkDecoderContext", database.AmCreatePostLinkContext(comm.Alias, ctxt.GetScratch("currentAlias").(string), topic.Number))
|
checker.SetContext("PostLinkDecoderContext", database.AmCreatePostLinkContext(comm.Alias, comm.Id,
|
||||||
|
ctxt.GetScratch("currentAlias").(string), topic.Number))
|
||||||
checker.Append(ctxt.FormField("pb"))
|
checker.Append(ctxt.FormField("pb"))
|
||||||
checker.Finish()
|
checker.Finish()
|
||||||
postText, _ := checker.Value()
|
postText, _ := checker.Value()
|
||||||
|
|||||||
+30
-34
@@ -26,7 +26,7 @@ import (
|
|||||||
"git.erbosoft.com/amy/amsterdam/database"
|
"git.erbosoft.com/amy/amsterdam/database"
|
||||||
"git.erbosoft.com/amy/amsterdam/email"
|
"git.erbosoft.com/amy/amsterdam/email"
|
||||||
"git.erbosoft.com/amy/amsterdam/ui"
|
"git.erbosoft.com/amy/amsterdam/ui"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v5"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ func ConfManage(ctxt ui.AmContext) (string, any) {
|
|||||||
comm := ctxt.CurrentCommunity()
|
comm := ctxt.CurrentCommunity()
|
||||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||||
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
||||||
urlStem := fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, ctxt.GetScratch("currentAlias"))
|
urlStem := ctxt.GetScratch("ConferenceLink").(string)
|
||||||
ctxt.VarMap().Set("confName", conf.Name)
|
ctxt.VarMap().Set("confName", conf.Name)
|
||||||
ctxt.VarMap().Set("urlStem", urlStem)
|
ctxt.VarMap().Set("urlStem", urlStem)
|
||||||
|
|
||||||
@@ -193,14 +193,13 @@ func ConfManage(ctxt ui.AmContext) (string, any) {
|
|||||||
* Data as a parameter for the command string.
|
* Data as a parameter for the command string.
|
||||||
*/
|
*/
|
||||||
func SetPseud(ctxt ui.AmContext) (string, any) {
|
func SetPseud(ctxt ui.AmContext) (string, any) {
|
||||||
comm := ctxt.CurrentCommunity()
|
|
||||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||||
pseud := ctxt.FormField("pseud")
|
pseud := ctxt.FormField("pseud")
|
||||||
err := conf.SetDefaultPseud(ctxt.Ctx(), ctxt.CurrentUser(), pseud)
|
err := conf.SetDefaultPseud(ctxt.Ctx(), ctxt.CurrentUser(), pseud)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s/manage", comm.Alias, ctxt.GetScratch("currentAlias"))
|
return "redirect", fmt.Sprintf("%s/manage", ctxt.GetScratch("ConferenceLink"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ConfFixseen marks all messages in a conference as read.
|
/* ConfFixseen marks all messages in a conference as read.
|
||||||
@@ -211,13 +210,12 @@ func SetPseud(ctxt ui.AmContext) (string, any) {
|
|||||||
* Data as a parameter for the command string.
|
* Data as a parameter for the command string.
|
||||||
*/
|
*/
|
||||||
func ConfFixseen(ctxt ui.AmContext) (string, any) {
|
func ConfFixseen(ctxt ui.AmContext) (string, any) {
|
||||||
comm := ctxt.CurrentCommunity()
|
|
||||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||||
err := conf.Fixseen(ctxt.Ctx(), ctxt.CurrentUser())
|
err := conf.Fixseen(ctxt.Ctx(), ctxt.CurrentUser())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s/manage", comm.Alias, ctxt.GetScratch("currentAlias"))
|
return "redirect", fmt.Sprintf("%s/manage", ctxt.GetScratch("ConferenceLink"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/* AddToHotlist adds the current community and conference to the user's hotlist..
|
/* AddToHotlist adds the current community and conference to the user's hotlist..
|
||||||
@@ -234,7 +232,7 @@ func AddToHotlist(ctxt ui.AmContext) (string, any) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, ctxt.GetScratch("currentAlias"))
|
return "redirect", ctxt.GetScratch("ConferenceLink").(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* HideTopic hides or shows the current topic for the current user.
|
/* HideTopic hides or shows the current topic for the current user.
|
||||||
@@ -254,7 +252,7 @@ func HideTopic(ctxt ui.AmContext) (string, any) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s/r/%d", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias"), topic.Number)
|
return "redirect", fmt.Sprintf("%s/r/%d", ctxt.GetScratch("ConferenceLink"), topic.Number)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* FreezeTopic freezes or unfreezes the current topic.
|
/* FreezeTopic freezes or unfreezes the current topic.
|
||||||
@@ -275,7 +273,7 @@ func FreezeTopic(ctxt ui.AmContext) (string, any) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s/r/%d", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias"), topic.Number)
|
return "redirect", fmt.Sprintf("%s/r/%d", ctxt.GetScratch("ConferenceLink"), topic.Number)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ArchiveTopic archives or unarchives the current topic.
|
/* ArchiveTopic archives or unarchives the current topic.
|
||||||
@@ -296,7 +294,7 @@ func ArchiveTopic(ctxt ui.AmContext) (string, any) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s/r/%d", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias"), topic.Number)
|
return "redirect", fmt.Sprintf("%s/r/%d", ctxt.GetScratch("ConferenceLink"), topic.Number)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* StickTopic sticks or unsticks the current topic.
|
/* StickTopic sticks or unsticks the current topic.
|
||||||
@@ -317,7 +315,7 @@ func StickTopic(ctxt ui.AmContext) (string, any) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s/r/%d", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias"), topic.Number)
|
return "redirect", fmt.Sprintf("%s/r/%d", ctxt.GetScratch("ConferenceLink"), topic.Number)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DeleteTopic deletes the current topic.
|
/* DeleteTopic deletes the current topic.
|
||||||
@@ -348,14 +346,14 @@ func DeleteTopic(ctxt ui.AmContext) (string, any) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias"))
|
return "redirect", ctxt.GetScratch("ConferenceLink").(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up to display the message box.
|
// Set up to display the message box.
|
||||||
mbox.SetMessage(fmt.Sprintf(`You are about to detele the topic <span class="font-bold text-red-600">"%s"</span>
|
mbox.SetMessage(fmt.Sprintf(`You are about to detele the topic <span class="font-bold text-red-600">"%s"</span>
|
||||||
from the <span class="font-bold text-red-600">"%s"</span> conference!`, topic.Name, conf.Name))
|
from the <span class="font-bold text-red-600">"%s"</span> conference!`, topic.Name, conf.Name))
|
||||||
mbox.SetLink("no", fmt.Sprintf("/comm/%s/conf/%s/r/%d", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias"), topic.Number))
|
mbox.SetLink("no", fmt.Sprintf("%s/r/%d", ctxt.GetScratch("ConferenceLink"), topic.Number))
|
||||||
mbox.SetLink("yes", fmt.Sprintf("/comm/%s/conf/%s/op/%d/delete", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias"), topic.Number))
|
mbox.SetLink("yes", fmt.Sprintf("%s/op/%d/delete", ctxt.GetScratch("ConferenceLink"), topic.Number))
|
||||||
return mbox.Render(ctxt)
|
return mbox.Render(ctxt)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,7 +388,7 @@ func HideMessage(ctxt ui.AmContext) (string, any) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s/r/%d?r=%d&ac=1", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias"), topic.Number, hdrs[0].Num)
|
return "redirect", fmt.Sprintf("%s/r/%d?r=%d&ac=1", ctxt.GetScratch("ConferenceLink"), topic.Number, hdrs[0].Num)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ScribbleMessage scribbles a topic message.
|
/* ScribbleMessage scribbles a topic message.
|
||||||
@@ -424,7 +422,7 @@ func ScribbleMessage(ctxt ui.AmContext) (string, any) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s/r/%d?r=%d&ac=1", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias"), topic.Number, hdrs[0].Num)
|
return "redirect", fmt.Sprintf("%s/r/%d?r=%d&ac=1", ctxt.GetScratch("ConferenceLink"), topic.Number, hdrs[0].Num)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NukeMessage nukes (deletes entirely) a topic message.
|
/* NukeMessage nukes (deletes entirely) a topic message.
|
||||||
@@ -466,11 +464,11 @@ func NukeMessage(ctxt ui.AmContext) (string, any) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s/r/%d", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias"), topic.Number)
|
return "redirect", fmt.Sprintf("%s/r/%d", ctxt.GetScratch("ConferenceLink"), topic.Number)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up to display the message box.
|
// Set up to display the message box.
|
||||||
link, err := hdrs[0].Link(ctxt.Ctx(), "community")
|
link, err := hdrs[0].Link(ctxt.Ctx(), ctxt.CurrentCommunity().Id, "community")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
@@ -480,8 +478,8 @@ func NukeMessage(ctxt ui.AmContext) (string, any) {
|
|||||||
}
|
}
|
||||||
mbox.SetMessage(fmt.Sprintf(`You are about to nuke message <span class="font-mono font-bold text-red-600"><%s></span>,
|
mbox.SetMessage(fmt.Sprintf(`You are about to nuke message <span class="font-mono font-bold text-red-600"><%s></span>,
|
||||||
originally composed by <span class="font-bold text-red-600"><%s></span>!`, link, creator.Username))
|
originally composed by <span class="font-bold text-red-600"><%s></span>!`, link, creator.Username))
|
||||||
mbox.SetLink("no", fmt.Sprintf("/comm/%s/conf/%s/r/%d?r=%d&ac=1", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias"), topic.Number, hdrs[0].Num))
|
mbox.SetLink("no", fmt.Sprintf("%s/r/%d?r=%d&ac=1", ctxt.GetScratch("ConferenceLink"), topic.Number, hdrs[0].Num))
|
||||||
mbox.SetLink("yes", fmt.Sprintf("/comm/%s/conf/%s/op/%d/nuke/%d", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias"), topic.Number, hdrs[0].Num))
|
mbox.SetLink("yes", fmt.Sprintf("%s/op/%d/nuke/%d", ctxt.GetScratch("ConferenceLink"), topic.Number, hdrs[0].Num))
|
||||||
return mbox.Render(ctxt)
|
return mbox.Render(ctxt)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -524,11 +522,11 @@ func PruneMessageAttachment(ctxt ui.AmContext) (string, any) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s/r/%d?r=%d&ac=1", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias"), topic.Number, hdrs[0].Num)
|
return "redirect", fmt.Sprintf("%s/r/%d?r=%d&ac=1", ctxt.GetScratch("ConferenceLink"), topic.Number, hdrs[0].Num)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up to display the message box.
|
// Set up to display the message box.
|
||||||
link, err := hdrs[0].Link(ctxt.Ctx(), "community")
|
link, err := hdrs[0].Link(ctxt.Ctx(), ctxt.CurrentCommunity().Id, "community")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
@@ -538,8 +536,8 @@ func PruneMessageAttachment(ctxt ui.AmContext) (string, any) {
|
|||||||
}
|
}
|
||||||
mbox.SetMessage(fmt.Sprintf(`You are about to prune the attachment of message <span class="font-mono font-bold text-red-600"><%s></span>,
|
mbox.SetMessage(fmt.Sprintf(`You are about to prune the attachment of message <span class="font-mono font-bold text-red-600"><%s></span>,
|
||||||
originally composed by <span class="font-bold text-red-600"><%s></span>!`, link, creator.Username))
|
originally composed by <span class="font-bold text-red-600"><%s></span>!`, link, creator.Username))
|
||||||
mbox.SetLink("no", fmt.Sprintf("/comm/%s/conf/%s/r/%d?r=%d&ac=1", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias"), topic.Number, hdrs[0].Num))
|
mbox.SetLink("no", fmt.Sprintf("%s/r/%d?r=%d&ac=1", ctxt.GetScratch("ConferenceLink"), topic.Number, hdrs[0].Num))
|
||||||
mbox.SetLink("yes", fmt.Sprintf("/comm/%s/conf/%s/op/%d/prune/%d", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias"), topic.Number, hdrs[0].Num))
|
mbox.SetLink("yes", fmt.Sprintf("%s/op/%d/prune/%d", ctxt.GetScratch("ConferenceLink"), topic.Number, hdrs[0].Num))
|
||||||
return mbox.Render(ctxt)
|
return mbox.Render(ctxt)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,7 +589,7 @@ func MoveMessageForm(ctxt ui.AmContext) (string, any) {
|
|||||||
|
|
||||||
ctxt.VarMap().Set("post", hdrs[0])
|
ctxt.VarMap().Set("post", hdrs[0])
|
||||||
ctxt.VarMap().Set("topMessage", topic.TopMessage)
|
ctxt.VarMap().Set("topMessage", topic.TopMessage)
|
||||||
formLink := fmt.Sprintf("/comm/%s/conf/%s/op/%d/move/%d", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias"), topic.Number, hdrs[0].Num)
|
formLink := fmt.Sprintf("%s/op/%d/move/%d", ctxt.GetScratch("ConferenceLink"), topic.Number, hdrs[0].Num)
|
||||||
ctxt.VarMap().Set("formLink", formLink)
|
ctxt.VarMap().Set("formLink", formLink)
|
||||||
ctxt.SetFrameTitle("Move Message")
|
ctxt.SetFrameTitle("Move Message")
|
||||||
|
|
||||||
@@ -625,7 +623,7 @@ func PublishMessage(ctxt ui.AmContext) (string, any) {
|
|||||||
if err = hdrs[0].Publish(ctxt.Ctx(), comm, ctxt.CurrentUser(), ctxt.RemoteIP()); err != nil {
|
if err = hdrs[0].Publish(ctxt.Ctx(), comm, ctxt.CurrentUser(), ctxt.RemoteIP()); err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s/r/%d?r=%d&ac=1", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias"), topic.Number, hdrs[0].Num)
|
return "redirect", fmt.Sprintf("%s/r/%d?r=%d&ac=1", ctxt.GetScratch("ConferenceLink"), topic.Number, hdrs[0].Num)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* MoveMessage moves a message to a different topic.
|
/* MoveMessage moves a message to a different topic.
|
||||||
@@ -654,7 +652,7 @@ func MoveMessage(ctxt ui.AmContext) (string, any) {
|
|||||||
return "error", EPOSTREF
|
return "error", EPOSTREF
|
||||||
}
|
}
|
||||||
if ctxt.FormFieldIsSet("cancel") {
|
if ctxt.FormFieldIsSet("cancel") {
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s/r/%d?r=%d&ac=1", comm.Alias, ctxt.GetScratch("currentAlias"), topic.Number, hdrs[0].Num)
|
return "redirect", fmt.Sprintf("%s/r/%d?r=%d&ac=1", ctxt.GetScratch("ConferenceLink"), topic.Number, hdrs[0].Num)
|
||||||
}
|
}
|
||||||
if !conf.TestPermission("Conference.Nuke", myLevel) || !conf.TestPermission("Conference.Post", myLevel) || topic.TopMessage == 0 {
|
if !conf.TestPermission("Conference.Nuke", myLevel) || !conf.TestPermission("Conference.Post", myLevel) || topic.TopMessage == 0 {
|
||||||
return "error", ENOPERM
|
return "error", ENOPERM
|
||||||
@@ -696,7 +694,7 @@ func MoveMessage(ctxt ui.AmContext) (string, any) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s/r/%d", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias"), topic.Number)
|
return "redirect", fmt.Sprintf("%s/r/%d", ctxt.GetScratch("ConferenceLink"), topic.Number)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TopicManage displays the "manage topic" page.
|
/* TopicManage displays the "manage topic" page.
|
||||||
@@ -709,8 +707,8 @@ func MoveMessage(ctxt ui.AmContext) (string, any) {
|
|||||||
func TopicManage(ctxt ui.AmContext) (string, any) {
|
func TopicManage(ctxt ui.AmContext) (string, any) {
|
||||||
comm := ctxt.CurrentCommunity()
|
comm := ctxt.CurrentCommunity()
|
||||||
topic := ctxt.GetScratch("currentTopic").(*database.Topic)
|
topic := ctxt.GetScratch("currentTopic").(*database.Topic)
|
||||||
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("%s/r/%d", ctxt.GetScratch("ConferenceLink"), topic.Number))
|
||||||
opsLink := fmt.Sprintf("/comm/%s/conf/%s/op/%d", comm.Alias, ctxt.GetScratch("currentAlias"), topic.Number)
|
opsLink := fmt.Sprintf("%s/op/%d", ctxt.GetScratch("ConferenceLink"), topic.Number)
|
||||||
ctxt.VarMap().Set("opsLink", opsLink)
|
ctxt.VarMap().Set("opsLink", opsLink)
|
||||||
ctxt.VarMap().Set("topicName", topic.Name)
|
ctxt.VarMap().Set("topicName", topic.Name)
|
||||||
|
|
||||||
@@ -750,7 +748,6 @@ func TopicSetSubscribe(ctxt ui.AmContext) (string, any) {
|
|||||||
if ctxt.CurrentUser().IsAnon {
|
if ctxt.CurrentUser().IsAnon {
|
||||||
return "error", ENOPERM
|
return "error", ENOPERM
|
||||||
}
|
}
|
||||||
comm := ctxt.CurrentCommunity()
|
|
||||||
topic := ctxt.GetScratch("currentTopic").(*database.Topic)
|
topic := ctxt.GetScratch("currentTopic").(*database.Topic)
|
||||||
flag, err := topic.IsSubscribed(ctxt.Ctx(), ctxt.CurrentUser())
|
flag, err := topic.IsSubscribed(ctxt.Ctx(), ctxt.CurrentUser())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -760,7 +757,7 @@ func TopicSetSubscribe(ctxt ui.AmContext) (string, any) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s/op/%d/manage", comm.Alias, ctxt.GetScratch("currentAlias"), topic.Number)
|
return "redirect", fmt.Sprintf("%s/op/%d/manage", ctxt.GetScratch("ConferenceLink"), topic.Number)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TopicRemoveBozo removes filtering from a specified user in the topic.
|
/* TopicRemoveBozo removes filtering from a specified user in the topic.
|
||||||
@@ -771,7 +768,6 @@ func TopicSetSubscribe(ctxt ui.AmContext) (string, any) {
|
|||||||
* Data as a parameter for the command string.
|
* Data as a parameter for the command string.
|
||||||
*/
|
*/
|
||||||
func TopicRemoveBozo(ctxt ui.AmContext) (string, any) {
|
func TopicRemoveBozo(ctxt ui.AmContext) (string, any) {
|
||||||
comm := ctxt.CurrentCommunity()
|
|
||||||
topic := ctxt.GetScratch("currentTopic").(*database.Topic)
|
topic := ctxt.GetScratch("currentTopic").(*database.Topic)
|
||||||
bozoUid, err := strconv.Atoi(ctxt.URLParam("uid"))
|
bozoUid, err := strconv.Atoi(ctxt.URLParam("uid"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -781,5 +777,5 @@ func TopicRemoveBozo(ctxt ui.AmContext) (string, any) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s/op/%d/manage", comm.Alias, ctxt.GetScratch("currentAlias"), topic.Number)
|
return "redirect", fmt.Sprintf("%s/op/%d/manage", ctxt.GetScratch("ConferenceLink"), topic.Number)
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-46
@@ -99,7 +99,7 @@ func EditConference(ctxt ui.AmContext) (string, any) {
|
|||||||
}
|
}
|
||||||
button := dlg.WhichButton(ctxt)
|
button := dlg.WhichButton(ctxt)
|
||||||
if button == "cancel" {
|
if button == "cancel" {
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s/manage", comm.Alias, ctxt.GetScratch("currentAlias"))
|
return "redirect", fmt.Sprintf("%s/manage", ctxt.GetScratch("ConferenceLink"))
|
||||||
} else if button != "update" {
|
} else if button != "update" {
|
||||||
dlg.SetCommunity(comm)
|
dlg.SetCommunity(comm)
|
||||||
dlg.SetConference(conf, ctxt.GetScratch("currentAlias").(string))
|
dlg.SetConference(conf, ctxt.GetScratch("currentAlias").(string))
|
||||||
@@ -128,7 +128,7 @@ func EditConference(ctxt ui.AmContext) (string, any) {
|
|||||||
return dlg.RenderError(ctxt, err.Error())
|
return dlg.RenderError(ctxt, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s/manage", comm.Alias, ctxt.GetScratch("currentAlias"))
|
return "redirect", fmt.Sprintf("%s/manage", ctxt.GetScratch("ConferenceLink"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ConferenceAliasForm displays the form for managing conference aliases.
|
/* ConferenceAliasForm displays the form for managing conference aliases.
|
||||||
@@ -148,8 +148,8 @@ func ConferenceAliasForm(ctxt ui.AmContext) (string, any) {
|
|||||||
|
|
||||||
ctxt.VarMap().Set("newAlias", "")
|
ctxt.VarMap().Set("newAlias", "")
|
||||||
ctxt.VarMap().Set("confName", conf.Name)
|
ctxt.VarMap().Set("confName", conf.Name)
|
||||||
ctxt.VarMap().Set("backLink", fmt.Sprintf("/comm/%s/conf/%s/manage", comm.Alias, ctxt.GetScratch("currentAlias")))
|
ctxt.VarMap().Set("backLink", fmt.Sprintf("%s/manage", ctxt.GetScratch("ConferenceLink")))
|
||||||
ctxt.VarMap().Set("selfLink", fmt.Sprintf("/comm/%s/conf/%s/aliases", comm.Alias, ctxt.GetScratch("currentAlias")))
|
ctxt.VarMap().Set("selfLink", fmt.Sprintf("%s/aliases", ctxt.GetScratch("ConferenceLink")))
|
||||||
ctxt.SetFrameTitle(fmt.Sprintf("Manage Conference Aliases: %s", conf.Name))
|
ctxt.SetFrameTitle(fmt.Sprintf("Manage Conference Aliases: %s", conf.Name))
|
||||||
|
|
||||||
if ctxt.HasParameter("del") {
|
if ctxt.HasParameter("del") {
|
||||||
@@ -159,7 +159,7 @@ func ConferenceAliasForm(ctxt ui.AmContext) (string, any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
aliases, err := conf.Aliases(ctxt.Ctx())
|
aliases, err := conf.Aliases(ctxt.Ctx(), comm.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
@@ -184,8 +184,8 @@ func ConferenceAliasAdd(ctxt ui.AmContext) (string, any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctxt.VarMap().Set("confName", conf.Name)
|
ctxt.VarMap().Set("confName", conf.Name)
|
||||||
ctxt.VarMap().Set("backLink", fmt.Sprintf("/comm/%s/conf/%s/manage", comm.Alias, ctxt.GetScratch("currentAlias")))
|
ctxt.VarMap().Set("backLink", fmt.Sprintf("%s/manage", ctxt.GetScratch("ConferenceLink")))
|
||||||
ctxt.VarMap().Set("selfLink", fmt.Sprintf("/comm/%s/conf/%s/aliases", comm.Alias, ctxt.GetScratch("currentAlias")))
|
ctxt.VarMap().Set("selfLink", fmt.Sprintf("%s/aliases", ctxt.GetScratch("ConferenceLink")))
|
||||||
ctxt.SetFrameTitle(fmt.Sprintf("Manage Conference Aliases: %s", conf.Name))
|
ctxt.SetFrameTitle(fmt.Sprintf("Manage Conference Aliases: %s", conf.Name))
|
||||||
|
|
||||||
newAlias := ctxt.FormField("na")
|
newAlias := ctxt.FormField("na")
|
||||||
@@ -206,7 +206,7 @@ func ConferenceAliasAdd(ctxt ui.AmContext) (string, any) {
|
|||||||
ctxt.VarMap().Set("errorMessage", err.Error())
|
ctxt.VarMap().Set("errorMessage", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
aliases, err := conf.Aliases(ctxt.Ctx())
|
aliases, err := conf.Aliases(ctxt.Ctx(), comm.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
@@ -241,8 +241,8 @@ func ConferenceMembers(ctxt ui.AmContext) (string, any) {
|
|||||||
// Set the first batch of page variables.
|
// Set the first batch of page variables.
|
||||||
ctxt.VarMap().Set("commName", comm.Name)
|
ctxt.VarMap().Set("commName", comm.Name)
|
||||||
ctxt.VarMap().Set("confName", conf.Name)
|
ctxt.VarMap().Set("confName", conf.Name)
|
||||||
ctxt.VarMap().Set("backLink", fmt.Sprintf("/comm/%s/conf/%s/manage", comm.Alias, ctxt.GetScratch("currentAlias")))
|
ctxt.VarMap().Set("backLink", fmt.Sprintf("%s/manage", ctxt.GetScratch("ConferenceLink")))
|
||||||
ctxt.VarMap().Set("selfLink", fmt.Sprintf("/comm/%s/conf/%s/members", comm.Alias, ctxt.GetScratch("currentAlias")))
|
ctxt.VarMap().Set("selfLink", fmt.Sprintf("%s/members", ctxt.GetScratch("ConferenceLink")))
|
||||||
ctxt.VarMap().Set("roleList", database.AmRoleList("Conference.UserLevels"))
|
ctxt.VarMap().Set("roleList", database.AmRoleList("Conference.UserLevels"))
|
||||||
ctxt.SetFrameTitle(fmt.Sprintf("Membership in Conference: %s", conf.Name))
|
ctxt.SetFrameTitle(fmt.Sprintf("Membership in Conference: %s", conf.Name))
|
||||||
|
|
||||||
@@ -381,7 +381,6 @@ func ConferenceMembers(ctxt ui.AmContext) (string, any) {
|
|||||||
* Data as a parameter for the command string.
|
* Data as a parameter for the command string.
|
||||||
*/
|
*/
|
||||||
func ConfCustomForm(ctxt ui.AmContext) (string, any) {
|
func ConfCustomForm(ctxt ui.AmContext) (string, any) {
|
||||||
comm := ctxt.CurrentCommunity()
|
|
||||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||||
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
||||||
if !conf.TestPermission("Conference.Change", myLevel) {
|
if !conf.TestPermission("Conference.Change", myLevel) {
|
||||||
@@ -394,7 +393,7 @@ func ConfCustomForm(ctxt ui.AmContext) (string, any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctxt.VarMap().Set("confName", conf.Name)
|
ctxt.VarMap().Set("confName", conf.Name)
|
||||||
ctxt.VarMap().Set("selfLink", fmt.Sprintf("/comm/%s/conf/%s/custom", comm.Alias, ctxt.GetScratch("currentAlias")))
|
ctxt.VarMap().Set("selfLink", fmt.Sprintf("%s/custom", ctxt.GetScratch("ConferenceLink")))
|
||||||
ctxt.VarMap().Set("topText", topBlock)
|
ctxt.VarMap().Set("topText", topBlock)
|
||||||
ctxt.VarMap().Set("bottomText", bottomBlock)
|
ctxt.VarMap().Set("bottomText", bottomBlock)
|
||||||
ctxt.SetFrameTitle(fmt.Sprintf("Customize Conference: %s", conf.Name))
|
ctxt.SetFrameTitle(fmt.Sprintf("Customize Conference: %s", conf.Name))
|
||||||
@@ -409,7 +408,6 @@ func ConfCustomForm(ctxt ui.AmContext) (string, any) {
|
|||||||
* Data as a parameter for the command string.
|
* Data as a parameter for the command string.
|
||||||
*/
|
*/
|
||||||
func ConfCustom(ctxt ui.AmContext) (string, any) {
|
func ConfCustom(ctxt ui.AmContext) (string, any) {
|
||||||
comm := ctxt.CurrentCommunity()
|
|
||||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||||
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
||||||
if !conf.TestPermission("Conference.Change", myLevel) {
|
if !conf.TestPermission("Conference.Change", myLevel) {
|
||||||
@@ -429,7 +427,7 @@ func ConfCustom(ctxt ui.AmContext) (string, any) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s/manage", comm.Alias, ctxt.GetScratch("currentAlias"))
|
return "redirect", fmt.Sprintf("%s/manage", ctxt.GetScratch("ConferenceLink"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ConfReports displays conference activity reports.
|
/* ConfReports displays conference activity reports.
|
||||||
@@ -440,7 +438,6 @@ func ConfCustom(ctxt ui.AmContext) (string, any) {
|
|||||||
* Data as a parameter for the command string.
|
* Data as a parameter for the command string.
|
||||||
*/
|
*/
|
||||||
func ConfReports(ctxt ui.AmContext) (string, any) {
|
func ConfReports(ctxt ui.AmContext) (string, any) {
|
||||||
comm := ctxt.CurrentCommunity()
|
|
||||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||||
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
||||||
if !conf.TestPermission("Conference.Read", myLevel) {
|
if !conf.TestPermission("Conference.Read", myLevel) {
|
||||||
@@ -448,7 +445,7 @@ func ConfReports(ctxt ui.AmContext) (string, any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctxt.VarMap().Set("confName", conf.Name)
|
ctxt.VarMap().Set("confName", conf.Name)
|
||||||
ctxt.VarMap().Set("selfLink", fmt.Sprintf("/comm/%s/conf/%s/activity", comm.Alias, ctxt.GetScratch("currentAlias")))
|
ctxt.VarMap().Set("selfLink", fmt.Sprintf("%s/activity", ctxt.GetScratch("ConferenceLink")))
|
||||||
|
|
||||||
if ctxt.HasParameter("r") {
|
if ctxt.HasParameter("r") {
|
||||||
// generate a report
|
// generate a report
|
||||||
@@ -504,7 +501,7 @@ func ConfReports(ctxt ui.AmContext) (string, any) {
|
|||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
ctxt.VarMap().Set("topics", topicList)
|
ctxt.VarMap().Set("topics", topicList)
|
||||||
ctxt.VarMap().Set("backLink", fmt.Sprintf("/comm/%s/conf/%s/manage", comm.Alias, ctxt.GetScratch("currentAlias")))
|
ctxt.VarMap().Set("backLink", fmt.Sprintf("%s/manage", ctxt.GetScratch("ConferenceLink")))
|
||||||
ctxt.SetFrameTitle(fmt.Sprintf("Conference Reports: %s", conf.Name))
|
ctxt.SetFrameTitle(fmt.Sprintf("Conference Reports: %s", conf.Name))
|
||||||
return "framed", "conf_reports.jet"
|
return "framed", "conf_reports.jet"
|
||||||
}
|
}
|
||||||
@@ -518,7 +515,6 @@ func ConfReports(ctxt ui.AmContext) (string, any) {
|
|||||||
* Data as a parameter for the command string.
|
* Data as a parameter for the command string.
|
||||||
*/
|
*/
|
||||||
func ConferenceEmailForm(ctxt ui.AmContext) (string, any) {
|
func ConferenceEmailForm(ctxt ui.AmContext) (string, any) {
|
||||||
comm := ctxt.CurrentCommunity()
|
|
||||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||||
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
||||||
if !conf.TestPermission("Conference.EMailParticipants", myLevel) {
|
if !conf.TestPermission("Conference.EMailParticipants", myLevel) {
|
||||||
@@ -531,7 +527,7 @@ func ConferenceEmailForm(ctxt ui.AmContext) (string, any) {
|
|||||||
}
|
}
|
||||||
ctxt.VarMap().Set("topics", topics)
|
ctxt.VarMap().Set("topics", topics)
|
||||||
ctxt.VarMap().Set("confName", conf.Name)
|
ctxt.VarMap().Set("confName", conf.Name)
|
||||||
ctxt.VarMap().Set("selfLink", fmt.Sprintf("/comm/%s/conf/%s/email", comm.Alias, ctxt.GetScratch("currentAlias")))
|
ctxt.VarMap().Set("selfLink", fmt.Sprintf("%s/email", ctxt.GetScratch("ConferenceLink")))
|
||||||
ctxt.VarMap().Set("porl", 0).Set("top", 0).Set("xday", false)
|
ctxt.VarMap().Set("porl", 0).Set("top", 0).Set("xday", false)
|
||||||
ctxt.VarMap().Set("day", 7).Set("subj", "").Set("pb", "")
|
ctxt.VarMap().Set("day", 7).Set("subj", "").Set("pb", "")
|
||||||
ctxt.SetFrameTitle(fmt.Sprintf("Conference E-Mail: %s", conf.Name))
|
ctxt.SetFrameTitle(fmt.Sprintf("Conference E-Mail: %s", conf.Name))
|
||||||
@@ -555,7 +551,7 @@ func ConferenceEmail(ctxt ui.AmContext) (string, any) {
|
|||||||
|
|
||||||
// Handle button presses.
|
// Handle button presses.
|
||||||
if ctxt.FormFieldIsSet("cancel") {
|
if ctxt.FormFieldIsSet("cancel") {
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s/manage", comm.Alias, ctxt.GetScratch("currentAlias"))
|
return "redirect", fmt.Sprintf("%s/manage", ctxt.GetScratch("ConferenceLink"))
|
||||||
} else if !ctxt.FormFieldIsSet("send") {
|
} else if !ctxt.FormFieldIsSet("send") {
|
||||||
return "error", EBUTTON
|
return "error", EBUTTON
|
||||||
}
|
}
|
||||||
@@ -646,7 +642,7 @@ func ConferenceEmail(ctxt ui.AmContext) (string, any) {
|
|||||||
log.Infof("ConferenceEmail delivery completed in %s", elapsed)
|
log.Infof("ConferenceEmail delivery completed in %s", elapsed)
|
||||||
})
|
})
|
||||||
|
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s/manage", comm.Alias, ctxt.GetScratch("currentAlias"))
|
return "redirect", fmt.Sprintf("%s/manage", ctxt.GetScratch("ConferenceLink"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ConferenceExportForm displays the form for exporting data from a conference.
|
/* ConferenceExportForm displays the form for exporting data from a conference.
|
||||||
@@ -657,7 +653,6 @@ func ConferenceEmail(ctxt ui.AmContext) (string, any) {
|
|||||||
* Data as a parameter for the command string.
|
* Data as a parameter for the command string.
|
||||||
*/
|
*/
|
||||||
func ConferenceExportForm(ctxt ui.AmContext) (string, any) {
|
func ConferenceExportForm(ctxt ui.AmContext) (string, any) {
|
||||||
comm := ctxt.CurrentCommunity()
|
|
||||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||||
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
||||||
if !conf.TestPermission("Conference.Change", myLevel) {
|
if !conf.TestPermission("Conference.Change", myLevel) {
|
||||||
@@ -671,7 +666,7 @@ func ConferenceExportForm(ctxt ui.AmContext) (string, any) {
|
|||||||
|
|
||||||
ctxt.VarMap().Set("topics", topics)
|
ctxt.VarMap().Set("topics", topics)
|
||||||
ctxt.VarMap().Set("confName", conf.Name)
|
ctxt.VarMap().Set("confName", conf.Name)
|
||||||
ctxt.VarMap().Set("selfLink", fmt.Sprintf("/comm/%s/conf/%s/export", comm.Alias, ctxt.GetScratch("currentAlias")))
|
ctxt.VarMap().Set("selfLink", fmt.Sprintf("%s/export", ctxt.GetScratch("ConferenceLink")))
|
||||||
ctxt.SetFrameTitle(fmt.Sprintf("Export Messages: %s", conf.Name))
|
ctxt.SetFrameTitle(fmt.Sprintf("Export Messages: %s", conf.Name))
|
||||||
return "framed", "conf_export.jet"
|
return "framed", "conf_export.jet"
|
||||||
}
|
}
|
||||||
@@ -684,7 +679,6 @@ func ConferenceExportForm(ctxt ui.AmContext) (string, any) {
|
|||||||
* Data as a parameter for the command string.
|
* Data as a parameter for the command string.
|
||||||
*/
|
*/
|
||||||
func ConferenceExport(ctxt ui.AmContext) (string, any) {
|
func ConferenceExport(ctxt ui.AmContext) (string, any) {
|
||||||
comm := ctxt.CurrentCommunity()
|
|
||||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||||
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
||||||
if !conf.TestPermission("Conference.Change", myLevel) {
|
if !conf.TestPermission("Conference.Change", myLevel) {
|
||||||
@@ -692,7 +686,7 @@ func ConferenceExport(ctxt ui.AmContext) (string, any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ctxt.FormFieldIsSet("cancel") {
|
if ctxt.FormFieldIsSet("cancel") {
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s/manage", comm.Alias, ctxt.GetScratch("currentAlias"))
|
return "redirect", fmt.Sprintf("%s/manage", ctxt.GetScratch("ConferenceLink"))
|
||||||
} else if !ctxt.FormFieldIsSet("export") {
|
} else if !ctxt.FormFieldIsSet("export") {
|
||||||
return "error", EBUTTON
|
return "error", EBUTTON
|
||||||
}
|
}
|
||||||
@@ -765,15 +759,16 @@ func ConferenceImport(ctxt ui.AmContext) (string, any) {
|
|||||||
return "error", ENOPERM
|
return "error", ENOPERM
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctxt.VarMap().Set("confName", conf.Name)
|
||||||
|
ctxt.VarMap().Set("selfLink", fmt.Sprintf("%s/import", ctxt.GetScratch("ConferenceLink")))
|
||||||
|
ctxt.SetFrameTitle("Import Messages: " + conf.Name)
|
||||||
|
|
||||||
if ctxt.Verb() == "GET" {
|
if ctxt.Verb() == "GET" {
|
||||||
ctxt.VarMap().Set("confName", conf.Name)
|
|
||||||
ctxt.VarMap().Set("selfLink", fmt.Sprintf("/comm/%s/conf/%s/import", comm.Alias, ctxt.GetScratch("currentAlias")))
|
|
||||||
ctxt.SetFrameTitle("Import Messages: " + conf.Name)
|
|
||||||
return "framed", "conf_import.jet"
|
return "framed", "conf_import.jet"
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctxt.FormFieldIsSet("cancel") {
|
if ctxt.FormFieldIsSet("cancel") {
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s/manage", comm.Alias, ctxt.GetScratch("currentAlias"))
|
return "redirect", fmt.Sprintf("%s/manage", ctxt.GetScratch("ConferenceLink"))
|
||||||
} else if !ctxt.FormFieldIsSet("import") {
|
} else if !ctxt.FormFieldIsSet("import") {
|
||||||
return "error", EBUTTON
|
return "error", EBUTTON
|
||||||
}
|
}
|
||||||
@@ -786,27 +781,18 @@ func ConferenceImport(ctxt ui.AmContext) (string, any) {
|
|||||||
mode = exports.VCIFTopicMatchNum
|
mode = exports.VCIFTopicMatchNum
|
||||||
default:
|
default:
|
||||||
ctxt.VarMap().Set("errorMessage", "Invalid matching parameter.")
|
ctxt.VarMap().Set("errorMessage", "Invalid matching parameter.")
|
||||||
ctxt.VarMap().Set("confName", conf.Name)
|
|
||||||
ctxt.VarMap().Set("selfLink", fmt.Sprintf("/comm/%s/conf/%s/import", comm.Alias, ctxt.GetScratch("currentAlias")))
|
|
||||||
ctxt.SetFrameTitle("Import Messages: " + conf.Name)
|
|
||||||
return "framed", "conf_import.jet"
|
return "framed", "conf_import.jet"
|
||||||
}
|
}
|
||||||
|
|
||||||
importData, err := ctxt.FormFile("idata")
|
importData, err := ctxt.FormFile("idata")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctxt.VarMap().Set("errorMessage", err.Error())
|
ctxt.VarMap().Set("errorMessage", err.Error())
|
||||||
ctxt.VarMap().Set("confName", conf.Name)
|
|
||||||
ctxt.VarMap().Set("selfLink", fmt.Sprintf("/comm/%s/conf/%s/import", comm.Alias, ctxt.GetScratch("currentAlias")))
|
|
||||||
ctxt.SetFrameTitle("Import Messages: " + conf.Name)
|
|
||||||
return "framed", "conf_import.jet"
|
return "framed", "conf_import.jet"
|
||||||
}
|
}
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
f, err := importData.Open()
|
f, err := importData.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctxt.VarMap().Set("errorMessage", err.Error())
|
ctxt.VarMap().Set("errorMessage", err.Error())
|
||||||
ctxt.VarMap().Set("confName", conf.Name)
|
|
||||||
ctxt.VarMap().Set("selfLink", fmt.Sprintf("/comm/%s/conf/%s/import", comm.Alias, ctxt.GetScratch("currentAlias")))
|
|
||||||
ctxt.SetFrameTitle("Import Messages: " + conf.Name)
|
|
||||||
return "framed", "conf_import.jet"
|
return "framed", "conf_import.jet"
|
||||||
}
|
}
|
||||||
topics, posts, scroll, err := exports.VCIFImportMessages(ctxt.Ctx(), f, comm, conf, mode, ctxt.FormFieldIsSet("create"), ctxt.CurrentUser(), ctxt.RemoteIP())
|
topics, posts, scroll, err := exports.VCIFImportMessages(ctxt.Ctx(), f, comm, conf, mode, ctxt.FormFieldIsSet("create"), ctxt.CurrentUser(), ctxt.RemoteIP())
|
||||||
@@ -814,13 +800,10 @@ func ConferenceImport(ctxt ui.AmContext) (string, any) {
|
|||||||
log.Infof("import messages operation completed in %v", time.Since(start))
|
log.Infof("import messages operation completed in %v", time.Since(start))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctxt.VarMap().Set("errorMessage", err.Error())
|
ctxt.VarMap().Set("errorMessage", err.Error())
|
||||||
ctxt.VarMap().Set("confName", conf.Name)
|
|
||||||
ctxt.VarMap().Set("selfLink", fmt.Sprintf("/comm/%s/conf/%s/import", comm.Alias, ctxt.GetScratch("currentAlias")))
|
|
||||||
ctxt.SetFrameTitle("Import Messages: " + conf.Name)
|
|
||||||
return "framed", "conf_import.jet"
|
return "framed", "conf_import.jet"
|
||||||
}
|
}
|
||||||
|
|
||||||
ctxt.VarMap().Set("backLink", fmt.Sprintf("/comm/%s/conf/%s/manage", comm.Alias, ctxt.GetScratch("currentAlias")))
|
ctxt.VarMap().Set("backLink", fmt.Sprintf("%s/manage", ctxt.GetScratch("ConferenceLink")))
|
||||||
ctxt.VarMap().Set("headline", fmt.Sprintf("Processed %d topic(s) and added %d new post(s).", topics, posts))
|
ctxt.VarMap().Set("headline", fmt.Sprintf("Processed %d topic(s) and added %d new post(s).", topics, posts))
|
||||||
ctxt.VarMap().Set("scroll", scroll)
|
ctxt.VarMap().Set("scroll", scroll)
|
||||||
ctxt.SetFrameTitle("Import Results")
|
ctxt.SetFrameTitle("Import Results")
|
||||||
@@ -858,8 +841,8 @@ func DeleteConference(ctxt ui.AmContext) (string, any) {
|
|||||||
// Set up to display the message box.
|
// Set up to display the message box.
|
||||||
mbox.SetMessage(fmt.Sprintf(`You are about to delete the conference <span class="font-bold text-red-600">"%s"</span>
|
mbox.SetMessage(fmt.Sprintf(`You are about to delete the conference <span class="font-bold text-red-600">"%s"</span>
|
||||||
from the <span class="font-bold text-red-600">"%s"</span> community!`, conf.Name, comm.Name))
|
from the <span class="font-bold text-red-600">"%s"</span> community!`, conf.Name, comm.Name))
|
||||||
mbox.SetLink("no", fmt.Sprintf("/comm/%s/conf/%s/manage", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias")))
|
mbox.SetLink("no", fmt.Sprintf("%s/manage", ctxt.GetScratch("ConferenceLink")))
|
||||||
mbox.SetLink("yes", fmt.Sprintf("/comm/%s/conf/%s/delete", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias")))
|
mbox.SetLink("yes", fmt.Sprintf("%s/delete", ctxt.GetScratch("ConferenceLink")))
|
||||||
return mbox.Render(ctxt)
|
return mbox.Render(ctxt)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -901,9 +884,12 @@ func CreateConference(ctxt ui.AmContext) (string, any) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
|
var urlbuf strings.Builder
|
||||||
|
urlbuf.WriteString(ctxt.GetScratch("CommunityLink").(string))
|
||||||
|
urlbuf.WriteString("/conf")
|
||||||
button := dlg.WhichButton(ctxt)
|
button := dlg.WhichButton(ctxt)
|
||||||
if button == "cancel" {
|
if button == "cancel" {
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf", comm.Alias)
|
return "redirect", urlbuf.String()
|
||||||
} else if button != "create" {
|
} else if button != "create" {
|
||||||
dlg.SetCommunity(comm)
|
dlg.SetCommunity(comm)
|
||||||
return dlg.RenderError(ctxt, "invalid button pressed")
|
return dlg.RenderError(ctxt, "invalid button pressed")
|
||||||
@@ -917,7 +903,9 @@ func CreateConference(ctxt ui.AmContext) (string, any) {
|
|||||||
return dlg.RenderError(ctxt, err.Error())
|
return dlg.RenderError(ctxt, err.Error())
|
||||||
}
|
}
|
||||||
log.Infof("Created conference '%s'", conf.Name)
|
log.Infof("Created conference '%s'", conf.Name)
|
||||||
return "redirect", fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, alias)
|
urlbuf.WriteString("/")
|
||||||
|
urlbuf.WriteString(alias)
|
||||||
|
return "redirect", urlbuf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ManageConferenceList displays the list for managing conferences.
|
/* ManageConferenceList displays the list for managing conferences.
|
||||||
|
|||||||
+41
-21
@@ -30,7 +30,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// AMSTERDAM_VERSION contains the version number of Amsterdam.
|
// AMSTERDAM_VERSION contains the version number of Amsterdam.
|
||||||
const AMSTERDAM_VERSION = "0.1.1"
|
const AMSTERDAM_VERSION = "0.2.0"
|
||||||
|
|
||||||
// AMSTERDAM_COPYRIGHT contains the copyright dates for Amsterdam.
|
// AMSTERDAM_COPYRIGHT contains the copyright dates for Amsterdam.
|
||||||
const AMSTERDAM_COPYRIGHT = "2025-2026"
|
const AMSTERDAM_COPYRIGHT = "2025-2026"
|
||||||
@@ -92,6 +92,7 @@ type AmConfig struct {
|
|||||||
Type string `yaml:"type"`
|
Type string `yaml:"type"`
|
||||||
} `yaml:"siteIcon"`
|
} `yaml:"siteIcon"`
|
||||||
SiteShortcutIcon string `yaml:"siteShortcutIcon"`
|
SiteShortcutIcon string `yaml:"siteShortcutIcon"`
|
||||||
|
SiteAppleIcon string `yaml:"siteAppleIcon"`
|
||||||
SiteLogo string `yaml:"siteLogo"`
|
SiteLogo string `yaml:"siteLogo"`
|
||||||
TopRefresh int `yaml:"topRefresh"`
|
TopRefresh int `yaml:"topRefresh"`
|
||||||
LoginCookieName string `yaml:"loginCookieName"`
|
LoginCookieName string `yaml:"loginCookieName"`
|
||||||
@@ -99,7 +100,10 @@ type AmConfig struct {
|
|||||||
SessionExpire string `yaml:"sessionExpire"`
|
SessionExpire string `yaml:"sessionExpire"`
|
||||||
UserAgreementResource string `yaml:"userAgreementResource"`
|
UserAgreementResource string `yaml:"userAgreementResource"`
|
||||||
PolicyResource string `yaml:"policyResource"`
|
PolicyResource string `yaml:"policyResource"`
|
||||||
|
FrameTemplate string `yaml:"frameTemplate"`
|
||||||
FooterTemplate string `yaml:"footerTemplate"`
|
FooterTemplate string `yaml:"footerTemplate"`
|
||||||
|
TopMenuId string `yaml:"topMenuId"`
|
||||||
|
FixedMenuId string `yaml:"fixedMenuId"`
|
||||||
DefaultCommunityLogo string `yaml:"defaultCommunityLogo"`
|
DefaultCommunityLogo string `yaml:"defaultCommunityLogo"`
|
||||||
DefaultUserPhoto string `yaml:"defaultUserPhoto"`
|
DefaultUserPhoto string `yaml:"defaultUserPhoto"`
|
||||||
WelcomeTitle string `yaml:"welcomeTitle"`
|
WelcomeTitle string `yaml:"welcomeTitle"`
|
||||||
@@ -147,6 +151,9 @@ type AmConfig struct {
|
|||||||
Prioritize string `yaml:"prioritize"`
|
Prioritize string `yaml:"prioritize"`
|
||||||
} `yaml:"countryList"`
|
} `yaml:"countryList"`
|
||||||
VeniceCompatibleImageURLs bool `yaml:"veniceCompatibleImageURLs"`
|
VeniceCompatibleImageURLs bool `yaml:"veniceCompatibleImageURLs"`
|
||||||
|
PanicRecovery struct {
|
||||||
|
StackDataSize string `yaml:"stackDataSize"`
|
||||||
|
} `yaml:"panicRecovery"`
|
||||||
} `yaml:"rendering"`
|
} `yaml:"rendering"`
|
||||||
Resources struct {
|
Resources struct {
|
||||||
ViewTemplateDir string `yaml:"viewTemplateDir"`
|
ViewTemplateDir string `yaml:"viewTemplateDir"`
|
||||||
@@ -166,7 +173,14 @@ type AmConfig struct {
|
|||||||
} `yaml:"posting"`
|
} `yaml:"posting"`
|
||||||
Tuning struct {
|
Tuning struct {
|
||||||
WorkerTasks int `yaml:"workerTasks"`
|
WorkerTasks int `yaml:"workerTasks"`
|
||||||
Queues struct {
|
Timeouts struct {
|
||||||
|
HttpRead int `yaml:"httpRead"`
|
||||||
|
HttpWrite int `yaml:"httpWrite"`
|
||||||
|
HttpIdle int `yaml:"httpIdle"`
|
||||||
|
PageExecute int `yaml:"pageExecute"`
|
||||||
|
PageRender int `yaml:"pageRender"`
|
||||||
|
} `yaml:"timeouts"`
|
||||||
|
Queues struct {
|
||||||
AuditWrites int `yaml:"auditWrites"`
|
AuditWrites int `yaml:"auditWrites"`
|
||||||
ContextRecycle int `yaml:"contextRecycle"`
|
ContextRecycle int `yaml:"contextRecycle"`
|
||||||
EmailRecycle int `yaml:"emailRecycle"`
|
EmailRecycle int `yaml:"emailRecycle"`
|
||||||
@@ -199,24 +213,25 @@ func (c *AmConfig) ExPath(path string) string {
|
|||||||
return filepath.Join(c.baseDir, path)
|
return filepath.Join(c.baseDir, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AmConfigComputed is the configuration values which are "computed" based only on values in AmConfig.
|
// AmConfigComputed is the configuration values which are "computed" based only on values in AmConfig and CommandLine.
|
||||||
type AmConfigComputed struct {
|
type AmConfigComputed struct {
|
||||||
DebugMode bool // are we in debug mode?
|
DebugMode bool // are we in debug mode?
|
||||||
LogLevel string // the logging level
|
LogLevel string // the logging level
|
||||||
Listen string // listen address
|
Listen string // listen address
|
||||||
DatabaseDriver string // name of database driver
|
DatabaseDriver string // name of database driver
|
||||||
DatabaseHost string // hostname for database
|
DatabaseHost string // hostname for database
|
||||||
DatabaseUser string // user name for database
|
DatabaseUser string // user name for database
|
||||||
DatabasePassword string // password for database
|
DatabasePassword string // password for database
|
||||||
DatabaseName string // database name
|
DatabaseName string // database name
|
||||||
MailHost string // SMTP host
|
MailHost string // SMTP host
|
||||||
MailPort int // SMTP port
|
MailPort int // SMTP port
|
||||||
MailTLS string // SMTP TLS setting
|
MailTLS string // SMTP TLS setting
|
||||||
MailAuthType string // SMTP auth type
|
MailAuthType string // SMTP auth type
|
||||||
MailUser string // SMTP user name
|
MailUser string // SMTP user name
|
||||||
MailPassword string // SMTP password
|
MailPassword string // SMTP password
|
||||||
UploadMaxSize int32 // maximum upload size in bytes
|
PanicRecoveryStack int32 // stack size for panic recovery
|
||||||
UploadNoCompress map[string]bool // which upload types are not compressed?
|
UploadMaxSize int32 // maximum upload size in bytes
|
||||||
|
UploadNoCompress map[string]bool // which upload types are not compressed?
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed default.yaml
|
//go:embed default.yaml
|
||||||
@@ -320,7 +335,7 @@ func overlayStructValue(dest, loaded, defaults reflect.Value) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// if we see this message, this function needs more work
|
// if we see this message, this function needs more work
|
||||||
log.Errorf("*** unable to deal with field %s of type %s", structField.Name, typ.Name())
|
log.Fatalf("*** unable to deal with field %s of type %s", structField.Name, typ.Name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -397,7 +412,12 @@ func SetupConfig() {
|
|||||||
GlobalComputedConfig.MailAuthType = util.IIF(CommandLine.MailAuthType != "", CommandLine.MailAuthType, GlobalConfig.Email.AuthType)
|
GlobalComputedConfig.MailAuthType = util.IIF(CommandLine.MailAuthType != "", CommandLine.MailAuthType, GlobalConfig.Email.AuthType)
|
||||||
GlobalComputedConfig.MailUser = util.IIF(CommandLine.MailUser != "", CommandLine.MailUser, GlobalConfig.Email.User)
|
GlobalComputedConfig.MailUser = util.IIF(CommandLine.MailUser != "", CommandLine.MailUser, GlobalConfig.Email.User)
|
||||||
GlobalComputedConfig.MailPassword = util.IIF(CommandLine.MailPassword != "", CommandLine.MailPassword, GlobalConfig.Email.Password)
|
GlobalComputedConfig.MailPassword = util.IIF(CommandLine.MailPassword != "", CommandLine.MailPassword, GlobalConfig.Email.Password)
|
||||||
tmp, err := humanize.ParseBytes(GlobalConfig.Posting.Uploads.MaxSize)
|
tmp, err := humanize.ParseBytes(GlobalConfig.Rendering.PanicRecovery.StackDataSize)
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
GlobalComputedConfig.PanicRecoveryStack = int32(tmp)
|
||||||
|
tmp, err = humanize.ParseBytes(GlobalConfig.Posting.Uploads.MaxSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err.Error())
|
panic(err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ site:
|
|||||||
path: "/img/builtin/AmsterdamIcon32.png"
|
path: "/img/builtin/AmsterdamIcon32.png"
|
||||||
type: "image/png"
|
type: "image/png"
|
||||||
siteShortcutIcon: "/img/builtin/AmsterdamIcon32.ico"
|
siteShortcutIcon: "/img/builtin/AmsterdamIcon32.ico"
|
||||||
|
siteAppleIcon: "/img/builtin/AmsterdamAppleIcon.png"
|
||||||
siteLogo: "/img/builtin/powered-by-amsterdam.png"
|
siteLogo: "/img/builtin/powered-by-amsterdam.png"
|
||||||
topRefresh: 300
|
topRefresh: 300
|
||||||
loginCookieName: AmsterdamAuth
|
loginCookieName: AmsterdamAuth
|
||||||
@@ -24,7 +25,10 @@ site:
|
|||||||
sessionExpire: "3h"
|
sessionExpire: "3h"
|
||||||
userAgreementResource: "useragreement.html"
|
userAgreementResource: "useragreement.html"
|
||||||
policyResource: "policy.html"
|
policyResource: "policy.html"
|
||||||
|
frameTemplate: "frame.jet"
|
||||||
footerTemplate: "footer.jet"
|
footerTemplate: "footer.jet"
|
||||||
|
topMenuId: "top"
|
||||||
|
fixedMenuId: "fixed"
|
||||||
defaultCommunityLogo: "/img/builtin/default-community.jpg"
|
defaultCommunityLogo: "/img/builtin/default-community.jpg"
|
||||||
defaultUserPhoto: "/img/builtin/no-user.png"
|
defaultUserPhoto: "/img/builtin/no-user.png"
|
||||||
welcomeTitle: "Welcome to Amsterdam"
|
welcomeTitle: "Welcome to Amsterdam"
|
||||||
@@ -71,6 +75,8 @@ rendering:
|
|||||||
countryList:
|
countryList:
|
||||||
prioritize: US
|
prioritize: US
|
||||||
veniceCompatibleImageURLs: false
|
veniceCompatibleImageURLs: false
|
||||||
|
panicRecovery:
|
||||||
|
stackDataSize: "4 KiB"
|
||||||
resources:
|
resources:
|
||||||
viewTemplateDir: ""
|
viewTemplateDir: ""
|
||||||
dialogTemplateDir: ""
|
dialogTemplateDir: ""
|
||||||
@@ -90,6 +96,12 @@ posting:
|
|||||||
- "image/png"
|
- "image/png"
|
||||||
tuning:
|
tuning:
|
||||||
workerTasks: 4
|
workerTasks: 4
|
||||||
|
timeouts:
|
||||||
|
httpRead: 30
|
||||||
|
httpWrite: 30
|
||||||
|
httpIdle: 120
|
||||||
|
pageExecute: 15
|
||||||
|
pageRender: 15
|
||||||
queues:
|
queues:
|
||||||
auditWrites: 16
|
auditWrites: 16
|
||||||
contextRecycle: 16
|
contextRecycle: 16
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ const (
|
|||||||
AuditAdminChangeUserAccount = 111
|
AuditAdminChangeUserAccount = 111
|
||||||
AuditAdminSetAccountSecurity = 112
|
AuditAdminSetAccountSecurity = 112
|
||||||
AuditAdminLockUnlockAccount = 113
|
AuditAdminLockUnlockAccount = 113
|
||||||
|
AuditAdminSetUserName = 114
|
||||||
AuditCommunityCreate = 201
|
AuditCommunityCreate = 201
|
||||||
AuditCommunitySetMembership = 202
|
AuditCommunitySetMembership = 202
|
||||||
AuditCommunityContactInfo = 203
|
AuditCommunityContactInfo = 203
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ auditReference:
|
|||||||
text: "Admin Set Account Security"
|
text: "Admin Set Account Security"
|
||||||
- code: 113
|
- code: 113
|
||||||
text: "Admin Lock/Unlock Account"
|
text: "Admin Lock/Unlock Account"
|
||||||
|
- code: 114
|
||||||
|
text: "Admin Set User Name"
|
||||||
- code: 201
|
- code: 201
|
||||||
text: "Create New Community"
|
text: "Create New Community"
|
||||||
- code: 202
|
- code: 202
|
||||||
|
|||||||
+10
-7
@@ -29,9 +29,9 @@ import (
|
|||||||
|
|
||||||
// Error classifications
|
// Error classifications
|
||||||
const (
|
const (
|
||||||
classUnspecified = 0
|
classUnspecified = iota // unspecified, barf
|
||||||
classNeedInstall = 1
|
classNeedInstall // need to install the database
|
||||||
classNeedConvert = 2
|
classNeedConvert // need to convert a Venice database
|
||||||
)
|
)
|
||||||
|
|
||||||
// MySQL Errors
|
// MySQL Errors
|
||||||
@@ -210,11 +210,11 @@ func prepareDB() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetupDb sets up the database and associated items.
|
// SetupDb sets up the database and associated items.
|
||||||
func SetupDb() (func(), error) {
|
func SetupDb() (string, func(), error) {
|
||||||
exitfns := make([]func(), 0, 2)
|
exitfns := make([]func(), 0, 2)
|
||||||
version, err := prepareDB()
|
version, err := prepareDB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "X", nil, err
|
||||||
}
|
}
|
||||||
db, err := sqlx.Connect(config.GlobalComputedConfig.DatabaseDriver, buildMysqlDSN(false))
|
db, err := sqlx.Connect(config.GlobalComputedConfig.DatabaseDriver, buildMysqlDSN(false))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -223,6 +223,7 @@ func SetupDb() (func(), error) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
if g.Version != version {
|
if g.Version != version {
|
||||||
log.Warnf("!! database version %s does not match prepared version %s", g.Version, version)
|
log.Warnf("!! database version %s does not match prepared version %s", g.Version, version)
|
||||||
|
version = g.Version
|
||||||
}
|
}
|
||||||
setupAdCache()
|
setupAdCache()
|
||||||
setupUserCache()
|
setupUserCache()
|
||||||
@@ -232,11 +233,11 @@ func SetupDb() (func(), error) {
|
|||||||
setupConferenceCache()
|
setupConferenceCache()
|
||||||
exitfns = append(exitfns, setupAuditWriter())
|
exitfns = append(exitfns, setupAuditWriter())
|
||||||
exitfns = append(exitfns, setupIPBanSweep())
|
exitfns = append(exitfns, setupIPBanSweep())
|
||||||
log.Infof("SetupDb(): database version %s", g.Version)
|
log.Infof("SetupDb(): database version %s", version)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
slices.Reverse(exitfns)
|
slices.Reverse(exitfns)
|
||||||
return func() {
|
return version, func() {
|
||||||
for _, f := range exitfns {
|
for _, f := range exitfns {
|
||||||
f()
|
f()
|
||||||
}
|
}
|
||||||
@@ -262,6 +263,8 @@ func transaction(ctx context.Context) (*sqlx.Tx, func() error, func()) {
|
|||||||
err = tx.Commit()
|
err = tx.Commit()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
live = false
|
live = false
|
||||||
|
} else {
|
||||||
|
log.Errorf("***COMMIT ERROR*** %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|||||||
+54
-59
@@ -17,6 +17,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -152,8 +153,8 @@ func (cs *ConferenceSettings) Save(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Aliases returns the list of aliases for this conference.
|
// Aliases returns the list of aliases for this conference.
|
||||||
func (c *Conference) Aliases(ctx context.Context) ([]string, error) {
|
func (c *Conference) Aliases(ctx context.Context, commid int32) ([]string, error) {
|
||||||
rs, err := amdb.QueryContext(ctx, "SELECT alias FROM confalias WHERE confid = ? ORDER BY alias", c.ConfId)
|
rs, err := amdb.QueryContext(ctx, "SELECT alias FROM confalias WHERE commid = ? AND confid = ? ORDER BY alias", commid, c.ConfId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -169,25 +170,22 @@ func (c *Conference) Aliases(ctx context.Context) ([]string, error) {
|
|||||||
return rc, nil
|
return rc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AliasesQ returns the list of aliases for this conference, quietly.
|
|
||||||
func (c *Conference) AliasesQ(ctx context.Context) []string {
|
|
||||||
rc, _ := c.Aliases(ctx)
|
|
||||||
return rc
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddAlias adds an alias to the conference.
|
// AddAlias adds an alias to the conference.
|
||||||
func (c *Conference) AddAlias(ctx context.Context, alias string, u *User, comm *Community, ipaddr string) error {
|
func (c *Conference) AddAlias(ctx context.Context, alias string, u *User, comm *Community, ipaddr string) error {
|
||||||
tmp := ""
|
tmp := ""
|
||||||
if err := amdb.GetContext(ctx, &tmp, "SELECT alias FROM confalias WHERE confid = ? AND alias = ?", c.ConfId, alias); err != sql.ErrNoRows {
|
if err := amdb.GetContext(ctx, &tmp, "SELECT alias FROM confalias WHERE commid = ? AND confid = ? AND alias = ?",
|
||||||
|
comm.Id, c.ConfId, alias); err != sql.ErrNoRows {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return fmt.Errorf("the alias '%s' is already in use by another conference", alias)
|
return fmt.Errorf("the alias '%s' is already in use by another conference", alias)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := amdb.ExecContext(ctx, "INSERT INTO confalias (confid, alias) VALUES (?, ?)", c.ConfId, alias); err != nil {
|
if _, err := amdb.ExecContext(ctx, "INSERT INTO confalias (commid, confid, alias) VALUES (?, ?, ?)",
|
||||||
|
comm.Id, c.ConfId, alias); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conferenceAliasMap.Store(confAliasMapKey(comm.Id, alias), c.ConfId)
|
||||||
AmStoreAudit(AmNewCommAudit(AuditConferenceAlias, u.Uid, comm.Id, ipaddr, fmt.Sprintf("conf=%d", c.ConfId), fmt.Sprintf("add=%s", alias)))
|
AmStoreAudit(AmNewCommAudit(AuditConferenceAlias, u.Uid, comm.Id, ipaddr, fmt.Sprintf("conf=%d", c.ConfId), fmt.Sprintf("add=%s", alias)))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -195,21 +193,23 @@ func (c *Conference) AddAlias(ctx context.Context, alias string, u *User, comm *
|
|||||||
// RemoveAlias removes an alias from the conference.
|
// RemoveAlias removes an alias from the conference.
|
||||||
func (c *Conference) RemoveAlias(ctx context.Context, alias string, u *User, comm *Community, ipaddr string) error {
|
func (c *Conference) RemoveAlias(ctx context.Context, alias string, u *User, comm *Community, ipaddr string) error {
|
||||||
aliasCount := 0
|
aliasCount := 0
|
||||||
if err := amdb.GetContext(ctx, &aliasCount, "SELECT COUNT(*) FROM confalias WHERE confid = ?", c.ConfId); err != nil {
|
if err := amdb.GetContext(ctx, &aliasCount, "SELECT COUNT(*) FROM confalias WHERE commid = ? AND confid = ?",
|
||||||
|
comm.Id, c.ConfId); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if aliasCount == 1 {
|
if aliasCount == 1 {
|
||||||
tmp := ""
|
tmp := ""
|
||||||
err := amdb.GetContext(ctx, &tmp, "SELECT alias FROM confalias WHERE confid = ? AND alias = ?", c.ConfId, alias)
|
err := amdb.GetContext(ctx, &tmp, "SELECT alias FROM confalias WHERE commid = ? AND confid = ? AND alias = ?",
|
||||||
|
comm.Id, c.ConfId, alias)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return errors.New("the conference must have at least one alias")
|
return errors.New("the conference must have at least one alias")
|
||||||
} else if err != sql.ErrNoRows {
|
} else if !errors.Is(err, sql.ErrNoRows) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rs, err := amdb.ExecContext(ctx, "DELETE FROM confalias WHERE confid = ? AND alias = ?", c.ConfId, alias)
|
rs, err := amdb.ExecContext(ctx, "DELETE FROM confalias WHERE commid = ? AND confid = ? AND alias = ?", comm.Id, c.ConfId, alias)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -221,6 +221,7 @@ func (c *Conference) RemoveAlias(ctx context.Context, alias string, u *User, com
|
|||||||
return errors.New("alias not found")
|
return errors.New("alias not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conferenceAliasMap.Delete(confAliasMapKey(comm.Id, alias))
|
||||||
AmStoreAudit(AmNewCommAudit(AuditConferenceAlias, u.Uid, comm.Id, ipaddr, fmt.Sprintf("conf=%d", c.ConfId), fmt.Sprintf("remove=%s", alias)))
|
AmStoreAudit(AmNewCommAudit(AuditConferenceAlias, u.Uid, comm.Id, ipaddr, fmt.Sprintf("conf=%d", c.ConfId), fmt.Sprintf("remove=%s", alias)))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -431,8 +432,8 @@ func (c *Conference) Settings(ctx context.Context, u *User) (*ConferenceSettings
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Link returns a link string to this conference.
|
// Link returns a link string to this conference.
|
||||||
func (c *Conference) Link(ctx context.Context, scope string) (string, error) {
|
func (c *Conference) Link(ctx context.Context, commid int32, scope string) (string, error) {
|
||||||
aliases, err := c.Aliases(ctx)
|
aliases, err := c.Aliases(ctx, commid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -440,9 +441,9 @@ func (c *Conference) Link(ctx context.Context, scope string) (string, error) {
|
|||||||
return fmt.Sprintf("%s.", aliases[0]), nil
|
return fmt.Sprintf("%s.", aliases[0]), nil
|
||||||
}
|
}
|
||||||
if scope == "global" {
|
if scope == "global" {
|
||||||
comms, err := c.ContainedBy(ctx)
|
comm, err := AmGetCommunity(ctx, commid)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return fmt.Sprintf("%s!%s", comms[0].Alias, aliases[0]), nil
|
return fmt.Sprintf("%s!%s", comm.Alias, aliases[0]), nil
|
||||||
}
|
}
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -872,22 +873,22 @@ func (c *Conference) Delete(ctx context.Context, comm *Community, u *User, ipadd
|
|||||||
|
|
||||||
// any references to conference other than this community?
|
// any references to conference other than this community?
|
||||||
refCount := 0
|
refCount := 0
|
||||||
if err := tx.GetContext(ctx, &refCount, "SELECT COUNT(*) FROM commtoconf WHERE confid = ? AND commid <> ?", c.ConfId, comm.Id); err != nil {
|
err := tx.GetContext(ctx, &refCount, "SELECT COUNT(*) FROM commtoconf WHERE confid = ? AND commid <> ?", c.ConfId, comm.Id)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// break the link with the community
|
// break the link with the community
|
||||||
if _, err := tx.ExecContext(ctx, "DELETE FROM commtoconf WHERE commid = ? AND confid = ?", comm.Id, c.ConfId); err != nil {
|
if _, err = tx.ExecContext(ctx, "DELETE FROM commtoconf WHERE commid = ? AND confid = ?", comm.Id, c.ConfId); err == nil {
|
||||||
|
_, err = tx.ExecContext(ctx, "DELETE FROM confalias WHERE commid = ? AND confid = ?", comm.Id, c.ConfId)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
if refCount == 0 {
|
if refCount == 0 {
|
||||||
// We have to delete all the conference core data now.
|
// We have to delete all the conference core data now.
|
||||||
_, err = tx.ExecContext(ctx, "DELETE FROM confs WHERE confid = ?", c.ConfId)
|
_, err = tx.ExecContext(ctx, "DELETE FROM confs WHERE confid = ?", c.ConfId)
|
||||||
if err == nil {
|
|
||||||
_, err = tx.ExecContext(ctx, "DELETE FROM confalias WHERE confid = ?", c.ConfId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -901,6 +902,9 @@ func (c *Conference) Delete(ctx context.Context, comm *Community, u *User, ipadd
|
|||||||
// kick the conference out of the cache
|
// kick the conference out of the cache
|
||||||
conferenceCache.Remove(c.ConfId)
|
conferenceCache.Remove(c.ConfId)
|
||||||
|
|
||||||
|
// simpler to just nuke the entire alias map
|
||||||
|
conferenceAliasMap.Clear()
|
||||||
|
|
||||||
// add an audit record
|
// add an audit record
|
||||||
AmStoreAudit(AmNewCommAudit(AuditConferenceDelete, u.Uid, comm.Id, ipaddr, fmt.Sprintf("confid=%d", c.ConfId)))
|
AmStoreAudit(AmNewCommAudit(AuditConferenceDelete, u.Uid, comm.Id, ipaddr, fmt.Sprintf("confid=%d", c.ConfId)))
|
||||||
|
|
||||||
@@ -955,16 +959,15 @@ func (*conferenceServiceVTable) OnDeleteCommunity(ctx context.Context, tx *sqlx.
|
|||||||
if _, err = tx.ExecContext(ctx, "DELETE FROM commtoconf WHERE commid = ? AND confid = ?", commid, confid); err != nil {
|
if _, err = tx.ExecContext(ctx, "DELETE FROM commtoconf WHERE commid = ? AND confid = ?", commid, confid); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if _, err = tx.ExecContext(ctx, "DELETE FROM confalias WHERE commid = ? AND confid = ?", commid, confid); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if refCount > 0 {
|
if refCount > 0 {
|
||||||
confids[i] = -1
|
confids[i] = -1
|
||||||
continue // done with this conference
|
continue // done with this conference
|
||||||
}
|
}
|
||||||
// We have to delete all the conference core data now.
|
// We have to delete all the conference core data now.
|
||||||
_, err = tx.ExecContext(ctx, "DELETE FROM confs WHERE confid = ?", confid)
|
if _, err = tx.ExecContext(ctx, "DELETE FROM confs WHERE confid = ?", confid); err != nil {
|
||||||
if err == nil {
|
|
||||||
_, err = tx.ExecContext(ctx, "DELETE FROM confalias WHERE confid = ?", confid)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// kick the conference out of the cache
|
// kick the conference out of the cache
|
||||||
@@ -973,7 +976,8 @@ func (*conferenceServiceVTable) OnDeleteCommunity(ctx context.Context, tx *sqlx.
|
|||||||
getConferenceMutex.Unlock()
|
getConferenceMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Just dump the whole conference property cache.
|
// Just dump the whole conference alias map and property cache.
|
||||||
|
conferenceAliasMap.Clear()
|
||||||
getConferencePropMutex.Lock()
|
getConferencePropMutex.Lock()
|
||||||
conferencePropCache.Purge()
|
conferencePropCache.Purge()
|
||||||
getConferencePropMutex.Unlock()
|
getConferencePropMutex.Unlock()
|
||||||
@@ -1030,27 +1034,38 @@ func AmGetConference(ctx context.Context, id int32) (*Conference, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// confAliasMapKey creates the key for the conference alias map.
|
||||||
|
func confAliasMapKey(commid int32, alias string) string {
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString(strconv.FormatInt(int64(commid), 10))
|
||||||
|
b.WriteByte(byte(':'))
|
||||||
|
b.WriteString(alias)
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
/* AmGetConferenceByAlias returns a conference given its alias.
|
/* AmGetConferenceByAlias returns a conference given its alias.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* ctx - Standard Go context value.
|
* ctx - Standard Go context value.
|
||||||
|
*. commid - Community ID to look under.
|
||||||
* alias - The alias to look up.
|
* alias - The alias to look up.
|
||||||
* Returns:
|
* Returns:
|
||||||
* Pointer to the conference, or nil.
|
* Pointer to the conference, or nil.
|
||||||
* Standard Go error status.
|
* Standard Go error status.
|
||||||
*/
|
*/
|
||||||
func AmGetConferenceByAlias(ctx context.Context, alias string) (*Conference, error) {
|
func AmGetConferenceByAlias(ctx context.Context, commid int32, alias string) (*Conference, error) {
|
||||||
var confid int32
|
var confid int32
|
||||||
xconf, ok := conferenceAliasMap.Load(alias)
|
key := confAliasMapKey(commid, alias)
|
||||||
|
xconf, ok := conferenceAliasMap.Load(key)
|
||||||
if ok {
|
if ok {
|
||||||
confid = xconf.(int32)
|
confid = xconf.(int32)
|
||||||
} else {
|
} else {
|
||||||
err := amdb.GetContext(ctx, &confid, "SELECT confid FROM confalias WHERE alias = ?", alias)
|
err := amdb.GetContext(ctx, &confid, "SELECT confid FROM confalias WHERE commid = ? AND alias = ?", commid, alias)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return nil, fmt.Errorf("alias not found: %s", alias)
|
return nil, fmt.Errorf("alias not found: %s", alias)
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
conferenceAliasMap.Store(alias, confid)
|
conferenceAliasMap.Store(key, confid)
|
||||||
}
|
}
|
||||||
return AmGetConference(ctx, confid)
|
return AmGetConference(ctx, confid)
|
||||||
}
|
}
|
||||||
@@ -1074,28 +1089,6 @@ func AmGetConferenceContainingPost(ctx context.Context, postId int64) (*Conferen
|
|||||||
return AmGetConference(ctx, 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(ctx context.Context, cid int32, alias string) (*Conference, error) {
|
|
||||||
var confid int32
|
|
||||||
err := amdb.GetContext(ctx, &confid, `SELECT c.confid FROM commtoconf c, confalias a WHERE c.confid = a.confid
|
|
||||||
AND c.commid = ? AND a.alias = ?`, cid, alias)
|
|
||||||
switch err {
|
|
||||||
case nil:
|
|
||||||
return AmGetConference(ctx, confid)
|
|
||||||
case sql.ErrNoRows:
|
|
||||||
return nil, errors.New("conference not found")
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
/* AmListConferences returns all conferences for a given community.
|
/* AmListConferences returns all conferences for a given community.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* ctx - Standard Go context value.
|
* ctx - Standard Go context value.
|
||||||
@@ -1125,7 +1118,7 @@ func AmListConferences(ctx context.Context, cid int32, showHidden bool) ([]*Conf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i := range rc {
|
for i := range rc {
|
||||||
err := amdb.GetContext(ctx, &(rc[i].Alias), "SELECT alias FROM confalias WHERE confid = ?", rc[i].ConfId)
|
err := amdb.GetContext(ctx, &(rc[i].Alias), "SELECT alias FROM confalias WHERE commid = ? AND confid = ?", cid, rc[i].ConfId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1271,7 +1264,7 @@ func AmCreateConference(ctx context.Context, comm *Community, name, alias, descr
|
|||||||
|
|
||||||
// Ensure the alias is not in use.
|
// Ensure the alias is not in use.
|
||||||
var tmp int32
|
var tmp int32
|
||||||
err := tx.GetContext(ctx, &tmp, "SELECT confid FROM confalias WHERE alias = ?", alias)
|
err := tx.GetContext(ctx, &tmp, "SELECT confid FROM confalias WHERE commid = ? AND alias = ?", comm.Id, alias)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil, fmt.Errorf("the alias '%s' is already in use by a different conference", alias)
|
return nil, fmt.Errorf("the alias '%s' is already in use by a different conference", alias)
|
||||||
} else if err != sql.ErrNoRows {
|
} else if err != sql.ErrNoRows {
|
||||||
@@ -1295,7 +1288,8 @@ func AmCreateConference(ctx context.Context, comm *Community, name, alias, descr
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Attach the alias to the conference.
|
// Attach the alias to the conference.
|
||||||
if _, err = tx.ExecContext(ctx, "INSERT INTO confalias (confid, alias) VALUES (?, ?)", rc.ConfId, alias); err != nil {
|
if _, err = tx.ExecContext(ctx, "INSERT INTO confalias (commid, confid, alias) VALUES (?, ?, ?)",
|
||||||
|
comm.Id, rc.ConfId, alias); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1329,6 +1323,7 @@ func AmCreateConference(ctx context.Context, comm *Community, name, alias, descr
|
|||||||
|
|
||||||
// Add the new conference to the cache.
|
// Add the new conference to the cache.
|
||||||
conferenceCache.Add(rc.ConfId, &rc)
|
conferenceCache.Add(rc.ConfId, &rc)
|
||||||
|
conferenceAliasMap.Store(confAliasMapKey(comm.Id, alias), rc.ConfId)
|
||||||
|
|
||||||
// Set the "pictures in posts" flag for the conference from the community default.
|
// Set the "pictures in posts" flag for the conference from the community default.
|
||||||
fcomm, err := comm.Flags(ctx)
|
fcomm, err := comm.Flags(ctx)
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# 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/.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
#
|
||||||
|
CREATE TABLE newconfalias (
|
||||||
|
commid INT NOT NULL,
|
||||||
|
confid INT NOT NULL,
|
||||||
|
alias VARCHAR(64) NOT NULL,
|
||||||
|
PRIMARY KEY (commid, alias),
|
||||||
|
INDEX confid_x (commid, confid)
|
||||||
|
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
INSERT INTO newconfalias (commid, confid, alias)
|
||||||
|
SELECT c.commid, c.confid, a.alias FROM commtoconf c, confalias a
|
||||||
|
WHERE c.confid = a.confid;
|
||||||
|
|
||||||
|
DROP TABLE confalias;
|
||||||
|
ALTER TABLE newconfalias RENAME TO confalias;
|
||||||
+48
-40
@@ -253,16 +253,16 @@ func (p *PostHeader) Text(ctx context.Context) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Link returns a link string to this post.
|
// Link returns a link string to this post.
|
||||||
func (p *PostHeader) Link(ctx context.Context, scope string) (string, error) {
|
func (p *PostHeader) Link(ctx context.Context, commid int32, scope string) (string, error) {
|
||||||
if scope == "topic" {
|
if scope == PLSCOPE_TOPIC {
|
||||||
return fmt.Sprintf("%d", p.Num), nil
|
return fmt.Sprintf("%d", p.Num), nil
|
||||||
}
|
}
|
||||||
if scope == "conference" || scope == "community" || scope == "global" {
|
if scope == PLSCOPE_CONFERENCE || scope == PLSCOPE_COMMUNITY || scope == PLSCOPE_GLOBAL {
|
||||||
topic, err := AmGetTopic(ctx, p.TopicId)
|
topic, err := AmGetTopic(ctx, p.TopicId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
parent, err := topic.Link(ctx, scope)
|
parent, err := topic.Link(ctx, commid, scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -628,47 +628,55 @@ func AmNewPost(ctx context.Context, conf *Conference, topic *Topic, user *User,
|
|||||||
* Array of post headers, or nil.
|
* Array of post headers, or nil.
|
||||||
* Standard Go error status.
|
* Standard Go error status.
|
||||||
*/
|
*/
|
||||||
func AmGetPublishedPosts(ctx context.Context) ([]*PostHeader, error) {
|
func AmGetPublishedPosts(ctx context.Context) ([]*PostHeader, []*Community, error) {
|
||||||
// Read the globals.
|
// Read the globals.
|
||||||
gv, err := AmGlobals(ctx)
|
gv, err := AmGlobals(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
// Read the published posts.
|
// Read the published posts.
|
||||||
rs, err := amdb.QueryContext(ctx, "SELECT postid FROM postpublish ORDER BY on_date DESC")
|
rs, err := amdb.QueryContext(ctx, "SELECT commid, postid FROM postpublish ORDER BY on_date DESC")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
// Extract post IDs to an array.
|
// Extract post IDs to an array.
|
||||||
|
cids := make([]int32, gv.FrontPagePosts)
|
||||||
pids := make([]int64, gv.FrontPagePosts)
|
pids := make([]int64, gv.FrontPagePosts)
|
||||||
i := 0
|
i := 0
|
||||||
for i < int(gv.FrontPagePosts) && rs.Next() {
|
for i < int(gv.FrontPagePosts) && rs.Next() {
|
||||||
if err = rs.Scan(&(pids[i])); err != nil {
|
if err = rs.Scan(&(cids[i]), &(pids[i])); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
if i == 0 { // no published posts, short-circuit response
|
if i == 0 { // no published posts, short-circuit response
|
||||||
return make([]*PostHeader, 0), nil
|
return make([]*PostHeader, 0), make([]*Community, 0), nil
|
||||||
}
|
}
|
||||||
if i < int(gv.FrontPagePosts) {
|
if i < int(gv.FrontPagePosts) {
|
||||||
|
cids = cids[:i]
|
||||||
pids = pids[:i] // truncate if we have fewer posts than spaces
|
pids = pids[:i] // truncate if we have fewer posts than spaces
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build the communities return array.
|
||||||
|
comms := make([]*Community, len(cids))
|
||||||
|
for i, cid := range cids {
|
||||||
|
comms[i], _ = AmGetCommunity(ctx, cid)
|
||||||
|
}
|
||||||
|
|
||||||
// Use the post IDs to build a SQL statement.
|
// Use the post IDs to build a SQL statement.
|
||||||
query, args, err := sqlx.In("SELECT * FROM posts WHERE postid IN (?)", pids)
|
query, args, err := sqlx.In("SELECT * FROM posts WHERE postid IN (?)", pids)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
query = amdb.Rebind(query)
|
query = amdb.Rebind(query)
|
||||||
|
|
||||||
// Use the SQL to read in all the post headers using a single database query.
|
// Use the SQL to read in all the post headers using a single database query.
|
||||||
var data []PostHeader
|
var data []PostHeader
|
||||||
if err = amdb.SelectContext(ctx, &data, query, args...); err != nil {
|
if err = amdb.SelectContext(ctx, &data, query, args...); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if len(data) < len(pids) {
|
if len(data) < len(pids) {
|
||||||
return nil, errors.New("internal error reading post headers")
|
return nil, nil, errors.New("internal error reading post headers")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the return array by making sure we point to the post headers in the same order the post IDs were returned.
|
// Build the return array by making sure we point to the post headers in the same order the post IDs were returned.
|
||||||
@@ -683,10 +691,10 @@ func AmGetPublishedPosts(ctx context.Context) ([]*PostHeader, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if q < len(pids) {
|
if q < len(pids) {
|
||||||
return nil, errors.New("internal error generating output")
|
return nil, nil, errors.New("internal error generating output")
|
||||||
}
|
}
|
||||||
|
|
||||||
return rc, nil
|
return rc, comms, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type PostSearchResult struct {
|
type PostSearchResult struct {
|
||||||
@@ -712,55 +720,55 @@ func decodeSearchScope(ctx context.Context, scopeValues []any) (string, *Communi
|
|||||||
}
|
}
|
||||||
if thisComm, ok := scopeValues[i].(*Community); ok {
|
if thisComm, ok := scopeValues[i].(*Community); ok {
|
||||||
if myComm != nil {
|
if myComm != nil {
|
||||||
return "error", nil, nil, nil, errors.New("cannot specify multiple communities")
|
return PLSCOPE_ERROR, nil, nil, nil, errors.New("cannot specify multiple communities")
|
||||||
}
|
}
|
||||||
myComm = thisComm
|
myComm = thisComm
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if thisConf, ok := scopeValues[i].(*Conference); ok {
|
if thisConf, ok := scopeValues[i].(*Conference); ok {
|
||||||
if myConf != nil {
|
if myConf != nil {
|
||||||
return "error", nil, nil, nil, errors.New("cannot specify multiple conferences")
|
return PLSCOPE_ERROR, nil, nil, nil, errors.New("cannot specify multiple conferences")
|
||||||
}
|
}
|
||||||
myConf = thisConf
|
myConf = thisConf
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if thisTopic, ok := scopeValues[i].(*Topic); ok {
|
if thisTopic, ok := scopeValues[i].(*Topic); ok {
|
||||||
if myTopic != nil {
|
if myTopic != nil {
|
||||||
return "error", nil, nil, nil, errors.New("cannot specify multiple topics")
|
return PLSCOPE_ERROR, nil, nil, nil, errors.New("cannot specify multiple topics")
|
||||||
}
|
}
|
||||||
myTopic = thisTopic
|
myTopic = thisTopic
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return "error", nil, nil, nil, errors.New("invalid item specified in scope")
|
return PLSCOPE_ERROR, nil, nil, nil, errors.New("invalid item specified in scope")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Based on which slots are full, determine the scope. Also error-check relations between the specified slots.
|
// Based on which slots are full, determine the scope. Also error-check relations between the specified slots.
|
||||||
if myComm == nil {
|
if myComm == nil {
|
||||||
if myConf != nil || myTopic != nil {
|
if myConf != nil || myTopic != nil {
|
||||||
return "error", nil, nil, nil, errors.New("conference/topic specified without community")
|
return PLSCOPE_ERROR, nil, nil, nil, errors.New("conference/topic specified without community")
|
||||||
}
|
}
|
||||||
return "global", nil, nil, nil, nil
|
return PLSCOPE_GLOBAL, nil, nil, nil, nil
|
||||||
}
|
}
|
||||||
if myConf == nil {
|
if myConf == nil {
|
||||||
if myTopic != nil {
|
if myTopic != nil {
|
||||||
return "error", nil, nil, nil, errors.New("topic specified without conference")
|
return PLSCOPE_ERROR, nil, nil, nil, errors.New("topic specified without conference")
|
||||||
}
|
}
|
||||||
return "community", myComm, nil, nil, nil
|
return PLSCOPE_COMMUNITY, myComm, nil, nil, nil
|
||||||
}
|
}
|
||||||
f, err := myConf.InCommunity(ctx, myComm)
|
f, err := myConf.InCommunity(ctx, myComm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", nil, nil, nil, err
|
return PLSCOPE_ERROR, nil, nil, nil, err
|
||||||
}
|
}
|
||||||
if !f {
|
if !f {
|
||||||
return "error", nil, nil, nil, errors.New("community does not contain conference")
|
return PLSCOPE_ERROR, nil, nil, nil, errors.New("community does not contain conference")
|
||||||
}
|
}
|
||||||
if myTopic == nil {
|
if myTopic == nil {
|
||||||
return "conference", myComm, myConf, nil, nil
|
return PLSCOPE_CONFERENCE, myComm, myConf, nil, nil
|
||||||
}
|
}
|
||||||
if myTopic.ConfId != myConf.ConfId {
|
if myTopic.ConfId != myConf.ConfId {
|
||||||
return "error", nil, nil, nil, errors.New("conference does not contain topic")
|
return PLSCOPE_ERROR, nil, nil, nil, errors.New("conference does not contain topic")
|
||||||
}
|
}
|
||||||
return "topic", myComm, myConf, myTopic, nil
|
return PLSCOPE_TOPIC, myComm, myConf, myTopic, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/* AmSearchPosts finds posts by using full text search on their contents.
|
/* AmSearchPosts finds posts by using full text search on their contents.
|
||||||
@@ -786,7 +794,7 @@ func AmSearchPosts(ctx context.Context, searchTerms string, u *User, offset, max
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the proper service index to match against the community services.
|
// Get the proper service index to match against the community services.
|
||||||
confService, err := AmGetServiceIndex("community", "Conference")
|
confService, err := AmGetServiceIndex(AM_DOMAIN_COMMUNITY, AM_SVC_CONFERENCE)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
@@ -794,7 +802,7 @@ func AmSearchPosts(ctx context.Context, searchTerms string, u *User, offset, max
|
|||||||
// Get the count of matching posts.
|
// Get the count of matching posts.
|
||||||
var count int
|
var count int
|
||||||
switch scope {
|
switch scope {
|
||||||
case "global":
|
case PLSCOPE_GLOBAL:
|
||||||
err = amdb.GetContext(ctx, &count, `SELECT COUNT(*)
|
err = amdb.GetContext(ctx, &count, `SELECT COUNT(*)
|
||||||
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
||||||
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
||||||
@@ -802,7 +810,7 @@ func AmSearchPosts(ctx context.Context, searchTerms string, u *User, offset, max
|
|||||||
LEFT JOIN confmember x ON (c.confid = x.confid AND u.uid = x.uid)
|
LEFT JOIN confmember x ON (c.confid = x.confid AND u.uid = x.uid)
|
||||||
WHERE u.uid = ? AND f.ftr_code = ? AND GREATEST(u.base_lvl,m.granted_lvl,s.granted_lvl,COALESCE(x.granted_lvl,0)) >= c.read_lvl
|
WHERE u.uid = ? AND f.ftr_code = ? AND GREATEST(u.base_lvl,m.granted_lvl,s.granted_lvl,COALESCE(x.granted_lvl,0)) >= c.read_lvl
|
||||||
AND p.scribble_uid IS NULL AND MATCH(d.data) AGAINST (?)`, u.Uid, confService, searchTerms)
|
AND p.scribble_uid IS NULL AND MATCH(d.data) AGAINST (?)`, u.Uid, confService, searchTerms)
|
||||||
case "community":
|
case PLSCOPE_COMMUNITY:
|
||||||
err = amdb.GetContext(ctx, &count, `SELECT COUNT(*)
|
err = amdb.GetContext(ctx, &count, `SELECT COUNT(*)
|
||||||
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
||||||
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
||||||
@@ -810,7 +818,7 @@ func AmSearchPosts(ctx context.Context, searchTerms string, u *User, offset, max
|
|||||||
LEFT JOIN confmember x ON (c.confid = x.confid AND u.uid = x.uid)
|
LEFT JOIN confmember x ON (c.confid = x.confid AND u.uid = x.uid)
|
||||||
WHERE u.uid = ? AND f.ftr_code = ? AND GREATEST(u.base_lvl,m.granted_lvl,s.granted_lvl,COALESCE(x.granted_lvl,0)) >= c.read_lvl
|
WHERE u.uid = ? AND f.ftr_code = ? AND GREATEST(u.base_lvl,m.granted_lvl,s.granted_lvl,COALESCE(x.granted_lvl,0)) >= c.read_lvl
|
||||||
AND q.commid = ? AND p.scribble_uid IS NULL AND MATCH(d.data) AGAINST (?)`, u.Uid, confService, comm.Id, searchTerms)
|
AND q.commid = ? AND p.scribble_uid IS NULL AND MATCH(d.data) AGAINST (?)`, u.Uid, confService, comm.Id, searchTerms)
|
||||||
case "conference":
|
case PLSCOPE_CONFERENCE:
|
||||||
err = amdb.GetContext(ctx, &count, `SELECT COUNT(*)
|
err = amdb.GetContext(ctx, &count, `SELECT COUNT(*)
|
||||||
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
||||||
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
||||||
@@ -818,7 +826,7 @@ func AmSearchPosts(ctx context.Context, searchTerms string, u *User, offset, max
|
|||||||
LEFT JOIN confmember x ON (c.confid = x.confid AND u.uid = x.uid)
|
LEFT JOIN confmember x ON (c.confid = x.confid AND u.uid = x.uid)
|
||||||
WHERE u.uid = ? AND f.ftr_code = ? AND GREATEST(u.base_lvl,m.granted_lvl,s.granted_lvl,COALESCE(x.granted_lvl,0)) >= c.read_lvl
|
WHERE u.uid = ? AND f.ftr_code = ? AND GREATEST(u.base_lvl,m.granted_lvl,s.granted_lvl,COALESCE(x.granted_lvl,0)) >= c.read_lvl
|
||||||
AND q.commid = ? AND c.confid = ? AND p.scribble_uid IS NULL AND MATCH(d.data) AGAINST (?)`, u.Uid, confService, comm.Id, conf.ConfId, searchTerms)
|
AND q.commid = ? AND c.confid = ? AND p.scribble_uid IS NULL AND MATCH(d.data) AGAINST (?)`, u.Uid, confService, comm.Id, conf.ConfId, searchTerms)
|
||||||
case "topic":
|
case PLSCOPE_TOPIC:
|
||||||
err = amdb.GetContext(ctx, &count, `SELECT COUNT(*)
|
err = amdb.GetContext(ctx, &count, `SELECT COUNT(*)
|
||||||
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
||||||
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
||||||
@@ -836,7 +844,7 @@ func AmSearchPosts(ctx context.Context, searchTerms string, u *User, offset, max
|
|||||||
// Get the matching posts themselves.
|
// Get the matching posts themselves.
|
||||||
var rs *sql.Rows
|
var rs *sql.Rows
|
||||||
switch scope {
|
switch scope {
|
||||||
case "global":
|
case PLSCOPE_GLOBAL:
|
||||||
rs, err = amdb.QueryContext(ctx, `SELECT q.commid, q.alias, c.confid, t.topicid, t.num, p.postid, p.num, u2.username, p.posted, p.linecount, d.data
|
rs, err = amdb.QueryContext(ctx, `SELECT q.commid, q.alias, c.confid, t.topicid, t.num, p.postid, p.num, u2.username, p.posted, p.linecount, d.data
|
||||||
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
||||||
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
||||||
@@ -845,7 +853,7 @@ func AmSearchPosts(ctx context.Context, searchTerms string, u *User, offset, max
|
|||||||
WHERE u.uid = ? AND f.ftr_code = ? AND GREATEST(u.base_lvl,m.granted_lvl,s.granted_lvl,COALESCE(x.granted_lvl,0)) >= c.read_lvl
|
WHERE u.uid = ? AND f.ftr_code = ? AND GREATEST(u.base_lvl,m.granted_lvl,s.granted_lvl,COALESCE(x.granted_lvl,0)) >= c.read_lvl
|
||||||
AND p.scribble_uid IS NULL AND MATCH(d.data) AGAINST (?) ORDER BY q.commname, c.name, t.num, p.num
|
AND p.scribble_uid IS NULL AND MATCH(d.data) AGAINST (?) ORDER BY q.commname, c.name, t.num, p.num
|
||||||
LIMIT ? OFFSET ?`, u.Uid, confService, searchTerms, max, offset)
|
LIMIT ? OFFSET ?`, u.Uid, confService, searchTerms, max, offset)
|
||||||
case "community":
|
case PLSCOPE_COMMUNITY:
|
||||||
rs, err = amdb.QueryContext(ctx, `SELECT q.commid, q.alias, c.confid, t.topicid, t.num, p.postid, p.num, u2.username, p.posted, p.linecount, d.data
|
rs, err = amdb.QueryContext(ctx, `SELECT q.commid, q.alias, c.confid, t.topicid, t.num, p.postid, p.num, u2.username, p.posted, p.linecount, d.data
|
||||||
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
||||||
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
||||||
@@ -854,7 +862,7 @@ func AmSearchPosts(ctx context.Context, searchTerms string, u *User, offset, max
|
|||||||
WHERE u.uid = ? AND f.ftr_code = ? AND GREATEST(u.base_lvl,m.granted_lvl,s.granted_lvl,COALESCE(x.granted_lvl,0)) >= c.read_lvl
|
WHERE u.uid = ? AND f.ftr_code = ? AND GREATEST(u.base_lvl,m.granted_lvl,s.granted_lvl,COALESCE(x.granted_lvl,0)) >= c.read_lvl
|
||||||
AND q.commid = ? AND p.scribble_uid IS NULL AND MATCH(d.data) AGAINST (?) ORDER BY q.commname, c.name, t.num, p.num
|
AND q.commid = ? AND p.scribble_uid IS NULL AND MATCH(d.data) AGAINST (?) ORDER BY q.commname, c.name, t.num, p.num
|
||||||
LIMIT ? OFFSET ?`, u.Uid, confService, comm.Id, searchTerms, max, offset)
|
LIMIT ? OFFSET ?`, u.Uid, confService, comm.Id, searchTerms, max, offset)
|
||||||
case "conference":
|
case PLSCOPE_CONFERENCE:
|
||||||
rs, err = amdb.QueryContext(ctx, `SELECT q.commid, q.alias, c.confid, t.topicid, t.num, p.postid, p.num, u2.username, p.posted, p.linecount, d.data
|
rs, err = amdb.QueryContext(ctx, `SELECT q.commid, q.alias, c.confid, t.topicid, t.num, p.postid, p.num, u2.username, p.posted, p.linecount, d.data
|
||||||
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
||||||
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
||||||
@@ -863,7 +871,7 @@ func AmSearchPosts(ctx context.Context, searchTerms string, u *User, offset, max
|
|||||||
WHERE u.uid = ? AND f.ftr_code = ? AND GREATEST(u.base_lvl,m.granted_lvl,s.granted_lvl,COALESCE(x.granted_lvl,0)) >= c.read_lvl
|
WHERE u.uid = ? AND f.ftr_code = ? AND GREATEST(u.base_lvl,m.granted_lvl,s.granted_lvl,COALESCE(x.granted_lvl,0)) >= c.read_lvl
|
||||||
AND q.commid = ? AND c.confid = ? AND p.scribble_uid IS NULL AND MATCH(d.data) AGAINST (?) ORDER BY q.commname, c.name, t.num, p.num
|
AND q.commid = ? AND c.confid = ? AND p.scribble_uid IS NULL AND MATCH(d.data) AGAINST (?) ORDER BY q.commname, c.name, t.num, p.num
|
||||||
LIMIT ? OFFSET ?`, u.Uid, confService, comm.Id, conf.ConfId, searchTerms, max, offset)
|
LIMIT ? OFFSET ?`, u.Uid, confService, comm.Id, conf.ConfId, searchTerms, max, offset)
|
||||||
case "topic":
|
case PLSCOPE_TOPIC:
|
||||||
rs, err = amdb.QueryContext(ctx, `SELECT q.commid, q.alias, c.confid, t.topicid, t.num, p.postid, p.num, u2.username, p.posted, p.linecount, d.data
|
rs, err = amdb.QueryContext(ctx, `SELECT q.commid, q.alias, c.confid, t.topicid, t.num, p.postid, p.num, u2.username, p.posted, p.linecount, d.data
|
||||||
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
FROM communities q JOIN commtoconf s ON s.commid = q.commid JOIN confs c ON c.confid = s.confid
|
||||||
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
JOIN commmember m ON m.commid = q.commid JOIN users u ON u.uid = m.uid JOIN commftrs f ON f.commid = q.commid
|
||||||
@@ -898,13 +906,13 @@ func AmSearchPosts(ctx context.Context, searchTerms string, u *User, offset, max
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, count, err
|
return nil, count, err
|
||||||
}
|
}
|
||||||
alias, err := conf.Aliases(ctx)
|
alias, err := conf.Aliases(ctx, commid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, count, err
|
return nil, count, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the post link.
|
// Build the post link.
|
||||||
plink := AmCreatePostLinkContext(commAlias, alias[0], topicNum)
|
plink := AmCreatePostLinkContext(commAlias, commid, alias[0], topicNum)
|
||||||
plink.FirstPost = postnum
|
plink.FirstPost = postnum
|
||||||
plink.LastPost = postnum
|
plink.LastPost = postnum
|
||||||
rc[i].PostLink = plink.AsString()
|
rc[i].PostLink = plink.AsString()
|
||||||
|
|||||||
+69
-45
@@ -20,9 +20,29 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Post link scopes.
|
||||||
|
const (
|
||||||
|
PLSCOPE_GLOBAL = "global"
|
||||||
|
PLSCOPE_COMMUNITY = "community"
|
||||||
|
PLSCOPE_CONFERENCE = "conference"
|
||||||
|
PLSCOPE_TOPIC = "topic"
|
||||||
|
PLSCOPE_ERROR = "error"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Post link classifications.
|
||||||
|
const (
|
||||||
|
PLCLASS_COMMUNITY = "community"
|
||||||
|
PLCLASS_CONFERENCE = "conference"
|
||||||
|
PLCLASS_TOPIC = "topic"
|
||||||
|
PLCLASS_POST = "post"
|
||||||
|
PLCLASS_POSTRANGE = "postrange"
|
||||||
|
PLCLASS_POSTOPENRANGE = "postopenrange"
|
||||||
|
)
|
||||||
|
|
||||||
// PostLinkData is the structure holding the decoded parts of the post link.
|
// PostLinkData is the structure holding the decoded parts of the post link.
|
||||||
type PostLinkData struct {
|
type PostLinkData struct {
|
||||||
Community string
|
Community string
|
||||||
|
CommId int32
|
||||||
Conference string
|
Conference string
|
||||||
Topic int16
|
Topic int16
|
||||||
FirstPost int32
|
FirstPost int32
|
||||||
@@ -36,6 +56,7 @@ func (d *PostLinkData) NeedsDBVerification() bool {
|
|||||||
|
|
||||||
// VerifyNames verifies the post link data against the database.
|
// VerifyNames verifies the post link data against the database.
|
||||||
func (d *PostLinkData) VerifyNames(ctx context.Context) error {
|
func (d *PostLinkData) VerifyNames(ctx context.Context) error {
|
||||||
|
commid := d.CommId
|
||||||
if d.Community != "" {
|
if d.Community != "" {
|
||||||
comm, err := AmGetCommunityByAlias(ctx, d.Community)
|
comm, err := AmGetCommunityByAlias(ctx, d.Community)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -44,9 +65,10 @@ func (d *PostLinkData) VerifyNames(ctx context.Context) error {
|
|||||||
if comm == nil {
|
if comm == nil {
|
||||||
return errors.New("community alias not found")
|
return errors.New("community alias not found")
|
||||||
}
|
}
|
||||||
|
commid = comm.Id
|
||||||
}
|
}
|
||||||
if d.Conference != "" {
|
if d.Conference != "" {
|
||||||
conf, err := AmGetConferenceByAlias(ctx, d.Conference)
|
conf, err := AmGetConferenceByAlias(ctx, commid, d.Conference)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -118,53 +140,53 @@ func (d *PostLinkData) Classify() (string, string) {
|
|||||||
if d.FirstPost == -1 {
|
if d.FirstPost == -1 {
|
||||||
return "", ""
|
return "", ""
|
||||||
} else if d.LastPost == -1 {
|
} else if d.LastPost == -1 {
|
||||||
return "topic", "postopenrange"
|
return PLSCOPE_TOPIC, PLCLASS_POSTOPENRANGE
|
||||||
} else if d.LastPost == d.FirstPost {
|
} else if d.LastPost == d.FirstPost {
|
||||||
return "topic", "post"
|
return PLSCOPE_TOPIC, PLCLASS_POST
|
||||||
} else {
|
} else {
|
||||||
return "topic", "postrange"
|
return PLSCOPE_TOPIC, PLCLASS_POSTRANGE
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if d.FirstPost == -1 {
|
if d.FirstPost == -1 {
|
||||||
return "conference", "topic"
|
return PLSCOPE_CONFERENCE, PLCLASS_TOPIC
|
||||||
} else if d.LastPost == -1 {
|
} else if d.LastPost == -1 {
|
||||||
return "conference", "postopenrange"
|
return PLSCOPE_CONFERENCE, PLCLASS_POSTOPENRANGE
|
||||||
} else if d.LastPost == d.FirstPost {
|
} else if d.LastPost == d.FirstPost {
|
||||||
return "conference", "post"
|
return PLSCOPE_CONFERENCE, PLCLASS_POST
|
||||||
} else {
|
} else {
|
||||||
return "conference", "postrange"
|
return PLSCOPE_CONFERENCE, PLCLASS_POSTRANGE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if d.Topic == -1 {
|
if d.Topic == -1 {
|
||||||
return "community", "conference"
|
return PLSCOPE_COMMUNITY, PLCLASS_CONFERENCE
|
||||||
} else {
|
} else {
|
||||||
if d.FirstPost == -1 {
|
if d.FirstPost == -1 {
|
||||||
return "community", "topic"
|
return PLSCOPE_COMMUNITY, PLCLASS_TOPIC
|
||||||
} else if d.LastPost == -1 {
|
} else if d.LastPost == -1 {
|
||||||
return "community", "postopenrange"
|
return PLSCOPE_COMMUNITY, PLCLASS_POSTOPENRANGE
|
||||||
} else if d.LastPost == d.FirstPost {
|
} else if d.LastPost == d.FirstPost {
|
||||||
return "community", "post"
|
return PLSCOPE_COMMUNITY, PLCLASS_POST
|
||||||
} else {
|
} else {
|
||||||
return "community", "postrange"
|
return PLSCOPE_COMMUNITY, PLCLASS_POSTRANGE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if d.Conference == "" {
|
if d.Conference == "" {
|
||||||
return "global", "community"
|
return PLSCOPE_GLOBAL, PLCLASS_COMMUNITY
|
||||||
} else {
|
} else {
|
||||||
if d.Topic == -1 {
|
if d.Topic == -1 {
|
||||||
return "global", "conference"
|
return PLSCOPE_GLOBAL, PLCLASS_CONFERENCE
|
||||||
} else {
|
} else {
|
||||||
if d.FirstPost == -1 {
|
if d.FirstPost == -1 {
|
||||||
return "global", "topic"
|
return PLSCOPE_GLOBAL, PLCLASS_TOPIC
|
||||||
} else if d.LastPost == -1 {
|
} else if d.LastPost == -1 {
|
||||||
return "global", "postopenrange"
|
return PLSCOPE_GLOBAL, PLCLASS_POSTOPENRANGE
|
||||||
} else if d.LastPost == d.FirstPost {
|
} else if d.LastPost == d.FirstPost {
|
||||||
return "global", "post"
|
return PLSCOPE_GLOBAL, PLCLASS_POST
|
||||||
} else {
|
} else {
|
||||||
return "global", "postrange"
|
return PLSCOPE_GLOBAL, PLCLASS_POSTRANGE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,25 +295,25 @@ func AmDecodePostLink(data string) (*PostLinkData, error) {
|
|||||||
if len(data) > maxLinkLength {
|
if len(data) > maxLinkLength {
|
||||||
return nil, errors.New("post link string too long")
|
return nil, errors.New("post link string too long")
|
||||||
}
|
}
|
||||||
rc := PostLinkData{
|
rc := new(PostLinkData{
|
||||||
Community: "",
|
Community: "",
|
||||||
Conference: "",
|
Conference: "",
|
||||||
Topic: -1,
|
Topic: -1,
|
||||||
FirstPost: -1,
|
FirstPost: -1,
|
||||||
LastPost: -1,
|
LastPost: -1,
|
||||||
}
|
})
|
||||||
|
|
||||||
work := data
|
work := data
|
||||||
// First test: Bang
|
// First test: Bang
|
||||||
pos := strings.IndexByte(work, '!')
|
pos := strings.IndexByte(work, '!')
|
||||||
if pos > 0 {
|
if pos > 0 {
|
||||||
err := validateCommunity(work[:pos], &rc)
|
err := validateCommunity(work[:pos], rc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
work = work[pos+1:]
|
work = work[pos+1:]
|
||||||
if len(work) == 0 {
|
if len(work) == 0 {
|
||||||
return &rc, nil // community link
|
return rc, nil // community link
|
||||||
}
|
}
|
||||||
} else if pos == 0 {
|
} else if pos == 0 {
|
||||||
return nil, errors.New("cannot have ! at beginning")
|
return nil, errors.New("cannot have ! at beginning")
|
||||||
@@ -303,14 +325,14 @@ func AmDecodePostLink(data string) (*PostLinkData, error) {
|
|||||||
// no dots in here, must be either "postlink" or "community!conference"
|
// no dots in here, must be either "postlink" or "community!conference"
|
||||||
var err error
|
var err error
|
||||||
if rc.Community == "" {
|
if rc.Community == "" {
|
||||||
err = decodePostRange(work, &rc)
|
err = decodePostRange(work, rc)
|
||||||
} else {
|
} else {
|
||||||
err = validateConference(work, &rc)
|
err = validateConference(work, rc)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &rc, nil
|
return rc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peel off the initial substring before the dot.
|
// Peel off the initial substring before the dot.
|
||||||
@@ -321,19 +343,19 @@ func AmDecodePostLink(data string) (*PostLinkData, error) {
|
|||||||
var err error
|
var err error
|
||||||
if rc.Community == "" {
|
if rc.Community == "" {
|
||||||
// it's either "conference." or "topic." - try the latter first
|
// it's either "conference." or "topic." - try the latter first
|
||||||
err = decodeTopicNumber(confOrTopic, &rc)
|
err = decodeTopicNumber(confOrTopic, rc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// it's not a topic number, try it as a conference name
|
// it's not a topic number, try it as a conference name
|
||||||
err = validateConference(confOrTopic, &rc)
|
err = validateConference(confOrTopic, rc)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// it was "community!conference."
|
// it was "community!conference."
|
||||||
err = validateConference(confOrTopic, &rc)
|
err = validateConference(confOrTopic, rc)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &rc, nil
|
return rc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Third test: Dot #2
|
// Third test: Dot #2
|
||||||
@@ -344,38 +366,38 @@ func AmDecodePostLink(data string) (*PostLinkData, error) {
|
|||||||
if rc.Community == "" {
|
if rc.Community == "" {
|
||||||
// either "conference.topic" or "topic.posts"
|
// either "conference.topic" or "topic.posts"
|
||||||
isTopic := false
|
isTopic := false
|
||||||
err = decodeTopicNumber(confOrTopic, &rc)
|
err = decodeTopicNumber(confOrTopic, rc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// it's "conference.topic"
|
// it's "conference.topic"
|
||||||
err = validateConference(confOrTopic, &rc)
|
err = validateConference(confOrTopic, rc)
|
||||||
isTopic = true
|
isTopic = true
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if isTopic {
|
if isTopic {
|
||||||
err = decodeTopicNumber(work, &rc)
|
err = decodeTopicNumber(work, rc)
|
||||||
} else {
|
} else {
|
||||||
err = decodePostRange(work, &rc)
|
err = decodePostRange(work, rc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// we have "community!conference.topic"
|
// we have "community!conference.topic"
|
||||||
err = validateConference(confOrTopic, &rc)
|
err = validateConference(confOrTopic, rc)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = decodeTopicNumber(work, &rc)
|
err = decodeTopicNumber(work, rc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &rc, nil
|
return rc, nil
|
||||||
} else if pos == 0 {
|
} else if pos == 0 {
|
||||||
return nil, errors.New("cannot have . at beginning of string")
|
return nil, errors.New("cannot have . at beginning of string")
|
||||||
}
|
}
|
||||||
|
|
||||||
// We definitely have "conference.topic.something" or "community!conference.topic.something"
|
// We definitely have "conference.topic.something" or "community!conference.topic.something"
|
||||||
err := validateConference(confOrTopic, &rc)
|
err := validateConference(confOrTopic, rc)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = decodeTopicNumber(work[:pos], &rc)
|
err = decodeTopicNumber(work[:pos], rc)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -383,21 +405,23 @@ func AmDecodePostLink(data string) (*PostLinkData, error) {
|
|||||||
work = work[pos+1:]
|
work = work[pos+1:]
|
||||||
if len(work) == 0 {
|
if len(work) == 0 {
|
||||||
// we had "conference.topic." or "communtiy!conference.topic.", those are both valid
|
// we had "conference.topic." or "communtiy!conference.topic.", those are both valid
|
||||||
return &rc, nil
|
return rc, nil
|
||||||
}
|
}
|
||||||
err = decodePostRange(work, &rc) // the rest must be the post range
|
err = decodePostRange(work, rc) // the rest must be the post range
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &rc, nil
|
return rc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func AmCreatePostLinkContext(community string, conference string, topic int16) *PostLinkData {
|
// AmCreatePostLinkContext creates a new empty post link context.
|
||||||
return &PostLinkData{
|
func AmCreatePostLinkContext(community string, commid int32, conference string, topic int16) *PostLinkData {
|
||||||
|
return new(PostLinkData{
|
||||||
Community: community,
|
Community: community,
|
||||||
|
CommId: commid,
|
||||||
Conference: conference,
|
Conference: conference,
|
||||||
Topic: topic,
|
Topic: topic,
|
||||||
FirstPost: -1,
|
FirstPost: -1,
|
||||||
LastPost: -1,
|
LastPost: -1,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-9
@@ -25,6 +25,20 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// The service domain names.
|
||||||
|
const (
|
||||||
|
AM_DOMAIN_COMMUNITY = "community"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The service names.
|
||||||
|
const (
|
||||||
|
AM_SVC_PROFILE = "Profile"
|
||||||
|
AM_SVC_ADMIN = "Admin"
|
||||||
|
AM_SVC_SYSADMIN = "SysAdmin"
|
||||||
|
AM_SVC_CONFERENCE = "Conference"
|
||||||
|
AM_SVC_MEMBERS = "Members"
|
||||||
|
)
|
||||||
|
|
||||||
// ServiceVTable is a series of functions called for services on specific events.
|
// ServiceVTable is a series of functions called for services on specific events.
|
||||||
type ServiceVTable interface {
|
type ServiceVTable interface {
|
||||||
OnNewCommunity(context.Context, *sqlx.Tx, *Community) error
|
OnNewCommunity(context.Context, *sqlx.Tx, *Community) error
|
||||||
@@ -114,13 +128,13 @@ func init() {
|
|||||||
serviceRoot.Domains[i].seqOrder = sqo
|
serviceRoot.Domains[i].seqOrder = sqo
|
||||||
serviceRoot.byName[dom.DomainName] = &(serviceRoot.Domains[i])
|
serviceRoot.byName[dom.DomainName] = &(serviceRoot.Domains[i])
|
||||||
}
|
}
|
||||||
dom := serviceRoot.byName["community"]
|
dom := serviceRoot.byName[AM_DOMAIN_COMMUNITY]
|
||||||
empty := emptyServiceVTable{}
|
empty := emptyServiceVTable{}
|
||||||
dom.byId["Profile"].vtable = &empty
|
dom.byId[AM_SVC_PROFILE].vtable = &empty
|
||||||
dom.byId["Admin"].vtable = &empty
|
dom.byId[AM_SVC_ADMIN].vtable = &empty
|
||||||
dom.byId["SysAdmin"].vtable = &empty
|
dom.byId[AM_SVC_SYSADMIN].vtable = &empty
|
||||||
dom.byId["Conference"].vtable = &(conferenceServiceVTable{})
|
dom.byId[AM_SVC_CONFERENCE].vtable = &(conferenceServiceVTable{})
|
||||||
dom.byId["Members"].vtable = &empty
|
dom.byId[AM_SVC_MEMBERS].vtable = &empty
|
||||||
}
|
}
|
||||||
|
|
||||||
// setupServicesCache sets up the services cache.
|
// setupServicesCache sets up the services cache.
|
||||||
@@ -166,7 +180,7 @@ func AmGetCommunityServices(ctx context.Context, cid int32) ([]*ServiceDef, erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
dom := serviceRoot.byName["community"]
|
dom := serviceRoot.byName[AM_DOMAIN_COMMUNITY]
|
||||||
a := make([]*ServiceDef, 0, len(dom.Services))
|
a := make([]*ServiceDef, 0, len(dom.Services))
|
||||||
for rs.Next() {
|
for rs.Next() {
|
||||||
var ndx int16
|
var ndx int16
|
||||||
@@ -198,7 +212,7 @@ func AmGetCommunityServicesTx(ctx context.Context, tx *sqlx.Tx, cid int32) ([]*S
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
dom := serviceRoot.byName["community"]
|
dom := serviceRoot.byName[AM_DOMAIN_COMMUNITY]
|
||||||
a := make([]*ServiceDef, 0, len(dom.Services))
|
a := make([]*ServiceDef, 0, len(dom.Services))
|
||||||
for rs.Next() {
|
for rs.Next() {
|
||||||
var ndx int16
|
var ndx int16
|
||||||
@@ -222,7 +236,7 @@ func AmGetCommunityServicesTx(ctx context.Context, tx *sqlx.Tx, cid int32) ([]*S
|
|||||||
* Standard Go error status.
|
* Standard Go error status.
|
||||||
*/
|
*/
|
||||||
func AmEstablishCommunityServices(ctx context.Context, tx *sqlx.Tx, c *Community) error {
|
func AmEstablishCommunityServices(ctx context.Context, tx *sqlx.Tx, c *Community) error {
|
||||||
dom := serviceRoot.byName["community"]
|
dom := serviceRoot.byName[AM_DOMAIN_COMMUNITY]
|
||||||
a := make([]*ServiceDef, 0, len(dom.Services))
|
a := make([]*ServiceDef, 0, len(dom.Services))
|
||||||
for i, svc := range dom.Services {
|
for i, svc := range dom.Services {
|
||||||
if svc.Default {
|
if svc.Default {
|
||||||
|
|||||||
+4
-4
@@ -43,15 +43,15 @@ type Topic struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Link returns a link string to this topic.
|
// Link returns a link string to this topic.
|
||||||
func (t *Topic) Link(ctx context.Context, scope string) (string, error) {
|
func (t *Topic) Link(ctx context.Context, commid int32, scope string) (string, error) {
|
||||||
if scope == "conference" {
|
if scope == PLSCOPE_CONFERENCE {
|
||||||
return fmt.Sprintf("%d.", t.Number), nil
|
return fmt.Sprintf("%d.", t.Number), nil
|
||||||
}
|
}
|
||||||
if scope == "community" || scope == "global" {
|
if scope == PLSCOPE_COMMUNITY || scope == PLSCOPE_GLOBAL {
|
||||||
conf, err := AmGetConference(ctx, t.ConfId)
|
conf, err := AmGetConference(ctx, t.ConfId)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
var plink string
|
var plink string
|
||||||
plink, err = conf.Link(ctx, scope)
|
plink, err = conf.Link(ctx, commid, scope)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if strings.HasSuffix(plink, ".") {
|
if strings.HasSuffix(plink, ".") {
|
||||||
return fmt.Sprintf("%s%d", plink, t.Number), nil
|
return fmt.Sprintf("%s%d", plink, t.Number), nil
|
||||||
|
|||||||
@@ -381,6 +381,18 @@ func (u *User) Prefs(ctx context.Context) (*UserPrefs, error) {
|
|||||||
return u.prefs, nil
|
return u.prefs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *User) SetUsername(ctx context.Context, username string, setter *User, ipaddr string) error {
|
||||||
|
u.Mutex.Lock()
|
||||||
|
_, err := amdb.ExecContext(ctx, "UPDATE users SET username = ? WHERE uid = ?", username, u.Uid)
|
||||||
|
u.Mutex.Unlock()
|
||||||
|
if err == nil {
|
||||||
|
u.Username = username
|
||||||
|
AmStoreAudit(AmNewAudit(AuditAdminSetUserName, setter.Uid, ipaddr, fmt.Sprintf("uid=%d", u.Uid),
|
||||||
|
fmt.Sprintf("newname=%s", username)))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
/* SetProfileData sets the "profile" variables for this user.
|
/* SetProfileData sets the "profile" variables for this user.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* ctx - Standard Go context value.
|
* ctx - Standard Go context value.
|
||||||
|
|||||||
@@ -1,5 +1,22 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Release 0.2.0 - April 29, 2026
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Architectural error: conference aliases now have community scope, rather than global scope.
|
||||||
|
* Fix to link for showing hidden posts.
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
* Added database initialization, conversion from Venice, and migration scripts.
|
||||||
|
* Admin can now change the name of a user account (issue #2).
|
||||||
|
* Apple site icon added (issue #5).
|
||||||
|
|
||||||
|
### Meta-Enhancements
|
||||||
|
|
||||||
|
* Action included to automagically build binaries when a release happens.
|
||||||
|
|
||||||
## Release 0.1.1 - April 11, 2020
|
## Release 0.1.1 - April 11, 2020
|
||||||
|
|
||||||
* Fixed a bug in post link resolution at post time which was causing the conference alias to be set incorrectly (issue #3).
|
* Fixed a bug in post link resolution at post time which was causing the conference alias to be set incorrectly (issue #3).
|
||||||
|
|||||||
@@ -234,6 +234,9 @@ func SetupMailSender() func() {
|
|||||||
emailRenderer.AddGlobal("AmsterdamVersion", config.AMSTERDAM_VERSION)
|
emailRenderer.AddGlobal("AmsterdamVersion", config.AMSTERDAM_VERSION)
|
||||||
emailRenderer.AddGlobal("AmsterdamCopyright", config.AMSTERDAM_COPYRIGHT)
|
emailRenderer.AddGlobal("AmsterdamCopyright", config.AMSTERDAM_COPYRIGHT)
|
||||||
emailRenderer.AddGlobal("GlobalConfig", config.GlobalConfig)
|
emailRenderer.AddGlobal("GlobalConfig", config.GlobalConfig)
|
||||||
|
emailRenderer.AddGlobal("PLSCOPE_COMMUNITY", database.PLSCOPE_COMMUNITY)
|
||||||
|
emailRenderer.AddGlobal("PLSCOPE_CONFERENCE", database.PLSCOPE_CONFERENCE)
|
||||||
|
emailRenderer.AddGlobal("PLSCOPE_TOPIC", database.PLSCOPE_TOPIC)
|
||||||
|
|
||||||
// Start the recycler.
|
// Start the recycler.
|
||||||
messageRecycleBin = make(chan *amMessage, config.GlobalConfig.Tuning.Queues.EmailRecycle)
|
messageRecycleBin = make(chan *amMessage, config.GlobalConfig.Tuning.Queues.EmailRecycle)
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ func AmDeliverSubscription(ctx context.Context, comm *database.Community, conf *
|
|||||||
vars.Set("communityName", comm.Name)
|
vars.Set("communityName", comm.Name)
|
||||||
vars.Set("conferenceName", conf.Name)
|
vars.Set("conferenceName", conf.Name)
|
||||||
vars.Set("topicName", topic.Name)
|
vars.Set("topicName", topic.Name)
|
||||||
pl := database.AmCreatePostLinkContext(comm.Alias, confAlias, topic.Number)
|
pl := database.AmCreatePostLinkContext(comm.Alias, comm.Id, confAlias, topic.Number)
|
||||||
vars.Set("topicLink", pl.AsString())
|
vars.Set("topicLink", pl.AsString())
|
||||||
vars.Set("pseud", realPseud)
|
vars.Set("pseud", realPseud)
|
||||||
vars.Set("text", realText)
|
vars.Set("text", realText)
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ 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
|
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
|
"{{ comm.JoinKey }}". You will then be able to take part in the conferences that are
|
||||||
going on in the community.
|
going on in the community.
|
||||||
{{ if mode == "conference" }}
|
{{ if mode == INVMODE_CONFERENCE }}
|
||||||
After you've joined the "{{ comm.Name }}" community, check out the
|
After you've joined the "{{ comm.Name }}" community, check out the
|
||||||
"{{ conf.Name }}" conference. To find it, after joining the community,
|
"{{ conf.Name }}" conference. To find it, after joining the community,
|
||||||
click "Conferences" on the left menu bar, then click on the
|
click "Conferences" on the left menu bar, then click on the
|
||||||
"{{ conf.Name }}" conference name in the conference list.
|
"{{ conf.Name }}" conference name in the conference list.
|
||||||
{{ else if mode == "topic" }}
|
{{ else if mode == INVMODE_TOPIC }}
|
||||||
After you've joined the "{{ comm.Name }}" community, check out the
|
After you've joined the "{{ comm.Name }}" community, check out the
|
||||||
"{{ topic.Name | raw }}" topic in the "{{ conf.Name }}" conference. To find it,
|
"{{ topic.Name | raw }}" topic in the "{{ conf.Name }}" conference. To find it,
|
||||||
after joining the community, click "Conferences" on the left menu bar, then
|
after joining the community, click "Conferences" on the left menu bar, then
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ 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"
|
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
|
button. You will then be able to take part in the conferences that are
|
||||||
going on in the community.
|
going on in the community.
|
||||||
{{ if mode == "conference" }}
|
{{ if mode == INVMODE_CONFERENCE }}
|
||||||
After you've joined the "{{ comm.Name }}" community, check out the
|
After you've joined the "{{ comm.Name }}" community, check out the
|
||||||
"{{ conf.Name }}" conference. To find it, after joining the community,
|
"{{ conf.Name }}" conference. To find it, after joining the community,
|
||||||
click "Conferences" on the left menu bar, then click on the
|
click "Conferences" on the left menu bar, then click on the
|
||||||
"{{ conf.Name }}" conference name in the conference list.
|
"{{ conf.Name }}" conference name in the conference list.
|
||||||
{{ else if mode == "topic" }}
|
{{ else if mode == INVMODE_TOPIC }}
|
||||||
After you've joined the "{{ comm.Name }}" community, check out the
|
After you've joined the "{{ comm.Name }}" community, check out the
|
||||||
"{{ topic.Name | raw }}" topic in the "{{ conf.Name }}" conference. To find it,
|
"{{ topic.Name | raw }}" topic in the "{{ conf.Name }}" conference. To find it,
|
||||||
after joining the community, click "Conferences" on the left menu bar, then
|
after joining the community, click "Conferences" on the left menu bar, then
|
||||||
|
|||||||
@@ -19,10 +19,9 @@ import (
|
|||||||
|
|
||||||
"git.erbosoft.com/amy/amsterdam/config"
|
"git.erbosoft.com/amy/amsterdam/config"
|
||||||
"git.erbosoft.com/amy/amsterdam/ui"
|
"git.erbosoft.com/amy/amsterdam/ui"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v5"
|
||||||
"github.com/labstack/echo/v4/middleware"
|
"github.com/labstack/echo/v5/middleware"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/time/rate"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// EBUTTON is the standard error for an unknown button.
|
// EBUTTON is the standard error for an unknown button.
|
||||||
@@ -74,9 +73,9 @@ func AmNotFoundHandler(ctxt ui.AmContext) (string, any) {
|
|||||||
* err - The error to be handled.
|
* err - The error to be handled.
|
||||||
* c - The Echo context error is being handled on.
|
* c - The Echo context error is being handled on.
|
||||||
*/
|
*/
|
||||||
func AmErrorHandler(err error, c echo.Context) {
|
func AmErrorHandler(c *echo.Context, err error) {
|
||||||
log.Infof("-> AmErrorHandler on path %s", c.Request().URL.Path)
|
log.Infof("-> AmErrorHandler on path %s", c.Request().URL.Path)
|
||||||
if c.Response().Committed {
|
if c.Response().(*echo.Response).Committed {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cerr := ui.AmWithTempContext(c, func(ctxt ui.AmContext) (string, any) {
|
cerr := ui.AmWithTempContext(c, func(ctxt ui.AmContext) (string, any) {
|
||||||
@@ -88,14 +87,14 @@ func AmErrorHandler(err error, c echo.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// rateLimitErrorHandler is called if there's an error getting the identifier for a connection (unlikely).
|
// rateLimitErrorHandler is called if there's an error getting the identifier for a connection (unlikely).
|
||||||
func rateLimitErrorHandler(c echo.Context, err error) error {
|
func rateLimitErrorHandler(c *echo.Context, err error) error {
|
||||||
return ui.AmWithTempContext(c, func(ctxt ui.AmContext) (string, any) {
|
return ui.AmWithTempContext(c, func(ctxt ui.AmContext) (string, any) {
|
||||||
return "error", err
|
return "error", err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// rateLimitDenyHandler is called if the rate limit is exceeded by a connection.
|
// rateLimitDenyHandler is called if the rate limit is exceeded by a connection.
|
||||||
func rateLimitDenyHandler(c echo.Context, identifier string, err error) error {
|
func rateLimitDenyHandler(c *echo.Context, identifier string, err error) error {
|
||||||
return ui.AmWithTempContext(c, func(ctxt ui.AmContext) (string, any) {
|
return ui.AmWithTempContext(c, func(ctxt ui.AmContext) (string, any) {
|
||||||
ctxt.VarMap().Set("identifier", identifier)
|
ctxt.VarMap().Set("identifier", identifier)
|
||||||
return "ratelimit", err
|
return "ratelimit", err
|
||||||
@@ -105,7 +104,7 @@ func rateLimitDenyHandler(c echo.Context, identifier string, err error) error {
|
|||||||
// AmSetupRateLimiter sets up the rate-limiting middleware.
|
// AmSetupRateLimiter sets up the rate-limiting middleware.
|
||||||
func AmSetupRateLimiter() echo.MiddlewareFunc {
|
func AmSetupRateLimiter() echo.MiddlewareFunc {
|
||||||
rcfg := middleware.RateLimiterMemoryStoreConfig{
|
rcfg := middleware.RateLimiterMemoryStoreConfig{
|
||||||
Rate: rate.Limit(config.GlobalConfig.Site.RateLimit.Rate),
|
Rate: config.GlobalConfig.Site.RateLimit.Rate,
|
||||||
Burst: config.GlobalConfig.Site.RateLimit.Burst,
|
Burst: config.GlobalConfig.Site.RateLimit.Burst,
|
||||||
ExpiresIn: time.Duration(config.GlobalConfig.Site.RateLimit.ExpireMinutes) * time.Minute,
|
ExpiresIn: time.Duration(config.GlobalConfig.Site.RateLimit.ExpireMinutes) * time.Minute,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -330,7 +330,7 @@ func commonFindGetBackend(ctxt ui.AmContext) (string, any) {
|
|||||||
*/
|
*/
|
||||||
func FindPostsPageCommunity(ctxt ui.AmContext) (string, any) {
|
func FindPostsPageCommunity(ctxt ui.AmContext) (string, any) {
|
||||||
comm := ctxt.CurrentCommunity()
|
comm := ctxt.CurrentCommunity()
|
||||||
ctxt.VarMap().Set("scope", "community")
|
ctxt.VarMap().Set("scope", database.PLSCOPE_COMMUNITY)
|
||||||
ctxt.VarMap().Set("entityName", comm.Name)
|
ctxt.VarMap().Set("entityName", comm.Name)
|
||||||
ctxt.VarMap().Set("backlink", fmt.Sprintf("/comm/%s/conf", comm.Alias))
|
ctxt.VarMap().Set("backlink", fmt.Sprintf("/comm/%s/conf", comm.Alias))
|
||||||
ctxt.VarMap().Set("postlink", fmt.Sprintf("/comm/%s/find", comm.Alias))
|
ctxt.VarMap().Set("postlink", fmt.Sprintf("/comm/%s/find", comm.Alias))
|
||||||
@@ -345,12 +345,11 @@ func FindPostsPageCommunity(ctxt ui.AmContext) (string, any) {
|
|||||||
* Data as a parameter for the command string.
|
* Data as a parameter for the command string.
|
||||||
*/
|
*/
|
||||||
func FindPostsPageConference(ctxt ui.AmContext) (string, any) {
|
func FindPostsPageConference(ctxt ui.AmContext) (string, any) {
|
||||||
comm := ctxt.CurrentCommunity()
|
|
||||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||||
ctxt.VarMap().Set("scope", "conference")
|
ctxt.VarMap().Set("scope", database.PLSCOPE_CONFERENCE)
|
||||||
ctxt.VarMap().Set("entityName", conf.Name)
|
ctxt.VarMap().Set("entityName", conf.Name)
|
||||||
ctxt.VarMap().Set("backlink", fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, ctxt.GetScratch("currentAlias")))
|
ctxt.VarMap().Set("backlink", ctxt.GetScratch("ConferenceLink").(string))
|
||||||
ctxt.VarMap().Set("postlink", fmt.Sprintf("/comm/%s/conf/%s/find", comm.Alias, ctxt.GetScratch("currentAlias")))
|
ctxt.VarMap().Set("postlink", fmt.Sprintf("%s/find", ctxt.GetScratch("ConferenceLink")))
|
||||||
return commonFindGetBackend(ctxt)
|
return commonFindGetBackend(ctxt)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,12 +361,11 @@ func FindPostsPageConference(ctxt ui.AmContext) (string, any) {
|
|||||||
* Data as a parameter for the command string.
|
* Data as a parameter for the command string.
|
||||||
*/
|
*/
|
||||||
func FindPostsPageTopic(ctxt ui.AmContext) (string, any) {
|
func FindPostsPageTopic(ctxt ui.AmContext) (string, any) {
|
||||||
comm := ctxt.CurrentCommunity()
|
|
||||||
topic := ctxt.GetScratch("currentTopic").(*database.Topic)
|
topic := ctxt.GetScratch("currentTopic").(*database.Topic)
|
||||||
ctxt.VarMap().Set("scope", "topic")
|
ctxt.VarMap().Set("scope", database.PLSCOPE_TOPIC)
|
||||||
ctxt.VarMap().Set("entityName", topic.Name)
|
ctxt.VarMap().Set("entityName", 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("%s/r/%d", ctxt.GetScratch("ConferenceLink"), topic.Number))
|
||||||
ctxt.VarMap().Set("postlink", fmt.Sprintf("/comm/%s/conf/%s/op/%d/find", comm.Alias, ctxt.GetScratch("currentAlias"), topic.Number))
|
ctxt.VarMap().Set("postlink", fmt.Sprintf("%s/op/%d/find", ctxt.GetScratch("ConferenceLink"), topic.Number))
|
||||||
return commonFindGetBackend(ctxt)
|
return commonFindGetBackend(ctxt)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,7 +417,7 @@ func commonFindPostBackend(ctxt ui.AmContext, comm *database.Community, conf *da
|
|||||||
*/
|
*/
|
||||||
func FindPostsCommunity(ctxt ui.AmContext) (string, any) {
|
func FindPostsCommunity(ctxt ui.AmContext) (string, any) {
|
||||||
comm := ctxt.CurrentCommunity()
|
comm := ctxt.CurrentCommunity()
|
||||||
ctxt.VarMap().Set("scope", "community")
|
ctxt.VarMap().Set("scope", database.PLSCOPE_COMMUNITY)
|
||||||
ctxt.VarMap().Set("entityName", comm.Name)
|
ctxt.VarMap().Set("entityName", comm.Name)
|
||||||
ctxt.VarMap().Set("backlink", fmt.Sprintf("/comm/%s/conf", comm.Alias))
|
ctxt.VarMap().Set("backlink", fmt.Sprintf("/comm/%s/conf", comm.Alias))
|
||||||
ctxt.VarMap().Set("postlink", fmt.Sprintf("/comm/%s/find", comm.Alias))
|
ctxt.VarMap().Set("postlink", fmt.Sprintf("/comm/%s/find", comm.Alias))
|
||||||
@@ -436,10 +434,10 @@ func FindPostsCommunity(ctxt ui.AmContext) (string, any) {
|
|||||||
func FindPostsConference(ctxt ui.AmContext) (string, any) {
|
func FindPostsConference(ctxt ui.AmContext) (string, any) {
|
||||||
comm := ctxt.CurrentCommunity()
|
comm := ctxt.CurrentCommunity()
|
||||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||||
ctxt.VarMap().Set("scope", "conference")
|
ctxt.VarMap().Set("scope", database.PLSCOPE_CONFERENCE)
|
||||||
ctxt.VarMap().Set("entityName", conf.Name)
|
ctxt.VarMap().Set("entityName", conf.Name)
|
||||||
ctxt.VarMap().Set("backlink", fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, ctxt.GetScratch("currentAlias")))
|
ctxt.VarMap().Set("backlink", ctxt.GetScratch("ConferenceLink").(string))
|
||||||
ctxt.VarMap().Set("postlink", fmt.Sprintf("/comm/%s/conf/%s/find", comm.Alias, ctxt.GetScratch("currentAlias")))
|
ctxt.VarMap().Set("postlink", fmt.Sprintf("%s/find", ctxt.GetScratch("ConferenceLink")))
|
||||||
return commonFindPostBackend(ctxt, comm, conf, nil)
|
return commonFindPostBackend(ctxt, comm, conf, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,9 +452,9 @@ func FindPostsTopic(ctxt ui.AmContext) (string, any) {
|
|||||||
comm := ctxt.CurrentCommunity()
|
comm := ctxt.CurrentCommunity()
|
||||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||||
topic := ctxt.GetScratch("currentTopic").(*database.Topic)
|
topic := ctxt.GetScratch("currentTopic").(*database.Topic)
|
||||||
ctxt.VarMap().Set("scope", "topic")
|
ctxt.VarMap().Set("scope", database.PLSCOPE_TOPIC)
|
||||||
ctxt.VarMap().Set("entityName", topic.Name)
|
ctxt.VarMap().Set("entityName", 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("%s/r/%d", ctxt.GetScratch("ConferenceLink"), topic.Number))
|
||||||
ctxt.VarMap().Set("postlink", fmt.Sprintf("/comm/%s/conf/%s/op/%d/find", comm.Alias, ctxt.GetScratch("currentAlias"), topic.Number))
|
ctxt.VarMap().Set("postlink", fmt.Sprintf("%s/op/%d/find", ctxt.GetScratch("ConferenceLink"), topic.Number))
|
||||||
return commonFindPostBackend(ctxt, comm, conf, topic)
|
return commonFindPostBackend(ctxt, comm, conf, topic)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,13 +14,12 @@ require (
|
|||||||
github.com/hashicorp/golang-lru v1.0.2
|
github.com/hashicorp/golang-lru v1.0.2
|
||||||
github.com/jmoiron/sqlx v1.4.0
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
github.com/klauspost/lctime v0.1.0
|
github.com/klauspost/lctime v0.1.0
|
||||||
github.com/labstack/echo/v4 v4.15.1
|
github.com/labstack/echo/v5 v5.1.1
|
||||||
github.com/labstack/gommon v0.4.2
|
github.com/labstack/gommon v0.5.0
|
||||||
github.com/sirupsen/logrus v1.9.4
|
github.com/sirupsen/logrus v1.9.4
|
||||||
github.com/tkuchiki/go-timezone v0.2.3
|
github.com/tkuchiki/go-timezone v0.2.3
|
||||||
golang.org/x/net v0.52.0
|
golang.org/x/net v0.53.0
|
||||||
golang.org/x/text v0.35.0
|
golang.org/x/text v0.36.0
|
||||||
golang.org/x/time v0.15.0
|
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,10 +28,10 @@ require (
|
|||||||
github.com/CloudyKit/fastprinter v0.0.0-20251202014920-1725d2651bd4 // indirect
|
github.com/CloudyKit/fastprinter v0.0.0-20251202014920-1725d2651bd4 // indirect
|
||||||
github.com/alexflint/go-scalar v1.2.0 // indirect
|
github.com/alexflint/go-scalar v1.2.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.22 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
golang.org/x/crypto v0.49.0 // indirect
|
|
||||||
golang.org/x/image v0.37.0 // indirect
|
golang.org/x/image v0.37.0 // indirect
|
||||||
golang.org/x/sys v0.42.0 // indirect
|
golang.org/x/sys v0.43.0 // indirect
|
||||||
|
golang.org/x/time v0.15.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,28 +1,19 @@
|
|||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||||
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c=
|
|
||||||
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
|
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
|
||||||
github.com/CloudyKit/fastprinter v0.0.0-20251202014920-1725d2651bd4 h1:DQ1+lDdBve+u+aovjh4wV6sYnvZKH0Hx8GaQOi4vYl8=
|
github.com/CloudyKit/fastprinter v0.0.0-20251202014920-1725d2651bd4 h1:DQ1+lDdBve+u+aovjh4wV6sYnvZKH0Hx8GaQOi4vYl8=
|
||||||
github.com/CloudyKit/fastprinter v0.0.0-20251202014920-1725d2651bd4/go.mod h1:eauGmjfZG874MOAEPVeqg21mZCbTOLW+tFe8F7NpfnY=
|
github.com/CloudyKit/fastprinter v0.0.0-20251202014920-1725d2651bd4/go.mod h1:eauGmjfZG874MOAEPVeqg21mZCbTOLW+tFe8F7NpfnY=
|
||||||
github.com/CloudyKit/jet/v6 v6.3.1 h1:6IAo5Cx21xrHVaR8zzXN5gJatKV/wO7Nf6bfCnCSbUw=
|
|
||||||
github.com/CloudyKit/jet/v6 v6.3.1/go.mod h1:lf8ksdNsxZt7/yH/3n4vJQWA9RUq4wpaHtArHhGVMOw=
|
|
||||||
github.com/CloudyKit/jet/v6 v6.3.2 h1:BPaX0lnXTZ9TniICiiK/0iJqzeGJ2ibvB4DjAqLMBSM=
|
github.com/CloudyKit/jet/v6 v6.3.2 h1:BPaX0lnXTZ9TniICiiK/0iJqzeGJ2ibvB4DjAqLMBSM=
|
||||||
github.com/CloudyKit/jet/v6 v6.3.2/go.mod h1:lf8ksdNsxZt7/yH/3n4vJQWA9RUq4wpaHtArHhGVMOw=
|
github.com/CloudyKit/jet/v6 v6.3.2/go.mod h1:lf8ksdNsxZt7/yH/3n4vJQWA9RUq4wpaHtArHhGVMOw=
|
||||||
github.com/alexflint/go-arg v1.6.0 h1:wPP9TwTPO54fUVQl4nZoxbFfKCcy5E6HBCumj1XVRSo=
|
|
||||||
github.com/alexflint/go-arg v1.6.0/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8=
|
|
||||||
github.com/alexflint/go-arg v1.6.1 h1:uZogJ6VDBjcuosydKgvYYRhh9sRCusjOvoOLZopBlnA=
|
github.com/alexflint/go-arg v1.6.1 h1:uZogJ6VDBjcuosydKgvYYRhh9sRCusjOvoOLZopBlnA=
|
||||||
github.com/alexflint/go-arg v1.6.1/go.mod h1:nQ0LFYftLJ6njcaee0sU+G0iS2+2XJQfA8I062D0LGc=
|
github.com/alexflint/go-arg v1.6.1/go.mod h1:nQ0LFYftLJ6njcaee0sU+G0iS2+2XJQfA8I062D0LGc=
|
||||||
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
|
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
|
||||||
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
|
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
|
||||||
github.com/biter777/countries v1.7.5 h1:MJ+n3+rSxWQdqVJU8eBy9RqcdH6ePPn4PJHocVWUa+Q=
|
github.com/biter777/countries v1.7.5 h1:MJ+n3+rSxWQdqVJU8eBy9RqcdH6ePPn4PJHocVWUa+Q=
|
||||||
github.com/biter777/countries v1.7.5/go.mod h1:1HSpZ526mYqKJcpT5Ti1kcGQ0L0SrXWIaptUWjFfv2E=
|
github.com/biter777/countries v1.7.5/go.mod h1:1HSpZ526mYqKJcpT5Ti1kcGQ0L0SrXWIaptUWjFfv2E=
|
||||||
github.com/bits-and-blooms/bitset v1.24.0 h1:H4x4TuulnokZKvHLfzVRTHJfFfnHEeSYJizujEZvmAM=
|
|
||||||
github.com/bits-and-blooms/bitset v1.24.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
|
||||||
github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=
|
github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=
|
||||||
github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/derekparker/trie v0.0.0-20230829180723-39f4de51ef7d h1:hUWoLdw5kvo2xCsqlsIBMvWUc1QCSsCYD2J2+Fg6YoU=
|
github.com/derekparker/trie v0.0.0-20230829180723-39f4de51ef7d h1:hUWoLdw5kvo2xCsqlsIBMvWUc1QCSsCYD2J2+Fg6YoU=
|
||||||
@@ -40,77 +31,44 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
|||||||
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||||
github.com/klauspost/lctime v0.1.0 h1:nINsuFc860M9cyYhT6vfg6U1USh7kiVBj/s/2b04U70=
|
github.com/klauspost/lctime v0.1.0 h1:nINsuFc860M9cyYhT6vfg6U1USh7kiVBj/s/2b04U70=
|
||||||
github.com/klauspost/lctime v0.1.0/go.mod h1:OwdMhr8tbQvusAsnilqkkgDQqivWlqyg0w5cfXkLiDk=
|
github.com/klauspost/lctime v0.1.0/go.mod h1:OwdMhr8tbQvusAsnilqkkgDQqivWlqyg0w5cfXkLiDk=
|
||||||
github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA=
|
github.com/labstack/echo/v5 v5.1.1 h1:4QkvKoS8ps5ch49t8b72QS9Z581ytgxhTzxuB/CBA2I=
|
||||||
github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ=
|
github.com/labstack/echo/v5 v5.1.1/go.mod h1:SyvlSdObGjRXeQfCCXW/sybkZdOOQZBmpKF0bvALaeo=
|
||||||
github.com/labstack/echo/v4 v4.15.1 h1:S9keusg26gZpjMmPqB5hOEvNKnmd1lNmcHrbbH2lnFs=
|
github.com/labstack/gommon v0.5.0 h1:6VSQ2NOzsnEJ5W6+84E0RbcaDDmgB6NIAzWCczTEe6c=
|
||||||
github.com/labstack/echo/v4 v4.15.1/go.mod h1:xmw1clThob0BSVRX1CRQkGQ/vjwcpOMjQZSZa9fKA/c=
|
github.com/labstack/gommon v0.5.0/go.mod h1:Rzlg7HHy1maLfzBYGg9NZcVuz1sA68HHhLjhcEllYE0=
|
||||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
|
||||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
|
||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
|
||||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/tkuchiki/go-timezone v0.2.3 h1:D3TVdIPrFsu9lxGxqNX2wsZwn1MZtTqTW0mdevMozHc=
|
github.com/tkuchiki/go-timezone v0.2.3 h1:D3TVdIPrFsu9lxGxqNX2wsZwn1MZtTqTW0mdevMozHc=
|
||||||
github.com/tkuchiki/go-timezone v0.2.3/go.mod h1:oFweWxYl35C/s7HMVZXiA19Jr9Y0qJHMaG/J2TES4LY=
|
github.com/tkuchiki/go-timezone v0.2.3/go.mod h1:oFweWxYl35C/s7HMVZXiA19Jr9Y0qJHMaG/J2TES4LY=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
|
||||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
|
||||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
|
||||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
|
||||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
|
||||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
|
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.37.0 h1:ZiRjArKI8GwxZOoEtUfhrBtaCN+4b/7709dlT6SSnQA=
|
golang.org/x/image v0.37.0 h1:ZiRjArKI8GwxZOoEtUfhrBtaCN+4b/7709dlT6SSnQA=
|
||||||
golang.org/x/image v0.37.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
|
golang.org/x/image v0.37.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
|
||||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
|
||||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
|
||||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
|
||||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
|
||||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
|
||||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
|
||||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
|
||||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
|
||||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
|
||||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
|
||||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
|
||||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
|
||||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
|
||||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
+6
-10
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Amsterdam Web Communities System
|
* Amsterdam Web Communities System
|
||||||
* Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
* Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
||||||
*
|
*
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
@@ -44,17 +44,17 @@ func (d *TrieDictionary) Size() int {
|
|||||||
// CheckWord returns true if a word is in the dictionary, false if not.
|
// CheckWord returns true if a word is in the dictionary, false if not.
|
||||||
func (d *TrieDictionary) CheckWord(word string) bool {
|
func (d *TrieDictionary) CheckWord(word string) bool {
|
||||||
d.mutex.Lock()
|
d.mutex.Lock()
|
||||||
defer d.mutex.Unlock()
|
|
||||||
_, rc := d.trie.Find(strings.ToLower(word))
|
_, rc := d.trie.Find(strings.ToLower(word))
|
||||||
|
d.mutex.Unlock()
|
||||||
return rc
|
return rc
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddWord adds a new word to the dictionary.
|
// AddWord adds a new word to the dictionary.
|
||||||
func (d *TrieDictionary) AddWord(word string) {
|
func (d *TrieDictionary) AddWord(word string) {
|
||||||
d.mutex.Lock()
|
d.mutex.Lock()
|
||||||
defer d.mutex.Unlock()
|
|
||||||
d.trie.Add(strings.ToLower(word), true)
|
d.trie.Add(strings.ToLower(word), true)
|
||||||
d.count++
|
d.count++
|
||||||
|
d.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// DelWord deletes a word from the dictionary.
|
// DelWord deletes a word from the dictionary.
|
||||||
@@ -89,12 +89,8 @@ func loadDict(d *TrieDictionary, words []byte) {
|
|||||||
|
|
||||||
// LoadTrieDict creates a TrieDictionary from a byte array that represents a word list (one word per line).
|
// LoadTrieDict creates a TrieDictionary from a byte array that represents a word list (one word per line).
|
||||||
func LoadTrieDict(words []byte) *TrieDictionary {
|
func LoadTrieDict(words []byte) *TrieDictionary {
|
||||||
rc := TrieDictionary{
|
rc := new(TrieDictionary{loaded: atomic.Bool{}, trie: trie.New(), count: 0})
|
||||||
loaded: atomic.Bool{},
|
|
||||||
trie: trie.New(),
|
|
||||||
count: 0,
|
|
||||||
}
|
|
||||||
rc.loaded.Store(false)
|
rc.loaded.Store(false)
|
||||||
go loadDict(&rc, words)
|
go loadDict(rc, words)
|
||||||
return &rc
|
return rc
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,10 +55,8 @@ func SetupDicts() {
|
|||||||
log.Errorf("failed to load external dictionary %s: %v", config.GlobalConfig.Posting.ExternalDictionary, err)
|
log.Errorf("failed to load external dictionary %s: %v", config.GlobalConfig.Posting.ExternalDictionary, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rw := spellingRewriter{
|
rw := new(spellingRewriter{dict: NewCompositeDict(dicts)})
|
||||||
dict: NewCompositeDict(dicts),
|
rewriterRegistry[rw.Name()] = rw
|
||||||
}
|
|
||||||
rewriterRegistry[rw.Name()] = &rw
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// spellingRewriter is a rewriter that flags spelling errors.
|
// spellingRewriter is a rewriter that flags spelling errors.
|
||||||
@@ -89,10 +87,10 @@ func (rw *spellingRewriter) Rewrite(ctx context.Context, data string, svc rewrit
|
|||||||
if rw.dict.CheckWord(data) {
|
if rw.dict.CheckWord(data) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return &markupData{
|
return new(markupData{
|
||||||
beginMarkup: defaultBeginError,
|
beginMarkup: defaultBeginError,
|
||||||
text: data,
|
text: data,
|
||||||
endMarkup: defaultEndError,
|
endMarkup: defaultEndError,
|
||||||
rescan: false,
|
rescan: false,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ func buildPostLink(decoded, context *database.PostLinkData) string {
|
|||||||
b.WriteString(context.Conference)
|
b.WriteString(context.Conference)
|
||||||
} else {
|
} else {
|
||||||
b.WriteString(decoded.Conference)
|
b.WriteString(decoded.Conference)
|
||||||
|
started = true
|
||||||
}
|
}
|
||||||
b.WriteString(".")
|
b.WriteString(".")
|
||||||
if decoded.Topic == -1 {
|
if decoded.Topic == -1 {
|
||||||
@@ -168,6 +169,7 @@ func (rw *postLinkRewriter) Rewrite(ctx context.Context, data string, svc rewrit
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
mydata.CommId = ctxt.CommId
|
||||||
err = mydata.VerifyNames(ctx)
|
err = mydata.VerifyNames(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+15
-5
@@ -21,6 +21,13 @@ import (
|
|||||||
"git.erbosoft.com/amy/amsterdam/ui"
|
"git.erbosoft.com/amy/amsterdam/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Invitation modes.
|
||||||
|
const (
|
||||||
|
INVMODE_COMMUNITY = "community"
|
||||||
|
INVMODE_CONFERENCE = "conference"
|
||||||
|
INVMODE_TOPIC = "topic"
|
||||||
|
)
|
||||||
|
|
||||||
/* InviteToCommunity displays the community invitation form.
|
/* InviteToCommunity displays the community invitation form.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* ctxt - The AmContext for the request.
|
* ctxt - The AmContext for the request.
|
||||||
@@ -59,7 +66,7 @@ func InviteToConference(ctxt ui.AmContext) (string, any) {
|
|||||||
ctxt.SetFrameTitle("Send Invitation")
|
ctxt.SetFrameTitle("Send Invitation")
|
||||||
ctxt.VarMap().Set("title", "Send Conference Invitation")
|
ctxt.VarMap().Set("title", "Send Conference Invitation")
|
||||||
ctxt.VarMap().Set("subtitle", conf.Name)
|
ctxt.VarMap().Set("subtitle", conf.Name)
|
||||||
ctxt.VarMap().Set("backlink", fmt.Sprintf("/comm/%s/conf/%s/manage", comm.Alias, ctxt.GetScratch("currentAlias")))
|
ctxt.VarMap().Set("backlink", fmt.Sprintf("%s/manage", ctxt.GetScratch("ConferenceLink")))
|
||||||
ctxt.VarMap().Set("cid", fmt.Sprintf("%d", comm.Id))
|
ctxt.VarMap().Set("cid", fmt.Sprintf("%d", comm.Id))
|
||||||
ctxt.VarMap().Set("confid", fmt.Sprintf("%d", conf.ConfId))
|
ctxt.VarMap().Set("confid", fmt.Sprintf("%d", conf.ConfId))
|
||||||
return "framed", "invite.jet"
|
return "framed", "invite.jet"
|
||||||
@@ -83,7 +90,7 @@ func InviteToTopic(ctxt ui.AmContext) (string, any) {
|
|||||||
ctxt.SetFrameTitle("Send Invitation")
|
ctxt.SetFrameTitle("Send Invitation")
|
||||||
ctxt.VarMap().Set("title", "Send Topic Invitation")
|
ctxt.VarMap().Set("title", "Send Topic Invitation")
|
||||||
ctxt.VarMap().Set("subtitle", topic.Name)
|
ctxt.VarMap().Set("subtitle", topic.Name)
|
||||||
ctxt.VarMap().Set("backlink", fmt.Sprintf("/comm/%s/conf/%s/op/%d/manage", comm.Alias, ctxt.GetScratch("currentAlias"), topic.Number))
|
ctxt.VarMap().Set("backlink", fmt.Sprintf("%s/op/%d/manage", ctxt.GetScratch("ConferenceLink"), topic.Number))
|
||||||
ctxt.VarMap().Set("cid", fmt.Sprintf("%d", comm.Id))
|
ctxt.VarMap().Set("cid", fmt.Sprintf("%d", comm.Id))
|
||||||
ctxt.VarMap().Set("confid", fmt.Sprintf("%d", conf.ConfId))
|
ctxt.VarMap().Set("confid", fmt.Sprintf("%d", conf.ConfId))
|
||||||
ctxt.VarMap().Set("topicid", fmt.Sprintf("%d", topic.TopicId))
|
ctxt.VarMap().Set("topicid", fmt.Sprintf("%d", topic.TopicId))
|
||||||
@@ -116,7 +123,7 @@ func InviteSend(ctxt ui.AmContext) (string, any) {
|
|||||||
} else {
|
} else {
|
||||||
return "error", EPARAM
|
return "error", EPARAM
|
||||||
}
|
}
|
||||||
mode := "community"
|
mode := INVMODE_COMMUNITY
|
||||||
var conf *database.Conference = nil
|
var conf *database.Conference = nil
|
||||||
var topic *database.Topic = nil
|
var topic *database.Topic = nil
|
||||||
if ctxt.FormFieldIsSet("confid") {
|
if ctxt.FormFieldIsSet("confid") {
|
||||||
@@ -145,9 +152,9 @@ func InviteSend(ctxt ui.AmContext) (string, any) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "errors", err
|
return "errors", err
|
||||||
}
|
}
|
||||||
mode = "topic"
|
mode = INVMODE_TOPIC
|
||||||
} else {
|
} else {
|
||||||
mode = "conference"
|
mode = INVMODE_CONFERENCE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addr := ctxt.FormField("addr")
|
addr := ctxt.FormField("addr")
|
||||||
@@ -168,6 +175,9 @@ func InviteSend(ctxt ui.AmContext) (string, any) {
|
|||||||
mailMessage.SetTemplate("invite_private.jet")
|
mailMessage.SetTemplate("invite_private.jet")
|
||||||
}
|
}
|
||||||
mailMessage.AddTo(addr, "")
|
mailMessage.AddTo(addr, "")
|
||||||
|
mailMessage.AddVariable("INVMODE_COMMUNITY", INVMODE_COMMUNITY)
|
||||||
|
mailMessage.AddVariable("INVMODE_CONFERENCE", INVMODE_CONFERENCE)
|
||||||
|
mailMessage.AddVariable("INVMODE_TOPIC", INVMODE_TOPIC)
|
||||||
mailMessage.AddVariable("comm", comm)
|
mailMessage.AddVariable("comm", comm)
|
||||||
mailMessage.AddVariable("conf", conf)
|
mailMessage.AddVariable("conf", conf)
|
||||||
mailMessage.AddVariable("topic", topic)
|
mailMessage.AddVariable("topic", topic)
|
||||||
|
|||||||
+74
-93
@@ -13,104 +13,90 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.erbosoft.com/amy/amsterdam/config"
|
"git.erbosoft.com/amy/amsterdam/config"
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v5"
|
||||||
glog "github.com/labstack/gommon/log"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DEFAULT_MAXLOG is the default maximum log file size (16 megabytes).
|
||||||
|
const DEFAULT_MAXLOG = 16 * 1024 * 1024
|
||||||
|
|
||||||
|
// LOG_ROTATE_INTERVAL is the interval, in seconds, at which we try to rotate the logfile.
|
||||||
|
const LOG_ROTATE_INTERVAL = 10
|
||||||
|
|
||||||
/*----------------------------------------------------------------------------
|
/*----------------------------------------------------------------------------
|
||||||
* Gommon-log to logrus adapter
|
* slog handler that outputs to Logrus
|
||||||
*----------------------------------------------------------------------------
|
*----------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* toglog converts a Logrus logging level to a glog one.
|
// slog2logrus converts slog levels to Logrus levels.
|
||||||
* Parameters:
|
var slog2logrus = map[slog.Level]log.Level{
|
||||||
* l - The Logrus log level to be converted.
|
slog.LevelDebug: log.DebugLevel,
|
||||||
* Returns:
|
slog.LevelInfo: log.InfoLevel,
|
||||||
* The equivalent glog log level.
|
slog.LevelWarn: log.WarnLevel,
|
||||||
*/
|
slog.LevelError: log.ErrorLevel,
|
||||||
func toglog(l log.Level) glog.Lvl {
|
|
||||||
switch l {
|
|
||||||
case log.DebugLevel:
|
|
||||||
return glog.DEBUG
|
|
||||||
case log.InfoLevel:
|
|
||||||
return glog.INFO
|
|
||||||
case log.WarnLevel:
|
|
||||||
return glog.WARN
|
|
||||||
case log.ErrorLevel:
|
|
||||||
return glog.ERROR
|
|
||||||
default:
|
|
||||||
return glog.OFF
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* fromglog converts a glog logging level to a Logrus one.
|
// SlogLogrusHandler implements slog.Handler and routes to Logrus.
|
||||||
* Parameters:
|
type SlogLogrusHandler struct {
|
||||||
* l - The glog log level to be converted.
|
fields log.Fields // fields defined in this handler
|
||||||
* Returns:
|
groupPrefix string // group prefix
|
||||||
* The equivalent Logrus log level.
|
|
||||||
*/
|
|
||||||
func fromglog(l glog.Lvl) log.Level {
|
|
||||||
switch l {
|
|
||||||
case glog.DEBUG:
|
|
||||||
return log.DebugLevel
|
|
||||||
case glog.INFO:
|
|
||||||
return log.InfoLevel
|
|
||||||
case glog.WARN:
|
|
||||||
return log.WarnLevel
|
|
||||||
case glog.ERROR:
|
|
||||||
return log.ErrorLevel
|
|
||||||
default:
|
|
||||||
return log.PanicLevel
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EchoLogrusAdapter implements echo.Logger using logrus.
|
// NewSlogLogrusHandler creates a SlogLogrusHandler with base information.
|
||||||
type EchoLogrusAdapter struct{}
|
func NewSlogLogrusHandler() *SlogLogrusHandler {
|
||||||
|
return new(SlogLogrusHandler{fields: make(log.Fields), groupPrefix: ""})
|
||||||
|
}
|
||||||
|
|
||||||
func (l *EchoLogrusAdapter) Output() io.Writer { return log.StandardLogger().Out }
|
// Enabled returns true if the specified log level is handled.
|
||||||
func (l *EchoLogrusAdapter) SetOutput(w io.Writer) { log.SetOutput(w) }
|
func (h *SlogLogrusHandler) Enabled(ctx context.Context, lvl slog.Level) bool {
|
||||||
func (l *EchoLogrusAdapter) Prefix() string { return "" }
|
return log.IsLevelEnabled(slog2logrus[lvl])
|
||||||
func (l *EchoLogrusAdapter) SetPrefix(p string) {}
|
}
|
||||||
func (l *EchoLogrusAdapter) Level() glog.Lvl { return toglog(log.GetLevel()) }
|
|
||||||
func (l *EchoLogrusAdapter) SetLevel(lvl glog.Lvl) { log.SetLevel(fromglog(lvl)) }
|
// Handle sends a slog.Record to the log output.
|
||||||
func (l *EchoLogrusAdapter) Print(i ...any) { log.Print(i...) }
|
func (h *SlogLogrusHandler) Handle(ctx context.Context, r slog.Record) error {
|
||||||
func (l *EchoLogrusAdapter) Printf(format string, args ...any) { log.Printf(format, args...) }
|
flds := make(log.Fields)
|
||||||
func (l *EchoLogrusAdapter) Printj(j glog.JSON) { log.WithFields(log.Fields(j)).Print() }
|
for k, v := range h.fields {
|
||||||
func (l *EchoLogrusAdapter) Debug(i ...any) { log.Debug(i...) }
|
flds[h.groupPrefix+k] = v
|
||||||
func (l *EchoLogrusAdapter) Debugf(format string, args ...any) { log.Debugf(format, args...) }
|
}
|
||||||
func (l *EchoLogrusAdapter) Debugj(j glog.JSON) { log.WithFields(log.Fields(j)).Debug() }
|
r.Attrs(func(a slog.Attr) bool {
|
||||||
func (l *EchoLogrusAdapter) Info(i ...any) { log.Info(i...) }
|
flds[h.groupPrefix+a.Key] = a.Value.Any()
|
||||||
func (l *EchoLogrusAdapter) Infof(format string, args ...any) { log.Infof(format, args...) }
|
return true
|
||||||
func (l *EchoLogrusAdapter) Infoj(j glog.JSON) { log.WithFields(log.Fields(j)).Info() }
|
})
|
||||||
func (l *EchoLogrusAdapter) Warn(i ...any) { log.Warn(i...) }
|
ntry := log.NewEntry(log.StandardLogger()).WithTime(r.Time).WithFields(flds)
|
||||||
func (l *EchoLogrusAdapter) Warnf(format string, args ...any) { log.Warnf(format, args...) }
|
ntry.Log(slog2logrus[r.Level], r.Message)
|
||||||
func (l *EchoLogrusAdapter) Warnj(j glog.JSON) { log.WithFields(log.Fields(j)).Warn() }
|
return nil
|
||||||
func (l *EchoLogrusAdapter) Error(i ...any) { log.Error(i...) }
|
}
|
||||||
func (l *EchoLogrusAdapter) Errorf(format string, args ...any) { log.Errorf(format, args...) }
|
|
||||||
func (l *EchoLogrusAdapter) Errorj(j glog.JSON) { log.WithFields(log.Fields(j)).Error() }
|
// WithAttrs creates a new Handler from this one, with extra attributes.
|
||||||
func (l *EchoLogrusAdapter) Fatal(i ...any) { log.Fatal(i...) }
|
func (h *SlogLogrusHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||||
func (l *EchoLogrusAdapter) Fatalf(format string, args ...any) { log.Fatalf(format, args...) }
|
newh := new(SlogLogrusHandler{fields: make(log.Fields), groupPrefix: h.groupPrefix})
|
||||||
func (l *EchoLogrusAdapter) Fatalj(j glog.JSON) { log.WithFields(log.Fields(j)).Fatal() }
|
maps.Copy(newh.fields, h.fields)
|
||||||
func (l *EchoLogrusAdapter) Panic(i ...any) { log.Panic(i...) }
|
for _, a := range attrs {
|
||||||
func (l *EchoLogrusAdapter) Panicf(format string, args ...any) { log.Panicf(format, args...) }
|
newh.fields[a.Key] = a.Value.Any()
|
||||||
func (l *EchoLogrusAdapter) Panicj(j glog.JSON) { log.WithFields(log.Fields(j)).Panic() }
|
}
|
||||||
func (l *EchoLogrusAdapter) SetHeader(h string) {}
|
return newh
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithGroup creates a new Handler from this one, with an extra group prefix.
|
||||||
|
func (h *SlogLogrusHandler) WithGroup(name string) slog.Handler {
|
||||||
|
newh := new(SlogLogrusHandler{fields: make(log.Fields), groupPrefix: h.groupPrefix + name + "."})
|
||||||
|
maps.Copy(newh.fields, h.fields)
|
||||||
|
return newh
|
||||||
|
}
|
||||||
|
|
||||||
/*----------------------------------------------------------------------------
|
/*----------------------------------------------------------------------------
|
||||||
* Echo middleware adapters
|
* Echo middleware adapters
|
||||||
@@ -119,13 +105,13 @@ func (l *EchoLogrusAdapter) SetHeader(h string) {}
|
|||||||
|
|
||||||
// LogrusMiddleware installs Logrus logging into the Echo middleware chain.
|
// LogrusMiddleware installs Logrus logging into the Echo middleware chain.
|
||||||
func LogrusMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
|
func LogrusMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c *echo.Context) error {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
err := next(c)
|
err := next(c)
|
||||||
stop := time.Now()
|
stop := time.Now()
|
||||||
|
|
||||||
req := c.Request()
|
req := c.Request()
|
||||||
res := c.Response()
|
res := c.Response().(*echo.Response)
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"remote_ip": c.RealIP(),
|
"remote_ip": c.RealIP(),
|
||||||
@@ -139,17 +125,6 @@ func LogrusMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogrusPanicLogging is a log function hooked into the recovery middleware.
|
|
||||||
func LogrusPanicLogging(c echo.Context, err error, stack []byte) error {
|
|
||||||
log.Errorf("[PANIC RECOVERY] %v", err)
|
|
||||||
scanner := bufio.NewScanner(bytes.NewReader(stack))
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := strings.ReplaceAll(scanner.Text(), "\t", " ")
|
|
||||||
log.Error(line)
|
|
||||||
}
|
|
||||||
return scanner.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
/*----------------------------------------------------------------------------
|
/*----------------------------------------------------------------------------
|
||||||
* Log output file implementation
|
* Log output file implementation
|
||||||
*----------------------------------------------------------------------------
|
*----------------------------------------------------------------------------
|
||||||
@@ -185,6 +160,7 @@ func (lf *amLogFile) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// rotate closes the log file and moves it to a new name, shuffling the previously stored log files by the same amount.
|
// rotate closes the log file and moves it to a new name, shuffling the previously stored log files by the same amount.
|
||||||
|
// N.B.: We must be holding lf.mutex.
|
||||||
func (lf *amLogFile) rotate() error {
|
func (lf *amLogFile) rotate() error {
|
||||||
if lf.keep == 0 && lf.keepCompressed == 0 {
|
if lf.keep == 0 && lf.keepCompressed == 0 {
|
||||||
return nil // degenerate case, keep the log file the same
|
return nil // degenerate case, keep the log file the same
|
||||||
@@ -287,7 +263,9 @@ func (lf *amLogFile) tryRotate() {
|
|||||||
if lf.curSize >= lf.maxSize {
|
if lf.curSize >= lf.maxSize {
|
||||||
err := lf.rotate()
|
err := lf.rotate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//log.Error("log rotation failed")
|
log.SetOutput(os.Stderr)
|
||||||
|
log.Errorf("log rotation failed: %v", err)
|
||||||
|
log.SetOutput(lf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lf.mutex.Unlock()
|
lf.mutex.Unlock()
|
||||||
@@ -327,8 +305,7 @@ func (lf *amLogFile) open(path string) error {
|
|||||||
|
|
||||||
// logScanner is a goroutine that monitors the log file to see when it needs rotating.
|
// logScanner is a goroutine that monitors the log file to see when it needs rotating.
|
||||||
func logScanner(ctx context.Context, lf *amLogFile, done chan bool) {
|
func logScanner(ctx context.Context, lf *amLogFile, done chan bool) {
|
||||||
d, _ := time.ParseDuration("10s")
|
t := time.NewTicker(LOG_ROTATE_INTERVAL * time.Second)
|
||||||
t := time.NewTicker(d)
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@@ -344,8 +321,10 @@ func logScanner(ctx context.Context, lf *amLogFile, done chan bool) {
|
|||||||
// SetupLogging sets up the log file based on the configuration data.
|
// SetupLogging sets up the log file based on the configuration data.
|
||||||
func SetupLogging() func() {
|
func SetupLogging() func() {
|
||||||
loglevel, err := log.ParseLevel(config.GlobalComputedConfig.LogLevel)
|
loglevel, err := log.ParseLevel(config.GlobalComputedConfig.LogLevel)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
loglevel = log.ErrorLevel
|
loglevel = log.ErrorLevel
|
||||||
|
} else {
|
||||||
|
log.Errorf("default log level not valid: %s (%v)", config.GlobalComputedConfig.LogLevel, err)
|
||||||
}
|
}
|
||||||
if config.GlobalComputedConfig.DebugMode && loglevel != log.TraceLevel {
|
if config.GlobalComputedConfig.DebugMode && loglevel != log.TraceLevel {
|
||||||
loglevel = log.DebugLevel
|
loglevel = log.DebugLevel
|
||||||
@@ -358,7 +337,8 @@ func SetupLogging() func() {
|
|||||||
amlog := new(amLogFile)
|
amlog := new(amLogFile)
|
||||||
maxlog, err := humanize.ParseBytes(config.GlobalConfig.Logging.MaxLogSize)
|
maxlog, err := humanize.ParseBytes(config.GlobalConfig.Logging.MaxLogSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
maxlog = 16 * 1024 * 1024 // default to 16 megabytes
|
log.Errorf("invalid value for max log size: %s (%v)", config.GlobalConfig.Logging.MaxLogSize, err)
|
||||||
|
maxlog = DEFAULT_MAXLOG
|
||||||
}
|
}
|
||||||
amlog.maxSize = int64(maxlog)
|
amlog.maxSize = int64(maxlog)
|
||||||
amlog.keep = config.GlobalConfig.Logging.KeepLogFiles
|
amlog.keep = config.GlobalConfig.Logging.KeepLogFiles
|
||||||
@@ -369,13 +349,14 @@ func SetupLogging() func() {
|
|||||||
ctx, cancelfunc = context.WithCancel(context.Background())
|
ctx, cancelfunc = context.WithCancel(context.Background())
|
||||||
done = make(chan bool)
|
done = make(chan bool)
|
||||||
go logScanner(ctx, amlog, done)
|
go logScanner(ctx, amlog, done)
|
||||||
|
} else {
|
||||||
|
log.Errorf("**** failed to open amlog: %v - logs will go to stdout", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if logfile == nil {
|
if logfile == nil {
|
||||||
log.SetOutput(os.Stdout)
|
log.SetOutput(os.Stdout)
|
||||||
} else {
|
} else {
|
||||||
log.SetOutput(logfile)
|
log.SetOutput(logfile)
|
||||||
|
|
||||||
}
|
}
|
||||||
log.SetLevel(loglevel)
|
log.SetLevel(loglevel)
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@@ -30,24 +33,40 @@ import (
|
|||||||
"git.erbosoft.com/amy/amsterdam/htmlcheck"
|
"git.erbosoft.com/amy/amsterdam/htmlcheck"
|
||||||
"git.erbosoft.com/amy/amsterdam/ui"
|
"git.erbosoft.com/amy/amsterdam/ui"
|
||||||
"git.erbosoft.com/amy/amsterdam/util"
|
"git.erbosoft.com/amy/amsterdam/util"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v5"
|
||||||
"github.com/labstack/echo/v4/middleware"
|
"github.com/labstack/echo/v5/middleware"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// READ_HEADER_TIMEOUT is the timeout value for reading headers in seconds. (Deliberately NOT configurable because this is a security issue)
|
||||||
|
const READ_HEADER_TIMEOUT = 2
|
||||||
|
|
||||||
|
// GRACEFUL_SHUTDOWN_TIMEOUT is the timeout value for a graceful shutdown.
|
||||||
|
const GRACEFUL_SHUTDOWN_TIMEOUT = 10 * time.Second
|
||||||
|
|
||||||
// GetAndPost is used to have functions that respond to both GET and POST on a URI.
|
// GetAndPost is used to have functions that respond to both GET and POST on a URI.
|
||||||
var GetAndPost = []string{http.MethodGet, http.MethodPost}
|
var GetAndPost = []string{http.MethodGet, http.MethodPost}
|
||||||
|
|
||||||
|
// myIPAddress returns the IP address of this computer.
|
||||||
|
func myIPAddress() net.IP {
|
||||||
|
conn, err := net.Dial("udp", "8.8.8.8:80")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
localAddr := conn.LocalAddr().(*net.UDPAddr)
|
||||||
|
return localAddr.IP
|
||||||
|
}
|
||||||
|
|
||||||
// setupEcho creates, configures, and returns a new Echo instance.
|
// setupEcho creates, configures, and returns a new Echo instance.
|
||||||
func setupEcho() *echo.Echo {
|
func setupEcho() *echo.Echo {
|
||||||
e := echo.New()
|
e := echo.New()
|
||||||
e.HideBanner = true
|
e.Logger = slog.New(NewSlogLogrusHandler())
|
||||||
e.Logger = &EchoLogrusAdapter{}
|
|
||||||
e.Renderer = &ui.TemplateRenderer{}
|
e.Renderer = &ui.TemplateRenderer{}
|
||||||
e.HTTPErrorHandler = AmErrorHandler
|
e.HTTPErrorHandler = AmErrorHandler
|
||||||
if !config.CommandLine.DebugPanic {
|
if !config.CommandLine.DebugPanic {
|
||||||
e.Use(middleware.RecoverWithConfig(middleware.RecoverConfig{
|
e.Use(middleware.RecoverWithConfig(middleware.RecoverConfig{
|
||||||
LogErrorFunc: LogrusPanicLogging,
|
StackSize: int(config.GlobalComputedConfig.PanicRecoveryStack),
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
log.Warn("WARNING: --debug-panic in effect - DO NOT use this in production!")
|
log.Warn("WARNING: --debug-panic in effect - DO NOT use this in production!")
|
||||||
@@ -215,17 +234,21 @@ func setupEcho() *echo.Echo {
|
|||||||
// ampool is the worker pool for one-shot background tasks.
|
// ampool is the worker pool for one-shot background tasks.
|
||||||
var ampool *util.WorkerPool
|
var ampool *util.WorkerPool
|
||||||
|
|
||||||
// SystemStartTime records the time since the system was started.
|
// SystemStartTime records the time the system was started.
|
||||||
var SystemStartTime time.Time
|
var SystemStartTime time.Time
|
||||||
|
|
||||||
// main is Ye Olde Main Function.
|
// main is Ye Olde Main Function.
|
||||||
func main() {
|
func main() {
|
||||||
SystemStartTime = time.Now()
|
SystemStartTime = time.Now()
|
||||||
|
|
||||||
|
// Determine my IP address.
|
||||||
|
myIP := myIPAddress()
|
||||||
|
|
||||||
// Configure the system.
|
// Configure the system.
|
||||||
config.SetupConfig()
|
config.SetupConfig()
|
||||||
closer := SetupLogging()
|
closer := SetupLogging()
|
||||||
defer closer()
|
defer closer()
|
||||||
closer, err := database.SetupDb()
|
dbVersion, closer, err := database.SetupDb()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("Database open failure: %v", err))
|
panic(fmt.Sprintf("Database open failure: %v", err))
|
||||||
}
|
}
|
||||||
@@ -236,12 +259,6 @@ func main() {
|
|||||||
closer = ui.SetupUILayer()
|
closer = ui.SetupUILayer()
|
||||||
defer closer()
|
defer closer()
|
||||||
|
|
||||||
// Determine my IP address and the admin user.
|
|
||||||
myIP, err := util.MyIPAddress()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up to trap SIGINT/SIGTERM and shut down gracefully
|
// Set up to trap SIGINT/SIGTERM and shut down gracefully
|
||||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||||
defer stop()
|
defer stop()
|
||||||
@@ -258,27 +275,41 @@ func main() {
|
|||||||
|
|
||||||
// Audit the startup
|
// Audit the startup
|
||||||
database.AmStoreAudit(database.AmNewAudit(database.AuditStartup, 0, myIP.String(),
|
database.AmStoreAudit(database.AmNewAudit(database.AuditStartup, 0, myIP.String(),
|
||||||
fmt.Sprintf("version=%s", config.AMSTERDAM_VERSION)))
|
fmt.Sprintf("version=%s", config.AMSTERDAM_VERSION), fmt.Sprintf("database=%s", dbVersion)))
|
||||||
defer func() {
|
defer func() {
|
||||||
// Audit the shutdown
|
// Audit the shutdown
|
||||||
database.AmStoreAudit(database.AmNewAudit(database.AuditShutdown, 0, myIP.String()))
|
database.AmStoreAudit(database.AmNewAudit(database.AuditShutdown, 0, myIP.String()))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Set up the start configuration.
|
||||||
|
sconf := echo.StartConfig{
|
||||||
|
Address: config.GlobalComputedConfig.Listen,
|
||||||
|
HideBanner: true,
|
||||||
|
HidePort: true,
|
||||||
|
GracefulTimeout: GRACEFUL_SHUTDOWN_TIMEOUT,
|
||||||
|
OnShutdownError: func(err error) {
|
||||||
|
log.Fatalf("error in shutting down the server: %v", err)
|
||||||
|
},
|
||||||
|
BeforeServeFunc: func(s *http.Server) error {
|
||||||
|
s.ReadTimeout = time.Duration(config.GlobalConfig.Tuning.Timeouts.HttpRead) * time.Second
|
||||||
|
s.WriteTimeout = time.Duration(config.GlobalConfig.Tuning.Timeouts.HttpWrite) * time.Second
|
||||||
|
s.IdleTimeout = time.Duration(config.GlobalConfig.Tuning.Timeouts.HttpIdle) * time.Second
|
||||||
|
s.ReadHeaderTimeout = READ_HEADER_TIMEOUT * time.Second
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
stime := time.Since(SystemStartTime)
|
stime := time.Since(SystemStartTime)
|
||||||
log.Infof("Amsterdam %s startup sequence completed in %v", config.AMSTERDAM_VERSION, stime)
|
log.Infof("Amsterdam %s startup sequence completed in %v", config.AMSTERDAM_VERSION, stime)
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
go func() {
|
go func() {
|
||||||
if err := e.Start(config.GlobalComputedConfig.Listen); err != nil && err != http.ErrServerClosed {
|
if err := sconf.Start(ctx, e); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
e.Logger.Fatalf("shutting down the server: %v", err)
|
log.Fatalf("shutting down the server: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Wait for the interrupt signal and then gracefully shut the server down.
|
// Wait for the context to be done, when the server is shut down.
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
log.Infof("Amsterdam shut down")
|
||||||
defer cancel()
|
|
||||||
if err := e.Shutdown(ctx); err != nil {
|
|
||||||
e.Logger.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+10
@@ -249,6 +249,7 @@ func UserManagementForm(ctxt ui.AmContext) (string, any) {
|
|||||||
var prefs *database.UserPrefs
|
var prefs *database.UserPrefs
|
||||||
prefs, err = user.Prefs(ctxt.Ctx())
|
prefs, err = user.Prefs(ctxt.Ctx())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
dlg.Field("user").Value = user.Username
|
||||||
dlg.Field("remind").Value = user.PassReminder
|
dlg.Field("remind").Value = user.PassReminder
|
||||||
dlg.Field("base_lvl").SetLevel(user.BaseLevel)
|
dlg.Field("base_lvl").SetLevel(user.BaseLevel)
|
||||||
dlg.Field("verify_email").SetChecked(user.VerifyEMail)
|
dlg.Field("verify_email").SetChecked(user.VerifyEMail)
|
||||||
@@ -327,6 +328,12 @@ func UserManagementSave(ctxt ui.AmContext) (string, any) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
var prefs *database.UserPrefs
|
var prefs *database.UserPrefs
|
||||||
prefs, err = user.Prefs(ctxt.Ctx())
|
prefs, err = user.Prefs(ctxt.Ctx())
|
||||||
|
if err == nil && user.Username != dlg.Field("user").Value {
|
||||||
|
u2, e := database.AmGetUserByName(ctxt.Ctx(), dlg.Field("user").Value, nil)
|
||||||
|
if e == nil && u2 != nil {
|
||||||
|
err = errors.New("user name is already in use")
|
||||||
|
}
|
||||||
|
}
|
||||||
if err == nil && !(dlg.Field("pass1").IsEmpty() && dlg.Field("pass2").IsEmpty()) {
|
if err == nil && !(dlg.Field("pass1").IsEmpty() && dlg.Field("pass2").IsEmpty()) {
|
||||||
p1 := dlg.Field("pass1").Value
|
p1 := dlg.Field("pass1").Value
|
||||||
if p1 == dlg.Field("pass2").Value {
|
if p1 == dlg.Field("pass2").Value {
|
||||||
@@ -378,6 +385,9 @@ func UserManagementSave(ctxt ui.AmContext) (string, any) {
|
|||||||
err = user.SaveFlags(ctxt.Ctx(), nf)
|
err = user.SaveFlags(ctxt.Ctx(), nf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err == nil && user.Username != dlg.Field("user").Value {
|
||||||
|
err = user.SetUsername(ctxt.Ctx(), dlg.Field("user").Value, ctxt.CurrentUser(), ctxt.RemoteIP())
|
||||||
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = user.SetProfileData(ctxt.Ctx(), dlg.Field("remind").Value, dlg.Field("dob").AsDate(), dlg.Field("descr").ValPtr(),
|
err = user.SetProfileData(ctxt.Ctx(), dlg.Field("remind").Value, dlg.Field("dob").AsDate(), dlg.Field("descr").ValPtr(),
|
||||||
ctxt.CurrentUser(), ctxt.RemoteIP())
|
ctxt.CurrentUser(), ctxt.RemoteIP())
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
"git.erbosoft.com/amy/amsterdam/database"
|
"git.erbosoft.com/amy/amsterdam/database"
|
||||||
"git.erbosoft.com/amy/amsterdam/ui"
|
"git.erbosoft.com/amy/amsterdam/ui"
|
||||||
"github.com/CloudyKit/jet/v6"
|
"github.com/CloudyKit/jet/v6"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v5"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ func renderSBConferences(ctx context.Context, u *database.User, sb *DisplaySideb
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var a []string
|
var a []string
|
||||||
if a, err = conf[i].Aliases(ctx); err != nil {
|
if a, err = conf[i].Aliases(ctx, comm[i].Id); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
alias[i] = a[0]
|
alias[i] = a[0]
|
||||||
@@ -176,9 +176,10 @@ func templateGetTopic(args jet.Arguments) reflect.Value {
|
|||||||
|
|
||||||
// templateTopicLink returns the link string for the given topic.
|
// templateTopicLink returns the link string for the given topic.
|
||||||
func templateTopicLink(args jet.Arguments) reflect.Value {
|
func templateTopicLink(args jet.Arguments) reflect.Value {
|
||||||
topic := args.Get(0).Convert(reflect.TypeFor[*database.Topic]()).Interface().(*database.Topic)
|
comm := args.Get(0).Convert(reflect.TypeFor[*database.Community]()).Interface().(*database.Community)
|
||||||
ctxt := args.Get(1).Convert(reflect.TypeFor[ui.AmContext]()).Interface().(ui.AmContext)
|
topic := args.Get(1).Convert(reflect.TypeFor[*database.Topic]()).Interface().(*database.Topic)
|
||||||
link, _ := topic.Link(ctxt.Ctx(), "global")
|
ctxt := args.Get(2).Convert(reflect.TypeFor[ui.AmContext]()).Interface().(ui.AmContext)
|
||||||
|
link, _ := topic.Link(ctxt.Ctx(), comm.Id, "global")
|
||||||
return reflect.ValueOf(link)
|
return reflect.ValueOf(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,12 +195,13 @@ func TopPage(ctxt ui.AmContext) (string, any) {
|
|||||||
ctxt.SetFrameTitle("My Front Page")
|
ctxt.SetFrameTitle("My Front Page")
|
||||||
|
|
||||||
// Retrieve the published posts.
|
// Retrieve the published posts.
|
||||||
hdrs, err := database.AmGetPublishedPosts(ctxt.Ctx())
|
hdrs, comms, err := database.AmGetPublishedPosts(ctxt.Ctx())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctxt.VarMap().Set("posts", hdrs)
|
ctxt.VarMap().Set("posts", hdrs)
|
||||||
|
ctxt.VarMap().Set("comms", comms)
|
||||||
ctxt.VarMap().SetFunc("post_getText", templatePostText)
|
ctxt.VarMap().SetFunc("post_getText", templatePostText)
|
||||||
ctxt.VarMap().SetFunc("post_getUserName", templateExtractUserName)
|
ctxt.VarMap().SetFunc("post_getUserName", templateExtractUserName)
|
||||||
ctxt.VarMap().SetFunc("post_topic", templateGetTopic)
|
ctxt.VarMap().SetFunc("post_topic", templateGetTopic)
|
||||||
@@ -283,24 +285,24 @@ func PolicyPage(ctxt ui.AmContext) (string, any) {
|
|||||||
func JumpToShortcut(ctxt ui.AmContext) (string, any) {
|
func JumpToShortcut(ctxt ui.AmContext) (string, any) {
|
||||||
link, err := database.AmDecodePostLink(ctxt.URLParam("postlink"))
|
link, err := database.AmDecodePostLink(ctxt.URLParam("postlink"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("not found: %s", ctxt.URLParam("postlink"))).SetInternal(err)
|
return "error", echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("not found: %s", ctxt.URLParam("postlink"))).Wrap(err)
|
||||||
}
|
}
|
||||||
scope, target := link.Classify()
|
scope, target := link.Classify()
|
||||||
if scope != "global" {
|
if scope != database.PLSCOPE_GLOBAL {
|
||||||
return "error", echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("not found: %s", ctxt.URLParam("postlink")))
|
return "error", echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("not found: %s", ctxt.URLParam("postlink")))
|
||||||
}
|
}
|
||||||
if err = link.VerifyNames(ctxt.Ctx()); err != nil {
|
if err = link.VerifyNames(ctxt.Ctx()); err != nil {
|
||||||
return "error", echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("not found: %s", ctxt.URLParam("postlink"))).SetInternal(err)
|
return "error", echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("not found: %s", ctxt.URLParam("postlink"))).Wrap(err)
|
||||||
}
|
}
|
||||||
targetURL := ""
|
targetURL := ""
|
||||||
switch target {
|
switch target {
|
||||||
case "community":
|
case database.PLCLASS_COMMUNITY:
|
||||||
targetURL = fmt.Sprintf("/comm/%s", link.Community)
|
targetURL = fmt.Sprintf("/comm/%s", link.Community)
|
||||||
case "conference":
|
case database.PLCLASS_CONFERENCE:
|
||||||
targetURL = fmt.Sprintf("/comm/%s/conf/%s", link.Community, link.Conference)
|
targetURL = fmt.Sprintf("/comm/%s/conf/%s", link.Community, link.Conference)
|
||||||
case "topic":
|
case database.PLCLASS_TOPIC:
|
||||||
targetURL = fmt.Sprintf("/comm/%s/conf/%s/r/%d", link.Community, link.Conference, link.Topic)
|
targetURL = fmt.Sprintf("/comm/%s/conf/%s/r/%d", link.Community, link.Conference, link.Topic)
|
||||||
case "post", "postrange", "postopenrange":
|
case database.PLCLASS_POST, database.PLCLASS_POSTRANGE, database.PLCLASS_POSTOPENRANGE:
|
||||||
targetURL = fmt.Sprintf("/comm/%s/conf/%s/r/%d?r=%d,%d", link.Community, link.Conference, link.Topic, link.FirstPost, link.LastPost)
|
targetURL = fmt.Sprintf("/comm/%s/conf/%s/r/%d?r=%d,%d", link.Community, link.Conference, link.Topic, link.FirstPost, link.LastPost)
|
||||||
default:
|
default:
|
||||||
return "error", fmt.Sprintf("invalid target '%s' for link: %s", target, ctxt.URLParam("postlink"))
|
return "error", fmt.Sprintf("invalid target '%s' for link: %s", target, ctxt.URLParam("postlink"))
|
||||||
|
|||||||
+6
-6
@@ -27,7 +27,7 @@ import (
|
|||||||
"git.erbosoft.com/amy/amsterdam/database"
|
"git.erbosoft.com/amy/amsterdam/database"
|
||||||
"git.erbosoft.com/amy/amsterdam/util"
|
"git.erbosoft.com/amy/amsterdam/util"
|
||||||
"github.com/CloudyKit/jet/v6"
|
"github.com/CloudyKit/jet/v6"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v5"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ type AmContext interface {
|
|||||||
|
|
||||||
// amContext is the internal structure that implements AmContext.
|
// amContext is the internal structure that implements AmContext.
|
||||||
type amContext struct {
|
type amContext struct {
|
||||||
echoContext echo.Context
|
echoContext *echo.Context
|
||||||
rendervars jet.VarMap
|
rendervars jet.VarMap
|
||||||
frameTitle string
|
frameTitle string
|
||||||
frameMeta map[int]map[string]string
|
frameMeta map[int]map[string]string
|
||||||
@@ -243,7 +243,7 @@ func (c *amContext) FormFieldIsSet(name string) bool {
|
|||||||
|
|
||||||
// FormFieldValues returns all values for a specified parameter name.
|
// FormFieldValues returns all values for a specified parameter name.
|
||||||
func (c *amContext) FormFieldValues(name string) ([]string, error) {
|
func (c *amContext) FormFieldValues(name string) ([]string, error) {
|
||||||
vals, err := c.echoContext.FormParams()
|
vals, err := c.echoContext.FormValues()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return make([]string, 0), err
|
return make([]string, 0), err
|
||||||
}
|
}
|
||||||
@@ -525,7 +525,7 @@ var amContextRecycleBin chan *amContext
|
|||||||
* Internal Amsterdam context structure pointer, or nil.
|
* Internal Amsterdam context structure pointer, or nil.
|
||||||
* Standard Go error status.
|
* Standard Go error status.
|
||||||
*/
|
*/
|
||||||
func newContext(ctxt echo.Context) (*amContext, error) {
|
func newContext(ctxt *echo.Context) (*amContext, error) {
|
||||||
var rc *amContext
|
var rc *amContext
|
||||||
tmp := freeContext.Get()
|
tmp := freeContext.Get()
|
||||||
if tmp == nil {
|
if tmp == nil {
|
||||||
@@ -593,7 +593,7 @@ func newContext(ctxt echo.Context) (*amContext, error) {
|
|||||||
* Returns:
|
* Returns:
|
||||||
* The associated AmContext.
|
* The associated AmContext.
|
||||||
*/
|
*/
|
||||||
func AmContextFromEchoContext(ctxt echo.Context) AmContext {
|
func AmContextFromEchoContext(ctxt *echo.Context) AmContext {
|
||||||
myctxt := ctxt.Get("__amsterdam_context")
|
myctxt := ctxt.Get("__amsterdam_context")
|
||||||
if myctxt != nil {
|
if myctxt != nil {
|
||||||
rc, ok := myctxt.(*amContext)
|
rc, ok := myctxt.(*amContext)
|
||||||
@@ -641,7 +641,7 @@ func setupContext() func() {
|
|||||||
|
|
||||||
// ContextCreator is middleware that creates and recycles the AmContext.
|
// ContextCreator is middleware that creates and recycles the AmContext.
|
||||||
func ContextCreator(next echo.HandlerFunc) echo.HandlerFunc {
|
func ContextCreator(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c *echo.Context) error {
|
||||||
myctxt, err := newContext(c)
|
myctxt, err := newContext(c)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = next(c)
|
err = next(c)
|
||||||
|
|||||||
+29
-31
@@ -18,12 +18,11 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"slices"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.erbosoft.com/amy/amsterdam/config"
|
"git.erbosoft.com/amy/amsterdam/config"
|
||||||
"git.erbosoft.com/amy/amsterdam/database"
|
"git.erbosoft.com/amy/amsterdam/database"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v5"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,6 +32,12 @@ import (
|
|||||||
be timed out as well as used to show the logged-in users. This is similar to the session support provided in J2EE servlets.
|
be timed out as well as used to show the logged-in users. This is similar to the session support provided in J2EE servlets.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// DEFAULT_SESSION_EXPIRE is the default time in which sessions will expire.
|
||||||
|
const DEFAULT_SESSION_EXPIRE = 1 * time.Hour
|
||||||
|
|
||||||
|
// The interval at which all sessions will be swept.
|
||||||
|
const SESSION_STORE_SWEEP_INTERVAL = 2 * time.Minute
|
||||||
|
|
||||||
// AmSessionOptions gives the options for the session.
|
// AmSessionOptions gives the options for the session.
|
||||||
type AmSessionOptions struct {
|
type AmSessionOptions struct {
|
||||||
Path string
|
Path string
|
||||||
@@ -244,11 +249,10 @@ func (sess *amSession) Hit() {
|
|||||||
|
|
||||||
// amSessionStore is the implementation structure for AmSessionStore.
|
// amSessionStore is the implementation structure for AmSessionStore.
|
||||||
type amSessionStore struct {
|
type amSessionStore struct {
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
sessions map[string]*amSession
|
sessions map[string]*amSession
|
||||||
maxEntries int
|
maxEntries int
|
||||||
expiry time.Duration
|
expiry time.Duration
|
||||||
sweepRunning atomic.Bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// createAmSessionStore creates the session store.
|
// createAmSessionStore creates the session store.
|
||||||
@@ -258,7 +262,6 @@ func createAmSessionStore(exp time.Duration) *amSessionStore {
|
|||||||
maxEntries: 0,
|
maxEntries: 0,
|
||||||
expiry: exp,
|
expiry: exp,
|
||||||
}
|
}
|
||||||
rc.sweepRunning.Store(true)
|
|
||||||
return rc
|
return rc
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,10 +342,15 @@ func (st *amSessionStore) SessionInfo() (int, []string, int) {
|
|||||||
* tick - Channel that "pulses" periodically to run the task.
|
* tick - Channel that "pulses" periodically to run the task.
|
||||||
* done - Channel we write to when we're done.
|
* done - Channel we write to when we're done.
|
||||||
*/
|
*/
|
||||||
func (st *amSessionStore) sweep(tick <-chan time.Time, done chan bool) {
|
func (st *amSessionStore) sweep(ctx context.Context, done chan bool) {
|
||||||
for range tick {
|
tkr := time.NewTicker(SESSION_STORE_SWEEP_INTERVAL)
|
||||||
if st.sweepRunning.Load() {
|
for {
|
||||||
log.Infof("session sweep running")
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
tkr.Stop()
|
||||||
|
done <- true
|
||||||
|
return
|
||||||
|
case <-tkr.C:
|
||||||
// phase 1 - identify expired sessions
|
// phase 1 - identify expired sessions
|
||||||
st.mutex.RLock()
|
st.mutex.RLock()
|
||||||
zap := make([]string, 0, len(st.sessions))
|
zap := make([]string, 0, len(st.sessions))
|
||||||
@@ -353,7 +361,9 @@ func (st *amSessionStore) sweep(tick <-chan time.Time, done chan bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
st.mutex.RUnlock()
|
st.mutex.RUnlock()
|
||||||
log.Infof("identified %d sessions to zap", len(zap))
|
if len(zap) > 0 {
|
||||||
|
log.Infof("identified %d sessions to zap", len(zap))
|
||||||
|
}
|
||||||
|
|
||||||
// phase 2 - get rid of the expired sessions
|
// phase 2 - get rid of the expired sessions
|
||||||
for _, k := range zap {
|
for _, k := range zap {
|
||||||
@@ -365,11 +375,8 @@ func (st *amSessionStore) sweep(tick <-chan time.Time, done chan bool) {
|
|||||||
}
|
}
|
||||||
st.mutex.Unlock()
|
st.mutex.Unlock()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
done <- true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sessionStore is the global session store.
|
// sessionStore is the global session store.
|
||||||
@@ -380,36 +387,27 @@ func setupSessionManager() func() {
|
|||||||
// get the time for the session to expire
|
// get the time for the session to expire
|
||||||
d, err := time.ParseDuration(config.GlobalConfig.Site.SessionExpire)
|
d, err := time.ParseDuration(config.GlobalConfig.Site.SessionExpire)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d, err = time.ParseDuration("1h")
|
log.Errorf("invalid session timeout value: %s", config.GlobalConfig.Site.SessionExpire)
|
||||||
if err != nil {
|
d = DEFAULT_SESSION_EXPIRE
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// create session store
|
// create session store
|
||||||
sessionStore = createAmSessionStore(d)
|
sessionStore = createAmSessionStore(d)
|
||||||
|
|
||||||
// get the clock value to run sweeps
|
|
||||||
d, err = time.ParseDuration("1s")
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// set up the sweep runner
|
// set up the sweep runner
|
||||||
tkr := time.NewTicker(d)
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
done := make(chan bool)
|
done := make(chan bool)
|
||||||
go sessionStore.sweep(tkr.C, done)
|
go sessionStore.sweep(ctx, done)
|
||||||
return func() {
|
return func() {
|
||||||
// stop the sweep runner
|
// stop the sweep runner
|
||||||
sessionStore.sweepRunning.Store(false)
|
cancel()
|
||||||
<-done
|
<-done
|
||||||
tkr.Stop()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SessionStoreInjector is middleware that injects the session store into the context variables.
|
// SessionStoreInjector is middleware that injects the session store into the context variables.
|
||||||
func SessionStoreInjector(next echo.HandlerFunc) echo.HandlerFunc {
|
func SessionStoreInjector(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c *echo.Context) error {
|
||||||
c.Set("AmSessionStore", sessionStore)
|
c.Set("AmSessionStore", sessionStore)
|
||||||
return next(c)
|
return next(c)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -14,6 +14,7 @@ package ui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
@@ -120,8 +121,7 @@ func AmLoadDialog(name string) (*Dialog, error) {
|
|||||||
f, err = extDialogs.Open(fmt.Sprintf("%s.yaml", name))
|
f, err = extDialogs.Open(fmt.Sprintf("%s.yaml", name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f = nil
|
f = nil
|
||||||
pe := err.(*fs.PathError)
|
if errors.Is(err, os.ErrInvalid) || errors.Is(err, os.ErrNotExist) {
|
||||||
if pe.Err == os.ErrInvalid || pe.Err == os.ErrNotExist {
|
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,15 @@ title: "Modify User Account"
|
|||||||
subtitle: "User: [USERNAME]"
|
subtitle: "User: [USERNAME]"
|
||||||
action: "/sysadmin/users/[USERNAME]"
|
action: "/sysadmin/users/[USERNAME]"
|
||||||
fields:
|
fields:
|
||||||
|
- type: "header"
|
||||||
|
name: "header0"
|
||||||
|
caption: "User Information"
|
||||||
|
- type: "ams_id"
|
||||||
|
name: "user"
|
||||||
|
caption: "User Name"
|
||||||
|
required: true
|
||||||
|
size: 32
|
||||||
|
maxlength: 64
|
||||||
- type: "header"
|
- type: "header"
|
||||||
name: "header1"
|
name: "header1"
|
||||||
caption: "Security Information"
|
caption: "Security Information"
|
||||||
|
|||||||
+3
-3
@@ -29,7 +29,7 @@ import (
|
|||||||
|
|
||||||
"git.erbosoft.com/amy/amsterdam/database"
|
"git.erbosoft.com/amy/amsterdam/database"
|
||||||
"github.com/disintegration/imaging"
|
"github.com/disintegration/imaging"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed static_images/*
|
//go:embed static_images/*
|
||||||
@@ -64,7 +64,7 @@ func mimeTypeFromFilename(filename string) string {
|
|||||||
* Returns:
|
* Returns:
|
||||||
* Standard Go error return.
|
* Standard Go error return.
|
||||||
*/
|
*/
|
||||||
func AmServeImage(c echo.Context) error {
|
func AmServeImage(c *echo.Context) error {
|
||||||
components := strings.SplitAfter(c.Request().URL.Path, "/")
|
components := strings.SplitAfter(c.Request().URL.Path, "/")
|
||||||
var err error = nil
|
var err error = nil
|
||||||
if len(components) == 4 {
|
if len(components) == 4 {
|
||||||
@@ -105,7 +105,7 @@ func AmServeImage(c echo.Context) error {
|
|||||||
* Returns:
|
* Returns:
|
||||||
* Standard Go error return.
|
* Standard Go error return.
|
||||||
*/
|
*/
|
||||||
func AmServeVeniceCompatibleImage(c echo.Context) error {
|
func AmServeVeniceCompatibleImage(c *echo.Context) error {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
var img *database.ImageStore
|
var img *database.ImageStore
|
||||||
|
|||||||
+21
-11
@@ -17,20 +17,21 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.erbosoft.com/amy/amsterdam/config"
|
"git.erbosoft.com/amy/amsterdam/config"
|
||||||
"git.erbosoft.com/amy/amsterdam/database"
|
"git.erbosoft.com/amy/amsterdam/database"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v5"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IPBanTest is middleware that handles the IP banning.
|
// IPBanTest is middleware that handles the IP banning.
|
||||||
func IPBanTest(next echo.HandlerFunc) echo.HandlerFunc {
|
func IPBanTest(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c *echo.Context) error {
|
||||||
// Check IP banning.
|
// Check IP banning.
|
||||||
banmsg, banerr := database.AmTestIPBan(c.Request().Context(), c.RealIP())
|
banmsg, banerr := database.AmTestIPBan(c.Request().Context(), c.RealIP())
|
||||||
if banerr != nil {
|
if banerr != nil {
|
||||||
c.Logger().Warnf("address %s could not be tested: %v", c.RealIP(), banerr)
|
log.Warnf("address %s could not be tested: %v", c.RealIP(), banerr)
|
||||||
// but let the request pass anyway
|
// but let the request pass anyway
|
||||||
} else if banmsg != "" {
|
} else if banmsg != "" {
|
||||||
amctxt := AmContextFromEchoContext(c)
|
amctxt := AmContextFromEchoContext(c)
|
||||||
@@ -42,7 +43,7 @@ func IPBanTest(next echo.HandlerFunc) echo.HandlerFunc {
|
|||||||
|
|
||||||
// CookieLoginTest is middleware that handles cookie logins.
|
// CookieLoginTest is middleware that handles cookie logins.
|
||||||
func CookieLoginTest(next echo.HandlerFunc) echo.HandlerFunc {
|
func CookieLoginTest(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c *echo.Context) error {
|
||||||
amctxt := AmContextFromEchoContext(c)
|
amctxt := AmContextFromEchoContext(c)
|
||||||
// Check for cookie login.
|
// Check for cookie login.
|
||||||
if amctxt.CurrentUser().IsAnon {
|
if amctxt.CurrentUser().IsAnon {
|
||||||
@@ -76,12 +77,16 @@ func CookieLoginTest(next echo.HandlerFunc) echo.HandlerFunc {
|
|||||||
|
|
||||||
// SetCommunity is middleware that sets the community context based on the URL.
|
// SetCommunity is middleware that sets the community context based on the URL.
|
||||||
func SetCommunity(next echo.HandlerFunc) echo.HandlerFunc {
|
func SetCommunity(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c *echo.Context) error {
|
||||||
ctxt := AmContextFromEchoContext(c)
|
ctxt := AmContextFromEchoContext(c)
|
||||||
err := ctxt.SetCommunityContext(ctxt.URLParam("cid"))
|
err := ctxt.SetCommunityContext(ctxt.URLParam("cid"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return AmSendPageData(c, ctxt, "error", echo.NewHTTPError(http.StatusNotFound).SetInternal(err))
|
return AmSendPageData(c, ctxt, "error", echo.NewHTTPError(http.StatusNotFound, err.Error()).Wrap(err))
|
||||||
}
|
}
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString("/comm/")
|
||||||
|
b.WriteString(ctxt.CurrentCommunity().Alias)
|
||||||
|
ctxt.SetScratch("CommunityLink", b.String())
|
||||||
ctxt.SetLeftMenu("community")
|
ctxt.SetLeftMenu("community")
|
||||||
return next(c)
|
return next(c)
|
||||||
}
|
}
|
||||||
@@ -89,10 +94,10 @@ func SetCommunity(next echo.HandlerFunc) echo.HandlerFunc {
|
|||||||
|
|
||||||
// ValidateConference is middleware that validates the user has access to the community's conference facility.
|
// ValidateConference is middleware that validates the user has access to the community's conference facility.
|
||||||
func ValidateConference(next echo.HandlerFunc) echo.HandlerFunc {
|
func ValidateConference(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c *echo.Context) error {
|
||||||
ctxt := AmContextFromEchoContext(c)
|
ctxt := AmContextFromEchoContext(c)
|
||||||
comm := ctxt.CurrentCommunity() // set by middleware
|
comm := ctxt.CurrentCommunity() // set by middleware
|
||||||
b, err := database.AmTestService(c.Request().Context(), comm, "Conference")
|
b, err := database.AmTestService(c.Request().Context(), comm, database.AM_SVC_CONFERENCE)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return AmSendPageData(c, ctxt, "error", err)
|
return AmSendPageData(c, ctxt, "error", err)
|
||||||
}
|
}
|
||||||
@@ -111,9 +116,9 @@ func ValidateConference(next echo.HandlerFunc) echo.HandlerFunc {
|
|||||||
|
|
||||||
// SetConference is middleware that sets the conference context based on the URL.
|
// SetConference is middleware that sets the conference context based on the URL.
|
||||||
func SetConference(next echo.HandlerFunc) echo.HandlerFunc {
|
func SetConference(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c *echo.Context) error {
|
||||||
ctxt := AmContextFromEchoContext(c)
|
ctxt := AmContextFromEchoContext(c)
|
||||||
conf, err := database.AmGetConferenceByAliasInCommunity(ctxt.Ctx(), ctxt.CurrentCommunity().Id, ctxt.URLParam("confid"))
|
conf, err := database.AmGetConferenceByAlias(ctxt.Ctx(), ctxt.CurrentCommunity().Id, ctxt.URLParam("confid"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return AmSendPageData(c, ctxt, "error", err)
|
return AmSendPageData(c, ctxt, "error", err)
|
||||||
}
|
}
|
||||||
@@ -128,13 +133,18 @@ func SetConference(next echo.HandlerFunc) echo.HandlerFunc {
|
|||||||
ctxt.SetScratch("currentConference", conf)
|
ctxt.SetScratch("currentConference", conf)
|
||||||
ctxt.SetScratch("currentAlias", ctxt.URLParam("confid"))
|
ctxt.SetScratch("currentAlias", ctxt.URLParam("confid"))
|
||||||
ctxt.SetScratch("levelInConference", myLevel)
|
ctxt.SetScratch("levelInConference", myLevel)
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString(ctxt.GetScratch("CommunityLink").(string))
|
||||||
|
b.WriteString("/conf/")
|
||||||
|
b.WriteString(ctxt.URLParam("confid"))
|
||||||
|
ctxt.SetScratch("ConferenceLink", b.String())
|
||||||
return next(c)
|
return next(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTopic is middleware that sets the topic context based on the URL.
|
// SetTopic is middleware that sets the topic context based on the URL.
|
||||||
func SetTopic(next echo.HandlerFunc) echo.HandlerFunc {
|
func SetTopic(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c *echo.Context) error {
|
||||||
ctxt := AmContextFromEchoContext(c)
|
ctxt := AmContextFromEchoContext(c)
|
||||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||||
|
|
||||||
|
|||||||
+145
-72
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Amsterdam Web Communities System
|
* Amsterdam Web Communities System
|
||||||
* Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
* Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
||||||
*
|
*
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* 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
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
@@ -13,18 +13,82 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.erbosoft.com/amy/amsterdam/config"
|
"git.erbosoft.com/amy/amsterdam/config"
|
||||||
"git.erbosoft.com/amy/amsterdam/database"
|
"git.erbosoft.com/amy/amsterdam/database"
|
||||||
"github.com/klauspost/lctime"
|
"github.com/klauspost/lctime"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v5"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// panicRecoveryErr is the error created for panic recovery.
|
||||||
|
type panicRecoveryErr struct {
|
||||||
|
Phase string // phase of operation
|
||||||
|
Err error // error value
|
||||||
|
Stack []byte // stack trace
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns the actual error string.
|
||||||
|
func (e *panicRecoveryErr) Error() string {
|
||||||
|
return fmt.Sprintf("[Panic Recovery in %s Phase] %s %s", e.Phase, e.Err.Error(), e.Stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap returns the error "nested" inside this error.
|
||||||
|
func (e *panicRecoveryErr) Unwrap() error {
|
||||||
|
return e.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// doFrameRender renders the outer frame template with an inner template.
|
||||||
|
func doFrameRender(ctxt *echo.Context, amctxt AmContext, statusCode int, innerPage string) error {
|
||||||
|
if amctxt.FrameTitle() == "" {
|
||||||
|
log.Errorf("*** NO FRAME TITLE set for path %s", amctxt.URLPath())
|
||||||
|
amctxt.SetFrameTitle("<<< NO FRAME TITLE >>>")
|
||||||
|
}
|
||||||
|
amctxt.VarMap().Set("__innerPage", innerPage)
|
||||||
|
menus := make([]*MenuDefinition, 2)
|
||||||
|
switch amctxt.LeftMenu() {
|
||||||
|
case "top":
|
||||||
|
menus[0] = AmMenu(config.GlobalConfig.Site.TopMenuId)
|
||||||
|
case "community":
|
||||||
|
comm := amctxt.CurrentCommunity()
|
||||||
|
if comm != nil {
|
||||||
|
md, err := AmBuildCommunityMenu(ctxt.Request().Context(), comm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
menus[0] = md
|
||||||
|
} else {
|
||||||
|
menus[0] = AmMenu(config.GlobalConfig.Site.TopMenuId)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("AmSendPageData(): unknown left menu context: %s", amctxt.LeftMenu())
|
||||||
|
}
|
||||||
|
menus[1] = AmMenu(config.GlobalConfig.Site.FixedMenuId)
|
||||||
|
amctxt.VarMap().Set("__leftMenus", menus)
|
||||||
|
ad, err := database.AmGetRandomAd(ctxt.Request().Context())
|
||||||
|
if err != nil {
|
||||||
|
ad = &database.Advert{
|
||||||
|
AdId: -1,
|
||||||
|
ImagePath: "",
|
||||||
|
PathStyle: -1,
|
||||||
|
Caption: nil,
|
||||||
|
LinkURL: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
amctxt.VarMap().Set("__bannerad", ad)
|
||||||
|
amctxt.VarMap().Set("__debugMode", config.GlobalComputedConfig.DebugMode)
|
||||||
|
if tmp := amctxt.GetScratch("frame_suppressLogin"); tmp != nil {
|
||||||
|
amctxt.VarMap().Set("__suppressLogin", true)
|
||||||
|
}
|
||||||
|
return ctxt.Render(statusCode, config.GlobalConfig.Site.FrameTemplate, amctxt)
|
||||||
|
}
|
||||||
|
|
||||||
/* AmSendPageData sends page data to the output based on the command string.
|
/* AmSendPageData sends page data to the output based on the command string.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* ctxt - The Echo context from the request.
|
* ctxt - The Echo context from the request.
|
||||||
@@ -44,40 +108,56 @@ import (
|
|||||||
* Returns:
|
* Returns:
|
||||||
* Standard Go error status.
|
* Standard Go error status.
|
||||||
*/
|
*/
|
||||||
func AmSendPageData(ctxt echo.Context, amctxt AmContext, command string, data any) error {
|
func AmSendPageData(ctxt *echo.Context, amctxt AmContext, command string, data any) error {
|
||||||
|
// Enable panic recovery.
|
||||||
|
if !config.CommandLine.DebugPanic {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
if r == http.ErrAbortHandler {
|
||||||
|
panic(r)
|
||||||
|
}
|
||||||
|
tmperr, ok := r.(error)
|
||||||
|
if !ok {
|
||||||
|
tmperr = fmt.Errorf("%v", r)
|
||||||
|
}
|
||||||
|
stack := make([]byte, config.GlobalComputedConfig.PanicRecoveryStack)
|
||||||
|
length := runtime.Stack(stack, false)
|
||||||
|
log.Errorf("[Panic Recovery in SendData Phase] %s %s", tmperr.Error(), stack[:length])
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
// Preprocess certain commands into different ones.
|
// Preprocess certain commands into different ones.
|
||||||
httprc := http.StatusOK
|
httprc := http.StatusOK
|
||||||
switch command {
|
switch command {
|
||||||
case "error":
|
case "error":
|
||||||
message := ""
|
message := fmt.Sprintf("Unspecified error in %s", ctxt.Request().URL.String())
|
||||||
if data == nil {
|
if data != nil {
|
||||||
message = fmt.Sprintf("Unspecified error in %s", ctxt.Request().URL.String())
|
if he, ok := data.(*echo.HTTPError); ok {
|
||||||
} else if he, ok := data.(*echo.HTTPError); ok {
|
httprc = he.Code
|
||||||
httprc = he.Code
|
m1 := he.Message
|
||||||
m1 := he.Message
|
e1 := he.Unwrap()
|
||||||
e1 := he.Unwrap()
|
if m1 == "" {
|
||||||
if m1 == nil || m1 == "" {
|
if e1 != nil {
|
||||||
if e1 == nil {
|
message = e1.Error()
|
||||||
message = fmt.Sprintf("Unspecified error in %s", ctxt.Request().URL.String())
|
}
|
||||||
} else {
|
} else {
|
||||||
message = e1.Error()
|
if e1 == nil {
|
||||||
|
message = fmt.Sprintf("%v", m1)
|
||||||
|
} else {
|
||||||
|
message = fmt.Sprintf("%v (%v)", m1, e1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else if er, ok := data.(error); ok {
|
||||||
|
message = er.Error()
|
||||||
} else {
|
} else {
|
||||||
if e1 == nil {
|
message = fmt.Sprintf("%v", data)
|
||||||
message = fmt.Sprintf("%v", m1)
|
|
||||||
} else {
|
|
||||||
message = fmt.Sprintf("%v (%v)", m1, e1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if er, ok := data.(error); ok {
|
|
||||||
message = er.Error()
|
|
||||||
} else {
|
|
||||||
message = fmt.Sprintf("%v", data)
|
|
||||||
}
|
}
|
||||||
if httprc < 400 {
|
if httprc < 400 {
|
||||||
httprc = http.StatusInternalServerError
|
httprc = http.StatusInternalServerError
|
||||||
}
|
}
|
||||||
amctxt.SetFrameTitle("Internal Server Error")
|
amctxt.SetFrameTitle(http.StatusText(httprc))
|
||||||
amctxt.VarMap().Set("error", message)
|
amctxt.VarMap().Set("error", message)
|
||||||
if tmp := amctxt.GetSession("lastKnownGood"); tmp != nil {
|
if tmp := amctxt.GetSession("lastKnownGood"); tmp != nil {
|
||||||
amctxt.VarMap().Set("recovery", tmp)
|
amctxt.VarMap().Set("recovery", tmp)
|
||||||
@@ -98,6 +178,11 @@ func AmSendPageData(ctxt echo.Context, amctxt AmContext, command string, data an
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process commands.
|
// Process commands.
|
||||||
|
oldreq := ctxt.Request()
|
||||||
|
ctx, cancel := context.WithTimeout(oldreq.Context(), time.Duration(config.GlobalConfig.Tuning.Timeouts.PageRender)*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
ctxt.SetRequest(oldreq.WithContext(ctx))
|
||||||
|
defer ctxt.SetRequest(oldreq)
|
||||||
var err error
|
var err error
|
||||||
switch command {
|
switch command {
|
||||||
case "bytes":
|
case "bytes":
|
||||||
@@ -113,47 +198,7 @@ func AmSendPageData(ctxt echo.Context, amctxt AmContext, command string, data an
|
|||||||
case "template":
|
case "template":
|
||||||
err = ctxt.Render(httprc, data.(string), amctxt)
|
err = ctxt.Render(httprc, data.(string), amctxt)
|
||||||
case "framed":
|
case "framed":
|
||||||
if amctxt.FrameTitle() == "" {
|
err = doFrameRender(ctxt, amctxt, httprc, data.(string))
|
||||||
ctxt.Logger().Errorf("*** NO FRAME TITLE set for path %s", amctxt.URLPath())
|
|
||||||
amctxt.SetFrameTitle("<<< NO FRAME TITLE >>>")
|
|
||||||
}
|
|
||||||
amctxt.VarMap().Set("__innerPage", data)
|
|
||||||
menus := make([]*MenuDefinition, 2)
|
|
||||||
switch amctxt.LeftMenu() {
|
|
||||||
case "top":
|
|
||||||
menus[0] = AmMenu("top")
|
|
||||||
case "community":
|
|
||||||
comm := amctxt.CurrentCommunity()
|
|
||||||
if comm != nil {
|
|
||||||
md, err := AmBuildCommunityMenu(ctxt.Request().Context(), comm)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
menus[0] = md
|
|
||||||
} else {
|
|
||||||
menus[0] = AmMenu("top")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("AmSendPageData(): unknown left menu context: %s", amctxt.LeftMenu())
|
|
||||||
}
|
|
||||||
menus[1] = AmMenu("fixed")
|
|
||||||
amctxt.VarMap().Set("__leftMenus", menus)
|
|
||||||
ad, err := database.AmGetRandomAd(ctxt.Request().Context())
|
|
||||||
if err != nil {
|
|
||||||
ad = &database.Advert{
|
|
||||||
AdId: -1,
|
|
||||||
ImagePath: "",
|
|
||||||
PathStyle: -1,
|
|
||||||
Caption: nil,
|
|
||||||
LinkURL: nil,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
amctxt.VarMap().Set("__bannerad", ad)
|
|
||||||
amctxt.VarMap().Set("__debugMode", config.GlobalComputedConfig.DebugMode)
|
|
||||||
if tmp := amctxt.GetScratch("frame_suppressLogin"); tmp != nil {
|
|
||||||
amctxt.VarMap().Set("__suppressLogin", true)
|
|
||||||
}
|
|
||||||
err = ctxt.Render(httprc, "frame.jet", amctxt)
|
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("AmSendPageData(): unknown rendering type: %s", command)
|
err = fmt.Errorf("AmSendPageData(): unknown rendering type: %s", command)
|
||||||
}
|
}
|
||||||
@@ -169,6 +214,34 @@ var expireTime string = lctime.Strftime("%c", time.Unix(1, 0))
|
|||||||
// AmPageFunc is the definition for an Amsterdam "page function" that handles most of the work and defers to the wrapper for rendering.
|
// AmPageFunc is the definition for an Amsterdam "page function" that handles most of the work and defers to the wrapper for rendering.
|
||||||
type AmPageFunc func(AmContext) (string, any)
|
type AmPageFunc func(AmContext) (string, any)
|
||||||
|
|
||||||
|
// callWrappedPageFunc calls the specified page functon inside a wrapper that handles timeouts and panic recovery.
|
||||||
|
func callWrappedPageFunc(f AmPageFunc, ctxt *echo.Context, amctxt AmContext) (command string, arg any) {
|
||||||
|
if !config.CommandLine.DebugPanic {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
if r == http.ErrAbortHandler {
|
||||||
|
panic(r)
|
||||||
|
}
|
||||||
|
tmperr, ok := r.(error)
|
||||||
|
if !ok {
|
||||||
|
tmperr = fmt.Errorf("%v", r)
|
||||||
|
}
|
||||||
|
stack := make([]byte, config.GlobalComputedConfig.PanicRecoveryStack)
|
||||||
|
length := runtime.Stack(stack, false)
|
||||||
|
arg = &panicRecoveryErr{Phase: "PageFunc", Err: tmperr, Stack: stack[:length]}
|
||||||
|
command = "error"
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
oldreq := ctxt.Request()
|
||||||
|
ctx, cancel := context.WithTimeout(oldreq.Context(), time.Duration(config.GlobalConfig.Tuning.Timeouts.PageExecute)*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
ctxt.SetRequest(oldreq.WithContext(ctx))
|
||||||
|
defer ctxt.SetRequest(oldreq)
|
||||||
|
command, arg = f(amctxt)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
/* AmWrap wraps the Amsterdam handler function in a wrapper that implements the spec for
|
/* AmWrap wraps the Amsterdam handler function in a wrapper that implements the spec for
|
||||||
* Echo handler functions.
|
* Echo handler functions.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
@@ -177,7 +250,7 @@ type AmPageFunc func(AmContext) (string, any)
|
|||||||
* The wrapped function.
|
* The wrapped function.
|
||||||
*/
|
*/
|
||||||
func AmWrap(myfunc AmPageFunc) echo.HandlerFunc {
|
func AmWrap(myfunc AmPageFunc) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c *echo.Context) error {
|
||||||
ctxt := AmContextFromEchoContext(c)
|
ctxt := AmContextFromEchoContext(c)
|
||||||
|
|
||||||
// Add the dynamic headers.
|
// Add the dynamic headers.
|
||||||
@@ -186,16 +259,16 @@ func AmWrap(myfunc AmPageFunc) echo.HandlerFunc {
|
|||||||
c.Response().Header().Set("Expires", expireTime)
|
c.Response().Header().Set("Expires", expireTime)
|
||||||
|
|
||||||
// Exec the wrapped function.
|
// Exec the wrapped function.
|
||||||
command, arg := myfunc(ctxt)
|
command, arg := callWrappedPageFunc(myfunc, c, ctxt)
|
||||||
if command != "error" && command != "ipban" {
|
if command != "error" && command != "ipban" {
|
||||||
ctxt.SetSession("lastKnownGood", ctxt.Locator())
|
ctxt.SetSession("lastKnownGood", ctxt.Locator())
|
||||||
}
|
}
|
||||||
if err := ctxt.SaveSession(); err != nil {
|
if err := ctxt.SaveSession(); err != nil {
|
||||||
c.Logger().Errorf("Session save error: %v", err)
|
log.Errorf("Session save error: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := AmSendPageData(c, ctxt, command, arg); err != nil {
|
if err := AmSendPageData(c, ctxt, command, arg); err != nil {
|
||||||
c.Logger().Errorf("Rendering error: %v", err)
|
log.Errorf("Rendering error: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -203,7 +276,7 @@ func AmWrap(myfunc AmPageFunc) echo.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AmWithTempContext runs a page function with a temporary context. Used in error handling.
|
// AmWithTempContext runs a page function with a temporary context. Used in error handling.
|
||||||
func AmWithTempContext(c echo.Context, fn AmPageFunc) error {
|
func AmWithTempContext(c *echo.Context, fn AmPageFunc) error {
|
||||||
var ctxt AmContext = nil
|
var ctxt AmContext = nil
|
||||||
myctxt := c.Get("__amsterdam_context")
|
myctxt := c.Get("__amsterdam_context")
|
||||||
if myctxt != nil {
|
if myctxt != nil {
|
||||||
@@ -225,7 +298,7 @@ func AmWithTempContext(c echo.Context, fn AmPageFunc) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call the function
|
// Call the function
|
||||||
command, arg := fn(ctxt)
|
command, arg := callWrappedPageFunc(fn, c, ctxt)
|
||||||
|
|
||||||
// Add the dynamic headers.
|
// Add the dynamic headers.
|
||||||
c.Response().Header().Set("Pragma", "No-cache")
|
c.Response().Header().Set("Pragma", "No-cache")
|
||||||
@@ -233,7 +306,7 @@ func AmWithTempContext(c echo.Context, fn AmPageFunc) error {
|
|||||||
c.Response().Header().Set("Expires", expireTime)
|
c.Response().Header().Set("Expires", expireTime)
|
||||||
|
|
||||||
if err := AmSendPageData(c, ctxt, command, arg); err != nil {
|
if err := AmSendPageData(c, ctxt, command, arg); err != nil {
|
||||||
c.Logger().Errorf("Rendering error: %v", err)
|
log.Errorf("Rendering error: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.erbosoft.com/amy/amsterdam/config"
|
"git.erbosoft.com/amy/amsterdam/config"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v5"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
)
|
)
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
+5
-2
@@ -31,7 +31,7 @@ import (
|
|||||||
"github.com/CloudyKit/jet/v6"
|
"github.com/CloudyKit/jet/v6"
|
||||||
"github.com/CloudyKit/jet/v6/loaders/embedfs"
|
"github.com/CloudyKit/jet/v6/loaders/embedfs"
|
||||||
"github.com/CloudyKit/jet/v6/loaders/multi"
|
"github.com/CloudyKit/jet/v6/loaders/multi"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v5"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -309,6 +309,9 @@ func setupTemplates() {
|
|||||||
views.AddGlobal("AmsterdamVersion", config.AMSTERDAM_VERSION)
|
views.AddGlobal("AmsterdamVersion", config.AMSTERDAM_VERSION)
|
||||||
views.AddGlobal("AmsterdamCopyright", config.AMSTERDAM_COPYRIGHT)
|
views.AddGlobal("AmsterdamCopyright", config.AMSTERDAM_COPYRIGHT)
|
||||||
views.AddGlobal("GlobalConfig", config.GlobalConfig)
|
views.AddGlobal("GlobalConfig", config.GlobalConfig)
|
||||||
|
views.AddGlobal("PLSCOPE_COMMUNITY", database.PLSCOPE_COMMUNITY)
|
||||||
|
views.AddGlobal("PLSCOPE_CONFERENCE", database.PLSCOPE_CONFERENCE)
|
||||||
|
views.AddGlobal("PLSCOPE_TOPIC", database.PLSCOPE_TOPIC)
|
||||||
views.AddGlobalFunc("iif", immediateIf)
|
views.AddGlobalFunc("iif", immediateIf)
|
||||||
views.AddGlobalFunc("postRewrite", postRewrite)
|
views.AddGlobalFunc("postRewrite", postRewrite)
|
||||||
views.AddGlobalFunc("MakeIntRange", makeIntRange)
|
views.AddGlobalFunc("MakeIntRange", makeIntRange)
|
||||||
@@ -363,7 +366,7 @@ type TemplateRenderer struct{}
|
|||||||
* Returns:
|
* Returns:
|
||||||
* Standard Go error status.
|
* Standard Go error status.
|
||||||
*/
|
*/
|
||||||
func (r *TemplateRenderer) Render(w io.Writer, name string, data any, c echo.Context) error {
|
func (r *TemplateRenderer) Render(c *echo.Context, w io.Writer, name string, data any) error {
|
||||||
defer util.MeasureTime(fmt.Sprintf("ui.Render(%s)", name))()
|
defer util.MeasureTime(fmt.Sprintf("ui.Render(%s)", name))()
|
||||||
|
|
||||||
view, err := views.GetTemplate(name)
|
view, err := views.GetTemplate(name)
|
||||||
|
|||||||
@@ -13,11 +13,11 @@
|
|||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<div class=" flex items-baseline gap-2">
|
<div class=" flex items-baseline gap-2">
|
||||||
<h1 class="text-blue-800 text-4xl font-bold">Find Posts</h1>
|
<h1 class="text-blue-800 text-4xl font-bold">Find Posts</h1>
|
||||||
{{ if scope == "community" }}
|
{{ if scope == PLSCOPE_COMMUNITY }}
|
||||||
<span class="text-blue-800 text-xl font-bold ml-2">in Community: {{ entityName }}</span>
|
<span class="text-blue-800 text-xl font-bold ml-2">in Community: {{ entityName }}</span>
|
||||||
{{ else if scope == "conference" }}
|
{{ else if scope == PLSCOPE_CONFERENCE }}
|
||||||
<span class="text-blue-800 text-xl font-bold ml-2">in Conference: {{ entityName }}</span>
|
<span class="text-blue-800 text-xl font-bold ml-2">in Conference: {{ entityName }}</span>
|
||||||
{{ else if scope == "topic" }}
|
{{ else if scope == PLSCOPE_TOPIC }}
|
||||||
<span class="text-blue-800 text-xl font-bold ml-2">in Topic: {{ entityName | raw }}</span>
|
<span class="text-blue-800 text-xl font-bold ml-2">in Topic: {{ entityName | raw }}</span>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
@@ -26,11 +26,11 @@
|
|||||||
|
|
||||||
<!-- Backlink -->
|
<!-- Backlink -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
{{ if scope == "community" }}
|
{{ if scope == PLSCOPE_COMMUNITY }}
|
||||||
<a class="text-blue-700 hover:text-blue-900 text-sm font-medium" href="{{ backlink }}">Return to Conference List</a>
|
<a class="text-blue-700 hover:text-blue-900 text-sm font-medium" href="{{ backlink }}">Return to Conference List</a>
|
||||||
{{ else if scope == "conference" }}
|
{{ else if scope == PLSCOPE_CONFERENCE }}
|
||||||
<a class="text-blue-700 hover:text-blue-900 text-sm font-medium" href="{{ backlink }}">Return to Topic List</a>
|
<a class="text-blue-700 hover:text-blue-900 text-sm font-medium" href="{{ backlink }}">Return to Topic List</a>
|
||||||
{{ else if scope == "topic" }}
|
{{ else if scope == PLSCOPE_TOPIC }}
|
||||||
<a class="text-blue-700 hover:text-blue-900 text-sm font-medium" href="{{ backlink }}">Return to Topic</a>
|
<a class="text-blue-700 hover:text-blue-900 text-sm font-medium" href="{{ backlink }}">Return to Topic</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
<title>{{ .FrameTitle() | raw }} - {{ GlobalConfig.Site.Title }}</title>
|
<title>{{ .FrameTitle() | raw }} - {{ GlobalConfig.Site.Title }}</title>
|
||||||
<link rel="icon" href="{{ GlobalConfig.Site.SiteIcon.Path }}" type="{{ GlobalConfig.Site.SiteIcon.Type }}" />
|
<link rel="icon" href="{{ GlobalConfig.Site.SiteIcon.Path }}" type="{{ GlobalConfig.Site.SiteIcon.Type }}" />
|
||||||
<link rel="shortcut icon" href="{{ GlobalConfig.Site.SiteShortcutIcon }}" />
|
<link rel="shortcut icon" href="{{ GlobalConfig.Site.SiteShortcutIcon }}" />
|
||||||
|
<link rel="apple-touch-icon" href="{{ GlobalConfig.Site.SiteAppleIcon }}" />
|
||||||
{{ range k, v := .FrameMetadata(0) }}
|
{{ range k, v := .FrameMetadata(0) }}
|
||||||
<meta http-equiv="{{ k }}" content="{{ v }}">
|
<meta http-equiv="{{ k }}" content="{{ v }}">
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
+2
-2
@@ -117,8 +117,8 @@
|
|||||||
{{ post_cur = p }}
|
{{ post_cur = p }}
|
||||||
{{ post_userName = post_getUserName(p, .) }}
|
{{ post_userName = post_getUserName(p, .) }}
|
||||||
{{ post_text = post_getText(p, .) }}
|
{{ post_text = post_getText(p, .) }}
|
||||||
{{ post_overrideLine = post_getOverrideLine(p, .) }}
|
{{ post_overrideLine = post_getOverrideLine(p, advancedControls, .) }}
|
||||||
{{ post_overrideLink = post_getOverrideLink(p, post_topicPermalink) }}
|
{{ post_overrideLink = post_getOverrideLink(p, advancedControls, post_topicLink) }}
|
||||||
{{ post_attach = post_getAttachmentInfo(p, .) }}
|
{{ post_attach = post_getAttachmentInfo(p, .) }}
|
||||||
{{ post_bozo = post_isBozo(p, post_topic, .) }}
|
{{ post_bozo = post_isBozo(p, post_topic, .) }}
|
||||||
{{ if showPics }}
|
{{ if showPics }}
|
||||||
|
|||||||
@@ -32,8 +32,8 @@
|
|||||||
{{ post_cur = p }}
|
{{ post_cur = p }}
|
||||||
{{ post_userName = post_getUserName(p, .) }}
|
{{ post_userName = post_getUserName(p, .) }}
|
||||||
{{ post_text = post_getText(p, .) }}
|
{{ post_text = post_getText(p, .) }}
|
||||||
{{ post_overrideLine = post_getOverrideLine(p, .) }}
|
{{ post_overrideLine = post_getOverrideLine(p, true, .) }}
|
||||||
{{ post_overrideLink = post_getOverrideLink(p, post_topicPermalink) }}
|
{{ post_overrideLink = post_getOverrideLink(p, true, post_topicLink) }}
|
||||||
{{ post_attach = post_getAttachmentInfo(p, .) }}
|
{{ post_attach = post_getAttachmentInfo(p, .) }}
|
||||||
{{ post_bozo = post_isBozo(p, post_topic, .) }}
|
{{ post_bozo = post_isBozo(p, post_topic, .) }}
|
||||||
{{ include "singlepost.jet" }}
|
{{ include "singlepost.jet" }}
|
||||||
|
|||||||
+1
-1
@@ -33,7 +33,7 @@
|
|||||||
{{ user = post_getUserName(p, .) }}
|
{{ user = post_getUserName(p, .) }}
|
||||||
{{ text = post_getText(p, .) }}
|
{{ text = post_getText(p, .) }}
|
||||||
{{ topic = post_topic(p, .) }}
|
{{ topic = post_topic(p, .) }}
|
||||||
{{ link = post_topicLink(topic, .) }}
|
{{ link = post_topicLink(comms[i], topic, .) }}
|
||||||
<div class="text-black text-sm">
|
<div class="text-black text-sm">
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<strong>{{ p.Pseud | raw }}</strong>
|
<strong>{{ p.Pseud | raw }}</strong>
|
||||||
|
|||||||
+2
-2
@@ -26,7 +26,7 @@ import (
|
|||||||
"git.erbosoft.com/amy/amsterdam/ui"
|
"git.erbosoft.com/amy/amsterdam/ui"
|
||||||
"git.erbosoft.com/amy/amsterdam/util"
|
"git.erbosoft.com/amy/amsterdam/util"
|
||||||
"github.com/biter777/countries"
|
"github.com/biter777/countries"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v5"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -382,7 +382,7 @@ func ShowProfile(ctxt ui.AmContext) (string, any) {
|
|||||||
// Gather the info on the current user.
|
// Gather the info on the current user.
|
||||||
user, err := database.AmGetUserByName(ctxt.Ctx(), ctxt.URLParam("uname"), nil)
|
user, err := database.AmGetUserByName(ctxt.Ctx(), ctxt.URLParam("uname"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", echo.NewHTTPError(http.StatusNotFound).SetInternal(err)
|
return "error", echo.NewHTTPError(http.StatusNotFound, err.Error()).Wrap(err)
|
||||||
}
|
}
|
||||||
ci, err := user.ContactInfo(ctxt.Ctx())
|
ci, err := user.ContactInfo(ctxt.Ctx())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -172,17 +171,6 @@ func Map[A, B any](in []A, fn func(A) B) []B {
|
|||||||
return rc
|
return rc
|
||||||
}
|
}
|
||||||
|
|
||||||
// MyIPAddress returns the local IP address of this machine.
|
|
||||||
func MyIPAddress() (net.IP, error) {
|
|
||||||
conn, err := net.Dial("udp", "8.8.8.8:80")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
localAddr := conn.LocalAddr().(*net.UDPAddr)
|
|
||||||
return localAddr.IP, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IIF is an "immediate-if" function returning its second argument if the first one is true, the third one if not.
|
// IIF is an "immediate-if" function returning its second argument if the first one is true, the third one if not.
|
||||||
func IIF[A any](expr bool, v1, v2 A) A {
|
func IIF[A any](expr bool, v1, v2 A) A {
|
||||||
if expr {
|
if expr {
|
||||||
|
|||||||
Reference in New Issue
Block a user