diff --git a/conferenceadmin.go b/conferenceadmin.go index 105d5ca..62716b3 100644 --- a/conferenceadmin.go +++ b/conferenceadmin.go @@ -17,6 +17,7 @@ import ( "git.erbosoft.com/amy/amsterdam/database" "git.erbosoft.com/amy/amsterdam/ui" "git.erbosoft.com/amy/amsterdam/util" + log "github.com/sirupsen/logrus" ) /* EditConferenceForm displays the dialog for editing the conference properties. @@ -144,3 +145,41 @@ func CreateConferenceForm(ctxt ui.AmContext) (string, any, error) { dlg.SetCommunity(comm) return dlg.Render(ctxt) } + +/* CreateConference creates a new conference. + * Parameters: + * ctxt - The AmContext for the request. + * Returns: + * Command string dictating what to be rendered. + * Data as a parameter for the command string. + * Standard Go error status. + */ +func CreateConference(ctxt ui.AmContext) (string, any, error) { + comm := ctxt.CurrentCommunity() + if !comm.TestPermission("Community.Create", ctxt.EffectiveLevel()) { + ctxt.SetRC(http.StatusForbidden) + return ui.ErrorPage(ctxt, ENOPERM) + } + + dlg, err := ui.AmLoadDialog("create_conference") + if err != nil { + return ui.ErrorPage(ctxt, err) + } + button := dlg.WhichButton(ctxt) + if button == "cancel" { + return "redirect", fmt.Sprintf("/comm/%s/conf", comm.Alias), nil + } else if button != "create" { + dlg.SetCommunity(comm) + return dlg.RenderError(ctxt, "invalid button pressed") + } + dlg.LoadFromForm(ctxt) + alias := dlg.Field("alias").Value + conf, err := database.AmCreateConference(ctxt.Ctx(), comm, dlg.Field("name").Value, alias, dlg.Field("descr").Value, + dlg.Field("ctype").Value == "1", dlg.Field("hide").IsChecked(), ctxt.CurrentUser(), ctxt.RemoteIP()) + if err != nil { + dlg.SetCommunity(comm) + return dlg.RenderError(ctxt, err.Error()) + } + log.Infof("Created conference '%s'", conf.Name) + return "redirect", fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, alias), nil +} diff --git a/database/conference.go b/database/conference.go index 3ce5700..3820a56 100644 --- a/database/conference.go +++ b/database/conference.go @@ -60,6 +60,9 @@ type ConferenceProperties struct { Data *string `db:"data"` // property data } +// Default spacing between sequence numbers in commtoconf table. +const COMMTOCONF_SEQ_SPACING = 10 + // Conference property indexes defined. const ( ConferencePropFlags = int32(0) @@ -708,3 +711,119 @@ func AmSetConferenceProperty(ctx context.Context, confid int32, ndx int32, val * } return err } + +/* AmCreateConference creates a new conference. + * Parameters: + * ctx - Standard Go context value. + * comm - Community to create this conference in. + * name - New conference name. + * alias - New conference alias. + * descr - Nw conference description. + * private - true to create a private conference, false to create a public one. + * hide_list - true if the conference should be hidden in the community conference list. + * u - User creating the conference; this user will become the conference host. + * ipaddr - IP address of the creation request. + * Returns: + * Pointer to the new conference, or nil. + * Standard Go error status. + */ +func AmCreateConference(ctx context.Context, comm *Community, name, alias, descr string, private, hide_list bool, u *User, ipaddr string) (*Conference, error) { + newConf := Conference{ + Name: name, + HideLevel: AmRoleList("Conference.Hide").Default().Level(), + NukeLevel: AmRoleList("Conference.Nuke").Default().Level(), + ChangeLevel: AmRoleList("Conference.Change").Default().Level(), + DeleteLevel: AmRoleList("Conference.Delete").Default().Level(), + } + if descr != "" { + newConf.Description = &descr + } + if private { + newConf.ReadLevel = AmDefaultRole("Conference.Read.Private").Level() + newConf.PostLevel = AmDefaultRole("Conference.Post.Private").Level() + newConf.CreateLevel = AmDefaultRole("Conference.Create.Private").Level() + } else { + newConf.ReadLevel = AmDefaultRole("Conference.Read.Public").Level() + newConf.PostLevel = AmDefaultRole("Conference.Post.Public").Level() + newConf.CreateLevel = AmDefaultRole("Conference.Create.Public").Level() + } + + var ar *AuditRecord = nil + defer func() { + AmStoreAudit(ar) + }() + success := false + tx := amdb.MustBegin() + defer func() { + if !success { + tx.Rollback() + } + }() + getConferenceMutex.Lock() + defer getConferenceMutex.Unlock() + + // Ensure the alias is not in use. + row := tx.QueryRowContext(ctx, "SELECT confid FROM confalias WHERE alias = ?", alias) + var tmp int32 + err := row.Scan(&tmp) + if err == nil { + return nil, fmt.Errorf("the alias '%s' is already in use by a different conference", alias) + } else if err != sql.ErrNoRows { + return nil, err + } + + // Create the conference record and then reload it so we have its ID available. + rs, err := tx.NamedExecContext(ctx, `INSERT INTO confs (createdate, read_lvl, post_lvl, create_lvl, hide_lvl, nuke_lvl, change_lvl, delete_lvl, name, descr) + VALUES (NOW(), :read_lvl, :post_lvl, :create_lvl, :hide_lvl, :nuke_lvl, :change_lvl, :delete_lvl, :name, :descr)`, &newConf) + if err != nil { + return nil, err + } + newId, err := rs.LastInsertId() + if err != nil { + return nil, err + } + var rc []Conference + err = tx.SelectContext(ctx, &rc, "SELECT * FROM confs WHERE confid = ?", int32(newId)) + if err != nil { + return nil, err + } else if len(rc) != 1 { + return nil, errors.New("internal error reading back conference") + } + + // Attach the alias to the conference. + _, err = tx.ExecContext(ctx, "INSERT INTO confalias (confid, alias) VALUES (?, ?)", rc[0].ConfId, alias) + if err != nil { + return nil, err + } + + // Get the current "last" sequence number. + row = tx.QueryRowContext(ctx, "SELECT MAX(sequence) FROM commtoconf WHERE commid = ?", comm.Id) + var seq int + err = row.Scan(&seq) + if err != nil { + return nil, err + } + + // Link the conference into the community, and set the hide flag. + _, err = tx.ExecContext(ctx, "INSERT INTO commtoconf (commid, confid, sequence, hide_list) VALUES (?, ?, ?, ?)", comm.Id, rc[0].ConfId, + int16(seq+COMMTOCONF_SEQ_SPACING), hide_list) + if err != nil { + return nil, err + } + + // Make the specified user the first host of the conference. + _, err = tx.ExecContext(ctx, "INSERT INTO confmember (confid, uid, granted_lvl) VALUES (?, ?, ?)", rc[0].ConfId, u.Uid, AmDefaultRole("Conference.NewHost").Level()) + if err != nil { + return nil, err + } + + if err = tx.Commit(); err != nil { + return nil, err + } + success = true + + // Add the new conference to the cache, and create our audit record. + conferenceCache.Add(rc[0].ConfId, &(rc[0])) + ar = AmNewAudit(AuditConferenceCreate, u.Uid, ipaddr, fmt.Sprintf("confid=%d", rc[0].ConfId), fmt.Sprintf("name=%s", name), fmt.Sprintf("alias=%s", alias)) + return &(rc[0]), nil +} diff --git a/docs/MISSINGFUNCS.md b/docs/MISSINGFUNCS.md index d6a44b2..ef6154d 100644 --- a/docs/MISSINGFUNCS.md +++ b/docs/MISSINGFUNCS.md @@ -22,7 +22,7 @@ _(italicized items can be deferred)_ - Conferences list: - ~~Find~~ - Manage (reorder/show/hide/delete) - - Create New + - ~~Create New~~ - Conferences List honor "hide in list" flag - Community Admin Menu: - Set Community Category diff --git a/main.go b/main.go index 1eeb7c2..8c6d207 100644 --- a/main.go +++ b/main.go @@ -101,6 +101,7 @@ func setupEcho() *echo.Echo { // conference group commGroup.GET("/create_conf", ui.AmWrap(CreateConferenceForm)) + commGroup.POST("/create_conf", ui.AmWrap(CreateConference)) commGroup.GET("/conf", ui.AmWrap(Conferences), ui.ValidateConference) confGroup := commGroup.Group("/conf/:confid", ui.ValidateConference, ui.SetConference) confGroup.GET("", ui.AmWrap(Topics)) diff --git a/ui/dialogs/create_conference.yaml b/ui/dialogs/create_conference.yaml index cf0dbac..4601a1d 100644 --- a/ui/dialogs/create_conference.yaml +++ b/ui/dialogs/create_conference.yaml @@ -10,6 +10,7 @@ name: "conf.create" formName: "newconfform" menuSelector: "community" title: "Create New Conference" +subtitle: "in Community: [CNAME]" action: "/comm/[CID]/create_conf" fields: - type: "text"