Mercurial > gemma
view pkg/controllers/user.go @ 5123:eeb45e3e0a5a queued-stage-done
Added mechanism to have sync import jobs on import queue.
Review jobs are now sync with a controller waiting for 20 secs before returning.
If all reviews return earlier the controller extists earlier, too.
If one or more decisions took longer they are run in background till they are
decided and the the controller returns a error message for these imports
that the process is st still running.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Thu, 26 Mar 2020 22:24:45 +0100 |
parents | e020e6e34ad7 |
children | b1b9b384540d |
line wrap: on
line source
// This is Free Software under GNU Affero General Public License v >= 3.0 // without warranty, see README.md and license for details. // // SPDX-License-Identifier: AGPL-3.0-or-later // License-Filename: LICENSES/AGPL-3.0.txt // // Copyright (C) 2018 by via donau // – Österreichische Wasserstraßen-Gesellschaft mbH // Software engineering by Intevation GmbH // // Author(s): // * Sascha L. Teichmann <sascha.teichmann@intevation.de> // * Tom Gottfried <tom.gottfried@intevation.de> // * Sascha Wilde <sascha.wilde@intevation.de> package controllers import ( "bytes" "database/sql" "fmt" "log" "net/http" "text/template" "time" "github.com/gorilla/mux" "gemma.intevation.de/gemma/pkg/auth" "gemma.intevation.de/gemma/pkg/misc" "gemma.intevation.de/gemma/pkg/models" "gemma.intevation.de/gemma/pkg/pgxutils" "gemma.intevation.de/gemma/pkg/scheduler" mw "gemma.intevation.de/gemma/pkg/middleware" ) const ( createUserSQL = `INSERT INTO users.list_users VALUES ($1, $2, $3, $4, NULL, $5)` createUserExtentSQL = `INSERT INTO users.list_users VALUES ($1, $2, $3, $4, ST_MakeBox2D(ST_Point($5, $6), ST_Point($7, $8)), $9)` updateUserUnprivSQL = `UPDATE users.list_users SET (pw, map_extent, email_address) = ($2, ST_MakeBox2D(ST_Point($3, $4), ST_Point($5, $6)), $7) WHERE username = $1` updateUserSQL = `UPDATE users.list_users SET (rolname, username, pw, country, map_extent, email_address) = ($2, $3, $4, $5, NULL, $6) WHERE username = $1` updateUserExtentSQL = `UPDATE users.list_users SET (rolname, username, pw, country, map_extent, email_address) = ($2, $3, $4, $5, ST_MakeBox2D(ST_Point($6, $7), ST_Point($8, $9)), $10) WHERE username = $1` deleteUserSQL = `DELETE FROM users.list_users WHERE username = $1` listUsersSQL = `SELECT rolname, username, country, email_address, ST_XMin(map_extent), ST_YMin(map_extent), ST_XMax(map_extent), ST_YMax(map_extent) FROM users.list_users` listUserSQL = `SELECT rolname, country, email_address, ST_XMin(map_extent), ST_YMin(map_extent), ST_XMax(map_extent), ST_YMax(map_extent) FROM users.list_users WHERE username = $1` ) var ( testSysadminNotifyMailTmpl = template.Must( template.New("sysadmin").Parse(`Dear {{ .User }}, this is a test email for the Gemma System Errors notification service. You recieved this mail, because a System Administrator triggered the test mail sending function at {{ .Timestamp }}. When a critical system error is detected an automated mail will be send to the address: {{ .Email }} with details on the error condition.`)) testWWAdminNotifyMailTmpl = template.Must( template.New("waterwayadmin").Parse(`Dear {{ .User }}, this is a test email for the Gemma System Imports notification service. You recieved this mail, because a System Administrator triggered the test mail sending function at {{ .Timestamp }}. When the status of an data import managed by you changes an automated mail will be send to the address: {{ .Email }} with details on the new import status (inkluding import errors) and details on the concerned import.`)) ) func deleteUser(req *http.Request) (jr mw.JSONResult, err error) { user := mux.Vars(req)["user"] if !models.UserName(user).IsValid() { err = mw.JSONError{ Code: http.StatusBadRequest, Message: "error: user invalid", } return } session, _ := auth.GetSession(req) if session.User == user { err = mw.JSONError{ Code: http.StatusBadRequest, Message: "error: cannot delete yourself", } return } ctx := req.Context() db := mw.JSONConn(req) // Remove scheduled tasks. ids, err2 := scheduler.ScheduledUserIDs(ctx, db, user) if err2 == nil { if len(ids) > 0 { go func() { scheduler.UnbindByIDs(ids) }() } } else { log.Printf("error: %v\n", err2) } var res sql.Result if res, err = db.ExecContext(ctx, deleteUserSQL, user); err != nil { return } if n, err2 := res.RowsAffected(); err2 == nil && n == 0 { err = mw.JSONError{ Code: http.StatusNotFound, Message: fmt.Sprintf("Cannot find user %s.", user), } return } // Running in a go routine should not be necessary. go func() { auth.Sessions.Logout(user) }() jr = mw.JSONResult{Code: http.StatusNoContent} return } func updateUser(req *http.Request) (jr mw.JSONResult, err error) { user := models.UserName(mux.Vars(req)["user"]) if !user.IsValid() { err = mw.JSONError{ Code: http.StatusBadRequest, Message: "error: user invalid", } return } newUser := mw.JSONInput(req).(*models.User) var res sql.Result db := mw.JSONConn(req) if s, _ := auth.GetSession(req); s.Roles.Has("sys_admin") { if newUser.Extent == nil { res, err = db.ExecContext( req.Context(), updateUserSQL, user, newUser.Role, newUser.User, newUser.Password, newUser.Country, newUser.Email, ) } else { res, err = db.ExecContext( req.Context(), updateUserExtentSQL, user, newUser.Role, newUser.User, newUser.Password, newUser.Country, newUser.Extent.X1, newUser.Extent.Y1, newUser.Extent.X2, newUser.Extent.Y2, newUser.Email, ) } } else { if newUser.Extent == nil { err = mw.JSONError{ Code: http.StatusBadRequest, Message: "extent is mandatory", } return } res, err = db.ExecContext( req.Context(), updateUserUnprivSQL, user, newUser.Password, newUser.Extent.X1, newUser.Extent.Y1, newUser.Extent.X2, newUser.Extent.Y2, newUser.Email, ) } if err != nil { return } if n, err2 := res.RowsAffected(); err2 == nil && n == 0 { err = mw.JSONError{ Code: http.StatusNotFound, Message: fmt.Sprintf("Cannot find user %s.", user), } return } if user != newUser.User { // Running in a go routine should not be necessary. go func() { auth.Sessions.Logout(string(user)) }() } jr = mw.JSONResult{ Code: http.StatusCreated, Result: struct { Result string `json:"result"` }{"success"}, } return } func createUser(req *http.Request) (jr mw.JSONResult, err error) { user := mw.JSONInput(req).(*models.User) db := mw.JSONConn(req) if user.Extent == nil { _, err = db.ExecContext( req.Context(), createUserSQL, user.Role, user.User, user.Password, user.Country, user.Email, ) } else { _, err = db.ExecContext( req.Context(), createUserExtentSQL, user.Role, user.User, user.Password, user.Country, user.Extent.X1, user.Extent.Y1, user.Extent.X2, user.Extent.Y2, user.Email, ) } if err != nil { m, c := pgxutils.ReadableError{Err: err}.MessageAndCode() err = mw.JSONError{Code: c, Message: m} return } jr = mw.JSONResult{ Code: http.StatusCreated, Result: struct { Result string `json:"result"` }{"success"}, } return } func listUsers(req *http.Request) (jr mw.JSONResult, err error) { var rows *sql.Rows rows, err = mw.JSONConn(req).QueryContext(req.Context(), listUsersSQL) if err != nil { return } defer rows.Close() var users []*models.User for rows.Next() { user := &models.User{Extent: &models.BoundingBox{}} if err = rows.Scan( &user.Role, &user.User, &user.Country, &user.Email, &user.Extent.X1, &user.Extent.Y1, &user.Extent.X2, &user.Extent.Y2, ); err != nil { return } users = append(users, user) } jr = mw.JSONResult{ Result: struct { Users []*models.User `json:"users"` }{users}, } return } func listUser(req *http.Request) (jr mw.JSONResult, err error) { user := models.UserName(mux.Vars(req)["user"]) if !user.IsValid() { err = mw.JSONError{ Code: http.StatusBadRequest, Message: "error: user invalid", } return } result := &models.User{ User: user, Extent: &models.BoundingBox{}, } err = mw.JSONConn(req).QueryRowContext(req.Context(), listUserSQL, user).Scan( &result.Role, &result.Country, &result.Email, &result.Extent.X1, &result.Extent.Y1, &result.Extent.X2, &result.Extent.Y2, ) switch { case err == sql.ErrNoRows: err = mw.JSONError{ Code: http.StatusNotFound, Message: fmt.Sprintf("Cannot find user %s.", user), } return case err != nil: return } jr.Result = result return } func sendTestMail(req *http.Request) (jr mw.JSONResult, err error) { user := models.UserName(mux.Vars(req)["user"]) if !user.IsValid() { err = mw.JSONError{ Code: http.StatusBadRequest, Message: "error: user invalid", } return } userData := &models.User{ User: user, Extent: &models.BoundingBox{}, } err = mw.JSONConn(req).QueryRowContext(req.Context(), listUserSQL, user).Scan( &userData.Role, &userData.Country, &userData.Email, &userData.Extent.X1, &userData.Extent.Y1, &userData.Extent.X2, &userData.Extent.Y2, ) switch { case err == sql.ErrNoRows: err = mw.JSONError{ Code: http.StatusNotFound, Message: fmt.Sprintf("Cannot find user %s.", user), } return case err != nil: return } var subject string var tmplVars = struct { User string Timestamp string Email string }{ User: string(user), Timestamp: time.Now().Format("2006-01-02 15:04:05"), Email: string(userData.Email), } var bodyTmpl *template.Template if userData.Role == "sys_admin" { subject = "Gemma: Sysadmin Notification TEST" bodyTmpl = testSysadminNotifyMailTmpl } else if userData.Role == "waterway_admin" { subject = "Gemma: Waterway Admin Notification TEST" bodyTmpl = testWWAdminNotifyMailTmpl } else { err = mw.JSONError{ Code: http.StatusBadRequest, Message: "Test mails can only be generated for admin roles.", } return } var buf bytes.Buffer if err := bodyTmpl.Execute(&buf, &tmplVars); err != nil { log.Printf("error: %v\n", err) } err = misc.SendMail(string(userData.Email), subject, buf.String()) if err != nil { return } jr.Result = &struct { Message string `json:"message"` }{"Sending test mail."} return }