mirror of
https://github.com/pushbits/server.git
synced 2025-04-30 10:46:55 +02:00
Partially implement updates of models
This commit is contained in:
parent
6a77df8373
commit
d621333b6e
9 changed files with 158 additions and 19 deletions
|
@ -1,7 +1,6 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
@ -15,6 +14,7 @@ import (
|
||||||
type ApplicationDatabase interface {
|
type ApplicationDatabase interface {
|
||||||
CreateApplication(application *model.Application) error
|
CreateApplication(application *model.Application) error
|
||||||
DeleteApplication(application *model.Application) error
|
DeleteApplication(application *model.Application) error
|
||||||
|
UpdateApplication(application *model.Application) error
|
||||||
GetApplicationByID(ID uint) (*model.Application, error)
|
GetApplicationByID(ID uint) (*model.Application, error)
|
||||||
GetApplicationByToken(token string) (*model.Application, error)
|
GetApplicationByToken(token string) (*model.Application, error)
|
||||||
}
|
}
|
||||||
|
@ -76,15 +76,11 @@ func (h *ApplicationHandler) DeleteApplication(ctx *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
application, err := h.DB.GetApplicationByID(deleteApplication.ID)
|
application, err := h.DB.GetApplicationByID(deleteApplication.ID)
|
||||||
|
if success := successOrAbort(ctx, http.StatusNotFound, err); !success {
|
||||||
if success := successOrAbort(ctx, http.StatusBadRequest, err); !success {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user := authentication.GetUser(ctx)
|
if !isCurrentUser(ctx, application.UserID) {
|
||||||
|
|
||||||
if user.ID != application.ID {
|
|
||||||
ctx.AbortWithError(http.StatusForbidden, errors.New("only owner can delete application"))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,3 +96,32 @@ func (h *ApplicationHandler) DeleteApplication(ctx *gin.Context) {
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, gin.H{})
|
ctx.JSON(http.StatusOK, gin.H{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateApplication updates an application with a certain ID.
|
||||||
|
func (h *ApplicationHandler) UpdateApplication(ctx *gin.Context) {
|
||||||
|
var updateApplication model.UpdateApplication
|
||||||
|
|
||||||
|
if success := successOrAbort(ctx, http.StatusBadRequest, ctx.BindUri(&updateApplication)); !success {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
application, err := h.DB.GetApplicationByID(updateApplication.ID)
|
||||||
|
if success := successOrAbort(ctx, http.StatusNotFound, err); !success {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isCurrentUser(ctx, application.UserID) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Updating application %s.\n", application.Name)
|
||||||
|
|
||||||
|
// TODO: Handle unbound members.
|
||||||
|
application.Name = updateApplication.Name
|
||||||
|
|
||||||
|
if success := successOrAbort(ctx, http.StatusInternalServerError, h.DB.UpdateApplication(application)); !success {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, gin.H{})
|
||||||
|
}
|
||||||
|
|
62
api/user.go
62
api/user.go
|
@ -5,6 +5,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/eikendev/pushbits/authentication"
|
||||||
"github.com/eikendev/pushbits/model"
|
"github.com/eikendev/pushbits/model"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
@ -14,6 +15,7 @@ import (
|
||||||
type UserDatabase interface {
|
type UserDatabase interface {
|
||||||
CreateUser(user model.ExternalUserWithCredentials) (*model.User, error)
|
CreateUser(user model.ExternalUserWithCredentials) (*model.User, error)
|
||||||
DeleteUser(user *model.User) error
|
DeleteUser(user *model.User) error
|
||||||
|
UpdateUser(user *model.User) error
|
||||||
GetUserByID(ID uint) (*model.User, error)
|
GetUserByID(ID uint) (*model.User, error)
|
||||||
GetUserByName(name string) (*model.User, error)
|
GetUserByName(name string) (*model.User, error)
|
||||||
GetApplications(user *model.User) ([]model.Application, error)
|
GetApplications(user *model.User) ([]model.Application, error)
|
||||||
|
@ -36,6 +38,16 @@ func (h *UserHandler) userExists(name string) bool {
|
||||||
return user != nil
|
return user != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *UserHandler) ensureIsNotLastAdmin(ctx *gin.Context) (int, error) {
|
||||||
|
if count, err := h.DB.AdminUserCount(); err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
} else if count == 1 {
|
||||||
|
return http.StatusBadRequest, errors.New("instance needs at least one privileged user")
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
// CreateUser creates a new user.
|
// CreateUser creates a new user.
|
||||||
func (h *UserHandler) CreateUser(ctx *gin.Context) {
|
func (h *UserHandler) CreateUser(ctx *gin.Context) {
|
||||||
var externalUser model.ExternalUserWithCredentials
|
var externalUser model.ExternalUserWithCredentials
|
||||||
|
@ -67,16 +79,14 @@ func (h *UserHandler) DeleteUser(ctx *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := h.DB.GetUserByID(deleteUser.ID)
|
user, err := h.DB.GetUserByID(deleteUser.ID)
|
||||||
if success := successOrAbort(ctx, http.StatusBadRequest, err); !success {
|
if success := successOrAbort(ctx, http.StatusNotFound, err); !success {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Last privileged user must not be deleted.
|
||||||
if user.IsAdmin {
|
if user.IsAdmin {
|
||||||
if count, err := h.DB.AdminUserCount(); err != nil {
|
if status, err := h.ensureIsNotLastAdmin(ctx); err != nil {
|
||||||
ctx.AbortWithError(http.StatusInternalServerError, err)
|
ctx.AbortWithError(status, err)
|
||||||
return
|
|
||||||
} else if count == 1 {
|
|
||||||
ctx.AbortWithError(http.StatusBadRequest, errors.New("cannot delete last admin user"))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,3 +110,43 @@ func (h *UserHandler) DeleteUser(ctx *gin.Context) {
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, gin.H{})
|
ctx.JSON(http.StatusOK, gin.H{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateUser updates a user with a certain ID.
|
||||||
|
func (h *UserHandler) UpdateUser(ctx *gin.Context) {
|
||||||
|
var updateUser model.UpdateUser
|
||||||
|
|
||||||
|
if success := successOrAbort(ctx, http.StatusBadRequest, ctx.BindUri(&updateUser)); !success {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := h.DB.GetUserByID(updateUser.ID)
|
||||||
|
if success := successOrAbort(ctx, http.StatusNotFound, err); !success {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
currentUser := authentication.GetUser(ctx)
|
||||||
|
|
||||||
|
// Last privileged user must not be taken privileges. Assumes that the current user has privileges.
|
||||||
|
if user.ID == currentUser.ID && !updateUser.IsAdmin {
|
||||||
|
if status, err := h.ensureIsNotLastAdmin(ctx); err != nil {
|
||||||
|
ctx.AbortWithError(status, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Updating user %s.\n", user.Name)
|
||||||
|
|
||||||
|
// If users can later update their own user, make sure they cannot give themselves privileges.
|
||||||
|
// TODO: Handle unbound members.
|
||||||
|
// TODO: Allow updating of password.
|
||||||
|
// TODO: Update rooms of applications when the user's MatrixID changes.
|
||||||
|
user.Name = updateUser.Name
|
||||||
|
user.MatrixID = updateUser.MatrixID
|
||||||
|
user.IsAdmin = updateUser.IsAdmin
|
||||||
|
|
||||||
|
if success := successOrAbort(ctx, http.StatusInternalServerError, h.DB.UpdateUser(user)); !success {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, gin.H{})
|
||||||
|
}
|
||||||
|
|
16
api/util.go
16
api/util.go
|
@ -1,6 +1,11 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/eikendev/pushbits/authentication"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -11,3 +16,14 @@ func successOrAbort(ctx *gin.Context, code int, err error) bool {
|
||||||
|
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isCurrentUser(ctx *gin.Context, ID uint) bool {
|
||||||
|
user := authentication.GetUser(ctx)
|
||||||
|
|
||||||
|
if user.ID != ID {
|
||||||
|
ctx.AbortWithError(http.StatusForbidden, errors.New("only owner can delete application"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
12
assert/assert.go
Normal file
12
assert/assert.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package assert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Assert panics if condition is false.
|
||||||
|
func Assert(condition bool) {
|
||||||
|
if !condition {
|
||||||
|
panic(errors.New("assertion failed"))
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package database
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"github.com/eikendev/pushbits/assert"
|
||||||
"github.com/eikendev/pushbits/model"
|
"github.com/eikendev/pushbits/model"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
@ -18,28 +19,37 @@ func (d *Database) DeleteApplication(application *model.Application) error {
|
||||||
return d.gormdb.Delete(application).Error
|
return d.gormdb.Delete(application).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateApplication updates an application.
|
||||||
|
func (d *Database) UpdateApplication(application *model.Application) error {
|
||||||
|
return d.gormdb.Save(application).Error
|
||||||
|
}
|
||||||
|
|
||||||
// GetApplicationByID returns the application with the given ID or nil.
|
// GetApplicationByID returns the application with the given ID or nil.
|
||||||
func (d *Database) GetApplicationByID(ID uint) (*model.Application, error) {
|
func (d *Database) GetApplicationByID(ID uint) (*model.Application, error) {
|
||||||
var app model.Application
|
var application model.Application
|
||||||
|
|
||||||
err := d.gormdb.First(&app, ID).Error
|
err := d.gormdb.First(&application, ID).Error
|
||||||
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &app, err
|
assert.Assert(application.ID == ID)
|
||||||
|
|
||||||
|
return &application, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetApplicationByToken returns the application with the given token or nil.
|
// GetApplicationByToken returns the application with the given token or nil.
|
||||||
func (d *Database) GetApplicationByToken(token string) (*model.Application, error) {
|
func (d *Database) GetApplicationByToken(token string) (*model.Application, error) {
|
||||||
var app model.Application
|
var application model.Application
|
||||||
|
|
||||||
err := d.gormdb.Where("token = ?", token).First(&app).Error
|
err := d.gormdb.Where("token = ?", token).First(&application).Error
|
||||||
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &app, err
|
assert.Assert(application.Token == token)
|
||||||
|
|
||||||
|
return &application, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package database
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"github.com/eikendev/pushbits/assert"
|
||||||
"github.com/eikendev/pushbits/model"
|
"github.com/eikendev/pushbits/model"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
@ -24,6 +25,11 @@ func (d *Database) DeleteUser(user *model.User) error {
|
||||||
return d.gormdb.Delete(user).Error
|
return d.gormdb.Delete(user).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateUser updates a user.
|
||||||
|
func (d *Database) UpdateUser(user *model.User) error {
|
||||||
|
return d.gormdb.Save(user).Error
|
||||||
|
}
|
||||||
|
|
||||||
// GetUserByID returns the user with the given ID or nil.
|
// GetUserByID returns the user with the given ID or nil.
|
||||||
func (d *Database) GetUserByID(ID uint) (*model.User, error) {
|
func (d *Database) GetUserByID(ID uint) (*model.User, error) {
|
||||||
var user model.User
|
var user model.User
|
||||||
|
@ -34,6 +40,8 @@ func (d *Database) GetUserByID(ID uint) (*model.User, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert.Assert(user.ID == ID)
|
||||||
|
|
||||||
return &user, err
|
return &user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,6 +55,8 @@ func (d *Database) GetUserByName(name string) (*model.User, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert.Assert(user.Name == name)
|
||||||
|
|
||||||
return &user, err
|
return &user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,3 +18,9 @@ type CreateApplication struct {
|
||||||
type DeleteApplication struct {
|
type DeleteApplication struct {
|
||||||
ID uint `uri:"id"`
|
ID uint `uri:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateApplication is used to process queries for updating applications.
|
||||||
|
type UpdateApplication struct {
|
||||||
|
ID uint `uri:"id" binding:"required"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
|
@ -71,3 +71,11 @@ func (u *User) IntoExternalUser() *ExternalUser {
|
||||||
type DeleteUser struct {
|
type DeleteUser struct {
|
||||||
ID uint `uri:"id"`
|
ID uint `uri:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateUser is used to process queries for updating users.
|
||||||
|
type UpdateUser struct {
|
||||||
|
ID uint `uri:"id" binding:"required"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
IsAdmin bool `json:"is_admin"`
|
||||||
|
MatrixID string `json:"matrix_id"`
|
||||||
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ func Create(debug bool, db *database.Database, dp *dispatcher.Dispatcher) *gin.E
|
||||||
{
|
{
|
||||||
applicationGroup.POST("", applicationHandler.CreateApplication)
|
applicationGroup.POST("", applicationHandler.CreateApplication)
|
||||||
applicationGroup.DELETE("/:id", applicationHandler.DeleteApplication)
|
applicationGroup.DELETE("/:id", applicationHandler.DeleteApplication)
|
||||||
|
applicationGroup.PUT("/:id", applicationHandler.UpdateApplication)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.POST("/message", auth.RequireApplicationToken(), notificationHandler.CreateNotification)
|
r.POST("/message", auth.RequireApplicationToken(), notificationHandler.CreateNotification)
|
||||||
|
@ -44,6 +45,7 @@ func Create(debug bool, db *database.Database, dp *dispatcher.Dispatcher) *gin.E
|
||||||
{
|
{
|
||||||
userGroup.POST("", userHandler.CreateUser)
|
userGroup.POST("", userHandler.CreateUser)
|
||||||
userGroup.DELETE("/:id", userHandler.DeleteUser)
|
userGroup.DELETE("/:id", userHandler.DeleteUser)
|
||||||
|
userGroup.PUT("/:id", userHandler.UpdateUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
|
Loading…
Add table
Reference in a new issue