Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6526c74ed2 | |||
| 10a347747e | |||
| 631215f6a0 | |||
| 759996b4cd | |||
| a068e17e65 | |||
| 57088e4680 | |||
| 17de55c5c2 | |||
| ea807cc55f | |||
| 14c6df9891 | |||
| 581319279a | |||
| 0e3c1a293e | |||
| 391d8ccc99 | |||
| 3deb11e0a5 | |||
| 1348d0225f | |||
| 70dcf82234 | |||
| 8c1fa9ebdc | |||
| 5a41509170 | |||
| 6a32db5adf | |||
| e55e02509b | |||
| c08b570457 | |||
| c8a331d90c | |||
| 056c02959a | |||
| 27bc2f033a | |||
| f4d1bb8f82 | |||
| d408eea48b | |||
| 3b298bf3fb |
@@ -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
|
||||
|
||||
# Ignore mac-specific files
|
||||
/.DS_Store
|
||||
.DS_Store
|
||||
|
||||
Vendored
+11
-1
@@ -11,6 +11,16 @@
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"name": "Blank Test",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}",
|
||||
"env": {
|
||||
"AMSTERDAM_DATABASE_NAME": "blank"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "EMinds Reborn",
|
||||
"type": "go",
|
||||
@@ -18,7 +28,7 @@
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}",
|
||||
"env": {
|
||||
"AMSTERDAM_CONFIG": "/u01/home/amy/Erbosoft/emreborn/amsterdam.yaml"
|
||||
"AMSTERDAM_CONFIG": "${env:HOME}/Erbosoft/emreborn/amsterdam.yaml"
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
@@ -114,14 +114,9 @@ in your `PATH`. Then run `go generate` to regenerate the CSS file before you run
|
||||
|
||||
## 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`
|
||||
|
||||
(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.
|
||||
Ensure a user in your database is granted all privileges to all tables in your new database.
|
||||
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:
|
||||
@@ -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).
|
||||
|
||||
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,
|
||||
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:
|
||||
|
||||
@@ -1,22 +1,10 @@
|
||||
# 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
|
||||
|
||||
* 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.
|
||||
* Database format migrations.
|
||||
* <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.</s>
|
||||
* ~~Database format migrations.~~
|
||||
* Allow use of Postgres as a database.
|
||||
* Dockerization.
|
||||
* 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?
|
||||
* 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
|
||||
|
||||
* 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.
|
||||
* Fix password storage. Straight SHA-1 hashes aren't gonna cut it. There are better ways.
|
||||
* Introduce OAuth authentication? (Related to above)
|
||||
|
||||
+23
-18
@@ -136,7 +136,7 @@ func Topics(ctxt ui.AmContext) (string, any) {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
traverser := ui.NewTopicTraverser(topics)
|
||||
ctxt.SetSession("topic.traverser", traverser)
|
||||
@@ -178,14 +178,13 @@ func Topics(ctxt ui.AmContext) (string, any) {
|
||||
* Data as a parameter for the command string.
|
||||
*/
|
||||
func NewTopicForm(ctxt ui.AmContext) (string, any) {
|
||||
comm := ctxt.CurrentCommunity()
|
||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
||||
if !conf.TestPermission("Conference.Create", myLevel) {
|
||||
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("urlStem", fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, ctxt.GetScratch("currentAlias")))
|
||||
ctxt.VarMap().Set("urlStem", ctxt.GetScratch("ConferenceLink").(string))
|
||||
ctxt.VarMap().Set("topicName", "")
|
||||
pseud, err := conf.DefaultPseud(ctxt.Ctx(), ctxt.CurrentUser())
|
||||
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")
|
||||
}
|
||||
|
||||
urlStem := fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, ctxt.GetScratch("currentAlias"))
|
||||
urlStem := ctxt.GetScratch("ConferenceLink").(string)
|
||||
if ctxt.FormFieldIsSet("cancel") {
|
||||
return "redirect", urlStem
|
||||
}
|
||||
@@ -247,7 +246,7 @@ func NewTopic(ctxt ui.AmContext) (string, any) {
|
||||
if err != nil {
|
||||
return "error", err
|
||||
}
|
||||
checker.SetContext("PostLinkDecoderContext", database.AmCreatePostLinkContext(comm.Alias, ctxt.URLParam("cid"), conf.TopTopic+1))
|
||||
checker.SetContext("PostLinkDecoderContext", database.AmCreatePostLinkContext(comm.Alias, comm.Id, ctxt.GetScratch("currentAlias").(string), conf.TopTopic+1))
|
||||
checker.Append(postdata)
|
||||
checker.Finish()
|
||||
v, _ = checker.Value()
|
||||
@@ -282,7 +281,7 @@ func NewTopic(ctxt ui.AmContext) (string, any) {
|
||||
if err != nil {
|
||||
return "error", err
|
||||
}
|
||||
checker.SetContext("PostLinkDecoderContext", database.AmCreatePostLinkContext(comm.Alias, ctxt.URLParam("cid"), 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.Finish()
|
||||
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.
|
||||
func templateOverrideLine(args jet.Arguments) reflect.Value {
|
||||
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 := ""
|
||||
if post.IsScribbled() {
|
||||
scr_date := ""
|
||||
@@ -402,7 +402,7 @@ func templateOverrideLine(args jet.Arguments) reflect.Value {
|
||||
} else {
|
||||
rc = fmt.Sprintf("<<<%v>>>", err)
|
||||
}
|
||||
} else if post.Hidden {
|
||||
} else if post.Hidden && !advanced {
|
||||
rc = fmt.Sprintf("(Hidden Message: %d Lines)", *post.LineCount)
|
||||
}
|
||||
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.
|
||||
func templateOverrideLink(args jet.Arguments) reflect.Value {
|
||||
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 := ""
|
||||
if post.Hidden {
|
||||
if post.Hidden && !advanced {
|
||||
rc = fmt.Sprintf("%s?r=%d&ac=1", root, post.Num)
|
||||
}
|
||||
return reflect.ValueOf(rc)
|
||||
@@ -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))
|
||||
ctxt.VarMap().Set("summaryLine", 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())
|
||||
plc.Community = comm.Alias
|
||||
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.LastPost = postRange[1]
|
||||
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)
|
||||
|
||||
// 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 {
|
||||
traverser.ClearTopic(topic.Number)
|
||||
nextTopic := traverser.NextTopic(topic.Number)
|
||||
@@ -686,7 +688,7 @@ func PostInTopic(ctxt ui.AmContext) (string, any) {
|
||||
topic := ctxt.GetScratch("currentTopic").(*database.Topic)
|
||||
ctxt.VarMap().Set("post_topic", topic)
|
||||
|
||||
urlStem := fmt.Sprintf("/comm/%s/conf/%s/r/%d", comm.Alias, ctxt.GetScratch("currentAlias"), topic.Number)
|
||||
urlStem := fmt.Sprintf("%s/r/%d", ctxt.GetScratch("ConferenceLink"), topic.Number)
|
||||
if ctxt.FormFieldIsSet("cancel") {
|
||||
return "redirect", urlStem
|
||||
}
|
||||
@@ -734,7 +736,7 @@ func PostInTopic(ctxt ui.AmContext) (string, any) {
|
||||
if err != nil {
|
||||
return "error", err
|
||||
}
|
||||
checker.SetContext("PostLinkDecoderContext", database.AmCreatePostLinkContext(comm.Alias, ctxt.URLParam("cid"), topic.Number))
|
||||
checker.SetContext("PostLinkDecoderContext", database.AmCreatePostLinkContext(comm.Alias, comm.Id, ctxt.GetScratch("currentAlias").(string), topic.Number))
|
||||
checker.Append(postdata)
|
||||
checker.Finish()
|
||||
v, _ = checker.Value()
|
||||
@@ -754,7 +756,7 @@ func PostInTopic(ctxt ui.AmContext) (string, any) {
|
||||
} else if ctxt.FormFieldIsSet("postnext") && len(urlNextTopic) > 0 {
|
||||
returnURL = urlNextTopic
|
||||
} else if ctxt.FormFieldIsSet("posttopics") {
|
||||
returnURL = fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, ctxt.GetScratch("currentAlias"))
|
||||
returnURL = ctxt.GetScratch("ConferenceLink").(string)
|
||||
} else {
|
||||
return "error", EBUTTON
|
||||
}
|
||||
@@ -772,10 +774,13 @@ func PostInTopic(ctxt ui.AmContext) (string, any) {
|
||||
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())
|
||||
plc.Community = comm.Alias
|
||||
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_getOverrideLink", templateOverrideLink)
|
||||
@@ -783,7 +788,6 @@ func PostInTopic(ctxt ui.AmContext) (string, any) {
|
||||
ctxt.VarMap().SetFunc("post_getUserName", templateExtractUserName)
|
||||
ctxt.VarMap().SetFunc("post_getAttachmentInfo", templateAttachmentInfo)
|
||||
ctxt.VarMap().SetFunc("post_isBozo", templateBozo)
|
||||
ctxt.VarMap().Set("post_stem", fmt.Sprintf("/comm/%s/conf/%s/r/%d", comm.Alias, ctxt.GetScratch("currentAlias"), topic.Number))
|
||||
ctxt.VarMap().Set("post_max", topic.TopMessage)
|
||||
ctxt.VarMap().Set("posts", posts)
|
||||
ctxt.VarMap().Set("topicName", topic.Name)
|
||||
@@ -805,7 +809,8 @@ func PostInTopic(ctxt ui.AmContext) (string, any) {
|
||||
if err != nil {
|
||||
return "error", err
|
||||
}
|
||||
checker.SetContext("PostLinkDecoderContext", database.AmCreatePostLinkContext(comm.Alias, ctxt.URLParam("cid"), topic.Number))
|
||||
checker.SetContext("PostLinkDecoderContext", database.AmCreatePostLinkContext(comm.Alias, comm.Id,
|
||||
ctxt.GetScratch("currentAlias").(string), topic.Number))
|
||||
checker.Append(ctxt.FormField("pb"))
|
||||
checker.Finish()
|
||||
postText, _ := checker.Value()
|
||||
|
||||
+29
-33
@@ -156,7 +156,7 @@ func ConfManage(ctxt ui.AmContext) (string, any) {
|
||||
comm := ctxt.CurrentCommunity()
|
||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||
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("urlStem", urlStem)
|
||||
|
||||
@@ -193,14 +193,13 @@ func ConfManage(ctxt ui.AmContext) (string, any) {
|
||||
* Data as a parameter for the command string.
|
||||
*/
|
||||
func SetPseud(ctxt ui.AmContext) (string, any) {
|
||||
comm := ctxt.CurrentCommunity()
|
||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||
pseud := ctxt.FormField("pseud")
|
||||
err := conf.SetDefaultPseud(ctxt.Ctx(), ctxt.CurrentUser(), pseud)
|
||||
if err != nil {
|
||||
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.
|
||||
@@ -211,13 +210,12 @@ func SetPseud(ctxt ui.AmContext) (string, any) {
|
||||
* Data as a parameter for the command string.
|
||||
*/
|
||||
func ConfFixseen(ctxt ui.AmContext) (string, any) {
|
||||
comm := ctxt.CurrentCommunity()
|
||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||
err := conf.Fixseen(ctxt.Ctx(), ctxt.CurrentUser())
|
||||
if err != nil {
|
||||
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..
|
||||
@@ -234,7 +232,7 @@ func AddToHotlist(ctxt ui.AmContext) (string, any) {
|
||||
if err != nil {
|
||||
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.
|
||||
@@ -254,7 +252,7 @@ func HideTopic(ctxt ui.AmContext) (string, any) {
|
||||
if err != nil {
|
||||
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.
|
||||
@@ -275,7 +273,7 @@ func FreezeTopic(ctxt ui.AmContext) (string, any) {
|
||||
if err != nil {
|
||||
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.
|
||||
@@ -296,7 +294,7 @@ func ArchiveTopic(ctxt ui.AmContext) (string, any) {
|
||||
if err != nil {
|
||||
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.
|
||||
@@ -317,7 +315,7 @@ func StickTopic(ctxt ui.AmContext) (string, any) {
|
||||
if err != nil {
|
||||
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.
|
||||
@@ -348,14 +346,14 @@ func DeleteTopic(ctxt ui.AmContext) (string, any) {
|
||||
if err != nil {
|
||||
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.
|
||||
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))
|
||||
mbox.SetLink("no", fmt.Sprintf("/comm/%s/conf/%s/r/%d", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias"), topic.Number))
|
||||
mbox.SetLink("yes", fmt.Sprintf("/comm/%s/conf/%s/op/%d/delete", 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("%s/op/%d/delete", ctxt.GetScratch("ConferenceLink"), topic.Number))
|
||||
return mbox.Render(ctxt)
|
||||
}
|
||||
|
||||
@@ -390,7 +388,7 @@ func HideMessage(ctxt ui.AmContext) (string, any) {
|
||||
if err != nil {
|
||||
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.
|
||||
@@ -424,7 +422,7 @@ func ScribbleMessage(ctxt ui.AmContext) (string, any) {
|
||||
if err != nil {
|
||||
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.
|
||||
@@ -466,11 +464,11 @@ func NukeMessage(ctxt ui.AmContext) (string, any) {
|
||||
if err != nil {
|
||||
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.
|
||||
link, err := hdrs[0].Link(ctxt.Ctx(), "community")
|
||||
link, err := hdrs[0].Link(ctxt.Ctx(), ctxt.CurrentCommunity().Id, "community")
|
||||
if err != nil {
|
||||
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>,
|
||||
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("yes", fmt.Sprintf("/comm/%s/conf/%s/op/%d/nuke/%d", 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("%s/op/%d/nuke/%d", ctxt.GetScratch("ConferenceLink"), topic.Number, hdrs[0].Num))
|
||||
return mbox.Render(ctxt)
|
||||
}
|
||||
|
||||
@@ -524,11 +522,11 @@ func PruneMessageAttachment(ctxt ui.AmContext) (string, any) {
|
||||
if err != nil {
|
||||
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.
|
||||
link, err := hdrs[0].Link(ctxt.Ctx(), "community")
|
||||
link, err := hdrs[0].Link(ctxt.Ctx(), ctxt.CurrentCommunity().Id, "community")
|
||||
if err != nil {
|
||||
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>,
|
||||
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("yes", fmt.Sprintf("/comm/%s/conf/%s/op/%d/prune/%d", 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("%s/op/%d/prune/%d", ctxt.GetScratch("ConferenceLink"), topic.Number, hdrs[0].Num))
|
||||
return mbox.Render(ctxt)
|
||||
}
|
||||
|
||||
@@ -591,7 +589,7 @@ func MoveMessageForm(ctxt ui.AmContext) (string, any) {
|
||||
|
||||
ctxt.VarMap().Set("post", hdrs[0])
|
||||
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.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 {
|
||||
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.
|
||||
@@ -654,7 +652,7 @@ func MoveMessage(ctxt ui.AmContext) (string, any) {
|
||||
return "error", EPOSTREF
|
||||
}
|
||||
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 {
|
||||
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.
|
||||
@@ -709,8 +707,8 @@ func MoveMessage(ctxt ui.AmContext) (string, any) {
|
||||
func TopicManage(ctxt ui.AmContext) (string, any) {
|
||||
comm := ctxt.CurrentCommunity()
|
||||
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))
|
||||
opsLink := fmt.Sprintf("/comm/%s/conf/%s/op/%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("%s/op/%d", ctxt.GetScratch("ConferenceLink"), topic.Number)
|
||||
ctxt.VarMap().Set("opsLink", opsLink)
|
||||
ctxt.VarMap().Set("topicName", topic.Name)
|
||||
|
||||
@@ -750,7 +748,6 @@ func TopicSetSubscribe(ctxt ui.AmContext) (string, any) {
|
||||
if ctxt.CurrentUser().IsAnon {
|
||||
return "error", ENOPERM
|
||||
}
|
||||
comm := ctxt.CurrentCommunity()
|
||||
topic := ctxt.GetScratch("currentTopic").(*database.Topic)
|
||||
flag, err := topic.IsSubscribed(ctxt.Ctx(), ctxt.CurrentUser())
|
||||
if err != nil {
|
||||
@@ -760,7 +757,7 @@ func TopicSetSubscribe(ctxt ui.AmContext) (string, any) {
|
||||
if err != nil {
|
||||
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.
|
||||
@@ -771,7 +768,6 @@ func TopicSetSubscribe(ctxt ui.AmContext) (string, any) {
|
||||
* Data as a parameter for the command string.
|
||||
*/
|
||||
func TopicRemoveBozo(ctxt ui.AmContext) (string, any) {
|
||||
comm := ctxt.CurrentCommunity()
|
||||
topic := ctxt.GetScratch("currentTopic").(*database.Topic)
|
||||
bozoUid, err := strconv.Atoi(ctxt.URLParam("uid"))
|
||||
if err != nil {
|
||||
@@ -781,5 +777,5 @@ func TopicRemoveBozo(ctxt ui.AmContext) (string, any) {
|
||||
if err != nil {
|
||||
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)
|
||||
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" {
|
||||
dlg.SetCommunity(comm)
|
||||
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 "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.
|
||||
@@ -148,8 +148,8 @@ func ConferenceAliasForm(ctxt ui.AmContext) (string, any) {
|
||||
|
||||
ctxt.VarMap().Set("newAlias", "")
|
||||
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("selfLink", fmt.Sprintf("/comm/%s/conf/%s/aliases", comm.Alias, ctxt.GetScratch("currentAlias")))
|
||||
ctxt.VarMap().Set("backLink", fmt.Sprintf("%s/manage", ctxt.GetScratch("ConferenceLink")))
|
||||
ctxt.VarMap().Set("selfLink", fmt.Sprintf("%s/aliases", ctxt.GetScratch("ConferenceLink")))
|
||||
ctxt.SetFrameTitle(fmt.Sprintf("Manage Conference Aliases: %s", conf.Name))
|
||||
|
||||
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 {
|
||||
return "error", err
|
||||
}
|
||||
@@ -184,8 +184,8 @@ func ConferenceAliasAdd(ctxt ui.AmContext) (string, any) {
|
||||
}
|
||||
|
||||
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("selfLink", fmt.Sprintf("/comm/%s/conf/%s/aliases", comm.Alias, ctxt.GetScratch("currentAlias")))
|
||||
ctxt.VarMap().Set("backLink", fmt.Sprintf("%s/manage", ctxt.GetScratch("ConferenceLink")))
|
||||
ctxt.VarMap().Set("selfLink", fmt.Sprintf("%s/aliases", ctxt.GetScratch("ConferenceLink")))
|
||||
ctxt.SetFrameTitle(fmt.Sprintf("Manage Conference Aliases: %s", conf.Name))
|
||||
|
||||
newAlias := ctxt.FormField("na")
|
||||
@@ -206,7 +206,7 @@ func ConferenceAliasAdd(ctxt ui.AmContext) (string, any) {
|
||||
ctxt.VarMap().Set("errorMessage", err.Error())
|
||||
}
|
||||
|
||||
aliases, err := conf.Aliases(ctxt.Ctx())
|
||||
aliases, err := conf.Aliases(ctxt.Ctx(), comm.Id)
|
||||
if err != nil {
|
||||
return "error", err
|
||||
}
|
||||
@@ -241,8 +241,8 @@ func ConferenceMembers(ctxt ui.AmContext) (string, any) {
|
||||
// Set the first batch of page variables.
|
||||
ctxt.VarMap().Set("commName", comm.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("selfLink", fmt.Sprintf("/comm/%s/conf/%s/members", comm.Alias, ctxt.GetScratch("currentAlias")))
|
||||
ctxt.VarMap().Set("backLink", fmt.Sprintf("%s/manage", ctxt.GetScratch("ConferenceLink")))
|
||||
ctxt.VarMap().Set("selfLink", fmt.Sprintf("%s/members", ctxt.GetScratch("ConferenceLink")))
|
||||
ctxt.VarMap().Set("roleList", database.AmRoleList("Conference.UserLevels"))
|
||||
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.
|
||||
*/
|
||||
func ConfCustomForm(ctxt ui.AmContext) (string, any) {
|
||||
comm := ctxt.CurrentCommunity()
|
||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
||||
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("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("bottomText", bottomBlock)
|
||||
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.
|
||||
*/
|
||||
func ConfCustom(ctxt ui.AmContext) (string, any) {
|
||||
comm := ctxt.CurrentCommunity()
|
||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
||||
if !conf.TestPermission("Conference.Change", myLevel) {
|
||||
@@ -429,7 +427,7 @@ func ConfCustom(ctxt ui.AmContext) (string, any) {
|
||||
if err != nil {
|
||||
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.
|
||||
@@ -440,7 +438,6 @@ func ConfCustom(ctxt ui.AmContext) (string, any) {
|
||||
* Data as a parameter for the command string.
|
||||
*/
|
||||
func ConfReports(ctxt ui.AmContext) (string, any) {
|
||||
comm := ctxt.CurrentCommunity()
|
||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
||||
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("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") {
|
||||
// generate a report
|
||||
@@ -504,7 +501,7 @@ func ConfReports(ctxt ui.AmContext) (string, any) {
|
||||
return "error", err
|
||||
}
|
||||
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))
|
||||
return "framed", "conf_reports.jet"
|
||||
}
|
||||
@@ -518,7 +515,6 @@ func ConfReports(ctxt ui.AmContext) (string, any) {
|
||||
* Data as a parameter for the command string.
|
||||
*/
|
||||
func ConferenceEmailForm(ctxt ui.AmContext) (string, any) {
|
||||
comm := ctxt.CurrentCommunity()
|
||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
||||
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("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("day", 7).Set("subj", "").Set("pb", "")
|
||||
ctxt.SetFrameTitle(fmt.Sprintf("Conference E-Mail: %s", conf.Name))
|
||||
@@ -555,7 +551,7 @@ func ConferenceEmail(ctxt ui.AmContext) (string, any) {
|
||||
|
||||
// Handle button presses.
|
||||
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") {
|
||||
return "error", EBUTTON
|
||||
}
|
||||
@@ -646,7 +642,7 @@ func ConferenceEmail(ctxt ui.AmContext) (string, any) {
|
||||
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.
|
||||
@@ -657,7 +653,6 @@ func ConferenceEmail(ctxt ui.AmContext) (string, any) {
|
||||
* Data as a parameter for the command string.
|
||||
*/
|
||||
func ConferenceExportForm(ctxt ui.AmContext) (string, any) {
|
||||
comm := ctxt.CurrentCommunity()
|
||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
||||
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("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))
|
||||
return "framed", "conf_export.jet"
|
||||
}
|
||||
@@ -684,7 +679,6 @@ func ConferenceExportForm(ctxt ui.AmContext) (string, any) {
|
||||
* Data as a parameter for the command string.
|
||||
*/
|
||||
func ConferenceExport(ctxt ui.AmContext) (string, any) {
|
||||
comm := ctxt.CurrentCommunity()
|
||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
||||
if !conf.TestPermission("Conference.Change", myLevel) {
|
||||
@@ -692,7 +686,7 @@ func ConferenceExport(ctxt ui.AmContext) (string, any) {
|
||||
}
|
||||
|
||||
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") {
|
||||
return "error", EBUTTON
|
||||
}
|
||||
@@ -765,15 +759,16 @@ func ConferenceImport(ctxt ui.AmContext) (string, any) {
|
||||
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" {
|
||||
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"
|
||||
}
|
||||
|
||||
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") {
|
||||
return "error", EBUTTON
|
||||
}
|
||||
@@ -786,27 +781,18 @@ func ConferenceImport(ctxt ui.AmContext) (string, any) {
|
||||
mode = exports.VCIFTopicMatchNum
|
||||
default:
|
||||
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"
|
||||
}
|
||||
|
||||
importData, err := ctxt.FormFile("idata")
|
||||
if err != nil {
|
||||
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"
|
||||
}
|
||||
start := time.Now()
|
||||
f, err := importData.Open()
|
||||
if err != nil {
|
||||
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"
|
||||
}
|
||||
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))
|
||||
if err != nil {
|
||||
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"
|
||||
}
|
||||
|
||||
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("scroll", scroll)
|
||||
ctxt.SetFrameTitle("Import Results")
|
||||
@@ -858,8 +841,8 @@ func DeleteConference(ctxt ui.AmContext) (string, any) {
|
||||
// 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>
|
||||
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("yes", fmt.Sprintf("/comm/%s/conf/%s/delete", ctxt.CurrentCommunity().Alias, ctxt.GetScratch("currentAlias")))
|
||||
mbox.SetLink("no", fmt.Sprintf("%s/manage", ctxt.GetScratch("ConferenceLink")))
|
||||
mbox.SetLink("yes", fmt.Sprintf("%s/delete", ctxt.GetScratch("ConferenceLink")))
|
||||
return mbox.Render(ctxt)
|
||||
}
|
||||
|
||||
@@ -901,9 +884,12 @@ func CreateConference(ctxt ui.AmContext) (string, any) {
|
||||
if err != nil {
|
||||
return "error", err
|
||||
}
|
||||
var urlbuf strings.Builder
|
||||
urlbuf.WriteString(ctxt.GetScratch("CommunityLink").(string))
|
||||
urlbuf.WriteString("/conf")
|
||||
button := dlg.WhichButton(ctxt)
|
||||
if button == "cancel" {
|
||||
return "redirect", fmt.Sprintf("/comm/%s/conf", comm.Alias)
|
||||
return "redirect", urlbuf.String()
|
||||
} else if button != "create" {
|
||||
dlg.SetCommunity(comm)
|
||||
return dlg.RenderError(ctxt, "invalid button pressed")
|
||||
@@ -917,7 +903,9 @@ func CreateConference(ctxt ui.AmContext) (string, any) {
|
||||
return dlg.RenderError(ctxt, err.Error())
|
||||
}
|
||||
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.
|
||||
|
||||
+2
-1
@@ -30,7 +30,7 @@ import (
|
||||
)
|
||||
|
||||
// AMSTERDAM_VERSION contains the version number of Amsterdam.
|
||||
const AMSTERDAM_VERSION = "0.1.0"
|
||||
const AMSTERDAM_VERSION = "0.2.0"
|
||||
|
||||
// AMSTERDAM_COPYRIGHT contains the copyright dates for Amsterdam.
|
||||
const AMSTERDAM_COPYRIGHT = "2025-2026"
|
||||
@@ -92,6 +92,7 @@ type AmConfig struct {
|
||||
Type string `yaml:"type"`
|
||||
} `yaml:"siteIcon"`
|
||||
SiteShortcutIcon string `yaml:"siteShortcutIcon"`
|
||||
SiteAppleIcon string `yaml:"siteAppleIcon"`
|
||||
SiteLogo string `yaml:"siteLogo"`
|
||||
TopRefresh int `yaml:"topRefresh"`
|
||||
LoginCookieName string `yaml:"loginCookieName"`
|
||||
|
||||
@@ -17,6 +17,7 @@ site:
|
||||
path: "/img/builtin/AmsterdamIcon32.png"
|
||||
type: "image/png"
|
||||
siteShortcutIcon: "/img/builtin/AmsterdamIcon32.ico"
|
||||
siteAppleIcon: "/img/builtin/AmsterdamAppleIcon.png"
|
||||
siteLogo: "/img/builtin/powered-by-amsterdam.png"
|
||||
topRefresh: 300
|
||||
loginCookieName: AmsterdamAuth
|
||||
|
||||
@@ -93,6 +93,7 @@ const (
|
||||
AuditAdminChangeUserAccount = 111
|
||||
AuditAdminSetAccountSecurity = 112
|
||||
AuditAdminLockUnlockAccount = 113
|
||||
AuditAdminSetUserName = 114
|
||||
AuditCommunityCreate = 201
|
||||
AuditCommunitySetMembership = 202
|
||||
AuditCommunityContactInfo = 203
|
||||
|
||||
@@ -44,6 +44,8 @@ auditReference:
|
||||
text: "Admin Set Account Security"
|
||||
- code: 113
|
||||
text: "Admin Lock/Unlock Account"
|
||||
- code: 114
|
||||
text: "Admin Set User Name"
|
||||
- code: 201
|
||||
text: "Create New Community"
|
||||
- code: 202
|
||||
|
||||
+187
-5
@@ -13,35 +13,217 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.erbosoft.com/amy/amsterdam/config"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"github.com/jmoiron/sqlx"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Error classifications
|
||||
const (
|
||||
classUnspecified = 0
|
||||
classNeedInstall = 1
|
||||
classNeedConvert = 2
|
||||
)
|
||||
|
||||
// MySQL Errors
|
||||
var errMySQLNoTable = &mysql.MySQLError{Number: 1146}
|
||||
var errMySQLNoColumn = &mysql.MySQLError{Number: 1054}
|
||||
|
||||
//go:embed mysql-install.sql
|
||||
var installScriptMySQL string
|
||||
|
||||
//go:embed mysql-convert.sql
|
||||
var convertScriptMySQL string
|
||||
|
||||
//go:embed mysql-migrate/*
|
||||
var migrationsMySQL embed.FS
|
||||
|
||||
// amdb is the reference to the Amsterdam database.
|
||||
var amdb *sqlx.DB
|
||||
|
||||
// buildMysqlDSN builds the MySQL DSN for the driver.
|
||||
func buildMysqlDSN() string {
|
||||
return fmt.Sprintf("%s:%s@tcp(%s)/%s?parseTime=true&loc=UTC",
|
||||
func buildMysqlDSN(multiStatement bool) string {
|
||||
rc := fmt.Sprintf("%s:%s@tcp(%s)/%s?parseTime=true&loc=UTC",
|
||||
config.GlobalComputedConfig.DatabaseUser,
|
||||
config.GlobalComputedConfig.DatabasePassword,
|
||||
config.GlobalComputedConfig.DatabaseHost,
|
||||
config.GlobalComputedConfig.DatabaseName)
|
||||
if multiStatement {
|
||||
rc += "&multiStatements=true"
|
||||
}
|
||||
return rc
|
||||
}
|
||||
|
||||
// classifyGetError classifies errors returns from the original get of the version number.
|
||||
func classifyGetError(err error) int {
|
||||
if errors.Is(err, errMySQLNoTable) {
|
||||
return classNeedInstall
|
||||
}
|
||||
if errors.Is(err, errMySQLNoColumn) {
|
||||
return classNeedConvert
|
||||
}
|
||||
return classUnspecified
|
||||
}
|
||||
|
||||
// databaseVersionNumber reads the version number from the database.
|
||||
func databaseVersionNumber(db *sqlx.DB) (string, error) {
|
||||
ver := ""
|
||||
err := db.Get(&ver, "SELECT version FROM globals")
|
||||
return ver, err
|
||||
}
|
||||
|
||||
// setDatabaseVersionNumber resets the version number in the database.
|
||||
func setDatabaseVersionNumber(db *sqlx.DB, version string) error {
|
||||
_, err := db.Exec("UPDATE globals SET version = ?", version)
|
||||
return err
|
||||
}
|
||||
|
||||
// databaseInstallScript returns the install script for the database.
|
||||
func databaseInstallScript() (string, error) {
|
||||
switch config.GlobalComputedConfig.DatabaseDriver {
|
||||
case "mysql":
|
||||
return installScriptMySQL, nil
|
||||
default:
|
||||
return "", fmt.Errorf("No install script for database driver: %s", config.GlobalComputedConfig.DatabaseDriver)
|
||||
}
|
||||
}
|
||||
|
||||
// databaseConvertScript returns the script to convert a Venice database to Amsterdam.
|
||||
func databaseConvertScript() (string, error) {
|
||||
switch config.GlobalComputedConfig.DatabaseDriver {
|
||||
case "mysql":
|
||||
return convertScriptMySQL, nil
|
||||
default: // N.B.: Not to be implemented for any database type besides MySQL!
|
||||
return "", fmt.Errorf("No conversion script for database driver: %s", config.GlobalComputedConfig.DatabaseDriver)
|
||||
}
|
||||
}
|
||||
|
||||
// databaseMigrationScripts returns the migration scripts to apply to the database.
|
||||
func databaseMigrationScripts(version string) (fs.FS, string, []string, error) {
|
||||
var myfs fs.FS
|
||||
var dirname string = ""
|
||||
var err error = nil
|
||||
switch config.GlobalComputedConfig.DatabaseDriver {
|
||||
case "mysql":
|
||||
myfs = migrationsMySQL
|
||||
dirname = "mysql-migrate"
|
||||
default:
|
||||
err = fmt.Errorf("No migration scripts for database driver: %s", config.GlobalComputedConfig.DatabaseDriver)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, "", make([]string, 0), err
|
||||
}
|
||||
rdfs := myfs.(fs.ReadDirFS)
|
||||
dents, err := rdfs.ReadDir(dirname)
|
||||
if err != nil {
|
||||
return nil, "", make([]string, 0), err
|
||||
}
|
||||
rc := make([]string, 0, len(dents))
|
||||
for _, d := range dents {
|
||||
s := strings.TrimSuffix(d.Name(), ".sql")
|
||||
m, err := regexp.Match(`\d{10}`, []byte(s))
|
||||
if err != nil {
|
||||
return nil, "", make([]string, 0), err
|
||||
}
|
||||
if m && s > version {
|
||||
rc = append(rc, d.Name())
|
||||
}
|
||||
}
|
||||
if len(rc) > 1 {
|
||||
slices.Sort(rc)
|
||||
}
|
||||
return myfs, dirname, rc, nil
|
||||
}
|
||||
|
||||
// prepareDB prepares the database if it's not yet been loaded.
|
||||
func prepareDB() (string, error) {
|
||||
dsn := buildMysqlDSN(true)
|
||||
log.Debugf("dsn=%s", dsn)
|
||||
db, err := sqlx.Connect(config.GlobalComputedConfig.DatabaseDriver, dsn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer db.Close()
|
||||
version, err := databaseVersionNumber(db)
|
||||
if err != nil {
|
||||
switch classifyGetError(err) {
|
||||
case classUnspecified:
|
||||
log.Errorf("*** cannot get version number: %v (%T)", err, err)
|
||||
return version, err
|
||||
case classNeedInstall:
|
||||
installScript, err := databaseInstallScript()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, err = db.Exec(installScript)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failure of install script: %w", err)
|
||||
}
|
||||
case classNeedConvert:
|
||||
convertScript, err := databaseConvertScript()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, err = db.Exec(convertScript)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failure of conversion script: %w", err)
|
||||
}
|
||||
}
|
||||
version, err = databaseVersionNumber(db)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
scriptfs, dirname, scripts, err := databaseMigrationScripts(version)
|
||||
if err == nil {
|
||||
log.Infof("%d migration script(s) to be applied", len(scripts))
|
||||
rffs := scriptfs.(fs.ReadFileFS)
|
||||
for _, script := range scripts {
|
||||
log.Infof("applying migration script: %s", script)
|
||||
var data []byte
|
||||
data, err = rffs.ReadFile(fmt.Sprintf("%s/%s", dirname, script))
|
||||
if err != nil {
|
||||
return version, fmt.Errorf("Unable to read migration script %s: %w", script, err)
|
||||
}
|
||||
_, err = db.Exec(string(data))
|
||||
if err != nil {
|
||||
return version, fmt.Errorf("Unable to apply migration script %s: %w", script, err)
|
||||
}
|
||||
nv := strings.TrimSuffix(script, ".sql")
|
||||
err = setDatabaseVersionNumber(db, nv)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
version = nv
|
||||
}
|
||||
}
|
||||
return version, err
|
||||
}
|
||||
|
||||
// SetupDb sets up the database and associated items.
|
||||
func SetupDb() (func(), error) {
|
||||
exitfns := make([]func(), 0, 2)
|
||||
db, err := sqlx.Connect(config.GlobalComputedConfig.DatabaseDriver, buildMysqlDSN())
|
||||
version, err := prepareDB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db, err := sqlx.Connect(config.GlobalComputedConfig.DatabaseDriver, buildMysqlDSN(false))
|
||||
if err == nil {
|
||||
amdb = db
|
||||
g, err := AmGlobals(context.Background())
|
||||
if err == nil {
|
||||
if g.Version != version {
|
||||
log.Warnf("!! database version %s does not match prepared version %s", g.Version, version)
|
||||
}
|
||||
setupAdCache()
|
||||
setupUserCache()
|
||||
setupContactsCache()
|
||||
@@ -53,8 +235,8 @@ func SetupDb() (func(), error) {
|
||||
log.Infof("SetupDb(): database version %s", g.Version)
|
||||
}
|
||||
}
|
||||
slices.Reverse(exitfns)
|
||||
return func() {
|
||||
slices.Reverse(exitfns)
|
||||
for _, f := range exitfns {
|
||||
f()
|
||||
}
|
||||
|
||||
+54
-59
@@ -17,6 +17,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -152,8 +153,8 @@ func (cs *ConferenceSettings) Save(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// Aliases returns the list of aliases for this conference.
|
||||
func (c *Conference) Aliases(ctx context.Context) ([]string, error) {
|
||||
rs, err := amdb.QueryContext(ctx, "SELECT alias FROM confalias WHERE confid = ? ORDER BY alias", c.ConfId)
|
||||
func (c *Conference) Aliases(ctx context.Context, commid int32) ([]string, error) {
|
||||
rs, err := amdb.QueryContext(ctx, "SELECT alias FROM confalias WHERE commid = ? AND confid = ? ORDER BY alias", commid, c.ConfId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -169,25 +170,22 @@ func (c *Conference) Aliases(ctx context.Context) ([]string, error) {
|
||||
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.
|
||||
func (c *Conference) AddAlias(ctx context.Context, alias string, u *User, comm *Community, ipaddr string) error {
|
||||
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 {
|
||||
return fmt.Errorf("the alias '%s' is already in use by another conference", alias)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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)))
|
||||
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.
|
||||
func (c *Conference) RemoveAlias(ctx context.Context, alias string, u *User, comm *Community, ipaddr string) error {
|
||||
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
|
||||
}
|
||||
|
||||
if aliasCount == 1 {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@@ -221,6 +221,7 @@ func (c *Conference) RemoveAlias(ctx context.Context, alias string, u *User, com
|
||||
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)))
|
||||
return nil
|
||||
}
|
||||
@@ -431,8 +432,8 @@ func (c *Conference) Settings(ctx context.Context, u *User) (*ConferenceSettings
|
||||
}
|
||||
|
||||
// Link returns a link string to this conference.
|
||||
func (c *Conference) Link(ctx context.Context, scope string) (string, error) {
|
||||
aliases, err := c.Aliases(ctx)
|
||||
func (c *Conference) Link(ctx context.Context, commid int32, scope string) (string, error) {
|
||||
aliases, err := c.Aliases(ctx, commid)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -440,9 +441,9 @@ func (c *Conference) Link(ctx context.Context, scope string) (string, error) {
|
||||
return fmt.Sprintf("%s.", aliases[0]), nil
|
||||
}
|
||||
if scope == "global" {
|
||||
comms, err := c.ContainedBy(ctx)
|
||||
comm, err := AmGetCommunity(ctx, commid)
|
||||
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
|
||||
}
|
||||
@@ -872,22 +873,22 @@ func (c *Conference) Delete(ctx context.Context, comm *Community, u *User, ipadd
|
||||
|
||||
// any references to conference other than this community?
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
var err error
|
||||
if refCount == 0 {
|
||||
// We have to delete all the conference core data now.
|
||||
_, 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 {
|
||||
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
|
||||
conferenceCache.Remove(c.ConfId)
|
||||
|
||||
// simpler to just nuke the entire alias map
|
||||
conferenceAliasMap.Clear()
|
||||
|
||||
// add an audit record
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
if _, err = tx.ExecContext(ctx, "DELETE FROM confalias WHERE commid = ? AND confid = ?", commid, confid); err != nil {
|
||||
return err
|
||||
}
|
||||
if refCount > 0 {
|
||||
confids[i] = -1
|
||||
continue // done with this conference
|
||||
}
|
||||
// We have to delete all the conference core data now.
|
||||
_, err = tx.ExecContext(ctx, "DELETE FROM confs WHERE confid = ?", confid)
|
||||
if err == nil {
|
||||
_, err = tx.ExecContext(ctx, "DELETE FROM confalias WHERE confid = ?", confid)
|
||||
}
|
||||
if err != nil {
|
||||
if _, err = tx.ExecContext(ctx, "DELETE FROM confs WHERE confid = ?", confid); err != nil {
|
||||
return err
|
||||
}
|
||||
// kick the conference out of the cache
|
||||
@@ -973,7 +976,8 @@ func (*conferenceServiceVTable) OnDeleteCommunity(ctx context.Context, tx *sqlx.
|
||||
getConferenceMutex.Unlock()
|
||||
}
|
||||
|
||||
// Just dump the whole conference property cache.
|
||||
// Just dump the whole conference alias map and property cache.
|
||||
conferenceAliasMap.Clear()
|
||||
getConferencePropMutex.Lock()
|
||||
conferencePropCache.Purge()
|
||||
getConferencePropMutex.Unlock()
|
||||
@@ -1030,27 +1034,38 @@ func AmGetConference(ctx context.Context, id int32) (*Conference, error) {
|
||||
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.
|
||||
* Parameters:
|
||||
* ctx - Standard Go context value.
|
||||
*. commid - Community ID to look under.
|
||||
* alias - The alias to look up.
|
||||
* Returns:
|
||||
* Pointer to the conference, or nil.
|
||||
* 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
|
||||
xconf, ok := conferenceAliasMap.Load(alias)
|
||||
key := confAliasMapKey(commid, alias)
|
||||
xconf, ok := conferenceAliasMap.Load(key)
|
||||
if ok {
|
||||
confid = xconf.(int32)
|
||||
} 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 {
|
||||
return nil, fmt.Errorf("alias not found: %s", alias)
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conferenceAliasMap.Store(alias, confid)
|
||||
conferenceAliasMap.Store(key, confid)
|
||||
}
|
||||
return AmGetConference(ctx, confid)
|
||||
}
|
||||
@@ -1074,28 +1089,6 @@ func AmGetConferenceContainingPost(ctx context.Context, postId int64) (*Conferen
|
||||
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.
|
||||
* Parameters:
|
||||
* ctx - Standard Go context value.
|
||||
@@ -1125,7 +1118,7 @@ func AmListConferences(ctx context.Context, cid int32, showHidden bool) ([]*Conf
|
||||
}
|
||||
}
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1271,7 +1264,7 @@ func AmCreateConference(ctx context.Context, comm *Community, name, alias, descr
|
||||
|
||||
// Ensure the alias is not in use.
|
||||
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 {
|
||||
return nil, fmt.Errorf("the alias '%s' is already in use by a different conference", alias)
|
||||
} 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.
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1329,6 +1323,7 @@ func AmCreateConference(ctx context.Context, comm *Community, name, alias, descr
|
||||
|
||||
// Add the new conference to the cache.
|
||||
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.
|
||||
fcomm, err := comm.Flags(ctx)
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# MySQL script for migrating a Venice database to the Amsterdam schema.
|
||||
# Written by Amy Bowersox <amy@erbosoft.com>
|
||||
#---------------------------------------------------------------------------
|
||||
# Amsterdam Web Communities System
|
||||
# Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
||||
#
|
||||
@@ -10,7 +7,6 @@
|
||||
#
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
#
|
||||
|
||||
ALTER TABLE globals RENAME COLUMN max_sig_mbr_page TO max_comm_mbr_page;
|
||||
ALTER TABLE globals RENAME COLUMN sig_create_lvl TO comm_create_lvl;
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
# MySQL script for initializing the Amsterdam database (contents only).
|
||||
# Written by Amy Bowersox <amy@erbosoft.com>
|
||||
#---------------------------------------------------------------------------
|
||||
# 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
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@@ -10,14 +7,6 @@
|
||||
#
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
# Table Creation
|
||||
##############################################################################
|
||||
|
||||
# The global parameters table. This is used for stuff that a Venice admin would be
|
||||
# likely to edit "on the fly." Stuff that can only be updated with a shutdown should go
|
||||
# in the XML config file. This table has ONLY ONE ROW!
|
||||
CREATE TABLE globals (
|
||||
version CHAR(10) NOT NULL,
|
||||
posts_per_page INT NOT NULL,
|
||||
@@ -30,14 +19,11 @@ CREATE TABLE globals (
|
||||
comm_create_lvl INT NOT NULL
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# The global properties table. The "ndx" parameter is used to indicate what
|
||||
# element is being loaded, and then the "data" element is parsed.
|
||||
CREATE TABLE propglobal (
|
||||
ndx INT NOT NULL PRIMARY KEY,
|
||||
data VARCHAR(255)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# The audit records table. Most "major" events add a record to this table.
|
||||
CREATE TABLE audit (
|
||||
record BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
on_date DATETIME NOT NULL,
|
||||
@@ -53,7 +39,6 @@ CREATE TABLE audit (
|
||||
INDEX comm_view (commid, on_date)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# The user information table.
|
||||
CREATE TABLE users (
|
||||
uid INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
username VARCHAR(64) NOT NULL,
|
||||
@@ -74,15 +59,12 @@ CREATE TABLE users (
|
||||
UNIQUE INDEX username_x (username)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# User preferences table.
|
||||
CREATE TABLE userprefs (
|
||||
uid INT NOT NULL PRIMARY KEY,
|
||||
tzid VARCHAR(64) DEFAULT 'UTC',
|
||||
localeid VARCHAR(64) DEFAULT 'en_US'
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# The per-user properties table. The "ndx" parameter is used to indicate what
|
||||
# element is being loaded, and then the "data" element is parsed.
|
||||
CREATE TABLE propuser (
|
||||
uid INT NOT NULL,
|
||||
ndx INT NOT NULL,
|
||||
@@ -90,7 +72,6 @@ CREATE TABLE propuser (
|
||||
PRIMARY KEY (uid, ndx)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# Indicates what the top-level "sidebox" configuration is for any given user.
|
||||
CREATE TABLE sideboxes (
|
||||
uid INT NOT NULL,
|
||||
boxid INT NOT NULL,
|
||||
@@ -100,7 +81,6 @@ CREATE TABLE sideboxes (
|
||||
INDEX inorder (uid, sequence)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# The contact information table. This is used for both users and communities.
|
||||
CREATE TABLE contacts (
|
||||
contactid INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
given_name VARCHAR(64),
|
||||
@@ -130,14 +110,12 @@ CREATE TABLE contacts (
|
||||
lastupdate DATETIME
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# A table listing email addresses which are barred from registering.
|
||||
CREATE TABLE emailban (
|
||||
address VARCHAR(255) NOT NULL PRIMARY KEY,
|
||||
by_uid INT,
|
||||
on_date DATETIME
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# The community table.
|
||||
CREATE TABLE communities (
|
||||
commid INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
createdate DATETIME NOT NULL,
|
||||
@@ -168,7 +146,6 @@ CREATE TABLE communities (
|
||||
INDEX list_alpha (catid, commname)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# The table mapping category IDs to category names.
|
||||
CREATE TABLE refcategory (
|
||||
catid INT NOT NULL PRIMARY KEY,
|
||||
parent INT NOT NULL,
|
||||
@@ -179,14 +156,12 @@ CREATE TABLE refcategory (
|
||||
UNIQUE INDEX display (parent, name)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# The table mapping communities and their associated features.
|
||||
CREATE TABLE commftrs (
|
||||
commid INT NOT NULL,
|
||||
ftr_code SMALLINT NOT NULL,
|
||||
PRIMARY KEY (commid, ftr_code)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# The table mapping members of a community and their access levels.
|
||||
CREATE TABLE commmember (
|
||||
commid INT NOT NULL,
|
||||
uid INT NOT NULL,
|
||||
@@ -196,7 +171,6 @@ CREATE TABLE commmember (
|
||||
PRIMARY KEY (commid, uid)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# A table listing users which have been banned from joining a community.
|
||||
CREATE TABLE commban (
|
||||
commid INT NOT NULL,
|
||||
uid INT NOT NULL,
|
||||
@@ -205,8 +179,6 @@ CREATE TABLE commban (
|
||||
PRIMARY KEY (commid, uid)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# The community properties table. The "index" parameter is used to indicate what
|
||||
# element is being loaded, and then the "data" element is parsed.
|
||||
CREATE TABLE propcomm (
|
||||
cid INT NOT NULL,
|
||||
ndx INT NOT NULL,
|
||||
@@ -214,8 +186,6 @@ CREATE TABLE propcomm (
|
||||
PRIMARY KEY (cid, ndx)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# The table describing conferences. Like original CW, confs may be linked to more
|
||||
# than one community.
|
||||
CREATE TABLE confs (
|
||||
confid INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
createdate DATETIME NOT NULL,
|
||||
@@ -235,7 +205,6 @@ CREATE TABLE confs (
|
||||
INDEX name_x (name)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# The table that links communities to conferences.
|
||||
CREATE TABLE commtoconf (
|
||||
commid INT NOT NULL,
|
||||
confid INT NOT NULL,
|
||||
@@ -246,15 +215,12 @@ CREATE TABLE commtoconf (
|
||||
INDEX display_ord (commid, sequence)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# The table listing "aliases" to a conference for post-linking purposes.
|
||||
CREATE TABLE confalias (
|
||||
confid INT NOT NULL,
|
||||
alias VARCHAR(64) NOT NULL PRIMARY KEY,
|
||||
INDEX confid_x (confid)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# A "membership" table for conferences used to control access to private conferences
|
||||
# and grant conference hosting powers.
|
||||
CREATE TABLE confmember (
|
||||
confid INT NOT NULL,
|
||||
uid INT NOT NULL,
|
||||
@@ -262,7 +228,6 @@ CREATE TABLE confmember (
|
||||
PRIMARY KEY (confid, uid)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# Holds "saved settings" for a user with respect to a conference.
|
||||
CREATE TABLE confsettings (
|
||||
confid INT NOT NULL,
|
||||
uid INT NOT NULL,
|
||||
@@ -272,7 +237,6 @@ CREATE TABLE confsettings (
|
||||
PRIMARY KEY (confid, uid)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# The "hot list" of conferences for a given user.
|
||||
CREATE TABLE confhotlist (
|
||||
uid INT NOT NULL,
|
||||
sequence SMALLINT NOT NULL,
|
||||
@@ -282,8 +246,6 @@ CREATE TABLE confhotlist (
|
||||
INDEX inorder (uid, sequence)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# The conference properties table. The "index" parameter is used to indicate what
|
||||
# element is being loaded, and then the "data" element is parsed.
|
||||
CREATE TABLE propconf (
|
||||
confid INT NOT NULL,
|
||||
ndx INT NOT NULL,
|
||||
@@ -291,15 +253,12 @@ CREATE TABLE propconf (
|
||||
PRIMARY KEY (confid, ndx)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# The conference custom HTML block table. There are two custom blocks, one at the
|
||||
# top of the page, one at the bottom, each a maximum of 64K in length.
|
||||
CREATE TABLE confcustom (
|
||||
confid INT NOT NULL PRIMARY KEY,
|
||||
htmltop TEXT,
|
||||
htmlbottom TEXT
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# The table describing topics within a conference.
|
||||
CREATE TABLE topics (
|
||||
topicid INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
confid INT NOT NULL,
|
||||
@@ -317,7 +276,6 @@ CREATE TABLE topics (
|
||||
INDEX by_date (confid, lastupdate)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# Holds "saved settings" for a user with respect to a topic.
|
||||
CREATE TABLE topicsettings (
|
||||
topicid INT NOT NULL,
|
||||
uid INT NOT NULL,
|
||||
@@ -329,8 +287,6 @@ CREATE TABLE topicsettings (
|
||||
PRIMARY KEY (topicid, uid)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# The "bozo filter" list for a topic, for use by users in filtering out
|
||||
# the rantings of other users who are bozos.
|
||||
CREATE TABLE topicbozo (
|
||||
topicid INT NOT NULL,
|
||||
uid INT NOT NULL,
|
||||
@@ -338,7 +294,6 @@ CREATE TABLE topicbozo (
|
||||
PRIMARY KEY (topicid, uid, bozo_uid)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# The "header" for a posted message.
|
||||
CREATE TABLE posts (
|
||||
postid BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
parent BIGINT NOT NULL DEFAULT 0,
|
||||
@@ -356,14 +311,12 @@ CREATE TABLE posts (
|
||||
INDEX child_order (parent, num)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# The actual message text.
|
||||
CREATE TABLE postdata (
|
||||
postid BIGINT NOT NULL PRIMARY KEY,
|
||||
data MEDIUMTEXT,
|
||||
FULLTEXT INDEX searchpost (data)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# Message attachment.
|
||||
CREATE TABLE postattach (
|
||||
postid BIGINT NOT NULL PRIMARY KEY,
|
||||
datalen INT,
|
||||
@@ -376,14 +329,12 @@ CREATE TABLE postattach (
|
||||
data MEDIUMBLOB
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# "Bookmark" table for posts we like.
|
||||
CREATE TABLE postdogear (
|
||||
uid INT NOT NULL,
|
||||
postid BIGINT NOT NULL,
|
||||
PRIMARY KEY (uid, postid)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# "Front page" publishing table.
|
||||
CREATE TABLE postpublish (
|
||||
commid INT NOT NULL,
|
||||
postid BIGINT NOT NULL PRIMARY KEY,
|
||||
@@ -392,7 +343,6 @@ CREATE TABLE postpublish (
|
||||
INDEX display_order (on_date, postid)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# Advertisement (actually quote, for now) banners
|
||||
CREATE TABLE adverts (
|
||||
adid INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
imagepath VARCHAR(255) NOT NULL,
|
||||
@@ -401,7 +351,6 @@ CREATE TABLE adverts (
|
||||
linkurl VARCHAR(255)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# Storage space for uploaded images.
|
||||
CREATE TABLE imagestore (
|
||||
imgid INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
typecode SMALLINT DEFAULT 0,
|
||||
@@ -411,7 +360,6 @@ CREATE TABLE imagestore (
|
||||
data MEDIUMBLOB
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
# Table listing IP addresses that are banned from logging in or registering.
|
||||
CREATE TABLE ipban (
|
||||
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
address_lo BIGINT UNSIGNED NOT NULL,
|
||||
@@ -427,13 +375,7 @@ CREATE TABLE ipban (
|
||||
INDEX by_date (block_on)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
|
||||
##############################################################################
|
||||
# Constant Data Population
|
||||
##############################################################################
|
||||
|
||||
# Populate the Category table.
|
||||
# Source: Mozilla Open Directory Project categorization system <http://dmoz.org>;
|
||||
# additional categorization from WebbMe categories
|
||||
INSERT INTO refcategory (catid, parent, symlink, name) VALUES
|
||||
(0, -1, -1, 'Unclassified'),
|
||||
(1, -1, -1, 'Arts'),
|
||||
@@ -801,10 +743,8 @@ INSERT INTO refcategory (catid, parent, symlink, name) VALUES
|
||||
(15, -1, -1, 'System');
|
||||
### -- LAST IS 363 -- ###
|
||||
|
||||
# Make sure the special "System" category is hidden.
|
||||
UPDATE refcategory SET hide_dir = 1, hide_search = 1 WHERE catid = 15;
|
||||
|
||||
# Create the initial advertisements (quotes).
|
||||
INSERT INTO adverts (imagepath) VALUES
|
||||
('images/ads/Brown.gif'),
|
||||
('images/ads/Caine.gif'),
|
||||
@@ -831,22 +771,14 @@ INSERT INTO adverts (imagepath) VALUES
|
||||
('images/ads/wonka.gif'),
|
||||
('images/ads/worf.gif');
|
||||
|
||||
##############################################################################
|
||||
# Database Initialization
|
||||
##############################################################################
|
||||
|
||||
# Initialize the system globals table.
|
||||
INSERT INTO globals (version, posts_per_page, old_posts_at_top, max_search_page, max_comm_mbr_page, max_conf_mbr_page,
|
||||
fp_posts, num_audit_page, comm_create_lvl)
|
||||
VALUES ('2026030501', 20, 2, 20, 50, 50, 10, 100, 1000);
|
||||
|
||||
# Initialize the global properies table.
|
||||
INSERT INTO propglobal (ndx, data)
|
||||
VALUES (0, '');
|
||||
|
||||
# Add the 'Anonymous Honyak' user to the users table.
|
||||
# (Do 'SELECT * FROM users WHERE is_anon = 1' to retrieve the AC user details.)
|
||||
# (UID = 1, CONTACTID = 1)
|
||||
INSERT INTO users (uid, username, passhash, contactid, is_anon, verify_email, base_lvl, created)
|
||||
VALUES (1, 'Anonymous_Honyak', '', 1, 1, 1, 100, '2000-12-01 00:00:00');
|
||||
INSERT INTO userprefs (uid) VALUES (1);
|
||||
@@ -854,15 +786,11 @@ INSERT INTO propuser (uid, ndx, data) VALUES (1, 0, '');
|
||||
INSERT INTO contacts (contactid, given_name, family_name, locality, region, pcode, country, email, owner_uid)
|
||||
VALUES (1, 'Anonymous', 'User', 'Anywhere', '', '', 'US', 'nobody@example.com', 1);
|
||||
|
||||
# Provide the default view for Anonymous Honyak. This view will be copied to all
|
||||
# new users.
|
||||
INSERT INTO sideboxes (uid, boxid, sequence, param)
|
||||
VALUES (1, 1, 100, NULL), (1, 2, 200, NULL), (1, 3, 300, NULL);
|
||||
INSERT INTO confhotlist (uid, sequence, commid, confid)
|
||||
VALUES (1, 100, 2, 2);
|
||||
|
||||
# Add the 'Administrator' user to the users table.
|
||||
# (UID = 2, CONTACTID = 2)
|
||||
INSERT INTO users (uid, username, passhash, contactid, verify_email, base_lvl, created)
|
||||
VALUES (2, 'Administrator', '', 2, 1, 64999, '2000-12-01 00:00:00');
|
||||
INSERT INTO userprefs (uid) VALUES (2);
|
||||
@@ -870,35 +798,27 @@ INSERT INTO propuser (uid, ndx, data) VALUES (2, 0, '');
|
||||
INSERT INTO contacts (contactid, given_name, family_name, locality, region, pcode, country, email, owner_uid)
|
||||
VALUES (2, 'System', 'Administrator', 'Anywhere', '', '', 'US', 'root@your.box.com', 2);
|
||||
|
||||
# Create the default view for Administrator.
|
||||
INSERT INTO sideboxes (uid, boxid, sequence, param)
|
||||
VALUES (2, 1, 100, NULL), (2, 2, 200, NULL), (2, 3, 300, NULL);
|
||||
INSERT INTO confhotlist (uid, sequence, commid, confid)
|
||||
VALUES (2, 100, 2, 2);
|
||||
|
||||
# Add the administration community to the communities table.
|
||||
# (COMMID = 1, CONTACTID = 3)
|
||||
INSERT INTO communities (commid, createdate, read_lvl, write_lvl, create_lvl, delete_lvl, join_lvl, contactid,
|
||||
host_uid, catid, hide_dir, hide_search, membersonly, is_admin, init_ftr,
|
||||
commname, language, synopsis, rules, joinkey, alias)
|
||||
VALUES (1, '2000-12-01 00:00:00', 63000, 63000, 63000, 65500, 63000, 3, 2, 15, 1, 1, 1, 1, 0,
|
||||
'Administration', 'en-US', 'Administrative Community', 'Administrators only!', '',
|
||||
'Admin');
|
||||
'Admin');
|
||||
INSERT INTO contacts (contactid, locality, country, owner_uid, owner_commid)
|
||||
VALUES (3, 'Anywhere', 'US', 2, 1);
|
||||
INSERT INTO propcomm (cid, ndx, data) VALUES (1, 0, '');
|
||||
|
||||
# Insert the desired features for the 'Administration' community.
|
||||
INSERT INTO commftrs (commid, ftr_code)
|
||||
VALUES (1, 0), (1, 1), (1, 2), (1, 3), (1, 4);
|
||||
|
||||
# Make the 'Administrator' user the host of the 'Administration' community. Also, the Administrator
|
||||
# cannot unjoin the community.
|
||||
INSERT INTO commmember (commid, uid, granted_lvl, locked)
|
||||
VALUES (1, 2, 58500, 1);
|
||||
|
||||
# Insert the "Administrative Notes" conference into the Administration community.
|
||||
# (CONFID = 1)
|
||||
INSERT INTO confs (confid, createdate, read_lvl, post_lvl, create_lvl, hide_lvl, nuke_lvl, change_lvl,
|
||||
delete_lvl, top_topic, name, descr)
|
||||
VALUES (1, '2000-12-01 00:00:00', 63000, 63000, 63000, 63000, 64999, 64999, 65500, 0,
|
||||
@@ -907,12 +827,8 @@ INSERT INTO commtoconf (commid, confid, sequence) VALUES (1, 1, 10);
|
||||
INSERT INTO confalias (confid, alias) VALUES (1, 'Admin_Notes');
|
||||
INSERT INTO propconf (confid, ndx, data) VALUES (1, 0, '');
|
||||
|
||||
# Make the Administrator the host-of-record of the "Administrative Notes" conference.
|
||||
INSERT INTO confmember (confid, uid, granted_lvl) VALUES (1, 2, 52500);
|
||||
|
||||
# Add the 'Coffeehouse' community. This is the equivalent of the old CommunityWare
|
||||
# 'Universal Community.'
|
||||
# (COMMID = 2, CONTACTID = 4)
|
||||
INSERT INTO communities (commid, createdate, read_lvl, write_lvl, create_lvl, delete_lvl, join_lvl, contactid,
|
||||
host_uid, catid, membersonly, init_ftr, commname, language, synopsis, rules, alias)
|
||||
VALUES (2, '2000-12-01 00:00:00', 100, 58000, 58000, 65500, 500, 4, 2, 0, 1, 0, 'Coffeehouse', 'en-US',
|
||||
@@ -922,22 +838,15 @@ INSERT INTO contacts (contactid, locality, country, owner_uid, owner_commid)
|
||||
VALUES (4, 'Anywhere', 'US', 2, 2);
|
||||
INSERT INTO propcomm (cid, ndx, data) VALUES (2, 0, '');
|
||||
|
||||
# Insert the desired features for Coffeehouse.
|
||||
INSERT INTO commftrs (commid, ftr_code)
|
||||
VALUES (2, 0), (2, 1), (2, 3), (2, 4);
|
||||
|
||||
# Make 'Anonymous Honyak' a member of Coffeehouse. This is important because new users will
|
||||
# have the membership list of Anonymous Honyak copied to their account on signup (but with
|
||||
# the 'member' access level).
|
||||
INSERT INTO commmember (commid, uid, granted_lvl, locked, hidden)
|
||||
VALUES (2, 1, 100, 1, 1);
|
||||
|
||||
# Make the 'Administrator' user the host of Coffeehouse.
|
||||
INSERT INTO commmember (commid, uid, granted_lvl, locked)
|
||||
VALUES (2, 2, 58500, 1);
|
||||
|
||||
# Insert the "General Discussion" conference into Coffeehouse.
|
||||
# (CONFID = 2)
|
||||
INSERT INTO confs (confid, createdate, read_lvl, post_lvl, create_lvl, hide_lvl, nuke_lvl, change_lvl,
|
||||
delete_lvl, top_topic, name, descr)
|
||||
VALUES (2, '2000-12-01 00:00:00', 6500, 6500, 6500, 52500, 52500, 52500, 58000, 0, 'General Discussion',
|
||||
@@ -946,11 +855,8 @@ INSERT INTO commtoconf (commid, confid, sequence) VALUES (2, 2, 10);
|
||||
INSERT INTO confalias (confid, alias) VALUES (2, 'General');
|
||||
INSERT INTO propconf (confid, ndx, data) VALUES (2, 0, '');
|
||||
|
||||
# Make the Administrator the host-of-record of the "General Discussion" conference.
|
||||
INSERT INTO confmember (confid, uid, granted_lvl) VALUES (2, 2, 52500);
|
||||
|
||||
# Insert the "Test Postings" conference into Coffeehouse.
|
||||
# (CONFID = 3)
|
||||
INSERT INTO confs (confid, createdate, read_lvl, post_lvl, create_lvl, hide_lvl, nuke_lvl, change_lvl,
|
||||
delete_lvl, top_topic, name, descr)
|
||||
VALUES (3, '2000-12-01 00:00:00', 6500, 6500, 6500, 52500, 52500, 52500, 58000, 0, 'Test Postings',
|
||||
@@ -959,5 +865,4 @@ INSERT INTO commtoconf (commid, confid, sequence) VALUES (2, 3, 20);
|
||||
INSERT INTO confalias (confid, alias) VALUES (3, 'Test');
|
||||
INSERT INTO propconf (confid, ndx, data) VALUES (3, 0, '');
|
||||
|
||||
# Make the Administrator the host-of-record of the "Test Postings" conference.
|
||||
INSERT INTO confmember (confid, uid, granted_lvl) VALUES (3, 2, 52500);
|
||||
@@ -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;
|
||||
@@ -0,0 +1,2 @@
|
||||
Migration scripts for MySQL go in this directory. Name them as YYYYMMDDNN.sql, where YYYYMMDD is the
|
||||
current date and NN is a two-digit sequence number beginning at 01.
|
||||
+24
-16
@@ -253,7 +253,7 @@ func (p *PostHeader) Text(ctx context.Context) (string, error) {
|
||||
}
|
||||
|
||||
// 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" {
|
||||
return fmt.Sprintf("%d", p.Num), nil
|
||||
}
|
||||
@@ -262,7 +262,7 @@ func (p *PostHeader) Link(ctx context.Context, scope string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
parent, err := topic.Link(ctx, scope)
|
||||
parent, err := topic.Link(ctx, commid, scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -628,47 +628,55 @@ func AmNewPost(ctx context.Context, conf *Conference, topic *Topic, user *User,
|
||||
* Array of post headers, or nil.
|
||||
* Standard Go error status.
|
||||
*/
|
||||
func AmGetPublishedPosts(ctx context.Context) ([]*PostHeader, error) {
|
||||
func AmGetPublishedPosts(ctx context.Context) ([]*PostHeader, []*Community, error) {
|
||||
// Read the globals.
|
||||
gv, err := AmGlobals(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
// 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 {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
// Extract post IDs to an array.
|
||||
cids := make([]int32, gv.FrontPagePosts)
|
||||
pids := make([]int64, gv.FrontPagePosts)
|
||||
i := 0
|
||||
for i < int(gv.FrontPagePosts) && rs.Next() {
|
||||
if err = rs.Scan(&(pids[i])); err != nil {
|
||||
return nil, err
|
||||
if err = rs.Scan(&(cids[i]), &(pids[i])); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
i++
|
||||
}
|
||||
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) {
|
||||
cids = cids[:i]
|
||||
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.
|
||||
query, args, err := sqlx.In("SELECT * FROM posts WHERE postid IN (?)", pids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
query = amdb.Rebind(query)
|
||||
|
||||
// Use the SQL to read in all the post headers using a single database query.
|
||||
var data []PostHeader
|
||||
if err = amdb.SelectContext(ctx, &data, query, args...); err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
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.
|
||||
@@ -683,10 +691,10 @@ func AmGetPublishedPosts(ctx context.Context) ([]*PostHeader, error) {
|
||||
}
|
||||
}
|
||||
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 {
|
||||
@@ -898,13 +906,13 @@ func AmSearchPosts(ctx context.Context, searchTerms string, u *User, offset, max
|
||||
if err != nil {
|
||||
return nil, count, err
|
||||
}
|
||||
alias, err := conf.Aliases(ctx)
|
||||
alias, err := conf.Aliases(ctx, commid)
|
||||
if err != nil {
|
||||
return nil, count, err
|
||||
}
|
||||
|
||||
// Build the post link.
|
||||
plink := AmCreatePostLinkContext(commAlias, alias[0], topicNum)
|
||||
plink := AmCreatePostLinkContext(commAlias, commid, alias[0], topicNum)
|
||||
plink.FirstPost = postnum
|
||||
plink.LastPost = postnum
|
||||
rc[i].PostLink = plink.AsString()
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
// PostLinkData is the structure holding the decoded parts of the post link.
|
||||
type PostLinkData struct {
|
||||
Community string
|
||||
CommId int32
|
||||
Conference string
|
||||
Topic int16
|
||||
FirstPost int32
|
||||
@@ -36,6 +37,7 @@ func (d *PostLinkData) NeedsDBVerification() bool {
|
||||
|
||||
// VerifyNames verifies the post link data against the database.
|
||||
func (d *PostLinkData) VerifyNames(ctx context.Context) error {
|
||||
commid := d.CommId
|
||||
if d.Community != "" {
|
||||
comm, err := AmGetCommunityByAlias(ctx, d.Community)
|
||||
if err != nil {
|
||||
@@ -44,9 +46,10 @@ func (d *PostLinkData) VerifyNames(ctx context.Context) error {
|
||||
if comm == nil {
|
||||
return errors.New("community alias not found")
|
||||
}
|
||||
commid = comm.Id
|
||||
}
|
||||
if d.Conference != "" {
|
||||
conf, err := AmGetConferenceByAlias(ctx, d.Conference)
|
||||
conf, err := AmGetConferenceByAlias(ctx, commid, d.Conference)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -392,9 +395,10 @@ func AmDecodePostLink(data string) (*PostLinkData, error) {
|
||||
return &rc, nil
|
||||
}
|
||||
|
||||
func AmCreatePostLinkContext(community string, conference string, topic int16) *PostLinkData {
|
||||
func AmCreatePostLinkContext(community string, commid int32, conference string, topic int16) *PostLinkData {
|
||||
return &PostLinkData{
|
||||
Community: community,
|
||||
CommId: commid,
|
||||
Conference: conference,
|
||||
Topic: topic,
|
||||
FirstPost: -1,
|
||||
|
||||
+2
-2
@@ -43,7 +43,7 @@ type Topic struct {
|
||||
}
|
||||
|
||||
// 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" {
|
||||
return fmt.Sprintf("%d.", t.Number), nil
|
||||
}
|
||||
@@ -51,7 +51,7 @@ func (t *Topic) Link(ctx context.Context, scope string) (string, error) {
|
||||
conf, err := AmGetConference(ctx, t.ConfId)
|
||||
if err == nil {
|
||||
var plink string
|
||||
plink, err = conf.Link(ctx, scope)
|
||||
plink, err = conf.Link(ctx, commid, scope)
|
||||
if err == nil {
|
||||
if strings.HasSuffix(plink, ".") {
|
||||
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
|
||||
}
|
||||
|
||||
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.
|
||||
* Parameters:
|
||||
* ctx - Standard Go context value.
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# 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
|
||||
|
||||
* Fixed a bug in post link resolution at post time which was causing the conference alias to be set incorrectly (issue #3).
|
||||
|
||||
## Release 0.1.0 - April 6, 2026
|
||||
|
||||
* Implemented all functionality from the last known version of the Venice Web Communities System, circa 2006.
|
||||
@@ -88,7 +88,7 @@ func AmDeliverSubscription(ctx context.Context, comm *database.Community, conf *
|
||||
vars.Set("communityName", comm.Name)
|
||||
vars.Set("conferenceName", conf.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("pseud", realPseud)
|
||||
vars.Set("text", realText)
|
||||
|
||||
@@ -345,12 +345,11 @@ func FindPostsPageCommunity(ctxt ui.AmContext) (string, any) {
|
||||
* Data as a parameter for the command string.
|
||||
*/
|
||||
func FindPostsPageConference(ctxt ui.AmContext) (string, any) {
|
||||
comm := ctxt.CurrentCommunity()
|
||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||
ctxt.VarMap().Set("scope", "conference")
|
||||
ctxt.VarMap().Set("entityName", conf.Name)
|
||||
ctxt.VarMap().Set("backlink", fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, ctxt.GetScratch("currentAlias")))
|
||||
ctxt.VarMap().Set("postlink", fmt.Sprintf("/comm/%s/conf/%s/find", comm.Alias, ctxt.GetScratch("currentAlias")))
|
||||
ctxt.VarMap().Set("backlink", ctxt.GetScratch("ConferenceLink").(string))
|
||||
ctxt.VarMap().Set("postlink", fmt.Sprintf("%s/find", ctxt.GetScratch("ConferenceLink")))
|
||||
return commonFindGetBackend(ctxt)
|
||||
}
|
||||
|
||||
@@ -362,12 +361,11 @@ func FindPostsPageConference(ctxt ui.AmContext) (string, any) {
|
||||
* Data as a parameter for the command string.
|
||||
*/
|
||||
func FindPostsPageTopic(ctxt ui.AmContext) (string, any) {
|
||||
comm := ctxt.CurrentCommunity()
|
||||
topic := ctxt.GetScratch("currentTopic").(*database.Topic)
|
||||
ctxt.VarMap().Set("scope", "topic")
|
||||
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("postlink", fmt.Sprintf("/comm/%s/conf/%s/op/%d/find", 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("%s/op/%d/find", ctxt.GetScratch("ConferenceLink"), topic.Number))
|
||||
return commonFindGetBackend(ctxt)
|
||||
}
|
||||
|
||||
@@ -438,8 +436,8 @@ func FindPostsConference(ctxt ui.AmContext) (string, any) {
|
||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||
ctxt.VarMap().Set("scope", "conference")
|
||||
ctxt.VarMap().Set("entityName", conf.Name)
|
||||
ctxt.VarMap().Set("backlink", fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, ctxt.GetScratch("currentAlias")))
|
||||
ctxt.VarMap().Set("postlink", fmt.Sprintf("/comm/%s/conf/%s/find", comm.Alias, ctxt.GetScratch("currentAlias")))
|
||||
ctxt.VarMap().Set("backlink", ctxt.GetScratch("ConferenceLink").(string))
|
||||
ctxt.VarMap().Set("postlink", fmt.Sprintf("%s/find", ctxt.GetScratch("ConferenceLink")))
|
||||
return commonFindPostBackend(ctxt, comm, conf, nil)
|
||||
}
|
||||
|
||||
@@ -456,7 +454,7 @@ func FindPostsTopic(ctxt ui.AmContext) (string, any) {
|
||||
topic := ctxt.GetScratch("currentTopic").(*database.Topic)
|
||||
ctxt.VarMap().Set("scope", "topic")
|
||||
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("postlink", fmt.Sprintf("/comm/%s/conf/%s/op/%d/find", 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("%s/op/%d/find", ctxt.GetScratch("ConferenceLink"), topic.Number))
|
||||
return commonFindPostBackend(ctxt, comm, conf, topic)
|
||||
}
|
||||
|
||||
@@ -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.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
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-20251202014920-1725d2651bd4 h1:DQ1+lDdBve+u+aovjh4wV6sYnvZKH0Hx8GaQOi4vYl8=
|
||||
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/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/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/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
|
||||
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/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/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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/derekparker/trie v0.0.0-20230829180723-39f4de51ef7d h1:hUWoLdw5kvo2xCsqlsIBMvWUc1QCSsCYD2J2+Fg6YoU=
|
||||
@@ -40,8 +31,6 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||
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/go.mod h1:OwdMhr8tbQvusAsnilqkkgDQqivWlqyg0w5cfXkLiDk=
|
||||
github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA=
|
||||
github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ=
|
||||
github.com/labstack/echo/v4 v4.15.1 h1:S9keusg26gZpjMmPqB5hOEvNKnmd1lNmcHrbbH2lnFs=
|
||||
github.com/labstack/echo/v4 v4.15.1/go.mod h1:xmw1clThob0BSVRX1CRQkGQ/vjwcpOMjQZSZa9fKA/c=
|
||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||
@@ -56,61 +45,33 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o
|
||||
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/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/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.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/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
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/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.37.0 h1:ZiRjArKI8GwxZOoEtUfhrBtaCN+4b/7709dlT6SSnQA=
|
||||
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.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
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.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
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/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/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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -127,6 +127,7 @@ func buildPostLink(decoded, context *database.PostLinkData) string {
|
||||
b.WriteString(context.Conference)
|
||||
} else {
|
||||
b.WriteString(decoded.Conference)
|
||||
started = true
|
||||
}
|
||||
b.WriteString(".")
|
||||
if decoded.Topic == -1 {
|
||||
@@ -168,6 +169,7 @@ func (rw *postLinkRewriter) Rewrite(ctx context.Context, data string, svc rewrit
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
mydata.CommId = ctxt.CommId
|
||||
err = mydata.VerifyNames(ctx)
|
||||
if err != nil {
|
||||
return nil
|
||||
|
||||
+2
-2
@@ -59,7 +59,7 @@ func InviteToConference(ctxt ui.AmContext) (string, any) {
|
||||
ctxt.SetFrameTitle("Send Invitation")
|
||||
ctxt.VarMap().Set("title", "Send Conference Invitation")
|
||||
ctxt.VarMap().Set("subtitle", conf.Name)
|
||||
ctxt.VarMap().Set("backlink", fmt.Sprintf("/comm/%s/conf/%s/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("confid", fmt.Sprintf("%d", conf.ConfId))
|
||||
return "framed", "invite.jet"
|
||||
@@ -83,7 +83,7 @@ func InviteToTopic(ctxt ui.AmContext) (string, any) {
|
||||
ctxt.SetFrameTitle("Send Invitation")
|
||||
ctxt.VarMap().Set("title", "Send Topic Invitation")
|
||||
ctxt.VarMap().Set("subtitle", topic.Name)
|
||||
ctxt.VarMap().Set("backlink", fmt.Sprintf("/comm/%s/conf/%s/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("confid", fmt.Sprintf("%d", conf.ConfId))
|
||||
ctxt.VarMap().Set("topicid", fmt.Sprintf("%d", topic.TopicId))
|
||||
|
||||
+10
@@ -249,6 +249,7 @@ func UserManagementForm(ctxt ui.AmContext) (string, any) {
|
||||
var prefs *database.UserPrefs
|
||||
prefs, err = user.Prefs(ctxt.Ctx())
|
||||
if err == nil {
|
||||
dlg.Field("user").Value = user.Username
|
||||
dlg.Field("remind").Value = user.PassReminder
|
||||
dlg.Field("base_lvl").SetLevel(user.BaseLevel)
|
||||
dlg.Field("verify_email").SetChecked(user.VerifyEMail)
|
||||
@@ -327,6 +328,12 @@ func UserManagementSave(ctxt ui.AmContext) (string, any) {
|
||||
if err == nil {
|
||||
var prefs *database.UserPrefs
|
||||
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()) {
|
||||
p1 := dlg.Field("pass1").Value
|
||||
if p1 == dlg.Field("pass2").Value {
|
||||
@@ -378,6 +385,9 @@ func UserManagementSave(ctxt ui.AmContext) (string, any) {
|
||||
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 {
|
||||
err = user.SetProfileData(ctxt.Ctx(), dlg.Field("remind").Value, dlg.Field("dob").AsDate(), dlg.Field("descr").ValPtr(),
|
||||
ctxt.CurrentUser(), ctxt.RemoteIP())
|
||||
|
||||
@@ -93,7 +93,7 @@ func renderSBConferences(ctx context.Context, u *database.User, sb *DisplaySideb
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
alias[i] = a[0]
|
||||
@@ -176,9 +176,10 @@ func templateGetTopic(args jet.Arguments) reflect.Value {
|
||||
|
||||
// templateTopicLink returns the link string for the given topic.
|
||||
func templateTopicLink(args jet.Arguments) reflect.Value {
|
||||
topic := args.Get(0).Convert(reflect.TypeFor[*database.Topic]()).Interface().(*database.Topic)
|
||||
ctxt := args.Get(1).Convert(reflect.TypeFor[ui.AmContext]()).Interface().(ui.AmContext)
|
||||
link, _ := topic.Link(ctxt.Ctx(), "global")
|
||||
comm := args.Get(0).Convert(reflect.TypeFor[*database.Community]()).Interface().(*database.Community)
|
||||
topic := args.Get(1).Convert(reflect.TypeFor[*database.Topic]()).Interface().(*database.Topic)
|
||||
ctxt := args.Get(2).Convert(reflect.TypeFor[ui.AmContext]()).Interface().(ui.AmContext)
|
||||
link, _ := topic.Link(ctxt.Ctx(), comm.Id, "global")
|
||||
return reflect.ValueOf(link)
|
||||
}
|
||||
|
||||
@@ -194,12 +195,13 @@ func TopPage(ctxt ui.AmContext) (string, any) {
|
||||
ctxt.SetFrameTitle("My Front Page")
|
||||
|
||||
// Retrieve the published posts.
|
||||
hdrs, err := database.AmGetPublishedPosts(ctxt.Ctx())
|
||||
hdrs, comms, err := database.AmGetPublishedPosts(ctxt.Ctx())
|
||||
if err != nil {
|
||||
return "error", err
|
||||
}
|
||||
|
||||
ctxt.VarMap().Set("posts", hdrs)
|
||||
ctxt.VarMap().Set("comms", comms)
|
||||
ctxt.VarMap().SetFunc("post_getText", templatePostText)
|
||||
ctxt.VarMap().SetFunc("post_getUserName", templateExtractUserName)
|
||||
ctxt.VarMap().SetFunc("post_topic", templateGetTopic)
|
||||
|
||||
+3
-2
@@ -342,7 +342,6 @@ func (st *amSessionStore) SessionInfo() (int, []string, int) {
|
||||
func (st *amSessionStore) sweep(tick <-chan time.Time, done chan bool) {
|
||||
for range tick {
|
||||
if st.sweepRunning.Load() {
|
||||
log.Infof("session sweep running")
|
||||
// phase 1 - identify expired sessions
|
||||
st.mutex.RLock()
|
||||
zap := make([]string, 0, len(st.sessions))
|
||||
@@ -353,7 +352,9 @@ func (st *amSessionStore) sweep(tick <-chan time.Time, done chan bool) {
|
||||
}
|
||||
}
|
||||
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
|
||||
for _, k := range zap {
|
||||
|
||||
@@ -15,6 +15,15 @@ title: "Modify User Account"
|
||||
subtitle: "User: [USERNAME]"
|
||||
action: "/sysadmin/users/[USERNAME]"
|
||||
fields:
|
||||
- type: "header"
|
||||
name: "header0"
|
||||
caption: "User Information"
|
||||
- type: "ams_id"
|
||||
name: "user"
|
||||
caption: "User Name"
|
||||
required: true
|
||||
size: 32
|
||||
maxlength: 64
|
||||
- type: "header"
|
||||
name: "header1"
|
||||
caption: "Security Information"
|
||||
|
||||
+11
-1
@@ -17,6 +17,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.erbosoft.com/amy/amsterdam/config"
|
||||
"git.erbosoft.com/amy/amsterdam/database"
|
||||
@@ -82,6 +83,10 @@ func SetCommunity(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
if err != nil {
|
||||
return AmSendPageData(c, ctxt, "error", echo.NewHTTPError(http.StatusNotFound).SetInternal(err))
|
||||
}
|
||||
var b strings.Builder
|
||||
b.WriteString("/comm/")
|
||||
b.WriteString(ctxt.CurrentCommunity().Alias)
|
||||
ctxt.SetScratch("CommunityLink", b.String())
|
||||
ctxt.SetLeftMenu("community")
|
||||
return next(c)
|
||||
}
|
||||
@@ -113,7 +118,7 @@ func ValidateConference(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
func SetConference(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
ctxt := AmContextFromEchoContext(c)
|
||||
conf, err := database.AmGetConferenceByAliasInCommunity(ctxt.Ctx(), ctxt.CurrentCommunity().Id, ctxt.URLParam("confid"))
|
||||
conf, err := database.AmGetConferenceByAlias(ctxt.Ctx(), ctxt.CurrentCommunity().Id, ctxt.URLParam("confid"))
|
||||
if err != nil {
|
||||
return AmSendPageData(c, ctxt, "error", err)
|
||||
}
|
||||
@@ -128,6 +133,11 @@ func SetConference(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
ctxt.SetScratch("currentConference", conf)
|
||||
ctxt.SetScratch("currentAlias", ctxt.URLParam("confid"))
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
@@ -17,6 +17,7 @@
|
||||
<title>{{ .FrameTitle() | raw }} - {{ GlobalConfig.Site.Title }}</title>
|
||||
<link rel="icon" href="{{ GlobalConfig.Site.SiteIcon.Path }}" type="{{ GlobalConfig.Site.SiteIcon.Type }}" />
|
||||
<link rel="shortcut icon" href="{{ GlobalConfig.Site.SiteShortcutIcon }}" />
|
||||
<link rel="apple-touch-icon" href="{{ GlobalConfig.Site.SiteAppleIcon }}" />
|
||||
{{ range k, v := .FrameMetadata(0) }}
|
||||
<meta http-equiv="{{ k }}" content="{{ v }}">
|
||||
{{ end }}
|
||||
|
||||
+2
-2
@@ -117,8 +117,8 @@
|
||||
{{ post_cur = p }}
|
||||
{{ post_userName = post_getUserName(p, .) }}
|
||||
{{ post_text = post_getText(p, .) }}
|
||||
{{ post_overrideLine = post_getOverrideLine(p, .) }}
|
||||
{{ post_overrideLink = post_getOverrideLink(p, post_topicPermalink) }}
|
||||
{{ post_overrideLine = post_getOverrideLine(p, advancedControls, .) }}
|
||||
{{ post_overrideLink = post_getOverrideLink(p, advancedControls, post_topicLink) }}
|
||||
{{ post_attach = post_getAttachmentInfo(p, .) }}
|
||||
{{ post_bozo = post_isBozo(p, post_topic, .) }}
|
||||
{{ if showPics }}
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
{{ post_cur = p }}
|
||||
{{ post_userName = post_getUserName(p, .) }}
|
||||
{{ post_text = post_getText(p, .) }}
|
||||
{{ post_overrideLine = post_getOverrideLine(p, .) }}
|
||||
{{ post_overrideLink = post_getOverrideLink(p, post_topicPermalink) }}
|
||||
{{ post_overrideLine = post_getOverrideLine(p, true, .) }}
|
||||
{{ post_overrideLink = post_getOverrideLink(p, true, post_topicLink) }}
|
||||
{{ post_attach = post_getAttachmentInfo(p, .) }}
|
||||
{{ post_bozo = post_isBozo(p, post_topic, .) }}
|
||||
{{ include "singlepost.jet" }}
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@
|
||||
{{ user = post_getUserName(p, .) }}
|
||||
{{ text = post_getText(p, .) }}
|
||||
{{ topic = post_topic(p, .) }}
|
||||
{{ link = post_topicLink(topic, .) }}
|
||||
{{ link = post_topicLink(comms[i], topic, .) }}
|
||||
<div class="text-black text-sm">
|
||||
<div class="mb-2">
|
||||
<strong>{{ p.Pseud | raw }}</strong>
|
||||
|
||||
Reference in New Issue
Block a user