diff --git a/README.md b/README.md index 6d95fea..d34ccc8 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ -->
Current Version - 2021.06.08
+Current Version - 2021.06.24
A self-hosted vehicle expense tracking system with support for multiple users.
diff --git a/server/controllers/auth.go b/server/controllers/auth.go
index b98b654..b130739 100644
--- a/server/controllers/auth.go
+++ b/server/controllers/auth.go
@@ -102,6 +102,11 @@ func userLogin(c *gin.Context) {
c.JSON(http.StatusForbidden, common.NewError("login", errors.New("Not Registered email or invalid password")))
return
}
+
+ if user.IsDisabled {
+ c.JSON(http.StatusForbidden, common.NewError("login", errors.New("Your user has been disabled by the admin. Please contact them to get it re-enabled.")))
+ return
+ }
UpdateContextUserModel(c, user.ID)
token, refreshToken := common.GenToken(user.ID, user.Role)
response := models.LoginResponse{
diff --git a/server/controllers/users.go b/server/controllers/users.go
index 1ea48c4..e9ad2ad 100644
--- a/server/controllers/users.go
+++ b/server/controllers/users.go
@@ -3,12 +3,17 @@ package controllers
import (
"net/http"
+ "github.com/akhilrex/hammond/common"
"github.com/akhilrex/hammond/db"
+ "github.com/akhilrex/hammond/models"
+ "github.com/akhilrex/hammond/service"
"github.com/gin-gonic/gin"
)
func RegisterUserController(router *gin.RouterGroup) {
router.GET("/users", allUsers)
+ router.POST("/users/:id/enable", ShouldBeAdmin(), enableUser)
+ router.POST("/users/:id/disable", ShouldBeAdmin(), disableUser)
}
func allUsers(c *gin.Context) {
@@ -20,3 +25,31 @@ func allUsers(c *gin.Context) {
c.JSON(http.StatusOK, users)
}
+func enableUser(c *gin.Context) {
+ var searchByIdQuery models.SearchByIdQuery
+ if err := c.ShouldBindUri(&searchByIdQuery); err == nil {
+ err := service.SetDisabledStatusForUser(searchByIdQuery.Id, false)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, err)
+ return
+ }
+ c.JSON(http.StatusOK, gin.H{})
+ } else {
+ c.JSON(http.StatusUnprocessableEntity, common.NewValidatorError(err))
+ }
+
+}
+func disableUser(c *gin.Context) {
+ var searchByIdQuery models.SearchByIdQuery
+ if err := c.ShouldBindUri(&searchByIdQuery); err == nil {
+ err := service.SetDisabledStatusForUser(searchByIdQuery.Id, true)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, err)
+ return
+ }
+ c.JSON(http.StatusOK, gin.H{})
+ } else {
+ c.JSON(http.StatusUnprocessableEntity, common.NewValidatorError(err))
+ }
+
+}
diff --git a/server/controllers/vehicle.go b/server/controllers/vehicle.go
index f5947d5..83148b7 100644
--- a/server/controllers/vehicle.go
+++ b/server/controllers/vehicle.go
@@ -20,6 +20,7 @@ func RegisterVehicleController(router *gin.RouterGroup) {
router.GET("/vehicles/:id/users", getVehicleUsers)
router.POST("/vehicles/:id/users/:subId", shareVehicle)
router.DELETE("/vehicles/:id/users/:subId", unshareVehicle)
+ router.POST("/vehicles/:id/users/:subId/transfer", transferVehicle)
router.GET("/me/vehicles", getMyVehicles)
router.GET("/me/stats", getMystats)
@@ -409,6 +410,22 @@ func shareVehicle(c *gin.Context) {
c.JSON(http.StatusUnprocessableEntity, common.NewValidatorError(err))
}
}
+func transferVehicle(c *gin.Context) {
+ var searchByIdQuery models.SubItemQuery
+
+ if err := c.ShouldBindUri(&searchByIdQuery); err == nil {
+
+ err := service.TransferVehicle(searchByIdQuery.Id, c.MustGet("userId").(string), searchByIdQuery.SubId)
+ if err != nil {
+ c.JSON(http.StatusUnprocessableEntity, common.NewError("shareVehicle", err))
+ return
+ }
+ c.JSON(http.StatusOK, gin.H{})
+
+ } else {
+ c.JSON(http.StatusUnprocessableEntity, common.NewValidatorError(err))
+ }
+}
func unshareVehicle(c *gin.Context) {
var searchByIdQuery models.SubItemQuery
diff --git a/server/db/dbModels.go b/server/db/dbModels.go
index 72188d0..d0966d1 100644
--- a/server/db/dbModels.go
+++ b/server/db/dbModels.go
@@ -17,6 +17,7 @@ type User struct {
Role Role `json:"role"`
Name string `json:"name"`
Vehicles []Vehicle `gorm:"many2many:user_vehicles;" json:"vehicles"`
+ IsDisabled bool `json:"isDisabled"`
}
func (b *User) MarshalJSON() ([]byte, error) {
diff --git a/server/db/dbfunctions.go b/server/db/dbfunctions.go
index ee06921..f2142e7 100644
--- a/server/db/dbfunctions.go
+++ b/server/db/dbfunctions.go
@@ -33,6 +33,11 @@ func FindOneUser(condition interface{}) (User, error) {
err := DB.Where(condition).First(&model).Error
return model, err
}
+func SetDisabledStatusForUser(userId string, isDisabled bool) error {
+ //Cannot do this for admin
+ tx := DB.Debug().Model(&User{}).Where("id= ? and role=?", userId, USER).Update("is_disabled", isDisabled)
+ return tx.Error
+}
func GetAllUsers() (*[]User, error) {
sorting := "created_at desc"
@@ -92,6 +97,16 @@ func ShareVehicle(vehicleId, userId string) error {
}
return nil
}
+func TransferVehicle(vehicleId, ownerId, newUserID string) error {
+
+ tx := DB.Model(&UserVehicle{}).Where("vehicle_id = ? AND user_id = ?", vehicleId, ownerId).Update("is_owner", false)
+ if tx.Error != nil {
+ return tx.Error
+ }
+ tx = DB.Model(&UserVehicle{}).Where("vehicle_id = ? AND user_id = ?", vehicleId, newUserID).Update("is_owner", true)
+
+ return tx.Error
+}
func UnshareVehicle(vehicleId, userId string) error {
var mapping UserVehicle
diff --git a/server/db/migrations.go b/server/db/migrations.go
index 6bb9d83..cddd3b3 100644
--- a/server/db/migrations.go
+++ b/server/db/migrations.go
@@ -14,10 +14,10 @@ type localMigration struct {
}
var migrations = []localMigration{
- // {
- // Name: "2020_11_03_04_42_SetDefaultDownloadStatus",
- // Query: "update podcast_items set download_status=2 where download_path!='' and download_status=0",
- // },
+ {
+ Name: "2021_06_24_04_42_SetUserDisabledFalse",
+ Query: "update users set is_disabled=0",
+ },
}
func RunMigrations() {
diff --git a/server/service/userService.go b/server/service/userService.go
index 1799371..6799afa 100644
--- a/server/service/userService.go
+++ b/server/service/userService.go
@@ -45,3 +45,6 @@ func UpdatePassword(id, password string) (bool, error) {
}
return true, nil
}
+func SetDisabledStatusForUser(userId string, isDisabled bool) error {
+ return db.SetDisabledStatusForUser(userId, isDisabled)
+}
diff --git a/server/service/vehicleService.go b/server/service/vehicleService.go
index 536c4c6..5866d7b 100644
--- a/server/service/vehicleService.go
+++ b/server/service/vehicleService.go
@@ -1,6 +1,8 @@
package service
import (
+ "fmt"
+
"github.com/akhilrex/hammond/db"
"github.com/akhilrex/hammond/models"
"gorm.io/gorm/clause"
@@ -59,6 +61,17 @@ func DeleteVehicle(vehicleId string) error {
func ShareVehicle(vehicleId, userId string) error {
return db.ShareVehicle(vehicleId, userId)
}
+func TransferVehicle(vehicleId, ownerId, newUserID string) error {
+ vehicleOwnerId, err := GetVehicleOwner(vehicleId)
+ if err != nil {
+ return err
+ }
+ if vehicleOwnerId != ownerId {
+ return fmt.Errorf("Only vehicle owner can transfer the vehicle")
+ }
+
+ return db.TransferVehicle(vehicleId, ownerId, newUserID)
+}
func UnshareVehicle(vehicleId, userId string) error {
return db.UnshareVehicle(vehicleId, userId)
}
diff --git a/ui/src/components/shareVehicle.vue b/ui/src/components/shareVehicle.vue
index baec3e9..d324b50 100644
--- a/ui/src/components/shareVehicle.vue
+++ b/ui/src/components/shareVehicle.vue
@@ -50,19 +50,61 @@ export default {
axios.delete(url).then((data) => {})
}
},
+ transferVehicle(model) {
+ if (!model.isShared) {
+ return
+ }
+ this.$buefy.dialog.confirm({
+ title: 'Transfer Vehicle',
+ message: 'Are you sure you want to do this? You will lose ownership and all editing rights if you confirm.',
+ cancelText: 'Cancel',
+ confirmText: 'Go Ahead',
+ onConfirm: () => {
+ var url = `/api/vehicles/${this.vehicle.id}/users/${model.id}/transfer`
+ axios
+ .post(url, {})
+ .then((data) => {
+ this.$buefy.toast.open({
+ message: 'Vehicle Transferred Successfully',
+ type: 'is-success',
+ duration: 3000,
+ })
+ setTimeout(() => {
+ this.$router.go()
+ }, 3000);
+ })
+ .catch((ex) => {
+ this.$buefy.toast.open({
+ duration: 5000,
+ message: ex.message,
+ position: 'is-bottom',
+ type: 'is-danger',
+ })
+ })
+ },
+ })
+ },
},
}
-
- There was an error logging in to your account.
+ There was an error logging in to your account. {{errorMessage}}
Share {{ vehicle.nickname }}
Current Version
- 2021.06.08
+ 2021.06.24
Website
diff --git a/ui/src/router/views/users.vue b/ui/src/router/views/users.vue
index d96ce03..6aa18ed 100644
--- a/ui/src/router/views/users.vue
+++ b/ui/src/router/views/users.vue
@@ -53,6 +53,36 @@ export default {
formatDate(date) {
return parseAndFormatDate(date)
},
+ changeDisabledStatus(userId,status){
+ this.$buefy.dialog.confirm({
+ title: status?'Disable User':"Enable User",
+ message: 'Are you sure you want to do this?',
+ cancelText: 'Cancel',
+ confirmText: 'Go Ahead',
+ onConfirm: () => {
+
+ var url = `/api/users/${userId}/${status?"disable":"enable"}`
+ axios
+ .post(url, {})
+ .then((data) => {
+ this.$buefy.toast.open({
+ message: status?"User disabled successfully":'User enabled successfully',
+ type: 'is-success',
+ duration: 3000,
+ })
+ this.getUsers();
+ })
+ .catch((ex) => {
+ this.$buefy.toast.open({
+ duration: 5000,
+ message: ex.message,
+ position: 'is-bottom',
+ type: 'is-danger',
+ })
+ })
+ },
+ })
+ },
resetUserForm() {
this.registerModel = {
name: '',
@@ -157,7 +187,7 @@ export default {
-