feat(invite): more progress on completing invite form and actually creating a real user, #2353

This commit is contained in:
Torkel Ödegaard
2015-07-20 17:46:48 +02:00
parent d75f96fdd5
commit ab54971763
9 changed files with 89 additions and 25 deletions

View File

@ -45,6 +45,7 @@ func Register(r *macaron.Macaron) {
// invited // invited
r.Get("/api/user/invite/:code", wrap(GetInviteInfoByCode)) r.Get("/api/user/invite/:code", wrap(GetInviteInfoByCode))
r.Post("/api/user/invite/complete", bind(dtos.CompleteInviteForm{}), wrap(CompleteInvite))
// reset password // reset password
r.Get("/user/password/send-reset-email", Index) r.Get("/user/password/send-reset-email", Index)
@ -97,7 +98,7 @@ func Register(r *macaron.Macaron) {
// invites // invites
r.Get("/invites", wrap(GetPendingOrgInvites)) r.Get("/invites", wrap(GetPendingOrgInvites))
r.Post("/invites", bind(dtos.AddInviteForm{}), wrap(AddOrgInvite)) r.Post("/invites", bind(dtos.AddInviteForm{}), wrap(AddOrgInvite))
r.Patch("/invites/:id/revoke", wrap(RevokeInvite)) r.Patch("/invites/:code/revoke", wrap(RevokeInvite))
}, regOrgAdmin) }, regOrgAdmin)
// create new org // create new org

View File

@ -14,3 +14,12 @@ type InviteInfo struct {
Name string `json:"name"` Name string `json:"name"`
Username string `json:"username"` Username string `json:"username"`
} }
type CompleteInviteForm struct {
InviteCode string `json:"inviteCode"`
Email string `json:"email" binding:"Required"`
Name string `json:"name"`
Username string `json:"username"`
Password string `json:"password"`
ConfirmPassword string `json:"confirmPassword"`
}

View File

@ -3,6 +3,8 @@ package api
import ( import (
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/events"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
@ -85,8 +87,7 @@ func AddOrgInvite(c *middleware.Context, inviteDto dtos.AddInviteForm) Response
func RevokeInvite(c *middleware.Context) Response { func RevokeInvite(c *middleware.Context) Response {
cmd := m.UpdateTempUserStatusCommand{ cmd := m.UpdateTempUserStatusCommand{
Id: c.ParamsInt64(":id"), Code: c.Params(":code"),
OrgId: c.OrgId,
Status: m.TmpUserRevoked, Status: m.TmpUserRevoked,
} }
@ -98,7 +99,7 @@ func RevokeInvite(c *middleware.Context) Response {
} }
func GetInviteInfoByCode(c *middleware.Context) Response { func GetInviteInfoByCode(c *middleware.Context) Response {
query := m.GetTempUsersByCodeQuery{Code: c.Params(":code")} query := m.GetTempUserByCodeQuery{Code: c.Params(":code")}
if err := bus.Dispatch(&query); err != nil { if err := bus.Dispatch(&query); err != nil {
if err == m.ErrTempUserNotFound { if err == m.ErrTempUserNotFound {
@ -115,3 +116,48 @@ func GetInviteInfoByCode(c *middleware.Context) Response {
return Json(200, &info) return Json(200, &info)
} }
func CompleteInvite(c *middleware.Context, completeInvite dtos.CompleteInviteForm) Response {
query := m.GetTempUserByCodeQuery{Code: completeInvite.InviteCode}
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrTempUserNotFound {
return ApiError(404, "Invite not found", nil)
}
return ApiError(500, "Failed to get invite", err)
}
invite := query.Result
cmd := m.CreateUserCommand{
Email: completeInvite.Email,
Name: completeInvite.Name,
Login: completeInvite.Username,
Password: completeInvite.Password,
}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "failed to create user", err)
}
user := cmd.Result
bus.Publish(&events.UserSignedUp{
Id: user.Id,
Name: user.Name,
Email: user.Email,
Login: user.Login,
})
// update temp user status
updateTmpUserCmd := m.UpdateTempUserStatusCommand{Code: invite.Code, Status: m.TmpUserCompleted}
if err := bus.Dispatch(&updateTmpUserCmd); err != nil {
return ApiError(500, "Failed to update invite status", err)
}
loginUserWithUser(&user, c)
metrics.M_Api_User_SignUp.Inc(1)
return ApiSuccess("User created and logged in")
}

View File

@ -56,8 +56,7 @@ type CreateTempUserCommand struct {
} }
type UpdateTempUserStatusCommand struct { type UpdateTempUserStatusCommand struct {
Id int64 Code string
OrgId int64
Status TempUserStatus Status TempUserStatus
} }
@ -68,7 +67,7 @@ type GetTempUsersForOrgQuery struct {
Result []*TempUserDTO Result []*TempUserDTO
} }
type GetTempUsersByCodeQuery struct { type GetTempUserByCodeQuery struct {
Code string Code string
Result *TempUser Result *TempUser

View File

@ -12,13 +12,13 @@ func init() {
bus.AddHandler("sql", CreateTempUser) bus.AddHandler("sql", CreateTempUser)
bus.AddHandler("sql", GetTempUsersForOrg) bus.AddHandler("sql", GetTempUsersForOrg)
bus.AddHandler("sql", UpdateTempUserStatus) bus.AddHandler("sql", UpdateTempUserStatus)
bus.AddHandler("sql", GetTempUsersByCode) bus.AddHandler("sql", GetTempUserByCode)
} }
func UpdateTempUserStatus(cmd *m.UpdateTempUserStatusCommand) error { func UpdateTempUserStatus(cmd *m.UpdateTempUserStatusCommand) error {
return inTransaction(func(sess *xorm.Session) error { return inTransaction(func(sess *xorm.Session) error {
var rawSql = "UPDATE temp_user SET status=? WHERE id=? and org_id=?" var rawSql = "UPDATE temp_user SET status=? WHERE code=?"
_, err := sess.Exec(rawSql, string(cmd.Status), cmd.Id, cmd.OrgId) _, err := sess.Exec(rawSql, string(cmd.Status), cmd.Code)
return err return err
}) })
} }
@ -70,7 +70,7 @@ func GetTempUsersForOrg(query *m.GetTempUsersForOrgQuery) error {
return err return err
} }
func GetTempUsersByCode(query *m.GetTempUsersByCodeQuery) error { func GetTempUserByCode(query *m.GetTempUserByCodeQuery) error {
var user m.TempUser var user m.TempUser
has, err := x.Table("temp_user").Where("code=?", query.Code).Get(&user) has, err := x.Table("temp_user").Where("code=?", query.Code).Get(&user)

View File

@ -17,6 +17,7 @@ func TestTempUserCommandsAndQueries(t *testing.T) {
cmd := m.CreateTempUserCommand{ cmd := m.CreateTempUserCommand{
OrgId: 2256, OrgId: 2256,
Name: "hello", Name: "hello",
Code: "asd",
Email: "e@as.co", Email: "e@as.co",
Status: m.TmpUserInvitePending, Status: m.TmpUserInvitePending,
} }
@ -32,7 +33,7 @@ func TestTempUserCommandsAndQueries(t *testing.T) {
}) })
Convey("Should be able update status", func() { Convey("Should be able update status", func() {
cmd2 := m.UpdateTempUserStatusCommand{OrgId: 2256, Status: m.TmpUserRevoked, Id: cmd.Result.Id} cmd2 := m.UpdateTempUserStatusCommand{Code: "asd", Status: m.TmpUserRevoked}
err := UpdateTempUserStatus(&cmd2) err := UpdateTempUserStatus(&cmd2)
So(err, ShouldBeNil) So(err, ShouldBeNil)
}) })

View File

@ -1,7 +1,8 @@
define([ define([
'angular', 'angular',
'config',
], ],
function (angular) { function (angular, config) {
'use strict'; 'use strict';
var module = angular.module('grafana.controllers'); var module = angular.module('grafana.controllers');
@ -10,20 +11,27 @@ function (angular) {
contextSrv.sidemenu = false; contextSrv.sidemenu = false;
$scope.user = {}; $scope.formModel = {};
$scope.init = function() { $scope.init = function() {
backendSrv.get('/api/user/invite/' + $routeParams.code).then(function(invite) { backendSrv.get('/api/user/invite/' + $routeParams.code).then(function(invite) {
$scope.user.name = invite.name; $scope.formModel.name = invite.name;
$scope.user.email = invite.email; $scope.formModel.email = invite.email;
$scope.user.username = invite.email; $scope.formModel.username = invite.email;
$scope.user.inviteId = invite.id; $scope.formModel.inviteCode = $routeParams.code;
$scope.greeting = invite.name || invite.email; $scope.greeting = invite.name || invite.email;
}); });
}; };
$scope.submit = function() { $scope.submit = function() {
if (!$scope.inviteForm.$valid) {
return;
}
backendSrv.post('/api/user/invite/complete', $scope.formModel).then(function() {
window.location.href = config.appSubUrl + '/';
});
}; };
$scope.init(); $scope.init();

View File

@ -40,7 +40,7 @@ function (angular) {
$scope.revokeInvite = function(invite, evt) { $scope.revokeInvite = function(invite, evt) {
evt.stopPropagation(); evt.stopPropagation();
backendSrv.patch('/api/org/invites/' + invite.id + '/revoke').then($scope.get); backendSrv.patch('/api/org/invites/' + invite.code + '/revoke').then($scope.get);
}; };
$scope.copyInviteToClipboard = function(evt) { $scope.copyInviteToClipboard = function(evt) {

View File

@ -27,7 +27,7 @@
Email Email
</li> </li>
<li> <li>
<input type="email" name="email" class="tight-form-input last" required ng-model='user.email' placeholder="Email" style="width: 253px"> <input type="email" name="email" class="tight-form-input last" required ng-model='formModel.email' placeholder="Email" style="width: 253px">
</li> </li>
</ul> </ul>
<div class="clearfix"></div> <div class="clearfix"></div>
@ -38,7 +38,7 @@
Name Name
</li> </li>
<li> <li>
<input type="text" name="name" class="tight-form-input last" ng-model='user.name' placeholder="Name (optional)" style="width: 253px"> <input type="text" name="name" class="tight-form-input last" ng-model='formModel.name' placeholder="Name (optional)" style="width: 253px">
</li> </li>
</ul> </ul>
<div class="clearfix"></div> <div class="clearfix"></div>
@ -49,7 +49,7 @@
Username Username
</li> </li>
<li> <li>
<input type="text" name="username" class="tight-form-input last" required ng-model='user.username' placeholder="Username" style="width: 253px"> <input type="text" name="username" class="tight-form-input last" required ng-model='formModel.username' placeholder="Username" style="width: 253px">
</li> </li>
</ul> </ul>
<div class="clearfix"></div> <div class="clearfix"></div>
@ -61,7 +61,7 @@
Password Password
</li> </li>
<li> <li>
<input type="password" name="password" class="tight-form-input last" required ng-model="user.password" id="inputPassword" style="width: 253px" placeholder="password"> <input type="password" name="password" class="tight-form-input last" required ng-model="formModel.password" id="inputPassword" style="width: 253px" placeholder="password">
</li> </li>
</ul> </ul>
<div class="clearfix"></div> <div class="clearfix"></div>
@ -72,7 +72,7 @@
Confirm Password Confirm Password
</li> </li>
<li> <li>
<input type="password" name="confirmPassword" class="tight-form-input last" required ng-model="user.confirmPassword" id="confirmPassword" style="width: 253px" placeholder="confirm password"> <input type="password" name="confirmPassword" class="tight-form-input last" required ng-model="formModel.confirmPassword" id="confirmPassword" style="width: 253px" placeholder="confirm password">
</li> </li>
</ul> </ul>
<div class="clearfix"></div> <div class="clearfix"></div>
@ -80,7 +80,7 @@
</div> </div>
<div style="margin-left: 147px; width: 254px;"> <div style="margin-left: 147px; width: 254px;">
<password-strength password="user.password"></password-strength> <password-strength password="formModel.password"></password-strength>
</div> </div>
<div class="login-submit-button-row"> <div class="login-submit-button-row">