@@ -9,48 +9,40 @@ import (
99 "github.com/gin-gonic/gin"
1010 "github.com/gotify/server/v3/auth"
1111 "github.com/gotify/server/v3/auth/password"
12+ "github.com/gotify/server/v3/database"
1213 "github.com/gotify/server/v3/model"
1314)
1415
15- // The UserDatabase interface for encapsulating database access.
16- type UserDatabase interface {
17- GetUsers () ([]* model.User , error )
18- GetUserByID (id uint ) (* model.User , error )
19- GetUserByName (name string ) (* model.User , error )
20- DeleteUserByID (id uint ) error
21- UpdateUser (user * model.User ) error
22- CreateUser (user * model.User ) error
23- CountUser (condition ... any ) (int64 , error )
24- }
16+ var errCannotDeleteLastAdmin = errors .New ("cannot delete last admin" )
2517
2618// UserChangeNotifier notifies listeners for user changes.
2719type UserChangeNotifier struct {
28- userDeletedCallbacks []func (uid uint ) error
29- userAddedCallbacks []func (uid uint ) error
20+ userDeletedCallbacks []func (tx * database. GormDatabase , uid uint ) error
21+ userAddedCallbacks []func (tx * database. GormDatabase , uid uint ) error
3022}
3123
3224// OnUserDeleted is called on user deletion.
33- func (c * UserChangeNotifier ) OnUserDeleted (cb func (uid uint ) error ) {
25+ func (c * UserChangeNotifier ) OnUserDeleted (cb func (tx * database. GormDatabase , uid uint ) error ) {
3426 c .userDeletedCallbacks = append (c .userDeletedCallbacks , cb )
3527}
3628
3729// OnUserAdded is called on user creation.
38- func (c * UserChangeNotifier ) OnUserAdded (cb func (uid uint ) error ) {
30+ func (c * UserChangeNotifier ) OnUserAdded (cb func (tx * database. GormDatabase , uid uint ) error ) {
3931 c .userAddedCallbacks = append (c .userAddedCallbacks , cb )
4032}
4133
42- func (c * UserChangeNotifier ) fireUserDeleted (uid uint ) error {
34+ func (c * UserChangeNotifier ) fireUserDeleted (tx * database. GormDatabase , uid uint ) error {
4335 for _ , cb := range c .userDeletedCallbacks {
44- if err := cb (uid ); err != nil {
36+ if err := cb (tx , uid ); err != nil {
4537 return err
4638 }
4739 }
4840 return nil
4941}
5042
51- func (c * UserChangeNotifier ) fireUserAdded (uid uint ) error {
43+ func (c * UserChangeNotifier ) fireUserAdded (tx * database. GormDatabase , uid uint ) error {
5244 for _ , cb := range c .userAddedCallbacks {
53- if err := cb (uid ); err != nil {
45+ if err := cb (tx , uid ); err != nil {
5446 return err
5547 }
5648 }
@@ -59,7 +51,7 @@ func (c *UserChangeNotifier) fireUserAdded(uid uint) error {
5951
6052// The UserAPI provides handlers for managing users.
6153type UserAPI struct {
62- DB UserDatabase
54+ DB * database. GormDatabase
6355 PasswordStrength int
6456 UserChangeNotifier * UserChangeNotifier
6557 Registration bool
@@ -233,11 +225,14 @@ func (a *UserAPI) CreateUser(ctx *gin.Context) {
233225 }
234226
235227 if existingUser == nil {
236- if success := successOrAbort (ctx , 500 , a .DB .CreateUser (internal )); ! success {
237- return
238- }
239- if err := a .UserChangeNotifier .fireUserAdded (internal .ID ); err != nil {
240- ctx .AbortWithError (500 , err )
228+ // this should not cause conflicts, so no need to retry
229+ err = a .DB .Txn (func (txdb * database.GormDatabase ) error {
230+ if err := txdb .CreateUser (internal ); err != nil {
231+ return err
232+ }
233+ return a .UserChangeNotifier .fireUserAdded (txdb , internal .ID )
234+ })
235+ if success := successOrAbort (ctx , 500 , err ); ! success {
241236 return
242237 }
243238 ctx .JSON (200 , toExternalUser (internal ))
@@ -343,19 +338,26 @@ func (a *UserAPI) DeleteUserByID(ctx *gin.Context) {
343338 return
344339 }
345340 if user != nil {
346- adminCount , err := a .DB .CountUser (& model.User {Admin : true })
347- if success := successOrAbort (ctx , 500 , err ); ! success {
348- return
349- }
350- if user .Admin && adminCount == 1 {
351- ctx .AbortWithError (400 , errors .New ("cannot delete last admin" ))
352- return
353- }
354- if err := a .UserChangeNotifier .fireUserDeleted (id ); err != nil {
355- ctx .AbortWithError (500 , err )
356- return
341+ for range 3 {
342+ err = a .DB .Txn (func (txdb * database.GormDatabase ) error {
343+ if err := txdb .DeleteUserByID (id ); err != nil {
344+ return err
345+ }
346+ anotherAdmin , err := txdb .GetUsers (& model.User {Admin : true })
347+ if err != nil {
348+ return err
349+ }
350+ if user .Admin && len (anotherAdmin ) == 0 {
351+ ctx .AbortWithError (400 , errCannotDeleteLastAdmin )
352+ return errCannotDeleteLastAdmin
353+ }
354+ return a .UserChangeNotifier .fireUserDeleted (txdb , id )
355+ })
356+ if err == nil || ctx .IsAborted () {
357+ return
358+ }
357359 }
358- successOrAbort (ctx , 500 , a . DB . DeleteUserByID ( id ) )
360+ successOrAbort (ctx , 500 , err )
359361 } else {
360362 ctx .AbortWithError (404 , errors .New ("user does not exist" ))
361363 }
@@ -470,15 +472,7 @@ func (a *UserAPI) UpdateUserByID(ctx *gin.Context) {
470472 return
471473 }
472474 if dbUser != nil {
473- adminCount , err := a .DB .CountUser (& model.User {Admin : true })
474- if success := successOrAbort (ctx , 500 , err ); ! success {
475- return
476- }
477- if ! updatedUser .Admin && dbUser .Admin && adminCount == 1 {
478- ctx .AbortWithError (400 , errors .New ("cannot delete last admin" ))
479- return
480- }
481-
475+ dbUserWasAdmin := dbUser .Admin
482476 dbUser .Name = updatedUser .Name
483477 dbUser .Admin = updatedUser .Admin
484478
@@ -494,10 +488,35 @@ func (a *UserAPI) UpdateUserByID(ctx *gin.Context) {
494488 }
495489 dbUser .Pass = pw
496490 }
497- if success := successOrAbort (ctx , 500 , a .DB .UpdateUser (dbUser )); ! success {
498- return
491+
492+ for range 3 {
493+ err = a .DB .Txn (func (txdb * database.GormDatabase ) error {
494+ if err := txdb .UpdateUser (dbUser ); err != nil {
495+ return err
496+ }
497+
498+ anotherAdmin , err := txdb .GetUsers (& model.User {Admin : true })
499+ if err != nil {
500+ return err
501+ }
502+ if ! updatedUser .Admin && dbUserWasAdmin && len (anotherAdmin ) == 0 {
503+ ctx .AbortWithError (400 , errCannotDeleteLastAdmin )
504+ return errCannotDeleteLastAdmin
505+ }
506+
507+ return nil
508+ })
509+
510+ if ctx .IsAborted () {
511+ return
512+ }
513+
514+ if err == nil {
515+ ctx .JSON (200 , toExternalUser (dbUser ))
516+ return
517+ }
499518 }
500- ctx .JSON ( 200 , toExternalUser ( dbUser ) )
519+ ctx .AbortWithError ( 500 , err )
501520 } else {
502521 ctx .AbortWithError (404 , errors .New ("user does not exist" ))
503522 }
0 commit comments