diff --git a/.gitignore b/.gitignore index af1682391a296805c7e98e14e0626b0f50118ea6..a6902832f325541bb40c94e75d382ac5fd86260d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ vestibule __debug_bin miscellaneous/mock_onlyoffice/data +data \ No newline at end of file diff --git a/data/test.db b/data/test.db index 63e82905f495f41d3f1acdcacdfc0c12b7eed19b..8616989263916b184bb39769ac9d9f20f3a3e14e 100644 Binary files a/data/test.db and b/data/test.db differ diff --git a/data/users.db b/data/users.db index 1fd01c942aefe82b974eb7234f74ad9f15356dd3..e9762194df4f577e3e5737e17077abe89a10a2e7 100644 Binary files a/data/users.db and b/data/users.db differ diff --git a/internal/mocks/mocks.go b/internal/mocks/mocks.go index b8af888e350b26a2731ffe173df15bb7f2cdeb1b..9ccdf8144df115be8922e8ed290728cbe8f59b3f 100644 --- a/internal/mocks/mocks.go +++ b/internal/mocks/mocks.go @@ -10,6 +10,9 @@ import ( "github.com/nicolaspernoud/vestibule/pkg/middlewares" ) +const literralContentType = "Content-Type" +const literralApplicationJson = "application/json" + var ( hostname = os.Getenv("HOSTNAME") port string @@ -38,12 +41,12 @@ func CreateMockOAuth2() *http.ServeMux { // Returns access token back to the user mux.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/x-www-form-urlencoded") + w.Header().Set(literralContentType, "application/x-www-form-urlencoded") w.Write([]byte("access_token=mocktoken&scope=user&token_type=bearer")) }) // Returns userinfo back to the user mux.HandleFunc("/userinfo", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") + w.Header().Set(literralContentType, literralApplicationJson) w.Write([]byte(`{ "displayName": "Us ER", "memberOf": [ @@ -56,7 +59,7 @@ func CreateMockOAuth2() *http.ServeMux { }) // Returns userinfo back to the user (with an admin user) mux.HandleFunc("/admininfo", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") + w.Header().Set(literralContentType, literralApplicationJson) w.Write([]byte(`{ "displayName": "Ad MIN", "memberOf": [ @@ -82,7 +85,7 @@ func CreateMockAPI() *http.ServeMux { frameSource := "https://static." + hostname + ":" + port mux.Handle("/", middlewares.Cors(func() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") + w.Header().Set(literralContentType, literralApplicationJson) w.Write([]byte(`{ "foo": "bar", "bar": "foo" diff --git a/internal/models/bankAccounts.go b/internal/models/bankAccounts.go index 7e7d17da23ea28217571dde2874f76357695b6a9..c3164f16cf2eeefb72080f4201a1f721b778558a 100644 --- a/internal/models/bankAccounts.go +++ b/internal/models/bankAccounts.go @@ -16,128 +16,228 @@ func (d *DataHandler) HandleBankAccounts(w http.ResponseWriter, r *http.Request) case "GET": switch auth.GetLoggedUserTechnical(w, r).Role { case "ADMIN": - if id != 0 { - var o BankAccount - if err := d.db.Preload("Operations").First(&o, id).Error; err != nil { - http.Error(w, "id does not exist", http.StatusNotFound) - return - } - json.NewEncoder(w).Encode(o) - } else { - var o []BankAccount - d.db.Preload("Operations").Find(&o) - json.NewEncoder(w).Encode(o) - } + d.getBankAccountAdmin(w, r, id) case "BANKER": - user := d.getLoggedUser(w, r).(UserBanker) - if id != 0 { - var o BankAccount - if err := d.db.Preload("Operations").First(&o, id).Error; err != nil { - http.Error(w, "id does not exist", http.StatusNotFound) - return - } - // Check that the bank account belong to a one of the banker's client - var userClient UserClient - if err := d.db.Where("id = ? and user_banker_id = ?", o.UserClientID, user.ID).First(&userClient).Error; err != nil { - http.Error(w, "You can not access this ressource", http.StatusForbidden) - return - } - json.NewEncoder(w).Encode(o) - } else { - var o []BankAccount - d.db.Preload("Operations").Where("user_client_id IN (?)", d.db.Table("user_clients").Select("id").Where("user_banker_id = ?", user.ID).QueryExpr()).Find(&o) - json.NewEncoder(w).Encode(o) - } + d.getBankAccountBanker(w, r, id) case "CLIENT": - user := d.getLoggedUser(w, r).(UserClient) - if id != 0 { - var o BankAccount - if err := d.db.Preload("Operations").Where("id = ? AND user_client_id = ?", id, user.ID).First(&o).Error; err != nil { - http.Error(w, "You can not access this ressource", http.StatusForbidden) - return - } - json.NewEncoder(w).Encode(o) - } else { - var o []BankAccount - d.db.Preload("Operations").Where("user_client_id = ?", user.ID).Find(&o) - json.NewEncoder(w).Encode(o) - } + d.getBankAccountClient(w, r, id) default: - http.Error(w, "Could not get role of logged user", http.StatusInternalServerError) + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) } case "POST": switch auth.GetLoggedUserTechnical(w, r).Role { case "ADMIN": - var o BankAccount - err := json.NewDecoder(r.Body).Decode(&o) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - if o.UserClientID != 0 { - d.db.Create(&o) - } else { - http.Error(w, "id of UserClient is missing", http.StatusNotFound) - } + d.postBankAccountAdmin(w, r, id) case "BANKER": - user := d.getLoggedUser(w, r).(UserBanker) - var o BankAccount - err := json.NewDecoder(r.Body).Decode(&o) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - - if o.UserClientID != 0 { - var userClient UserClient - if err := d.db.Where("id = ? and user_banker_id = ?", o.UserClientID, user.ID).First(&userClient).Error; err != nil { - http.Error(w, "You can not access this ressource", http.StatusForbidden) - return - } - - d.db.Create(&o) - } else { - http.Error(w, "id of UserClient is missing", http.StatusNotFound) - } + d.postBankAccountBanker(w, r, id) case "CLIENT": - http.Error(w, "You're not authorize to execute this method on this ressource.", http.StatusMethodNotAllowed) + d.postBankAccountClient(w, r, id) default: - http.Error(w, "Could not get role of logged user", http.StatusInternalServerError) + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) + } + case "PUT": + switch auth.GetLoggedUserTechnical(w, r).Role { + case "ADMIN": + d.putBankAccountAdmin(w, r, id) + case "BANKER": + d.putBankAccountBanker(w, r, id) + case "CLIENT": + d.putBankAccountClient(w, r, id) + default: + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) } case "DELETE": switch auth.GetLoggedUserTechnical(w, r).Role { case "ADMIN": - if id != 0 { - var o BankAccount - if err := d.db.First(&o, id).Error; err != nil { - http.Error(w, "id does not exist", http.StatusNotFound) - return - } - d.db.Delete(&o) - } else { - http.Error(w, "id is missing", http.StatusNotFound) - } + d.deleteBankAccountAdmin(w, r, id) case "BANKER": - user := d.getLoggedUser(w, r).(UserBanker) - if id != 0 { - var o BankAccount - if err := d.db.First(&o, id).Error; err != nil { - http.Error(w, "id does not exist", http.StatusNotFound) - return - } - var userClient UserClient - if err := d.db.Where("id = ? and user_banker_id = ?", o.UserClientID, user.ID).First(&userClient).Error; err != nil { - http.Error(w, "You can not access this ressource", http.StatusForbidden) - return - } - d.db.Delete(&o) - } else { - http.Error(w, "id is missing", http.StatusNotFound) - } + d.deleteBankAccountBanker(w, r, id) case "CLIENT": - http.Error(w, "You're not authorize to execute this method on this ressource.", http.StatusMethodNotAllowed) + d.deleteBankAccountClient(w, r, id) default: - http.Error(w, "Could not get role of logged user", http.StatusInternalServerError) + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) } default: http.Error(w, "method not allowed", 400) } } + +func (d *DataHandler) getBankAccountAdmin(w http.ResponseWriter, r *http.Request, id int) { + if id != 0 { + var o BankAccount + if err := d.db.Preload("Operations").First(&o, id).Error; err != nil { + http.Error(w, ErrorIDDoesNotExist, http.StatusNotFound) + return + } + json.NewEncoder(w).Encode(o) + } else { + var o []BankAccount + d.db.Preload("Operations").Find(&o) + json.NewEncoder(w).Encode(o) + } +} + +func (d *DataHandler) getBankAccountBanker(w http.ResponseWriter, r *http.Request, id int) { + user := d.getLoggedUser(w, r).(UserBanker) + if id != 0 { + var o BankAccount + if err := d.db.Preload("Operations").First(&o, id).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + // Check that the bank account belong to a one of the banker's client + var userClient UserClient + if err := d.db.Where(reqIDAndBankerID, o.UserClientID, user.ID).First(&userClient).Error; err != nil { + http.Error(w, ErrorCannotAccessRessource, http.StatusForbidden) + return + } + json.NewEncoder(w).Encode(o) + } else { + var o []BankAccount + d.db.Preload("Operations").Where("user_client_id IN (?)", d.db.Table("user_clients").Select("id").Where("user_banker_id = ?", user.ID).QueryExpr()).Find(&o) + json.NewEncoder(w).Encode(o) + } +} + +func (d *DataHandler) getBankAccountClient(w http.ResponseWriter, r *http.Request, id int) { + user := d.getLoggedUser(w, r).(UserClient) + if id != 0 { + var o BankAccount + if err := d.db.Preload("Operations").Where("id = ? AND user_client_id = ?", id, user.ID).First(&o).Error; err != nil { + http.Error(w, ErrorCannotAccessRessource, http.StatusForbidden) + return + } + json.NewEncoder(w).Encode(o) + } else { + var o []BankAccount + d.db.Preload("Operations").Where("user_client_id = ?", user.ID).Find(&o) + json.NewEncoder(w).Encode(o) + } +} + +func (d *DataHandler) postBankAccountAdmin(w http.ResponseWriter, r *http.Request, id int) { + var o BankAccount + err := json.NewDecoder(r.Body).Decode(&o) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + if o.UserClientID == 0 { + http.Error(w, ErrorUserIDIsMissing, http.StatusInternalServerError) + } + d.db.Create(&o) + d.db.Last(&o) + json.NewEncoder(w).Encode(o) +} + +func (d *DataHandler) postBankAccountBanker(w http.ResponseWriter, r *http.Request, id int) { + user := d.getLoggedUser(w, r).(UserBanker) + var o BankAccount + err := json.NewDecoder(r.Body).Decode(&o) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + if o.UserClientID == 0 { + http.Error(w, ErrorUserIDIsMissing, http.StatusInternalServerError) + + } + var userClient UserClient + if err := d.db.Where(reqIDAndBankerID, o.UserClientID, user.ID).First(&userClient).Error; err != nil { + http.Error(w, ErrorCannotAccessRessource, http.StatusForbidden) + return + } + + d.db.Create(&o) + d.db.Last(&o) + json.NewEncoder(w).Encode(o) +} + +func (d *DataHandler) postBankAccountClient(w http.ResponseWriter, r *http.Request, id int) { + http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) +} + +func (d *DataHandler) putBankAccountAdmin(w http.ResponseWriter, r *http.Request, id int) { + var o BankAccount + if err := d.db.Preload("Operations").First(&o, id).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + var bankAccount BankAccount + err := json.NewDecoder(r.Body).Decode(&bankAccount) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + o.BankOverdraft = bankAccount.BankOverdraft + o.Type = bankAccount.Type + if o.UserClientID == 0 { + http.Error(w, ErrorUserIDIsMissing, http.StatusInternalServerError) + } + d.db.Save(&o) + json.NewEncoder(w).Encode(o) +} + +func (d *DataHandler) putBankAccountBanker(w http.ResponseWriter, r *http.Request, id int) { + user := d.getLoggedUser(w, r).(UserBanker) + var o BankAccount + if err := d.db.Preload("Operations").First(&o, id).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + var bankAccount BankAccount + err := json.NewDecoder(r.Body).Decode(&bankAccount) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + o.BankOverdraft = bankAccount.BankOverdraft + o.Type = bankAccount.Type + if o.UserClientID == 0 { + http.Error(w, ErrorUserIDIsMissing, http.StatusInternalServerError) + } + var userClient UserClient + if err := d.db.Where(reqIDAndBankerID, o.UserClientID, user.ID).First(&userClient).Error; err != nil { + http.Error(w, ErrorCannotAccessRessource, http.StatusForbidden) + return + } + d.db.Save(&o) + json.NewEncoder(w).Encode(o) +} + +func (d *DataHandler) putBankAccountClient(w http.ResponseWriter, r *http.Request, id int) { + http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) +} + +func (d *DataHandler) deleteBankAccountAdmin(w http.ResponseWriter, r *http.Request, id int) { + if id != 0 { + var o BankAccount + if err := d.db.First(&o, id).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + d.db.Delete(&o) + } else { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + } +} + +func (d *DataHandler) deleteBankAccountBanker(w http.ResponseWriter, r *http.Request, id int) { + user := d.getLoggedUser(w, r).(UserBanker) + if id != 0 { + var o BankAccount + if err := d.db.First(&o, id).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + var userClient UserClient + if err := d.db.Where(reqIDAndBankerID, o.UserClientID, user.ID).First(&userClient).Error; err != nil { + http.Error(w, ErrorCannotAccessRessource, http.StatusForbidden) + return + } + d.db.Delete(&o) + } else { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + } +} + +func (d *DataHandler) deleteBankAccountClient(w http.ResponseWriter, r *http.Request, id int) { + http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) +} diff --git a/internal/models/bankers.go b/internal/models/bankers.go index e78875c12ceec3f9a1c97a2e81542a20535b58bc..f0383677337e55b9b155185ea73724b259cfd6fd 100644 --- a/internal/models/bankers.go +++ b/internal/models/bankers.go @@ -16,89 +16,159 @@ func (d *DataHandler) HandleBankers(w http.ResponseWriter, r *http.Request) { case "GET": switch auth.GetLoggedUserTechnical(w, r).Role { case "ADMIN": - if id != 0 { - var o UserBanker - if err := d.db.Preload("UserClients").Where("id = ?", id).First(&o).Error; err != nil { - http.Error(w, "id does not exist", http.StatusNotFound) - return - } - json.NewEncoder(w).Encode(o) - } else { - var o []UserBanker - d.db.Preload("UserClients").Find(&o) - json.NewEncoder(w).Encode(o) - } + d.getBankerAdmin(w, r, id) case "BANKER": - user := d.getLoggedUser(w, r).(UserBanker) - if id != 0 { - var o UserBanker - if err := d.db.Preload("UserClients").Where("id = ?", id).First(&o).Error; err != nil { - http.Error(w, "id does not exist", http.StatusNotFound) - return - } - if o.ID != user.ID { - http.Error(w, "You can not access this ressource", http.StatusForbidden) - return - } - json.NewEncoder(w).Encode(o) - } else { - http.Error(w, "You can not access this ressource", http.StatusForbidden) - } + d.getBankerBanker(w, r, id) case "CLIENT": - user := d.getLoggedUser(w, r).(UserClient) - if id != 0 && int(user.ID) == id { - var userClient UserClient - if err := d.db.Where("user_id = ?", user.ID).First(&userClient).Error; err != nil { - http.Error(w, "id does not exist", http.StatusNotFound) - return - } - var o UserBanker - if err := d.db.Where("id = ?", userClient.UserBankerID).First(&o).Error; err != nil { - http.Error(w, "id does not exist", http.StatusNotFound) - return - } - json.NewEncoder(w).Encode(o) - } else { - http.Error(w, "You can not access this ressource", http.StatusForbidden) - } + d.getBankerClient(w, r, id) default: - http.Error(w, "Could not get role of logged user", http.StatusInternalServerError) + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) } case "POST": switch auth.GetLoggedUserTechnical(w, r).Role { case "ADMIN": - var o UserBanker - err := json.NewDecoder(r.Body).Decode(&o) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - d.db.Create(&o) + d.postBankerAdmin(w, r, id) case "BANKER", "CLIENT": - http.Error(w, "You're not authorize to execute this method on this ressource.", http.StatusMethodNotAllowed) + d.postBankerClient(w, r, id) default: - http.Error(w, "Could not get role of logged user", http.StatusInternalServerError) + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) } + case "PUT": + switch auth.GetLoggedUserTechnical(w, r).Role { + case "ADMIN": + d.putBankerAdmin(w, r, id) + case "BANKER", "CLIENT": + d.putBankerClient(w, r, id) + default: + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) + } case "DELETE": switch auth.GetLoggedUserTechnical(w, r).Role { case "ADMIN": - if id != 0 { - var o UserBanker - if err := d.db.First(&o, id).Error; err != nil { - http.Error(w, "id does not exist", http.StatusNotFound) - return - } - d.db.Delete(&o) - } else { - http.Error(w, "id is missing", http.StatusNotFound) - } + d.deleteBankerAdmin(w, r, id) case "BANKER", "CLIENT": - http.Error(w, "You're not authorize to execute this method on this ressource.", http.StatusMethodNotAllowed) + d.deleteBankerClient(w, r, id) default: - http.Error(w, "Could not get role of logged user", http.StatusInternalServerError) + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) } default: http.Error(w, "method not allowed", 400) } } + +func (d *DataHandler) getBankerAdmin(w http.ResponseWriter, r *http.Request, id int) { + if id != 0 { + var o UserBanker + if err := d.db.Preload("UserClients").Where(reqID, id).First(&o).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + json.NewEncoder(w).Encode(o) + } else { + var o []UserBanker + d.db.Preload("UserClients").Find(&o) + json.NewEncoder(w).Encode(o) + } +} + +func (d *DataHandler) getBankerBanker(w http.ResponseWriter, r *http.Request, id int) { + user := d.getLoggedUser(w, r).(UserBanker) + if id != 0 { + var o UserBanker + if err := d.db.Preload("UserClients").Where(reqID, id).First(&o).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + if o.ID != user.ID { + http.Error(w, ErrorCannotAccessRessource, http.StatusForbidden) + return + } + json.NewEncoder(w).Encode(o) + } else { + http.Error(w, ErrorCannotAccessRessource, http.StatusForbidden) + } +} + +func (d *DataHandler) getBankerClient(w http.ResponseWriter, r *http.Request, id int) { + user := d.getLoggedUser(w, r).(UserClient) + if id != 0 && int(user.ID) == id { + var userClient UserClient + if err := d.db.Where(reqUserID, user.ID).First(&userClient).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + var o UserBanker + if err := d.db.Where(reqID, userClient.UserBankerID).First(&o).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + json.NewEncoder(w).Encode(o) + } else { + http.Error(w, ErrorCannotAccessRessource, http.StatusForbidden) + } +} + +func (d *DataHandler) postBankerAdmin(w http.ResponseWriter, r *http.Request, id int) { + var o UserBanker + err := json.NewDecoder(r.Body).Decode(&o) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + // check userID is not already present in DB + var client UserClient + if err := d.db.Where(reqUserID, o.UserID).First(&client).Error; err == nil { + http.Error(w, "UserID is already bind to a Client", http.StatusNotFound) + return + } + var banker UserBanker + if err := d.db.Where(reqUserID, o.UserID).First(&banker).Error; err == nil { + http.Error(w, "UserID is already bind to a Banker", http.StatusNotFound) + return + } + d.db.Create(&o) + d.db.Last(&o) + json.NewEncoder(w).Encode(o) +} + +func (d *DataHandler) postBankerClient(w http.ResponseWriter, r *http.Request, id int) { + http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) +} + +func (d *DataHandler) putBankerAdmin(w http.ResponseWriter, r *http.Request, id int) { + var o UserBanker + if err := d.db.Preload("UserClients").Where(reqID, id).First(&o).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + var updatedBanker UserBanker + err := json.NewDecoder(r.Body).Decode(&updatedBanker) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + o.Name = updatedBanker.Name + d.db.Save(&o) + json.NewEncoder(w).Encode(o) +} + +func (d *DataHandler) putBankerClient(w http.ResponseWriter, r *http.Request, id int) { + http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) +} + +func (d *DataHandler) deleteBankerAdmin(w http.ResponseWriter, r *http.Request, id int) { + if id != 0 { + var o UserBanker + if err := d.db.First(&o, id).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + d.db.Delete(&o) + } else { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + } +} + +func (d *DataHandler) deleteBankerClient(w http.ResponseWriter, r *http.Request, id int) { + http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) +} diff --git a/internal/models/clients.go b/internal/models/clients.go index a58b1ad1fdedf66fb6c7dba5276dddebc9fbef96..996269427593b9c5bef1cb425279c0c64f1ec773 100644 --- a/internal/models/clients.go +++ b/internal/models/clients.go @@ -17,120 +17,221 @@ func (d *DataHandler) HandleClients(w http.ResponseWriter, r *http.Request) { switch auth.GetLoggedUserTechnical(w, r).Role { case "ADMIN": - if id != 0 { - var o UserClient - if err := d.db.Preload("BankAccounts").Where("id = ?", id).First(&o).Error; err != nil { - http.Error(w, "id does not exist", http.StatusNotFound) - return - } - json.NewEncoder(w).Encode(o) - } else { - var o []UserClient - d.db.Preload("BankAccounts").Find(&o) - json.NewEncoder(w).Encode(o) - } + d.getClientAdmin(w, r, id) case "BANKER": - user := d.getLoggedUser(w, r).(UserBanker) - if id != 0 { - var o UserClient - if err := d.db.Preload("BankAccounts").Where("id = ?", id).First(&o).Error; err != nil { - http.Error(w, "id does not exist", http.StatusNotFound) - return - } - if o.UserBankerID != user.ID { - http.Error(w, "You can not access this ressource", http.StatusForbidden) - return - } - json.NewEncoder(w).Encode(o) - } else { - var o []UserClient - d.db.Preload("BankAccounts").Find(&o) - json.NewEncoder(w).Encode(o) - } + d.getClientBanker(w, r, id) case "CLIENT": - user := d.getLoggedUser(w, r).(UserClient) - if id != 0 && int(user.ID) == id { - var o UserClient - if err := d.db.Preload("BankAccounts").Where("id = ?", id).First(&o).Error; err != nil { - http.Error(w, "id does not exist", http.StatusNotFound) - return - } - json.NewEncoder(w).Encode(o) - } else if id == 0 { - var o []UserClient - d.db.Preload("BankAccounts").Where("id = ?", user.ID).Find(&o) - json.NewEncoder(w).Encode(o) - } else { - http.Error(w, "You can not access this ressource", http.StatusForbidden) - return - } + d.getClientClient(w, r, id) default: - http.Error(w, "Could not get role of logged user", http.StatusInternalServerError) + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) } case "POST": switch auth.GetLoggedUserTechnical(w, r).Role { case "ADMIN": - var o UserClient - err := json.NewDecoder(r.Body).Decode(&o) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - d.db.Create(&o) + d.postClientAdmin(w, r, id) case "BANKER": - user := d.getLoggedUser(w, r).(UserBanker) - var o UserClient - err := json.NewDecoder(r.Body).Decode(&o) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - o.UserBankerID = user.ID - d.db.Create(&o) + d.postClientBanker(w, r, id) case "CLIENT": - http.Error(w, "You're not authorize to execute this method on this ressource.", http.StatusMethodNotAllowed) + d.postClientClient(w, r, id) default: - http.Error(w, "Could not get role of logged user", http.StatusInternalServerError) + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) + } + case "PUT": + switch auth.GetLoggedUserTechnical(w, r).Role { + case "ADMIN": + d.putClientAdmin(w, r, id) + case "BANKER": + d.putClientBanker(w, r, id) + case "CLIENT": + d.putClientClient(w, r, id) + default: + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) } case "DELETE": switch auth.GetLoggedUserTechnical(w, r).Role { case "ADMIN": - if id != 0 { - var o UserClient - if err := d.db.Where("id = ?", id).First(&o).Error; err != nil { - http.Error(w, "id does not exist", http.StatusNotFound) - return - } - // Delete bank accounts of user - d.db.Where("user_client_id = ?", o.ID).Delete(&BankAccount{}) - - d.db.Delete(&o) - } else { - http.Error(w, "id is missing", http.StatusNotFound) - } + d.deleteClientAdmin(w, r, id) case "BANKER": - user := d.getLoggedUser(w, r).(UserBanker) - if id != 0 { - var o UserClient - if err := d.db.Where("id = ?", id).First(&o).Error; err != nil { - http.Error(w, "id does not exist", http.StatusNotFound) - return - } - if o.UserBankerID != user.ID { - http.Error(w, "You're not authorize to execute this method on this ressource.", http.StatusMethodNotAllowed) - return - } - // Delete bank accounts of user - d.db.Where("user_client_id = ?", o.ID).Delete(&BankAccount{}) - - d.db.Delete(&o) - } else { - http.Error(w, "id is missing", http.StatusNotFound) - } + d.deleteClientBanker(w, r, id) case "CLIENT": - http.Error(w, "You're not authorize to execute this method on this ressource.", http.StatusMethodNotAllowed) + d.deleteClientClient(w, r, id) default: - http.Error(w, "Could not get role of logged user", http.StatusInternalServerError) + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) } default: http.Error(w, "method not allowed", 400) } } + +func (d *DataHandler) getClientAdmin(w http.ResponseWriter, r *http.Request, id int) { + if id != 0 { + var o UserClient + if err := d.db.Preload("BankAccounts").Where(reqID, id).First(&o).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + json.NewEncoder(w).Encode(o) + } else { + var o []UserClient + d.db.Preload("BankAccounts").Find(&o) + json.NewEncoder(w).Encode(o) + } +} + +func (d *DataHandler) getClientBanker(w http.ResponseWriter, r *http.Request, id int) { + user := d.getLoggedUser(w, r).(UserBanker) + if id != 0 { + var o UserClient + if err := d.db.Preload("BankAccounts").Where(reqID, id).First(&o).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + if o.UserBankerID != user.ID { + http.Error(w, ErrorCannotAccessRessource, http.StatusForbidden) + return + } + json.NewEncoder(w).Encode(o) + } else { + var o []UserClient + d.db.Preload("BankAccounts").Where("user_banker_id = ?", user.ID).Find(&o) + json.NewEncoder(w).Encode(o) + } +} + +func (d *DataHandler) getClientClient(w http.ResponseWriter, r *http.Request, id int) { + user := d.getLoggedUser(w, r).(UserClient) + if id != 0 && int(user.ID) == id { + var o UserClient + if err := d.db.Preload("BankAccounts").Where(reqID, id).First(&o).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + json.NewEncoder(w).Encode(o) + } else if id == 0 { + var o []UserClient + d.db.Preload("BankAccounts").Where(reqID, user.ID).Find(&o) + json.NewEncoder(w).Encode(o) + } else { + http.Error(w, ErrorCannotAccessRessource, http.StatusForbidden) + return + } +} + +func (d *DataHandler) postClientAdmin(w http.ResponseWriter, r *http.Request, id int) { + var o UserClient + err := json.NewDecoder(r.Body).Decode(&o) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + // check userID is not already present in DB + var client UserClient + if err := d.db.Where(reqUserID, o.UserID).First(&client).Error; err == nil { + http.Error(w, "UserID is already bind to a Client", http.StatusNotFound) + return + } + var banker UserBanker + if err := d.db.Where(reqUserID, o.UserID).First(&banker).Error; err == nil { + http.Error(w, "UserID is already bind to a Banker", http.StatusNotFound) + return + } + d.db.Create(&o) + d.db.Last(&o) + json.NewEncoder(w).Encode(o) +} + +func (d *DataHandler) postClientBanker(w http.ResponseWriter, r *http.Request, id int) { + user := d.getLoggedUser(w, r).(UserBanker) + var o UserClient + err := json.NewDecoder(r.Body).Decode(&o) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + o.UserBankerID = user.ID + d.db.Create(&o) + d.db.Last(&o) + json.NewEncoder(w).Encode(o) +} + +func (d *DataHandler) postClientClient(w http.ResponseWriter, r *http.Request, id int) { + http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) +} + +func (d *DataHandler) putClientAdmin(w http.ResponseWriter, r *http.Request, id int) { + var o UserClient + if err := d.db.Preload("BankAccounts").Where(reqID, id).First(&o).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + var updatedUser UserClient + err := json.NewDecoder(r.Body).Decode(&updatedUser) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + o.Name = updatedUser.Name + o.UserBankerID = updatedUser.UserBankerID + d.db.Save(&o) + json.NewEncoder(w).Encode(o) +} + +func (d *DataHandler) putClientBanker(w http.ResponseWriter, r *http.Request, id int) { + user := d.getLoggedUser(w, r).(UserBanker) + var o UserClient + if err := d.db.Preload("BankAccounts").Where(reqID, id).First(&o).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + var updatedUser UserClient + err := json.NewDecoder(r.Body).Decode(&updatedUser) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + o.Name = updatedUser.Name + o.UserBankerID = user.ID + d.db.Save(&o) + json.NewEncoder(w).Encode(o) +} + +func (d *DataHandler) putClientClient(w http.ResponseWriter, r *http.Request, id int) { + http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) +} + +func (d *DataHandler) deleteClientAdmin(w http.ResponseWriter, r *http.Request, id int) { + if id != 0 { + var o UserClient + if err := d.db.Where(reqID, id).First(&o).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + // Delete bank accounts of user + d.db.Where("user_client_id = ?", o.ID).Delete(&BankAccount{}) + + d.db.Delete(&o) + } else { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + } +} + +func (d *DataHandler) deleteClientBanker(w http.ResponseWriter, r *http.Request, id int) { + user := d.getLoggedUser(w, r).(UserBanker) + if id != 0 { + var o UserClient + if err := d.db.Where(reqID, id).First(&o).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + if o.UserBankerID != user.ID { + http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) + return + } + // Delete bank accounts of user + d.db.Where("user_client_id = ?", o.ID).Delete(&BankAccount{}) + + d.db.Delete(&o) + } else { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + } +} + +func (d *DataHandler) deleteClientClient(w http.ResponseWriter, r *http.Request, id int) { + http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) +} diff --git a/internal/models/models.go b/internal/models/models.go index 6ab1dbfbd58002a3cbc1893a9ae7f8ccc724592a..3a812bce2d8b1fc41270da61b56f4578989b497d 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -17,6 +17,28 @@ type DataHandler struct { db *gorm.DB } +// ErrorIDDoesNotExist = "id does not exist" +const ErrorIDDoesNotExist = "id does not exist" + +// ErrorIDIsMissing = "id is missing" +const ErrorIDIsMissing = "id is missing" + +// ErrorCannotAccessRessource = "You can not access this ressource" +const ErrorCannotAccessRessource = "You can not access this ressource" + +// ErrorRoleOfLoggedUser = "Could not get role of logged user" +const ErrorRoleOfLoggedUser = "Could not get role of logged user" + +// ErrorNotAuthorizeMethodOnRessource = "You're not authorize to execute this method on this ressource." +const ErrorNotAuthorizeMethodOnRessource = "You're not authorize to execute this method on this ressource." + +// ErrorUserIDIsMissing = "id of UserClient is missing" +const ErrorUserIDIsMissing = "id of UserClient is missing" + +const reqID = "id = ?" +const reqUserID = "user_id = ?" +const reqIDAndBankerID = "id = ? and user_banker_id = ?" + // NewDataHandler init a DataHandler and returns a pointer to it func NewDataHandler() *DataHandler { db, err := gorm.Open("sqlite3", "./data/test.db") @@ -57,7 +79,7 @@ type UserClient struct { CreatedAt time.Time `json:"-"` UpdatedAt time.Time `json:"-"` DeletedAt *time.Time `json:"-"` - UserID int + UserID int `gorm:"not null;unique"` Name string UserBankerID uint BankAccounts []BankAccount @@ -69,7 +91,7 @@ type UserBanker struct { CreatedAt time.Time `json:"-"` UpdatedAt time.Time `json:"-"` DeletedAt *time.Time `json:"-"` - UserID int + UserID int `gorm:"not null;unique"` Name string UserClients []UserClient } @@ -104,19 +126,19 @@ func (d *DataHandler) getLoggedUser(w http.ResponseWriter, r *http.Request) inte user := auth.GetLoggedUserTechnical(w, r) if user.Role != "" && (user.Role == "BANKER") { var o UserBanker - if err := d.db.Where("user_id = ?", user.ID).First(&o).Error; err != nil { + if err := d.db.Where(reqUserID, user.ID).First(&o).Error; err != nil { o := UserBanker{UserID: user.ID, Name: user.Login} d.db.Create(&o) - d.db.Where("user_id = ?", user.ID).First(&o) + d.db.Where(reqUserID, user.ID).First(&o) return o } return o } else if user.Role != "" && (user.Role == "CLIENT") { var o UserClient - if err := d.db.Where("user_id = ?", user.ID).First(&o).Error; err != nil { + if err := d.db.Where(reqUserID, user.ID).First(&o).Error; err != nil { o := UserClient{UserID: user.ID, Name: user.Login} d.db.Create(&o) - d.db.Where("user_id = ?", user.ID).First(&o) + d.db.Where(reqUserID, user.ID).First(&o) return o } diff --git a/internal/models/operations.go b/internal/models/operations.go index f3793bb30ad86f4294be2c80ed624e62f0798596..90114c589c804a8c9d9f74818b16f9e03726256e 100644 --- a/internal/models/operations.go +++ b/internal/models/operations.go @@ -17,108 +17,123 @@ func (d *DataHandler) HandleOperations(w http.ResponseWriter, r *http.Request) { case "GET": switch auth.GetLoggedUserTechnical(w, r).Role { case "ADMIN", "BANKER", "CLIENT": - if id != 0 { - var o Operation - if err := d.db.First(&o, id).Error; err != nil { - http.Error(w, "id does not exist", http.StatusNotFound) - return - } - json.NewEncoder(w).Encode(o) - } else { - var o []Operation - d.db.Find(&o) - json.NewEncoder(w).Encode(o) - } + d.getOperationClient(w, r, id) default: - http.Error(w, "Could not get role of logged user", http.StatusInternalServerError) + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) } case "POST": switch auth.GetLoggedUserTechnical(w, r).Role { case "ADMIN", "BANKER", "CLIENT": - var o Operation - err := json.NewDecoder(r.Body).Decode(&o) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - - var debtor BankAccount - var creditor BankAccount - if err := d.db.Where("id = ?", o.Debtor).First(&debtor).Error; err != nil { - http.Error(w, "Can not find debtor account", http.StatusInternalServerError) - return - } - if err := d.db.First(&creditor, o.Creditor).Error; err != nil { - http.Error(w, "Can not find creditor account", http.StatusInternalServerError) - return - } - if (debtor.Amount + o.Amount) <= debtor.BankOverdraft { - http.Error(w, "Not enough money", http.StatusExpectationFailed) - return - } - // Update BankAccounts - debtor.Amount += o.Amount - creditor.Amount -= o.Amount - d.db.Save(&debtor) - d.db.Save(&creditor) - - now := time.Now() - o.Date = now - d.db.Create(&o) - - // Add the operation to creditor - op := Operation{ - Debtor: o.Creditor, - Amount: -o.Amount, - Date: now, - Creditor: o.Debtor, - } - d.db.Create(&op) - + d.postOperationClient(w, r, id) default: - http.Error(w, "Could not get role of logged user", http.StatusInternalServerError) + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) } case "DELETE": switch auth.GetLoggedUserTechnical(w, r).Role { case "ADMIN", "BANKER": - if id != 0 { - var o Operation - if err := d.db.First(&o, id).Error; err != nil { - http.Error(w, "id does not exist", http.StatusNotFound) - return - } - // update BankAccounts - var debtor BankAccount - var creditor BankAccount - if err := d.db.First(&debtor, o.Debtor).Error; err == nil { - if err := d.db.First(&creditor, o.Creditor).Error; err == nil { - // Update BankAccounts - debtor.Amount -= o.Amount - creditor.Amount += o.Amount - d.db.Save(&debtor) - d.db.Save(&creditor) - - // Get the operation of the creditor - var op Operation - if err := d.db.First(&op, id+1).Error; err != nil { - http.Error(w, "id does not exist", http.StatusNotFound) - return - } - - // Delete the operations - d.db.Delete(&o) - d.db.Delete(&op) - } - } - } else { - http.Error(w, "id is missing", http.StatusNotFound) - } + d.deleteOperationBanker(w, r, id) case "CLIENT": - http.Error(w, "You're not authorize to execute this method on this ressource.", http.StatusMethodNotAllowed) + d.deleteOperationClient(w, r, id) default: - http.Error(w, "Could not get role of logged user", http.StatusInternalServerError) + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) } default: http.Error(w, "method not allowed", http.StatusMethodNotAllowed) } } + +func (d *DataHandler) getOperationClient(w http.ResponseWriter, r *http.Request, id int) { + if id != 0 { + var o Operation + if err := d.db.First(&o, id).Error; err != nil { + http.Error(w, ErrorIDDoesNotExist, http.StatusNotFound) + return + } + json.NewEncoder(w).Encode(o) + } else { + var o []Operation + d.db.Find(&o) + json.NewEncoder(w).Encode(o) + } +} + +func (d *DataHandler) postOperationClient(w http.ResponseWriter, r *http.Request, id int) { + var o Operation + err := json.NewDecoder(r.Body).Decode(&o) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + var debtor BankAccount + var creditor BankAccount + if err := d.db.Where(reqID, o.Debtor).First(&debtor).Error; err != nil { + http.Error(w, "Can not find debtor account", http.StatusInternalServerError) + return + } + if err := d.db.First(&creditor, o.Creditor).Error; err != nil { + http.Error(w, "Can not find creditor account", http.StatusInternalServerError) + return + } + if (debtor.Amount + o.Amount) <= debtor.BankOverdraft { + http.Error(w, "Not enough money", http.StatusExpectationFailed) + return + } + // Update BankAccounts + debtor.Amount += o.Amount + creditor.Amount -= o.Amount + d.db.Save(&debtor) + d.db.Save(&creditor) + + now := time.Now() + o.Date = now + d.db.Create(&o) + + // Add the operation to creditor + op := Operation{ + Debtor: o.Creditor, + Amount: -o.Amount, + Date: now, + Creditor: o.Debtor, + } + d.db.Create(&op) +} + +func (d *DataHandler) deleteOperationBanker(w http.ResponseWriter, r *http.Request, id int) { + if id != 0 { + var o Operation + if err := d.db.First(&o, id).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + // update BankAccounts + var debtor BankAccount + var creditor BankAccount + if err := d.db.First(&debtor, o.Debtor).Error; err == nil { + if err := d.db.First(&creditor, o.Creditor).Error; err == nil { + // Update BankAccounts + debtor.Amount -= o.Amount + creditor.Amount += o.Amount + d.db.Save(&debtor) + d.db.Save(&creditor) + + // Get the operation of the creditor + var op Operation + if err := d.db.First(&op, id+1).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + + // Delete the operations + d.db.Delete(&o) + d.db.Delete(&op) + } + } + } else { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + } +} + +func (d *DataHandler) deleteOperationClient(w http.ResponseWriter, r *http.Request, id int) { + http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) +} diff --git a/internal/rootmux/admin_test.go b/internal/rootmux/admin_test.go index efea4aaee99c29983d87b631605503ebee3ad5da..945d11fc542ee8df12e2e746dd404ed60b4b1cee 100644 --- a/internal/rootmux/admin_test.go +++ b/internal/rootmux/admin_test.go @@ -56,6 +56,7 @@ func AdminTests(t *testing.T) { // Try to delete the first operation do("DELETE", "/api/Operations/1", xsrfHeader, ``, 200, "") } + const apiAdminUsers = "/api/admin/users/" userTests := func() { response := do("GET", "/api/common/WhoAmI", noH, "", 200, "") token := auth.TokenData{} @@ -63,11 +64,11 @@ func AdminTests(t *testing.T) { xsrfHeader := tester.Header{Key: "XSRF-TOKEN", Value: token.XSRFToken} // Create a Client - do("POST", "/api/admin/users/", xsrfHeader, `{"login":"UserTest","password": "password","role":"CLIENT"}`, 200, `[{"id":1,"idOAuth":"","login":"Dupond"`) + do("POST", apiAdminUsers, xsrfHeader, `{"login":"UserTest","password": "password","role":"CLIENT"}`, 200, `{"id":7,"idOAuth":"","login":"UserTest","role":"CLIENT","passwordHash":"`) // Create a Banker - do("POST", "/api/admin/users/", xsrfHeader, `{"login":"BankerTest","password": "password","role":"BANKER"}`, 200, `[{"id":1,"idOAuth":"","login":"Dupond"`) + do("POST", apiAdminUsers, xsrfHeader, `{"login":"BankerTest","password": "password","role":"BANKER"}`, 200, `{"id":8,"idOAuth":"","login":"BankerTest","role":"BANKER","passwordHash":"`) // Get all users - do("GET", "/api/admin/users/", xsrfHeader, ``, 200, `[{"id":1,"idOAuth":"","login":"Dupond"`) + do("GET", apiAdminUsers, xsrfHeader, ``, 200, `[{"id":1,"idOAuth":"","login":"Dupond"`) // Delete created users do("DELETE", "/api/admin/users/7", xsrfHeader, ``, 200, ``) do("DELETE", "/api/admin/users/8", xsrfHeader, ``, 200, ``) diff --git a/internal/rootmux/banker_test.go b/internal/rootmux/banker_test.go index 3e78e49f8bbb4e6a8b8d119ab4fe05c4fcb32e5a..d2ab2e9086a5a970fbf645ae1ed8d7cc1662e202 100644 --- a/internal/rootmux/banker_test.go +++ b/internal/rootmux/banker_test.go @@ -40,7 +40,7 @@ func BankerTests(t *testing.T) { // Try to get another banker's client should fail with 405 do("GET", "/api/UserClients/2", xsrfHeader, "", 403, `You can not access this ressource`) // Try to get all the clients of the banker - do("GET", "/api/UserClients", xsrfHeader, "", 200, `[{"ID":1,"UserID":1,"Name":"Dupond","UserBankerID":1,"BankAccounts":[{"ID":1,"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":458,"BankOverdraft":-100,"Operations":null}]},{"ID":2,"UserID":2,"Name":"Boulangerie","UserBankerID":2,"BankAccounts":[{"ID":2,"Number":"02-01","UserClientID":2,"Type":"checking-account","Amount":4745,"BankOverdraft":-500,"Operations":null}]},{"ID":3,"UserID":7,"Name":"Dupond","UserBankerID":1,"BankAccounts":[]}]`) + do("GET", "/api/UserClients", xsrfHeader, "", 200, `[{"ID":1,"UserID":1,"Name":"Dupond","UserBankerID":1,"BankAccounts":[{"ID":1,"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":458,"BankOverdraft":-100,"Operations":null}]},{"ID":3,"UserID":7,"Name":"Dupond","UserBankerID":1,"BankAccounts":[]}]`) // Try to delete a banker client do("DELETE", "/api/UserClients/3", xsrfHeader, ``, 200, ``) // Try to delete the bakery client should fail with 405 diff --git a/internal/rootmux/client_test.go b/internal/rootmux/client_test.go index 0b04943c8397abb6c94575e575f7431252c02f91..c6229a881edc97bc02b2477eedf28478bb265e50 100644 --- a/internal/rootmux/client_test.go +++ b/internal/rootmux/client_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "testing" + "forge.grandlyon.com/apoyen/sdk-go/internal/models" "forge.grandlyon.com/apoyen/sdk-go/pkg/auth" "forge.grandlyon.com/apoyen/sdk-go/pkg/tester" ) @@ -23,11 +24,11 @@ func ClientTests(t *testing.T) { xsrfHeader := tester.Header{Key: "XSRF-TOKEN", Value: token.XSRFToken} // Try to create a client should fail with 405 - do("POST", "/api/UserClients", xsrfHeader, `{"ID":11,"UserID":"11","Name":"Dupont"}`, 405, "You're not authorize to execute this method on this ressource.") + do("POST", "/api/UserClients", xsrfHeader, `{"ID":11,"UserID":"11","Name":"Dupont"}`, 405, models.ErrorNotAuthorizeMethodOnRessource) // Try to create a banker should fail with 405 - do("POST", "/api/UserBankers", xsrfHeader, `{"ID":11,"UserID":"11","Name":"Banker"}`, 405, "You're not authorize to execute this method on this ressource.") + do("POST", "/api/UserBankers", xsrfHeader, `{"ID":11,"UserID":"11","Name":"Banker"}`, 405, models.ErrorNotAuthorizeMethodOnRessource) // Try to create a BankAccount should fail with 405 - do("POST", "/api/BankAccounts", xsrfHeader, `{"Number":"01-02","UserClientID":1,"Type":"saving-account","Amount":1287,"BankOverdraft":0}`, 405, "You're not authorize to execute this method on this ressource.") + do("POST", "/api/BankAccounts", xsrfHeader, `{"Number":"01-02","UserClientID":1,"Type":"saving-account","Amount":1287,"BankOverdraft":0}`, 405, models.ErrorNotAuthorizeMethodOnRessource) // Client should be able to create an operation do("POST", "/api/Operations", xsrfHeader, `{"Debtor":1,"Amount":-100,"Creditor":2}`, 200, ``) @@ -47,11 +48,11 @@ func ClientTests(t *testing.T) { do("GET", "/api/UserClients", xsrfHeader, "", 200, `[{"ID":1,"UserID":1,"Name":"Dupond","UserBankerID":1,"BankAccounts":[{"ID":1,"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":358,"BankOverdraft":-100,"Operations":null}]}]`) // Try to delete the first operation should fail - do("DELETE", "/api/Operations/1", xsrfHeader, ``, 405, "You're not authorize to execute this method on this ressource.") + do("DELETE", "/api/Operations/1", xsrfHeader, ``, 405, models.ErrorNotAuthorizeMethodOnRessource) // Try to delete the saving account of Dupond should fail - do("DELETE", "/api/BankAccounts/2", xsrfHeader, ``, 405, "You're not authorize to execute this method on this ressource.") + do("DELETE", "/api/BankAccounts/2", xsrfHeader, ``, 405, models.ErrorNotAuthorizeMethodOnRessource) // Try to delete the client Dupond should fail - do("DELETE", "/api/UserClients/2", xsrfHeader, ``, 405, "You're not authorize to execute this method on this ressource.") + do("DELETE", "/api/UserClients/2", xsrfHeader, ``, 405, models.ErrorNotAuthorizeMethodOnRessource) } // Do a in memory login with an known user do("POST", "/Login", noH, `{"login": "Dupond","password": "password"}`, 200, "") diff --git a/internal/rootmux/rootmux_test.go b/internal/rootmux/rootmux_test.go index 992209b402b7622ebebf85ce292d3522d43faa02..78b3337109d145795971a198ffe0d3121329abee 100644 --- a/internal/rootmux/rootmux_test.go +++ b/internal/rootmux/rootmux_test.go @@ -89,26 +89,28 @@ func appTests(t *testing.T) { json.Unmarshal([]byte(response), &token) xsrfHeader := tester.Header{Key: "XSRF-TOKEN", Value: token.XSRFToken} + const apiOperation1 = "/api/Operations/1" + const apiBankAccount1 = "/api/BankAccounts/1" // Add invalid operation between client and Bakery must be refused with 417 (Expectation failed) do("POST", "/api/Operations", xsrfHeader, `{"Debtor":1,"Amount":-1789,"Creditor":2}`, 417, "Not enough money") // Add an operation between Dupond and Bakery and verify that bank accounts are updated and opposite operation is created do("POST", "/api/Operations", xsrfHeader, `{"Debtor":1,"Amount":-100,"Creditor":2}`, 200, "") - do("GET", "/api/Operations/1", xsrfHeader, "", 200, `{"ID":1,"Debtor":1,"Amount":-100`) + do("GET", apiOperation1, xsrfHeader, "", 200, `{"ID":1,"Debtor":1,"Amount":-100`) do("GET", "/api/Operations/2", xsrfHeader, "", 200, `{"ID":2,"Debtor":2,"Amount":100`) - do("GET", "/api/BankAccounts/1", xsrfHeader, "", 200, `{"ID":1,"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":358,"BankOverdraft":-100,"Operations":[{"ID":1,"Debtor":1,"Amount":-100,"Date":"`) + do("GET", apiBankAccount1, xsrfHeader, "", 200, `{"ID":1,"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":358,"BankOverdraft":-100,"Operations":[{"ID":1,"Debtor":1,"Amount":-100,"Date":"`) do("GET", "/api/BankAccounts/2", xsrfHeader, "", 200, `{"ID":2,"Number":"02-01","UserClientID":2,"Type":"checking-account","Amount":4845,"BankOverdraft":-500,"Operations":[{"ID":2,"Debtor":2,"Amount":100,"Date":`) // Try to delete the first operation, the opposite operation should also have been deleted and bank accounts updated - do("DELETE", "/api/Operations/1", xsrfHeader, ``, 200, "") - do("GET", "/api/Operations/1", xsrfHeader, "", 404, `id does not exist`) + do("DELETE", apiOperation1, xsrfHeader, ``, 200, "") + do("GET", apiOperation1, xsrfHeader, "", 404, `id does not exist`) do("GET", "/api/Operations/2", xsrfHeader, "", 404, `id does not exist`) - do("GET", "/api/BankAccounts/1", xsrfHeader, "", 200, `{"ID":1,"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":458,"BankOverdraft":-100,"Operations":[]}`) + do("GET", apiBankAccount1, xsrfHeader, "", 200, `{"ID":1,"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":458,"BankOverdraft":-100,"Operations":[]}`) do("GET", "/api/BankAccounts/2", xsrfHeader, "", 200, `{"ID":2,"Number":"02-01","UserClientID":2,"Type":"checking-account","Amount":4745,"BankOverdraft":-500,"Operations":[]}`) // Delete a client should also delete his banks accounts do("DELETE", "/api/UserClients/1", xsrfHeader, ``, 200, "") - do("GET", "/api/BankAccounts/1", xsrfHeader, "", 404, `id does not exist`) + do("GET", apiBankAccount1, xsrfHeader, "", 404, `id does not exist`) } // Do an OAuth2 login with an known admin diff --git a/internal/rootmux/unlogged_test.go b/internal/rootmux/unlogged_test.go index 2a445f0d5435502cdb77a4b9365b7fa04ee0a89e..dedec8e383ea0932752fefae97957a3e9980875b 100644 --- a/internal/rootmux/unlogged_test.go +++ b/internal/rootmux/unlogged_test.go @@ -8,6 +8,9 @@ import ( /** UNLOGGED USER TESTS (this tests are to check that the security protections works) **/ + +const errorExtractingToken = `error extracting token` + func UnLoggedUserTests(t *testing.T) { // Create the tester ts, do, _ := createTester(t) @@ -23,30 +26,30 @@ func UnLoggedUserTests(t *testing.T) { // Without authent API calls must fails with 401 error extracting token // Try to get the first client should fail - do("GET", "/api/UserClients/1", noH, "", 401, `error extracting token`) + do("GET", "/api/UserClients/1", noH, "", 401, errorExtractingToken) // Try to get all clients should fail - do("GET", "/api/UserClients", noH, "", 401, `error extracting token`) + do("GET", "/api/UserClients", noH, "", 401, errorExtractingToken) // Try to get the first operation should fail - do("GET", "/api/UserBankers/1", noH, "", 401, `error extracting token`) + do("GET", "/api/UserBankers/1", noH, "", 401, errorExtractingToken) // Try to get all clients should fail - do("GET", "/api/UserBankers", noH, "", 401, `error extracting token`) + do("GET", "/api/UserBankers", noH, "", 401, errorExtractingToken) // Try to get the first operation should fail - do("GET", "/api/Operations/1", noH, "", 401, `error extracting token`) + do("GET", "/api/Operations/1", noH, "", 401, errorExtractingToken) // Try to get all operations should fail - do("GET", "/api/Operations", noH, "", 401, `error extracting token`) + do("GET", "/api/Operations", noH, "", 401, errorExtractingToken) // Try to get the first BankAccount should fail - do("GET", "/api/BankAccounts/1", noH, "", 401, `error extracting token`) + do("GET", "/api/BankAccounts/1", noH, "", 401, errorExtractingToken) // Try to get all BankAccounts should fail - do("GET", "/api/BankAccounts", noH, "", 401, `error extracting token`) + do("GET", "/api/BankAccounts", noH, "", 401, errorExtractingToken) // Unlogged user should not be able to create a Client - do("POST", "/api/UserClients", noH, `{"Name":"Dupond"}`, 401, `error extracting token`) + do("POST", "/api/UserClients", noH, `{"Name":"Dupond"}`, 401, errorExtractingToken) // Unlogged user should not be able to create a Banker - do("POST", "/api/UserBankers", noH, `{"Name":"Dupond"}`, 401, `error extracting token`) + do("POST", "/api/UserBankers", noH, `{"Name":"Dupond"}`, 401, errorExtractingToken) // Unlogged user should not be able to create a Bank Account - do("POST", "/api/BankAccounts", noH, `{"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":458,"BankOverdraft":-100}`, 401, `error extracting token`) + do("POST", "/api/BankAccounts", noH, `{"Number":"01-01","UserClientID":1,"Type":"checking-account","Amount":458,"BankOverdraft":-100}`, 401, errorExtractingToken) // Unlogged user should not be able to create an Operation - do("POST", "/api/Operations", noH, `{"Debtor":1,"Amount":-100,"Creditor":3}`, 401, `error extracting token`) + do("POST", "/api/Operations", noH, `{"Debtor":1,"Amount":-100,"Creditor":3}`, 401, errorExtractingToken) // Unlogged user should not be able to delete an Operation do("DELETE", "/api/Operations/1", noH, ``, 401, "error extracting token") diff --git a/pkg/auth/dbusers.go b/pkg/auth/dbusers.go index 9d10593efc3bc750b585f47fa36d211b17671a0f..5b9c4d6d5e425eb403f3f788754cebd2271b0cb1 100644 --- a/pkg/auth/dbusers.go +++ b/pkg/auth/dbusers.go @@ -24,17 +24,28 @@ func NewDataHandler() *DataHandler { return &DataHandler{db: db} } -func (d *DataHandler) saveUser(user User) error { +func (d *DataHandler) createUser(user User) error { d.db.Create(&user) return nil } +func (d *DataHandler) saveUser(user User) error { + d.db.Save(&user) + return nil +} + func (d *DataHandler) getUsers() []User { var users []User d.db.Find(&users) return users } +func (d *DataHandler) getUser(id int) User { + var user User + d.db.First(&user, id) + return user +} + func (d *DataHandler) removeUser(id int) error { var o User if err := d.db.First(&o, id).Error; err != nil { diff --git a/pkg/auth/inmemory.go b/pkg/auth/inmemory.go index bbb0944d76eb638f1571df55b04eb0857a6cd9f9..870944ec9bb29ded5fff3b9a0197f1629fd51210 100644 --- a/pkg/auth/inmemory.go +++ b/pkg/auth/inmemory.go @@ -68,6 +68,8 @@ func ProcessUsers(w http.ResponseWriter, req *http.Request) { d.SendUsers(w, req) case "POST": d.AddUser(w, req) + case "PUT": + d.UpdateUser(w, req) case "DELETE": d.DeleteUser(w, req) default: @@ -108,22 +110,73 @@ func (d *DataHandler) AddUser(w http.ResponseWriter, req *http.Request) { newUser.PasswordHash = string(hash) newUser.Password = "" } - // Select the new id for the user - newUser.ID = 1 + // Check login don't already exist for _, val := range users { - if newUser.ID <= val.ID { - newUser.ID = val.ID + 1 - } if newUser.Login == val.Login { http.Error(w, "login already exists", 400) return } } - d.saveUser(newUser) - d.SendUsers(w, req) + d.createUser(newUser) + d.db.Last(&newUser) + json.NewEncoder(w).Encode(newUser) + +} + +// UpdateUser update an user +func (d *DataHandler) UpdateUser(w http.ResponseWriter, req *http.Request) { + id, _ := strconv.Atoi(strings.TrimPrefix(req.URL.Path, "/users/")) + if id != 0 { + user := d.getUser(id) + if req.Body == nil { + http.Error(w, "please send a request body", http.StatusBadRequest) + return + } + var newUser User + err := json.NewDecoder(req.Body).Decode(&newUser) + if _, ok := err.(*json.UnmarshalTypeError); !ok && err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + // check that login doesn't already exist + if user.Login != newUser.Login { + var users = d.getUsers() + for _, val := range users { + if newUser.Login == val.Login { + http.Error(w, "login already exists", 400) + return + } + } + } + user.IDOAuth = newUser.IDOAuth + user.Login = newUser.Login + user.DisplayName = newUser.DisplayName + user.Name = newUser.Name + user.Surname = newUser.Surname + user.Role = newUser.Role + // Encrypt the password with bcrypt if appropriate + if newUser.Password == "" && newUser.PasswordHash == "" { + http.Error(w, "passwords cannot be blank", http.StatusBadRequest) + return + } + if newUser.Password != "" { + hash, err := bcrypt.GenerateFromPassword([]byte(newUser.Password), bcrypt.DefaultCost) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + user.PasswordHash = string(hash) + user.Password = "" + } + d.saveUser(user) + json.NewEncoder(w).Encode(user) + } else { + http.Error(w, "Please provide an ID", http.StatusBadRequest) + return + } } -// DeleteUser adds an user +// DeleteUser remove an user func (d *DataHandler) DeleteUser(w http.ResponseWriter, req *http.Request) { pathElements := strings.Split(req.URL.Path, "/") idx, err := strconv.Atoi(pathElements[len(pathElements)-1]) @@ -136,7 +189,6 @@ func (d *DataHandler) DeleteUser(w http.ResponseWriter, req *http.Request) { http.Error(w, err.Error(), 400) return } - d.SendUsers(w, req) } // MatchUser attempt to find the given user against users in configuration file diff --git a/pkg/auth/oauth2.go b/pkg/auth/oauth2.go index 1458a6c6c60efc351a3e59460a26c011236a0786..73a1f3918001bf1f87aa39c338c4a3ebffa54296 100644 --- a/pkg/auth/oauth2.go +++ b/pkg/auth/oauth2.go @@ -188,6 +188,6 @@ func (d *DataHandler) addUserInMemory(userOauth2 UserOAuth2) (User, error) { } } // Sauvegarder l'utilisateur dans InMemory - d.saveUser(user) + d.createUser(user) return user, nil } diff --git a/pkg/log/log_test.go b/pkg/log/log_test.go index c503ba29534bb72ab9c9df4147251e0c6937c289..54311c7738e542398bfb6cae3b3406f10f5c2315 100644 --- a/pkg/log/log_test.go +++ b/pkg/log/log_test.go @@ -7,6 +7,8 @@ import ( "testing" ) +const londonIPAdress = "81.2.69.142:1234" + func TestGetCityAndCountryFromRequest(t *testing.T) { ipDbLocation = "../../configs/ipgeodatabase/GeoLite2-City.mmdb" @@ -15,7 +17,7 @@ func TestGetCityAndCountryFromRequest(t *testing.T) { requestFromLocalHost.RemoteAddr = "[::1]:1234" requestFromLondon := httptest.NewRequest("GET", "/test", strings.NewReader("")) - requestFromLondon.RemoteAddr = "81.2.69.142:1234" + requestFromLondon.RemoteAddr = londonIPAdress requestWithLocalIP := httptest.NewRequest("GET", "/test", strings.NewReader("")) diff --git a/web/components/bankerPage/bankAccount.js b/web/components/bankerPage/bankAccount.js new file mode 100644 index 0000000000000000000000000000000000000000..cb8811642bae4091f27a93e0aa5c37cd42bb35fd --- /dev/null +++ b/web/components/bankerPage/bankAccount.js @@ -0,0 +1,155 @@ +// Imports +import * as Auth from "/services/auth/auth.js"; +import * as Messages from "/services/messages/messages.js"; +import { RandomString } from "/services/common/common.js"; +import * as Operations from "/components/bankerPage/operations.js"; + +// DOM elements + +// local variables +let current_user; + +export async function mount(where, parent) { + const bankAccount = new BankAccount(parent); + await bankAccount.mount(where); + return bankAccount; +} + +class BankAccount { + constructor(parent) { + // Random id seed + this.prefix = RandomString(8); + this.parent = parent; + } + + async mount(where) { + current_user = await Auth.GetUser(); + + document.getElementById(where).innerHTML = /* HTML */ ` + <div class="container"> + <div class="columns"> + <div class="column is-one-third"> + <h5 class="title is-5">Informations du compte bancaire <em id="account-number"></em></h5> + <div class="container"> + <div class="field" id="users-modal-role-container"> + <label>Type de compte</label><br /> + <div class="control select"> + <select name="type" id="account-type" ${current_user.role == "CLIENT" ? `disabled` : ``}> + <option value="checking-account">Compte courant</option> + <option value="saving-account">Compte épargne</option> + </select> + </div> + </div> + <div class="field"> + <label>Découvert autorisé</label> + <div class="control"> + <input class="input" type="number" id="account-overdraft" ${current_user.role == "CLIENT" ? `disabled` : ``} /> + </div> + </div> + ${current_user.role != "CLIENT" + ? /* HTML */ ` <button id="account-save" class="button is-success">Sauvegarder</button> + <button id="account-cancel" class="button">Annuler</button>` + : ``} + </div> + </div> + <div class="column"> + <h5 class="title is-5">Opérations du compte bancaire</h5> + <div id="operations"></div> + </div> + </div> + </div> + `; + } + + async handleDom() { + document.getElementById("account-number").innerHTML = this.bankAccount.Number; + document.getElementById("account-type").value = this.bankAccount.Type; + document.getElementById("account-overdraft").value = this.bankAccount.BankOverdraft; + let operationHandler = await Operations.mount("operations", this); + await operationHandler.displayOperations(this.bankAccount); + + if (current_user.role != "CLIENT") { + let bankAccount = this; + document.getElementById(`account-cancel`).addEventListener("click", function () { + bankAccount.handleDom(); + }); + document.getElementById(`account-save`).addEventListener("click", function () { + bankAccount.bankAccount.BankOverdraft = parseInt(document.getElementById("account-overdraft").value); + bankAccount.bankAccount.Type = document.getElementById("account-type").value; + bankAccount.postBankAccount(); + }); + } + } + + editBankAccount(bankAccount) { + this.bankAccount = bankAccount; + this.isNew = false; + this.handleDom(); + } + + newBankAccount(client) { + this.bankAccount = {}; + this.bankAccount.ID = 0; + this.bankAccount.Number = "0" + client.ID + "-0" + (client.BankAccounts.length + 1); + this.bankAccount.UserClientID = client.ID; + this.bankAccount.Type = document.getElementById("account-type").value; + this.bankAccount.Amount = 0; + this.bankAccount.BankOverdraft = 0; + this.isNew = true; + this.handleDom(); + } + + viewBankAccount(bankAccount) { + this.bankAccount = bankAccount; + this.handleDom(); + } + + async deleteAccount(bankAccount) { + try { + const response = await fetch("/api/BankAccounts/" + bankAccount.ID, { + method: "delete", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + }); + if (response.status !== 200) { + throw new Error(`BankAccount could not be deleted (status ${response.status})`); + } + document.getElementById(`accounts-account-${bankAccount.ID}`).remove(); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + } + + async postBankAccount() { + let method; + if (this.isNew) method = "POST"; + else method = "PUT"; + + try { + const response = await fetch("/api/BankAccounts/" + this.bankAccount.ID, { + method: method, + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + body: JSON.stringify({ + ID: this.bankAccount.ID, + Number: this.bankAccount.Number, + UserClientID: this.bankAccount.UserClientID, + Type: this.bankAccount.Type, + Amount: this.bankAccount.Amount, + BankOverdraft: this.bankAccount.BankOverdraft, + }), + }); + if (response.status !== 200) { + throw new Error(`Banker could not be updated (status ${response.status})`); + } + await this.parent.updateBankAccounts(); + document.getElementById(`accounts-account-edit-${this.bankAccount.ID}`).click(); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + } +} diff --git a/web/components/bankerPage/bankerPage.js b/web/components/bankerPage/bankerPage.js new file mode 100644 index 0000000000000000000000000000000000000000..93f65b56561da36b33cc80d6a9654ccfef842c40 --- /dev/null +++ b/web/components/bankerPage/bankerPage.js @@ -0,0 +1,97 @@ +// Imports +import { RandomString } from "/services/common/common.js"; +import * as Messages from "/services/messages/messages.js"; +import * as Auth from "/services/auth/auth.js"; +import * as ClientDetails from "/components/bankerPage/clientDetails.js"; + +// DOM elements + +// local variables +let clients; +let current_user; + +export async function mount(where) { + const bankerPage = new BankerPage(); + await bankerPage.mount(where); + await bankerPage.displayClients(); +} + +class BankerPage { + constructor() { + // Random id seed + this.prefix = RandomString(8); + } + + async mount(where) { + document.getElementById(where).innerHTML = /* HTML */ ` + <div class="container"> + <h1 class="title">Page du banquier</h1> + </div> + <section class="section"> + <div class="container"> + <div class="columns"> + <div class="column is-one-third"> + <h5 class="title is-5">Mes clients</h5> + <div class="table-container"> + <table class="table is-bordered is-narrow is-hoverable is-fullwidth"> + <thead> + <tr class="is-selected"> + <th>Id</th> + <th>Nom</th> + </tr> + </thead> + <tbody id="clients"></tbody> + </table> + </div> + </div> + <div class="column"> + <h5 class="title is-5">Comptes bancaires de <em id="client-title">Veuillez sélectionner un client</em></h5> + <div class="container"> + <div id="bank-accounts"></div> + </div> + </div> + </div> + </div> + </section> + <section class="section> + <div class="container" id="bank-account"></div> + </section> + `; + current_user = await Auth.GetUser(); + } + + async displayClients() { + try { + const response = await fetch("/api/UserClients/", { + method: "GET", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + }); + if (response.status !== 200) { + throw new Error(`Users could not be fetched (status ${response.status})`); + } + clients = await response.json(); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + const markup = clients.map((client) => this.clientTemplate(client)).join(""); + document.getElementById("clients").innerHTML = markup; + clients.map((client) => { + document.getElementById(`clients-client-${client.ID}`).addEventListener("click", function () { + document.getElementById("client-title").innerHTML = client.Name; + ClientDetails.mount("bank-accounts", client); + }); + }); + } + + clientTemplate(client) { + return /* HTML */ ` + <tr id="clients-client-${client.ID}"> + <td>${client.ID}</td> + <td>${client.Name}</td> + </tr> + `; + } +} diff --git a/web/components/bankerPage/clientDetails.js b/web/components/bankerPage/clientDetails.js new file mode 100644 index 0000000000000000000000000000000000000000..91a4543f44d33ca04272624e504cfb58f316a768 --- /dev/null +++ b/web/components/bankerPage/clientDetails.js @@ -0,0 +1,139 @@ +// Imports +import * as Auth from "/services/auth/auth.js"; +import * as Messages from "/services/messages/messages.js"; +import { RandomString } from "/services/common/common.js"; +import * as BankAccount from "/components/bankerPage/bankAccount.js"; + +// DOM elements + +// local variables +let current_user; + +export async function mount(where, client) { + const clientDetails = new ClientDetails(client); + await clientDetails.mount(where); + clientDetails.displayBankAccounts(); +} + +class ClientDetails { + constructor(client) { + // Random id seed + this.prefix = RandomString(8); + this.client = client; + } + + async mount(where) { + current_user = await Auth.GetUser(); + + document.getElementById(where).innerHTML = /* HTML */ ` + <div class="table-container"> + <table class="table is-bordered is-narrow is-hoverable is-fullwidth"> + <thead> + <tr class="is-selected"> + <th>Id</th> + <th>Numéro</th> + <th>Type</th> + <th>Montant</th> + <th>Découvert autorisé</th> + <th>Actions</th> + </tr> + </thead> + <tbody id="accounts"></tbody> + </table> + + ${current_user.role != "CLIENT" + ? ` + <nav class="level"> + <div class="level-left"></div> + <!-- Right side --> + <div class="level-right"> + <button id="bankAccount-new" class="button is-success"> + <span class="icon is-small"> + <i class="fas fa-plus"></i> + </span> + </button> + </div> + </nav>` + : ``} + </div> + `; + } + + async displayBankAccounts() { + const markup = this.client.BankAccounts.map((bankAccount) => this.bankAccountTemplate(bankAccount)).join(""); + document.getElementById("accounts").innerHTML = markup; + let accountHandler = await BankAccount.mount("bank-account", this); + + this.client.BankAccounts.map((bankAccount) => { + if (current_user.role == "CLIENT") { + document.getElementById(`accounts-account-view-${bankAccount.ID}`).addEventListener("click", function () { + document.getElementById("account-title").innerHTML = bankAccount.Number; + accountHandler.viewBankAccount(bankAccount); + }); + } else { + document.getElementById(`accounts-account-edit-${bankAccount.ID}`).addEventListener("click", function () { + accountHandler.editBankAccount(bankAccount); + }); + document.getElementById(`accounts-account-delete-${bankAccount.ID}`).addEventListener("click", function () { + accountHandler.deleteAccount(bankAccount); + }); + let clientDetails = this; + document.getElementById(`bankAccount-new`).addEventListener("click", function () { + accountHandler.newBankAccount(clientDetails.client); + }); + } + }); + } + + bankAccountTemplate(bankAccount) { + return /* HTML */ ` + <tr id="accounts-account-${bankAccount.ID}"> + <td>${bankAccount.ID}</td> + <td>${bankAccount.Number}</td> + <td>${bankAccount.Type}</td> + <td>${bankAccount.Amount}</td> + <td>${bankAccount.BankOverdraft}</td> + <td> + ${current_user.role == "CLIENT" + ? `<a id="accounts-account-view-${bankAccount.ID}" class="button is-link is-small"> + <span>Visualiser</span> + <span class="icon is-small"> + <i class="fas fa-pen"></i> + </span> + </a>` + : `<a id="accounts-account-edit-${bankAccount.ID}" class="button is-link is-small"> + <span>Modifier</span> + <span class="icon is-small"> + <i class="fas fa-pen"></i> + </span> + </a> + <a id="accounts-account-delete-${bankAccount.ID}" class="button is-danger is-small"> + <span>Supprimer</span> + <span class="icon is-small"> + <i class="fas fa-times"></i> + </span> + </a>`} + </td> + </tr> + `; + } + + async updateBankAccounts() { + try { + const response = await fetch("/api/UserClients/" + this.client.ID, { + method: "GET", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + }); + if (response.status !== 200) { + throw new Error(`Users could not be fetched (status ${response.status})`); + } + this.client = await response.json(); + await this.displayBankAccounts(); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + } +} diff --git a/web/components/bankerPage/operations.js b/web/components/bankerPage/operations.js new file mode 100644 index 0000000000000000000000000000000000000000..50c0c50d8182b73b69107b5e2045192bd91c37c0 --- /dev/null +++ b/web/components/bankerPage/operations.js @@ -0,0 +1,247 @@ +// Imports +import * as Auth from "/services/auth/auth.js"; +import * as Messages from "/services/messages/messages.js"; +import * as Users from "/components/users/users.js"; +import { RandomString } from "/services/common/common.js"; + +// DOM elements + +// local variables +let current_user; + +export async function mount(where, parent) { + const operation = new Operation(parent); + await operation.mount(where); + operation.handleDom(); + return operation; +} + +class Operation { + constructor(parent) { + // Random id seed + this.prefix = RandomString(8); + this.parent = parent; + } + + async mount(where) { + current_user = await Auth.GetUser(); + + document.getElementById(where).innerHTML = /* HTML */ ` + <div class="table-container"> + <table class="table is-bordered is-narrow is-hoverable is-fullwidth"> + <thead> + <tr class="is-selected"> + <th>Id</th> + <th>Montant</th> + <th>Client</th> + <th>Date</th> + ${current_user.role != "CLIENT" ? `<th>Actions</th>` : ``} + </tr> + </thead> + <tbody id="operations-table"></tbody> + </table> + </div> + <nav class="level"> + <div class="level-left"></div> + <!-- Right side --> + <div class="level-right"> + <button id="operation-new" class="button is-success"> + <span class="icon is-small"> + <i class="fas fa-plus"></i> + </span> + </button> + </div> + </nav> + + <div class="modal" id="operation-modal"> + <div class="modal-background"></div> + <div class="modal-card" id="operation-modal-card"> + <header class="modal-card-head"> + <p class="modal-card-title">Ajout d'une opération</p> + <button class="delete" aria-label="close" id="operation-modal-close"></button> + </header> + <section class="modal-card-body"> + <div class="field"> + <label>Montant</label> + <div class="control"> + <input class="input" type="text" id="operation-modal-amount" /> + </div> + </div> + <div class="field"> + <label>Compte bancaire</label><br /> + <div class="control select"> + <select name="bankAccount" id="operation-modal-bankAccount"> </select> + </div> + </div> + </section> + <footer class="modal-card-foot"> + <button id="operation-modal-save" class="button is-success">Sauvegarder</button> + <button id="operation-modal-cancel" class="button">Annuler</button> + </footer> + </div> + </div> + `; + } + + handleDom() { + let operationHandler = this; + document.getElementById(`operation-new`).addEventListener("click", function () { + Users.toggleModal("operation-modal", "operation-modal-card"); + operationHandler.newOperation(); + }); + + document.getElementById(`operation-modal-close`).addEventListener("click", function () { + Users.toggleModal("operation-modal", "operation-modal-card"); + }); + document.getElementById(`operation-modal-cancel`).addEventListener("click", function () { + Users.toggleModal("operation-modal", "operation-modal-card"); + }); + document.getElementById(`operation-modal-save`).addEventListener("click", function () { + operationHandler.postOperation(); + }); + } + + async displayOperations(bankAccount) { + this.bankAccount = await this.updateBankAccount(bankAccount.ID); + const markup = this.bankAccount.Operations.map((operation) => this.operationTemplate(operation)).join(""); + document.getElementById("operations-table").innerHTML = markup; + + let operationHandler = this; + if (current_user.role != "CLIENT") { + this.bankAccount.Operations.map((operation) => { + document.getElementById(`operations-operation-delete-${operation.ID}`).addEventListener("click", function () { + operationHandler.deleteOperation(operation); + }); + }); + } + } + + operationTemplate(operation) { + return /* HTML */ ` + <tr id="operations-operation-${operation.ID}"> + <td>${operation.ID}</td> + <td>${operation.Amount}</td> + <td>${operation.Creditor}</td> + <td>${new Date(operation.Date).toLocaleDateString()}</td> + ${current_user.role != "CLIENT" + ? ` + <td> + <a id="operations-operation-delete-${operation.ID}" class="button is-danger is-small"> + <span>Supprimer</span> + <span class="icon is-small"> + <i class="fas fa-times"></i> + </span> + </a> + </td> + ` + : ``} + </tr> + `; + } + + async updateBankAccount(bankAccountID) { + try { + const response = await fetch("/api/BankAccounts/" + bankAccountID, { + method: "GET", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + }); + if (response.status !== 200) { + throw new Error(`BankAccount could not be fetched (status ${response.status})`); + } + return await response.json(); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + } + + async deleteOperation(operation) { + try { + const response = await fetch("/api/Operations/" + operation.ID, { + method: "delete", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + }); + if (response.status !== 200) { + throw new Error(`Operation could not be deleted (status ${response.status})`); + } + document.getElementById(`operations-operation-${operation.ID}`).remove(); + await this.parent.parent.updateBankAccounts(); + document.getElementById(`accounts-account-edit-${this.bankAccount.ID}`).click(); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + } + + async newOperation() { + try { + const response = await fetch("/api/BankAccounts/", { + method: "GET", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + }); + if (response.status !== 200) { + throw new Error(`BankAccounts could not be fetched (status ${response.status})`); + } + let bankAccounts = await response.json(); + + let select = document.getElementById("operation-modal-bankAccount"); + bankAccounts.forEach(async (bankAccount) => { + if (bankAccount.ID != this.bankAccount.ID) { + let el = document.createElement("option"); + let client = await this.getClientById(bankAccount.UserClientID); + el.textContent = client.Name + " - " + bankAccount.Number; + el.value = bankAccount.ID; + select.appendChild(el); + } + }); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + } + + async postOperation() { + try { + const response = await fetch("/api/Operations/", { + method: "POST", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + body: JSON.stringify({ + Debtor: this.bankAccount.ID, + Creditor: parseInt(document.getElementById("operation-modal-bankAccount").value), + Amount: parseInt(document.getElementById("operation-modal-amount").value), + }), + }); + if (response.status !== 200) { + throw new Error(`Operation could not be created (status ${response.status})`); + } + await this.displayOperations(this.bankAccount); + await this.parent.parent.updateBankAccounts(); + document.getElementById(`accounts-account-edit-${this.bankAccount.ID}`).click(); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + Users.toggleModal("operation-modal", "operation-modal-card"); + } + + async getClientById(clientID) { + const response = await fetch("/api/UserClients/" + clientID, { + method: "GET", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + }); + if (response.status !== 200) { + throw new Error(`Could not get clients (status ${response.status})`); + } + return await response.json(); + } +} diff --git a/web/components/clientPage/clientPage.js b/web/components/clientPage/clientPage.js new file mode 100644 index 0000000000000000000000000000000000000000..b5a1fd28522870dbd2ae5234d1eb30d14608a5e2 --- /dev/null +++ b/web/components/clientPage/clientPage.js @@ -0,0 +1,48 @@ +// Imports +import { RandomString } from "/services/common/common.js"; +import * as Auth from "/services/auth/auth.js"; +import * as ClientDetails from "/components/bankerPage/clientDetails.js"; + +// DOM elements + +// local variables +let current_user; + +export async function mount(where) { + const clientPage = new ClientPage(); + await clientPage.mount(where); +} + +class ClientPage { + constructor() { + // Random id seed + this.prefix = RandomString(8); + } + + async mount(where) { + document.getElementById(where).innerHTML = /* HTML */ ` + <div class="container"> + <h1 class="title">Ma banque</h1> + </div> + + <section class="section"> + <div class="columns"> + <div class="column is-one-third"> + <h5 class="title is-5">Mes comptes</h5> + <div id="bank-accounts"></div> + </div> + <div class="column"> + <h5 class="title is-5">Compte bancaire n°<em id="account-title"> Veuillez sélectionner un compte</em></h5> + <div class="container"> + <div id="bank-account"></div> + </div> + </div> + </div> + </section> + `; + current_user = await Auth.GetUser(); + + let client = await Auth.getClientByUserID(current_user.id, current_user); + ClientDetails.mount("bank-accounts", client); + } +} diff --git a/web/components/home/home.js b/web/components/home/home.js new file mode 100644 index 0000000000000000000000000000000000000000..97b7678e4d7dc7bce02a857ee115411ad6bb91ee --- /dev/null +++ b/web/components/home/home.js @@ -0,0 +1,20 @@ +// Imports + +// DOM elements +let mountpoint; + +// local variables + +export async function mount(where) { + mountpoint = where; + document.getElementById(mountpoint).innerHTML = /* HTML */ ` + <section class="section"> + <div class="container"> + <h1 class="title">Accueil de la GO-banque</h1> + <h2 class="subtitle"> + Ceci est une application de démonstration du SDK écrit en GO avec GORM et Vanilla JS avec le framework CSS Bulma. + </h2> + </div> + </section> + `; +} diff --git a/web/components/navbar/navbar.js b/web/components/navbar/navbar.js index 6a724a0884de353f3681e0dc191acea5de3bee50..f558982cea37e2e1147542e68a73b938cca22e53 100644 --- a/web/components/navbar/navbar.js +++ b/web/components/navbar/navbar.js @@ -24,9 +24,9 @@ export function mount(mountpoint) { // Hamburger menu const burger = document.getElementById("navbar-burger"); menu = document.getElementById("navbar-menu"); - const openClose = e => { + const openClose = (e) => { if (burger.classList.contains("is-active")) { - AnimateCSS(menu, "slideOutRight", function() { + AnimateCSS(menu, "slideOutRight", function () { menu.classList.remove("is-active"); burger.classList.remove("is-active"); }); @@ -51,9 +51,13 @@ export async function CreateMenu() { user === undefined ? `` : /* HTML */ ` - ${user.isAdmin + <a class="navbar-item" href="#home"><i class="navbar-menu-icon fas fa-home"></i>Accueil</a> + ${user.role == "CLIENT" ? /* HTML */ ` <a class="navbar-item" href="#client"><i class="navbar-menu-icon fas fa-money-check"></i>Mes comptes</a> ` : ""} + ${user.role == "BANKER" ? /* HTML */ ` <a class="navbar-item" href="#banker"><i class="navbar-menu-icon fas fa-university"></i>Mes Clients</a> ` : ""} + ${user.role == "ADMIN" ? /* HTML */ ` - <a class="navbar-item" href="#users"><i class="navbar-menu-icon fas fa-users"></i>Users</a> + <a class="navbar-item" href="#banker"><i class="navbar-menu-icon fas fa-university"></i>Mes Clients</a> + <a class="navbar-item" href="#users"><i class="navbar-menu-icon fas fa-users"></i>Utilisateurs</a> ` : ""} ` @@ -62,12 +66,8 @@ export async function CreateMenu() { <div class="navbar-end"> ${ user === undefined - ? /* HTML */ ` - <a class="navbar-item" href="#login"><i class="navbar-menu-icon fas fa-sign-in-alt"></i>Log in</a> - ` - : /* HTML */ ` - <a class="navbar-item" href="/Logout"><i class="navbar-menu-icon fas fa-sign-out-alt"></i>Log out</a> - ` + ? /* HTML */ ` <a class="navbar-item" href="#login"><i class="navbar-menu-icon fas fa-sign-in-alt"></i>Connexion</a> ` + : /* HTML */ ` <a class="navbar-item" href="/Logout"><i class="navbar-menu-icon fas fa-sign-out-alt"></i>Déconnexion</a> ` } </div> `; diff --git a/web/components/users/handleBanker.js b/web/components/users/handleBanker.js new file mode 100644 index 0000000000000000000000000000000000000000..dbe644e60d931ae3b1542271babea3b69943726c --- /dev/null +++ b/web/components/users/handleBanker.js @@ -0,0 +1,120 @@ +// Imports +import { RandomString } from "/services/common/common.js"; +import * as Messages from "/services/messages/messages.js"; +import * as Auth from "/services/auth/auth.js"; +import * as Users from "/components/users/users.js"; + +// DOM elements +let name_field; +let banker_name_field; +let banker_id_field; + +// local variables +let current_user; +let update_banker; +let user_id; + +export async function mount(where, parent) { + const handleBanker = new HandleBanker(parent); + await handleBanker.mount(where); + handleBanker.handleDom(); + return handleBanker; +} + +class HandleBanker { + constructor(parent) { + // Random id seed + this.prefix = RandomString(8); + this.parent = parent; + } + + async mount(where) { + document.getElementById(where).innerHTML = /* HTML */ ` + <div class="modal-background"></div> + <div class="modal-card" id="banker-modal-card"> + <header class="modal-card-head"> + <p class="modal-card-title">Ajout/Supression d'un banquier</p> + <button class="delete" aria-label="close" id="banker-modal-close"></button> + </header> + <section class="modal-card-body"> + <div class="field"> + <label>Id</label> + <div class="control"> + <input class="input" type="number" id="banker-modal-id" disabled /> + </div> + </div> + <div class="field"> + <label>Nom</label> + <div class="control"> + <input class="input" type="text" id="banker-modal-name" /> + </div> + </div> + </section> + <footer class="modal-card-foot"> + <button id="banker-modal-save" class="button is-success">Sauvegarder</button> + <button id="banker-modal-cancel" class="button">Annuler</button> + </footer> + </div> + `; + current_user = await Auth.GetUser(); + } + + handleDom() { + name_field = document.getElementById("users-modal-name"); + banker_id_field = document.getElementById("banker-modal-id"); + banker_name_field = document.getElementById("banker-modal-name"); + + document.getElementById(`banker-modal-close`).addEventListener("click", function () { + Users.toggleModal("banker-modal", "banker-modal-card"); + }); + document.getElementById(`banker-modal-cancel`).addEventListener("click", function () { + Users.toggleModal("banker-modal", "banker-modal-card"); + }); + let bankerHandler = this + document.getElementById(`banker-modal-save`).addEventListener("click", function () { + bankerHandler.postBanker(); + }); + } + + async editBanker(banker) { + update_banker = true; + banker_id_field.value = banker.ID; + banker_name_field.value = banker.Name; + Users.toggleModal("banker-modal", "banker-modal-card"); + } + + async newBanker(userID) { + update_banker = false; + user_id = userID; + banker_name_field.value = name_field.value; + Users.toggleModal("banker-modal", "banker-modal-card"); + } + + async postBanker() { + let method; + if (update_banker) method = "PUT"; + else method = "POST"; + + try { + const response = await fetch("/api/UserBankers/" + banker_id_field.value, { + method: method, + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + body: JSON.stringify({ + ID: parseInt(banker_id_field.value), + Name: banker_name_field.value, + UserID: parseInt(user_id), + }), + }); + if (response.status !== 200) { + throw new Error(`Banker could not be updated (status ${response.status})`); + } + this.parent.displayUsers(); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + Users.toggleModal("banker-modal", "banker-modal-card"); + } +} diff --git a/web/components/users/handleClient.js b/web/components/users/handleClient.js new file mode 100644 index 0000000000000000000000000000000000000000..74bf9dcfeead8ae6b325750ba660e7290b5304c4 --- /dev/null +++ b/web/components/users/handleClient.js @@ -0,0 +1,163 @@ +// Imports +import { RandomString } from "/services/common/common.js"; +import * as Messages from "/services/messages/messages.js"; +import * as Auth from "/services/auth/auth.js"; +import * as Users from "/components/users/users.js"; + +// DOM elements +let name_field; +let client_id_field; +let client_name_field; +let client_bankerID_field; + +// local variables +let current_user; +let bankers = []; +let update_client; +let user_id; + +export async function mount(where, parent) { + const handleClient = new HandleClient(parent); + await handleClient.mount(where); + handleClient.handleDom(); + return handleClient; +} + +class HandleClient { + constructor(parent) { + // Random id seed + this.prefix = RandomString(8); + this.parent = parent; + } + + async mount(where) { + document.getElementById(where).innerHTML = /* HTML */ ` + <div class="modal-background"></div> + <div class="modal-card" id="client-modal-card"> + <header class="modal-card-head"> + <p class="modal-card-title">Ajout/Supression d'un client</p> + <button class="delete" aria-label="close" id="client-modal-close"></button> + </header> + <section class="modal-card-body"> + <div class="field"> + <label>Id</label> + <div class="control"> + <input class="input" type="number" id="client-modal-id" disabled /> + </div> + </div> + <div class="field"> + <label>Nom</label> + <div class="control"> + <input class="input" type="text" id="client-modal-name" /> + </div> + </div> + <div class="field" id="users-modal-role-container"> + <label>Banquier</label><br /> + <div class="control select"> + <select name="banker" id="client-modal-banker-id"> </select> + </div> + </div> + </section> + <footer class="modal-card-foot"> + <button id="client-modal-save" class="button is-success">Sauvegarder</button> + <button id="client-modal-cancel" class="button">Annuler</button> + </footer> + </div> + `; + current_user = await Auth.GetUser(); + } + + handleDom() { + name_field = document.getElementById("users-modal-name"); + client_id_field = document.getElementById("client-modal-id"); + client_name_field = document.getElementById("client-modal-name"); + client_bankerID_field = document.getElementById("client-modal-banker-id"); + + document.getElementById(`client-modal-close`).addEventListener("click", function () { + Users.toggleModal("client-modal", "client-modal-card"); + }); + document.getElementById(`client-modal-cancel`).addEventListener("click", function () { + Users.toggleModal("client-modal", "client-modal-card"); + }); + let clientHandler = this; + document.getElementById(`client-modal-save`).addEventListener("click", function () { + clientHandler.postClient(); + }); + } + + async editClient(client) { + await this.refreshBankers(); + update_client = true; + client_id_field.value = client.ID; + client_name_field.value = client.Name; + client_bankerID_field.value = client.UserBankerID; + Users.toggleModal("client-modal", "client-modal-card"); + } + + async newClient(userID) { + await this.refreshBankers(); + update_client = false; + user_id = userID; + console.log(user_id) + client_name_field.value = name_field.value; + client_bankerID_field.value = 0; + Users.toggleModal("client-modal", "client-modal-card"); + } + + async refreshBankers() { + const response = await fetch("/api/UserBankers/", { + method: "GET", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + }); + if (response.status !== 200) { + throw new Error(`Users could not be updated (status ${response.status})`); + } + bankers = await response.json(); + + for (let i = client_bankerID_field.options.length - 1; i >= 0; i--) { + client_bankerID_field.remove(i); + } + + let el = document.createElement("option"); + el.textContent = "Veuillez sélectionner un banquier"; + el.value = 0; + client_bankerID_field.appendChild(el); + bankers.forEach((banker) => { + el = document.createElement("option"); + el.textContent = banker.Name; + el.value = banker.ID; + client_bankerID_field.appendChild(el); + }); + } + + async postClient() { + let method; + if (update_client) method = "PUT"; + else method = "POST"; + + try { + const response = await fetch("/api/UserClients/" + client_id_field.value, { + method: method, + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + body: JSON.stringify({ + ID: parseInt(client_id_field.value), + UserID: parseInt(user_id), + Name: client_name_field.value, + UserBankerID: parseInt(client_bankerID_field.value), + }), + }); + if (response.status !== 200) { + throw new Error(`Client could not be updated (status ${response.status})`); + } + this.parent.displayUsers(); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + Users.toggleModal("client-modal", "client-modal-card"); + } +} diff --git a/web/components/users/handleUser.js b/web/components/users/handleUser.js new file mode 100644 index 0000000000000000000000000000000000000000..21d615864778cc487ecabdcf9c7d100d1bd57a9e --- /dev/null +++ b/web/components/users/handleUser.js @@ -0,0 +1,262 @@ +// Imports +import { RandomString } from "/services/common/common.js"; +import * as Messages from "/services/messages/messages.js"; +import * as Auth from "/services/auth/auth.js"; +import * as Users from "/components/users/users.js"; + +// DOM elements +let id_field; +let idOAuth_field; +let login_field; +let displayName_field; +let password_field; +let passwordhash_field; +let name_field; +let surname_field; +let role_field; + +// local variables +let current_user; +let update_user; + +export async function mount(where, parent) { + const handleUser = new HandleUser(parent); + await handleUser.mount(where); + handleUser.handleDom(); + return handleUser; +} + +class HandleUser { + constructor(parent) { + // Random id seed + this.prefix = RandomString(8); + this.parent = parent; + } + + async mount(where) { + document.getElementById(where).innerHTML = /* HTML */ ` + <div class="modal-background"></div> + <div class="modal-card" id="users-modal-card"> + <header class="modal-card-head"> + <p class="modal-card-title">Ajout/modification d'un utilisateur</p> + <button class="delete" aria-label="close" id="users-modal-close"></button> + </header> + <section class="modal-card-body"> + <div class="field"> + <label>Id</label> + <div class="control"> + <input class="input" type="number" id="users-modal-id" disabled /> + </div> + </div> + <div class="field"> + <label>IdOAuth</label> + <div class="control"> + <input class="input" type="text" id="users-modal-idoauth" /> + </div> + </div> + <div class="field"> + <label>Identifiant</label> + <div class="control"> + <input class="input" type="text" id="users-modal-login" /> + </div> + </div> + <div class="field" id="users-modal-password-container"> + <label>Mot de passe</label> + <div class="control"> + <input class="input" type="text" id="users-modal-password" /> + </div> + </div> + <div class="field" id="users-modal-password-container"> + <div class="control"> + <input class="input" type="hidden" id="users-modal-passwordhash" /> + </div> + </div> + <div class="field" id="users-modal-name-container"> + <label>Nom</label> + <div class="control"> + <input class="input" type="text" id="users-modal-name" /> + </div> + </div> + <div class="field" id="users-modal-name-container"> + <label>Nom complet</label> + <div class="control"> + <input class="input" type="text" id="users-modal-displayName" /> + </div> + </div> + <div class="field" id="users-modal-surname-container"> + <label>Surnom</label> + <div class="control"> + <input class="input" type="text" id="users-modal-surname" /> + </div> + </div> + <div class="field" id="users-modal-role-container"> + <label>Rôle</label><br /> + <div class="control select"> + <select name="role" id="users-modal-role"> + <option value="CLIENT">Client</option> + <option value="BANKER">Banquier</option> + <option value="ADMIN">Administrateur</option> + </select> + </div> + </div> + </section> + <footer class="modal-card-foot"> + <button id="users-modal-save" class="button is-success">Sauvegarder</button> + <button id="users-modal-cancel" class="button">Annuler</button> + </footer> + </div> + `; + current_user = await Auth.GetUser(); + } + + handleDom() { + id_field = document.getElementById("users-modal-id"); + idOAuth_field = document.getElementById("users-modal-idoauth"); + login_field = document.getElementById("users-modal-login"); + password_field = document.getElementById("users-modal-password"); + passwordhash_field = document.getElementById("users-modal-passwordhash"); + displayName_field = document.getElementById("users-modal-displayName"); + name_field = document.getElementById("users-modal-name"); + surname_field = document.getElementById("users-modal-surname"); + role_field = document.getElementById("users-modal-role"); + + document.getElementById(`users-modal-close`).addEventListener("click", function () { + Users.toggleModal("users-modal", "users-modal-card"); + }); + document.getElementById(`users-modal-cancel`).addEventListener("click", function () { + Users.toggleModal("users-modal", "users-modal-card"); + }); + let userHandler = this; + document.getElementById(`users-modal-save`).addEventListener("click", function () { + userHandler.postUser(); + }); + document.getElementById(`users-new`).addEventListener("click", function () { + userHandler.newUser(); + }); + } + + async newUser() { + update_user = false; + id_field.value = ""; + login_field.value = ""; + password_field.value = RandomString(48); + passwordhash_field.value = ""; + name_field.value = ""; + displayName_field.value = ""; + surname_field.value = ""; + role_field.value = ""; + Users.toggleModal("users-modal", "users-modal-card"); + } + + async deleteUser(user) { + try { + const response = await fetch("/api/admin/users/" + user.id, { + method: "delete", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + }); + if (response.status !== 200) { + throw new Error(`User could not be deleted (status ${response.status})`); + } + document.getElementById(`users-user-${user.id}`).remove(); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + } + + editUser(user) { + this.parent.cleanUser(user); + update_user = true; + id_field.value = user.id; + idOAuth_field.value = user.idOAuth; + login_field.value = user.login; + password_field.value = ""; + passwordhash_field.value = user.passwordHash; + name_field.value = user.name; + displayName_field.value = user.displayName; + surname_field.value = user.surname; + role_field.value = user.role; + Users.toggleModal("users-modal", "users-modal-card"); + } + + async updateUsers() { + try { + const response = await fetch("/api/admin/users/", { + method: "GET", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + }); + if (response.status !== 200) { + throw new Error(`Users could not be fetched (status ${response.status})`); + } + return await response.json(); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + } + + async getBankerByUserID(userID) { + let bankerToSend = undefined; + const response = await fetch("/api/UserBankers/", { + method: "GET", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + }); + if (response.status !== 200) { + throw new Error(`Could not get bankers (status ${response.status})`); + } + let bankers = await response.json(); + bankers.forEach((banker) => { + if (banker.UserID == userID) { + bankerToSend = banker; + } + }); + return bankerToSend; + } + + async postUser() { + let method; + if (update_user) method = "PUT"; + else method = "POST"; + + try { + const response = await fetch("/api/admin/users/" + id_field.value, { + method: method, + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + body: JSON.stringify({ + id: id_field.value, + login: login_field.value, + password: password_field.value, + passwordHash: passwordhash_field.value, + name: name_field.value, + surname: surname_field.value, + role: role_field.value, + }), + }); + if (response.status !== 200) { + throw new Error(`User could not be updated or created (status ${response.status})`); + } + let user = await response.json(); + if (user.role == "BANKER") { + let banker = await this.getBankerByUserID(user.id); + if (banker != undefined) this.parent.HandleBanker.editBanker(banker); + else this.parent.HandleBanker.newBanker(user.id); + } else if (user.role == "CLIENT") { + let client = await Auth.getClientByUserID(user.id, current_user); + if (client != undefined) this.parent.HandleClient.editClient(client); + else this.parent.HandleClient.newClient(user.id); + } else Users.displayUsers(); + } catch (e) { + Messages.Show("is-warning", e.message); + console.error(e); + } + Users.toggleModal("users-modal", "users-modal-card"); + } +} diff --git a/web/components/users/users.js b/web/components/users/users.js index 5d508f334554e5440c15333501524fa2751f98c9..958c8e147a22c1bc0322420204985baf56342478 100644 --- a/web/components/users/users.js +++ b/web/components/users/users.js @@ -1,288 +1,126 @@ // Imports -import * as Messages from "/services/messages/messages.js"; import { AnimateCSS, RandomString } from "/services/common/common.js"; import * as Auth from "/services/auth/auth.js"; import { Delete } from "/services/common/delete.js"; +import * as HandleUser from "/components/users/handleUser.js"; +import * as HandleBanker from "/components/users/handleBanker.js"; +import * as HandleClient from "/components/users/handleClient.js"; // DOM elements -let mountpoint; -let id_field; -let login_field; -let password_field; -let passwordhash_field; -let name_field; -let surname_field; -let roles_field; // local variables -let users; let current_user; export async function mount(where) { - mountpoint = where; - document.getElementById(mountpoint).innerHTML = /* HTML */ ` - <div class="table-container"> - <table class="table is-bordered is-narrow is-hoverable is-fullwidth"> - <thead> - <tr class="is-selected"> - <th>Id</th> - <th>Login</th> - <th>Password</th> - <th>Hash</th> - <th>Name</th> - <th>Surname</th> - <th>Roles</th> - <th>Actions</th> - </tr> - </thead> - <tbody id="users"></tbody> - </table> - </div> - <button id="users-new" class="button is-primary"> - <span class="icon is-small"> - <i class="fas fa-plus"></i> - </span> - </button> - - <div class="modal" id="users-modal"> - <div class="modal-background"></div> - <div class="modal-card" id="users-modal-card"> - <header class="modal-card-head"> - <p class="modal-card-title">Add/Edit user</p> - <button class="delete" aria-label="close" id="users-modal-close"></button> - </header> - <section class="modal-card-body"> - <div class="field"> - <label>Id</label> - <div class="control"> - <input class="input" type="number" id="users-modal-id" /> - </div> - </div> - <div class="field"> - <label>Login</label> - <div class="control"> - <input class="input" type="text" id="users-modal-login" /> - </div> - </div> - <div class="field" id="users-modal-password-container"> - <label>Password</label> - <div class="control"> - <input class="input" type="text" id="users-modal-password" /> - </div> - </div> - <div class="field" id="users-modal-password-container"> - <label>Password Hash</label> - <div class="control"> - <input class="input" type="text" id="users-modal-passwordhash" /> - </div> - </div> - <div class="field" id="users-modal-name-container"> - <label>Name</label> - <div class="control"> - <input class="input" type="text" id="users-modal-name" /> - </div> - </div> - <div class="field" id="users-modal-surname-container"> - <label>Surname</label> - <div class="control"> - <input class="input" type="text" id="users-modal-surname" /> - </div> - </div> - <div class="field" id="users-modal-roles-container"> - <label>Roles (separated with commas)</label> - <div class="control"> - <input class="input" type="text" id="users-modal-roles" /> - </div> - </div> - </section> - <footer class="modal-card-foot"> - <button id="users-modal-save" class="button is-success">Save changes</button> - <button id="users-modal-cancel" class="button">Cancel</button> - </footer> - </div> - </div> - `; - registerModalFields(); - current_user = await Auth.GetUser(); - await firstShowUsers(); + const userPage = new Users(); + await userPage.mount(where); } -function cleanUser(user) { - let props = ["password", "name", "surname", "memberOf"]; - for (const prop of props) { - user[prop] = user[prop] === undefined ? "" : user[prop]; +class Users { + constructor() { + // Random id seed + this.prefix = RandomString(8); } -} -function userTemplate(user) { - cleanUser(user); - return /* HTML */ ` - <tr id="users-user-${user.id}"> - <th>${user.id}</th> - <td>${user.login}</td> - <td>${user.password}</td> - <td>${user.passwordHash === undefined ? "" : "..."}</td> - <td>${user.name}</td> - <td>${user.surname}</td> - <td>${user.memberOf}</td> - <td> - <a id="users-user-edit-${user.id}" class="button is-link is-small"> - <span>Edit</span> - <span class="icon is-small"> - <i class="fas fa-pen"></i> - </span> - </a> - <a id="users-user-delete-${user.id}" class="button is-danger is-small"> - <span>Delete</span> - <span class="icon is-small"> - <i class="fas fa-times"></i> - </span> - </a> - </td> - </tr> - `; -} + async mount(where) { + const mountpoint = where; + document.getElementById(mountpoint).innerHTML = /* HTML */ ` + <div class="table-container"> + <table class="table is-bordered is-narrow is-hoverable is-fullwidth"> + <thead> + <tr class="is-selected"> + <th>Id</th> + <th>IdOAuth</th> + <th>Identifiant</th> + <th>Nom complet</th> + <th>Nom</th> + <th>Surnom</th> + <th>Rôle</th> + <th>Actions</th> + </tr> + </thead> + <tbody id="users"></tbody> + </table> + </div> + <button id="users-new" class="button is-primary"> + <span class="icon is-small"> + <i class="fas fa-plus"></i> + </span> + </button> -function displayUsers() { - const markup = users.map(user => userTemplate(user)).join(""); - document.getElementById("users").innerHTML = markup; - users.map(user => { - document.getElementById(`users-user-edit-${user.id}`).addEventListener("click", function() { - editUser(user); - }); - document.getElementById(`users-user-delete-${user.id}`).addEventListener("click", function() { - new Delete(() => { - deleteUser(user); - }); - }); - }); -} + <div class="modal" id="users-modal"></div> -async function firstShowUsers() { - try { - const response = await fetch("/api/admin/users/", { - method: "GET", - headers: new Headers({ - "XSRF-Token": current_user.xsrftoken - }) - }); - if (response.status !== 200) { - throw new Error(`Users could not be fetched (status ${response.status})`); - } - users = await response.json(); - displayUsers(); - } catch (e) { - Messages.Show("is-warning", e.message); - console.error(e); + <div class="modal" id="banker-modal"></div> + + <div class="modal" id="client-modal"></div> + `; + current_user = await Auth.GetUser(); + this.HandleUser = await HandleUser.mount("users-modal", this); + this.HandleBanker = await HandleBanker.mount("banker-modal", this); + this.HandleClient = await HandleClient.mount("client-modal", this); + await this.displayUsers(); } -} -async function deleteUser(user) { - try { - const response = await fetch("/api/admin/users/" + user.id, { - method: "delete", - headers: new Headers({ - "XSRF-Token": current_user.xsrftoken - }) + async displayUsers() { + let users = await this.HandleUser.updateUsers(); + const markup = users.map((user) => this.userTemplate(user)).join(""); + document.getElementById("users").innerHTML = markup; + let userHandler = this.HandleUser; + users.map((user) => { + document.getElementById(`users-user-edit-${user.id}`).addEventListener("click", function () { + userHandler.editUser(user); + }); + document.getElementById(`users-user-delete-${user.id}`).addEventListener("click", function () { + new Delete(() => { + userHandler.deleteUser(user); + }); + }); }); - if (response.status !== 200) { - throw new Error(`User could not be deleted (status ${response.status})`); - } - document.getElementById(`users-user-${user.id}`).remove(); - } catch (e) { - Messages.Show("is-warning", e.message); - console.error(e); } -} -function registerModalFields() { - id_field = document.getElementById("users-modal-id"); - login_field = document.getElementById("users-modal-login"); - password_field = document.getElementById("users-modal-password"); - passwordhash_field = document.getElementById("users-modal-passwordhash"); - name_field = document.getElementById("users-modal-name"); - surname_field = document.getElementById("users-modal-surname"); - roles_field = document.getElementById("users-modal-roles"); - document.getElementById(`users-modal-close`).addEventListener("click", function() { - toggleModal(); - }); - document.getElementById(`users-modal-cancel`).addEventListener("click", function() { - toggleModal(); - }); - document.getElementById(`users-modal-save`).addEventListener("click", function() { - postUser(); - }); - document.getElementById(`users-new`).addEventListener("click", function() { - newUser(); - }); - password_field.addEventListener("click", function() { - password_field.value = RandomString(48); - }); -} - -async function editUser(user) { - cleanUser(user); - id_field.value = user.id; - login_field.value = user.login; - password_field.value = user.passwordHash !== "" ? "" : RandomString(48); - passwordhash_field.value = user.passwordHash; - name_field.value = user.name; - surname_field.value = user.surname; - roles_field.value = user.memberOf; - toggleModal(); -} - -async function newUser() { - let maxid = 0; - users.map(function(user) { - if (parseInt(user.id) > maxid) maxid = user.id; - }); - maxid++; - id_field.value = maxid.toString(); - login_field.value = ""; - password_field.value = RandomString(48); - passwordhash_field.value = ""; - name_field.value = ""; - surname_field.value = ""; - roles_field.value = ""; - toggleModal(); -} - -async function postUser() { - try { - const response = await fetch("/api/admin/users/", { - method: "POST", - headers: new Headers({ - "XSRF-Token": current_user.xsrftoken - }), - body: JSON.stringify({ - id: id_field.value, - login: login_field.value, - password: password_field.value, - passwordHash: passwordhash_field.value, - name: name_field.value, - surname: surname_field.value, - memberOf: roles_field.value.split(",") - }) - }); - if (response.status !== 200) { - throw new Error(`Users could not be updated (status ${response.status})`); + cleanUser(user) { + let props = ["password", "name", "surname", "memberOf"]; + for (const prop of props) { + user[prop] = user[prop] === undefined ? "" : user[prop]; } - users = await response.json(); - displayUsers(); - } catch (e) { - Messages.Show("is-warning", e.message); - console.error(e); } - toggleModal(); + + userTemplate(user) { + this.cleanUser(user); + return /* HTML */ ` + <tr id="users-user-${user.id}"> + <td>${user.id}</td> + <td>${user.idOAuth}</td> + <td>${user.login}</td> + <td>${user.displayName == undefined ? "" : user.displayName}</td> + <td>${user.name}</td> + <td>${user.surname}</td> + <td>${user.role}</td> + <td> + <a id="users-user-edit-${user.id}" class="button is-link is-small"> + <span>Modifier</span> + <span class="icon is-small"> + <i class="fas fa-pen"></i> + </span> + </a> + <a id="users-user-delete-${user.id}" class="button is-danger is-small"> + <span>Supprimer</span> + <span class="icon is-small"> + <i class="fas fa-times"></i> + </span> + </a> + </td> + </tr> + `; + } } -function toggleModal() { - const modal = document.getElementById("users-modal"); - const card = document.getElementById("users-modal-card"); +export function toggleModal(modalID, cardID) { + const modal = document.getElementById(modalID); + const card = document.getElementById(cardID); if (modal.classList.contains("is-active")) { AnimateCSS(modal, "fadeOut"); - AnimateCSS(card, "zoomOut", function() { + AnimateCSS(card, "zoomOut", function () { modal.classList.remove("is-active"); }); } else { diff --git a/web/index.html b/web/index.html index 717b32451387a14c601d927fc717c9c6778648aa..1b39b9c71505a24232b6ddcc9b175c7226878cba 100644 --- a/web/index.html +++ b/web/index.html @@ -1,17 +1,19 @@ <!DOCTYPE html> <html lang="en" class="has-navbar-fixed-top"> - <title>SDK-GO</title> - <meta charset="UTF-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - <link rel="icon" href="assets/brand/favicon.ico" /> - <link rel="manifest" href="assets/brand/manifest.json" /> - <link rel="stylesheet" href="assets/bulma.min.css" /> - <link rel="stylesheet" href="assets/animate.css" /> - <link rel="stylesheet" href="style.css" /> - <script defer src="assets/fontawesome/brands.min.js"></script> - <script defer src="assets/fontawesome/solid.min.js"></script> - <script defer src="assets/fontawesome/fontawesome.min.js"></script> - <script defer type="module" src="main.js"></script> + <head> + <title>SDK-GO</title> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="icon" href="assets/brand/favicon.ico" /> + <link rel="manifest" href="assets/brand/manifest.json" /> + <link rel="stylesheet" href="assets/bulma.min.css" /> + <link rel="stylesheet" href="assets/animate.css" /> + <link rel="stylesheet" href="style.css" /> + <script defer src="assets/fontawesome/brands.min.js"></script> + <script defer src="assets/fontawesome/solid.min.js"></script> + <script defer src="assets/fontawesome/fontawesome.min.js"></script> + <script defer type="module" src="main.js"></script> + </head> <body> <!-- Navbar --> <nav id="navbar" class="navbar is-dark is-fixed-top" role="navigation" aria-label="main navigation"></nav> @@ -23,18 +25,18 @@ </div> <!-- Footer --> - <nav class="navbar is-dark is-fixed-bottom"> + <nav aria-label="footer-nav" class="navbar is-dark is-fixed-bottom"> <div class="navbar-brand"> <div class="navbar-item"> <div class="buttons"> - <a class="button is-primary" href="https://www.github.com/nicolaspernoud/Vestibule" target="_blank"> + <a class="button is-primary" href="https://www.github.com/nicolaspernoud/Vestibule" target="_blank" rel="noopener noreferrer"> <span class="icon"> - <i class="fab fa-github"></i> + <em class="fab fa-github"></em> </span> </a> - <a class="button is-danger" href="https://forge.grandlyon.com/NPERNOUD/vestibule" target="_blank"> + <a class="button is-danger" href="https://forge.grandlyon.com/NPERNOUD/vestibule" target="_blank" rel="noopener noreferrer"> <span class="icon"> - <i class="fab fa-gitlab"></i> + <em class="fab fa-gitlab"></em> </span> </a> </div> @@ -42,7 +44,7 @@ </div> <div class="navbar-menu"> <div class="navbar-end"> - <div class="navbar-item"><p>v4.2.0</p></div> + <div class="navbar-item"><p>v1.0.0</p></div> </div> </div> </nav> diff --git a/web/main.js b/web/main.js index f6e9a35bd75a0b98e72cc6023cc2ed4ca0e62666..947158044d8dd777ec93c9afede7882edbcd1d96 100644 --- a/web/main.js +++ b/web/main.js @@ -1,10 +1,14 @@ +import * as Home from "/components/home/home.js"; import * as Users from "/components/users/users.js"; +import * as Client from "/components/clientPage/clientPage.js"; +import * as Banker from "/components/bankerPage/bankerPage.js"; import * as Login from "/components/login/login.js"; import * as Navbar from "/components/navbar/navbar.js"; import { AnimateCSS } from "/services/common/common.js"; const mountPoint = document.getElementById("main"); const spinner = document.getElementById("spinner"); +let sysInfoInterval; document.addEventListener("DOMContentLoaded", function() { Navbar.mount("navbar"); @@ -13,19 +17,35 @@ document.addEventListener("DOMContentLoaded", function() { }); async function navigate() { + clearInterval(sysInfoInterval); switch (location.hash) { + case "#home": + load(mountPoint, async function() { + await Home.mount("main"); + }); + break; case "#users": load(mountPoint, async function() { await Users.mount("main"); }); break; + case "#client": + load(mountPoint, async function() { + await Client.mount("main"); + }); + break; + case "#banker": + load(mountPoint, async function() { + await Banker.mount("main"); + }); + break; case "#login": load(mountPoint, async function() { await Login.mount("main"); }); break; default: - location.hash = "#users"; + location.hash = "#home"; break; } } diff --git a/web/services/auth/auth.js b/web/services/auth/auth.js index 6333d8459b0f7e15d1166873db74fbd5af5f2cd9..ffe80d77a498377593309322506fcb0346faf28a 100644 --- a/web/services/auth/auth.js +++ b/web/services/auth/auth.js @@ -21,3 +21,23 @@ export async function GetUser() { location.hash = "#login"; } } + +export async function getClientByUserID(userID, current_user) { + let clientToSend = undefined; + const response = await fetch("/api/UserClients/", { + method: "GET", + headers: new Headers({ + "XSRF-Token": current_user.xsrftoken, + }), + }); + if (response.status !== 200) { + throw new Error(`Could not get clients (status ${response.status})`); + } + let clients = await response.json(); + clients.forEach((client) => { + if (client.UserID == userID) { + clientToSend = client; + } + }); + return clientToSend; +} diff --git a/web/style.css b/web/style.css index 861673e967fee9109296c3d4dbe9fe2693a80069..e3fd68954006a09aa10e5af3e75e14fbddec4980 100644 --- a/web/style.css +++ b/web/style.css @@ -87,3 +87,7 @@ img { .media a { cursor: auto; } + +.select, select{ + width :100%; +} \ No newline at end of file