Skip to content
This repository has been archived by the owner on Jan 10, 2022. It is now read-only.

Commit

Permalink
added full multilanguage support (on backend and web)
Browse files Browse the repository at this point in the history
  • Loading branch information
sergeyglazyrindev committed Oct 28, 2021
1 parent f4502bc commit 47e783b
Show file tree
Hide file tree
Showing 67 changed files with 1,058 additions and 313 deletions.
14 changes: 7 additions & 7 deletions blueprint/auth/interfaces/direct.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,29 +54,29 @@ func (ap *DirectAuthProvider) Signin(c *gin.Context) {
directAPISigninByField := core.CurrentConfig.D.Uadmin.DirectAPISigninByField
db.Db.Model(core.User{}).Where(fmt.Sprintf("%s = ?", directAPISigninByField), json.SigninField).First(&user)
if user.ID == 0 {
c.JSON(http.StatusBadRequest, core.APIBadResponse("login credentials are incorrect"))
c.JSON(http.StatusBadRequest, core.APIBadResponseWithCode("login_credentials_incorrect", "login credentials are incorrect"))
return
}
if !user.Active {
c.JSON(http.StatusBadRequest, core.APIBadResponse("this user is inactive"))
c.JSON(http.StatusBadRequest, core.APIBadResponseWithCode("user_inactive", "this user is inactive"))
return
}
if !user.IsPasswordUsable {
c.JSON(http.StatusBadRequest, core.APIBadResponse("this user doesn't have a password"))
c.JSON(http.StatusBadRequest, core.APIBadResponseWithCode("password_is_not_configured", "this user doesn't have a password"))
return
}
err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(json.Password+user.Salt))
if err != nil {
c.JSON(http.StatusBadRequest, core.APIBadResponse("login credentials are incorrect"))
c.JSON(http.StatusBadRequest, core.APIBadResponseWithCode("login_credentials_incorrect", "login credentials are incorrect"))
return
}
if user.GeneratedOTPToVerify != "" {
if json.OTP == "" {
c.JSON(http.StatusBadRequest, core.APIBadResponse("otp is required"))
c.JSON(http.StatusBadRequest, core.APIBadResponseWithCode("otp_required", "otp is required"))
return
}
if user.GeneratedOTPToVerify != json.OTP {
c.JSON(http.StatusBadRequest, core.APIBadResponse("otp provided by user is wrong"))
c.JSON(http.StatusBadRequest, core.APIBadResponseWithCode("otp_is_wrong", "otp provided by user is wrong"))
return
}
user.GeneratedOTPToVerify = ""
Expand Down Expand Up @@ -214,7 +214,7 @@ func (ap *DirectAuthProvider) IsAuthenticated(c *gin.Context) {
return
}
if sessionAdapter.IsExpired() {
c.JSON(http.StatusBadRequest, core.APIBadResponse("session expired"))
c.JSON(http.StatusBadRequest, core.APIBadResponseWithCode("session_expired", "session expired"))
return
}
c.JSON(http.StatusOK, GetUserForAPI(sessionAdapter.GetUser()))
Expand Down
12 changes: 6 additions & 6 deletions blueprint/auth/interfaces/direct_for_admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,24 +52,24 @@ func (ap *DirectAuthForAdminProvider) Signin(c *gin.Context) {
var user = core.GenerateUserModel()
db.Model(core.User{}).Where(&core.User{Username: json.SigninField}).First(user)
if user.GetID() == 0 {
c.JSON(http.StatusBadRequest, core.APIBadResponse("login credentials are incorrect."))
c.JSON(http.StatusBadRequest, core.APIBadResponseWithCode("login_credentials_incorrect", "login credentials are incorrect."))
return
}
if !user.GetActive() {
c.JSON(http.StatusBadRequest, core.APIBadResponse("this user is inactive"))
c.JSON(http.StatusBadRequest, core.APIBadResponseWithCode("user_inactive", "this user is inactive"))
return
}
if !user.GetIsSuperUser() && !user.GetIsStaff() {
c.JSON(http.StatusBadRequest, core.APIBadResponse("this user doesn't have an access to admin panel"))
c.JSON(http.StatusBadRequest, core.APIBadResponseWithCode("user_has_no_access_to_admin_panel", "this user doesn't have an access to admin panel"))
return
}
if !user.GetIsPasswordUsable() {
c.JSON(http.StatusBadRequest, core.APIBadResponse("this user doesn't have a password"))
c.JSON(http.StatusBadRequest, core.APIBadResponseWithCode("password_is_not_configured", "this user doesn't have a password"))
return
}
err := bcrypt.CompareHashAndPassword([]byte(user.GetPassword()), []byte(json.Password+user.GetSalt()))
if err != nil {
c.JSON(http.StatusBadRequest, core.APIBadResponse("login credentials are incorrect."))
c.JSON(http.StatusBadRequest, core.APIBadResponseWithCode("login_credentials_incorrect", "login credentials are incorrect."))
return
}
sessionAdapterRegistry := sessionsblueprint.ConcreteBlueprint.SessionAdapterRegistry
Expand Down Expand Up @@ -206,7 +206,7 @@ func (ap *DirectAuthForAdminProvider) IsAuthenticated(c *gin.Context) {
return
}
if sessionAdapter.IsExpired() {
c.JSON(http.StatusBadRequest, core.APIBadResponse("session expired"))
c.JSON(http.StatusBadRequest, core.APIBadResponseWithCode("session_expired", "session expired"))
return
}
c.JSON(http.StatusOK, getUserForUadminPanel(sessionAdapter.GetUser()))
Expand Down
3 changes: 1 addition & 2 deletions blueprint/language/language.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package language

import (
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/sergeyglazyrindev/uadmin/blueprint/language/migrations"
Expand Down Expand Up @@ -47,7 +46,7 @@ func (b Blueprint) InitRouter(app core.IApp, group *gin.RouterGroup) {
lang := &core.Language{}
uadminDatabase.Db.Where(&core.Language{Default: true}).First(lang)
if lang.ID != 0 && ID != strconv.Itoa(int(lang.ID)) {
return errors.New("only one default language could be configured")
return core.NewHTTPErrorResponse("only_one_default_language_allowed", "only one default language could be configured")
}
return nil
})
Expand Down
3 changes: 1 addition & 2 deletions blueprint/sessions/interfaces/db.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package interfaces

import (
"fmt"
"github.com/sergeyglazyrindev/uadmin/core"
"time"
)
Expand Down Expand Up @@ -59,7 +58,7 @@ func (s *DbSession) GetByKey(sessionKey string) (ISessionProvider, error) {
var session core.Session
db.Db.Model(&core.Session{}).Where(&core.Session{Key: sessionKey}).Preload("User").First(&session)
if session.ID == 0 {
return nil, fmt.Errorf("no session with key %s found", sessionKey)
return nil, core.NewHTTPErrorResponse("session_not_found", "no session with key %s found", sessionKey)
}
return &DbSession{
session: &session,
Expand Down
4 changes: 2 additions & 2 deletions blueprint/sessions/sessions.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func (b Blueprint) InitRouter(app core.IApp, group *gin.RouterGroup) {
return
}
contentType := c.Request.Header.Get("Content-Type")
if contentType == "application/json" {
if strings.Contains(contentType, "application/json") {
c.Next()
return
}
Expand Down Expand Up @@ -84,7 +84,7 @@ func (b Blueprint) InitRouter(app core.IApp, group *gin.RouterGroup) {
return
}
contentType := c.Request.Header.Get("Content-Type")
if contentType == "application/json" {
if strings.Contains(contentType, "application/json") {
c.Next()
return
}
Expand Down
40 changes: 20 additions & 20 deletions blueprint/user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func (b Blueprint) InitRouter(app core.IApp, group *gin.RouterGroup) {
user := core.GenerateUserModel()
db.Model(core.GenerateUserModel()).Where(&core.User{Email: json.Email}).First(user)
if user.GetID() == 0 {
ctx.JSON(http.StatusBadRequest, core.APIBadResponse("User with this email not found"))
ctx.JSON(http.StatusBadRequest, core.APIBadResponseWithCode("user_not_found", "User with this email not found"))
return
}
templateWriter := bytes.NewBuffer([]byte{})
Expand Down Expand Up @@ -135,11 +135,11 @@ func (b Blueprint) InitRouter(app core.IApp, group *gin.RouterGroup) {
var oneTimeAction core.OneTimeAction
db.Model(core.OneTimeAction{}).Where(&core.OneTimeAction{Code: json.Code, IsUsed: false}).Preload("User").First(&oneTimeAction)
if oneTimeAction.ID == 0 {
ctx.JSON(http.StatusBadRequest, core.APIBadResponse("No such code found"))
ctx.JSON(http.StatusBadRequest, core.APIBadResponseWithCode("code_not_found", "No such code found"))
return
}
if oneTimeAction.ExpiresOn.Before(time.Now()) {
ctx.JSON(http.StatusBadRequest, core.APIBadResponse("Code is expired"))
ctx.JSON(http.StatusBadRequest, core.APIBadResponseWithCode("code_expired", "Code is expired"))
return
}
passwordValidationStruct := &PasswordValidationStruct{
Expand Down Expand Up @@ -369,14 +369,14 @@ func (b Blueprint) InitRouter(app core.IApp, group *gin.RouterGroup) {
return ret
}
userGroupsWidget.LeftSelectTitle = "Available groups"
userGroupsWidget.LeftSelectHelp = "This is the list of available groups. You may choose some by selecting them in the box below and then clicking the \"Choose\" arrow between the two boxes."
userGroupsWidget.LeftSearchSelectHelp = "Type into this box to filter down the list of available groups."
userGroupsWidget.LeftSelectHelp = "available_groups_left"
userGroupsWidget.LeftSearchSelectHelp = "available_groups_search_help"
userGroupsWidget.LeftHelpChooseAll = "Click to choose all groups at once."
userGroupsWidget.RightSelectTitle = "Chosen groups"
userGroupsWidget.RightSelectHelp = "This is the list of chosen groups. You may remove some by selecting them in the box below and then clicking the \"Remove\" arrow between the two boxes."
userGroupsWidget.RightSelectHelp = "chosen_groups_left"
userGroupsWidget.RightSearchSelectHelp = ""
userGroupsWidget.RightHelpChooseAll = "Click to remove all chosen groups at once."
userGroupsWidget.HelpText = "The groups this user belongs to. A user will get all permissions granted to each of their groups. Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
userGroupsWidget.HelpText = "group_widget_help"
permissionsField, _ := form.FieldRegistry.GetByName("Permissions")
permissionsField.SetUpField = func(w core.IWidget, modelI interface{}, v interface{}, afo core.IAdminFilterObjects) error {
model := modelI.(*core.User)
Expand Down Expand Up @@ -414,14 +414,14 @@ func (b Blueprint) InitRouter(app core.IApp, group *gin.RouterGroup) {
return ret
}
permissionsWidget.LeftSelectTitle = "Available user permissions"
permissionsWidget.LeftSelectHelp = "This is the list of available user permissions. You may choose some by selecting them in the box below and then clicking the \"Choose\" arrow between the two boxes."
permissionsWidget.LeftSearchSelectHelp = "Type into this box to filter down the list of available user permissions."
permissionsWidget.LeftSelectHelp = "available_permissions_left"
permissionsWidget.LeftSearchSelectHelp = "available_permissions_search_help"
permissionsWidget.LeftHelpChooseAll = "Click to choose all user permissions at once."
permissionsWidget.RightSelectTitle = "Chosen user permissions"
permissionsWidget.RightSelectHelp = "This is the list of chosen user permissions. You may remove some by selecting them in the box below and then clicking the \"Remove\" arrow between the two boxes."
permissionsWidget.RightSelectHelp = "chosen_permissions_left"
permissionsWidget.RightSearchSelectHelp = ""
permissionsWidget.RightHelpChooseAll = "Click to remove all chosen user permissions at once."
permissionsWidget.HelpText = "Specific permissions for this user. Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
permissionsWidget.HelpText = "permission_widget_help"
permissionsWidget.PopulateRightSide = func() []*core.SelectOptGroup {
ret := make([]*core.SelectOptGroup, 0)
user := modelI.(*core.User)
Expand Down Expand Up @@ -545,14 +545,14 @@ func (b Blueprint) InitRouter(app core.IApp, group *gin.RouterGroup) {
return ret
}
permissionsWidget.LeftSelectTitle = "Available permissions"
permissionsWidget.LeftSelectHelp = "This is the list of available permissions. You may choose some by selecting them in the box below and then clicking the \"Choose\" arrow between the two boxes."
permissionsWidget.LeftSearchSelectHelp = "Type into this box to filter down the list of available user permissions."
permissionsWidget.LeftSelectHelp = "available_permissions_left"
permissionsWidget.LeftSearchSelectHelp = "available_permissions_search_help"
permissionsWidget.LeftHelpChooseAll = "Click to choose all user permissions at once."
permissionsWidget.RightSelectTitle = "Chosen permissions"
permissionsWidget.RightSelectHelp = "This is the list of chosen permissions. You may remove some by selecting them in the box below and then clicking the \"Remove\" arrow between the two boxes."
permissionsWidget.RightSelectHelp = "chosen_permissions_left"
permissionsWidget.RightSearchSelectHelp = ""
permissionsWidget.RightHelpChooseAll = "Click to remove all chosen permissions at once."
permissionsWidget.HelpText = "Specific permissions for this user. Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
permissionsWidget.HelpText = "permission_widget_help"
permissionsWidget.PopulateRightSide = func() []*core.SelectOptGroup {
ret := make([]*core.SelectOptGroup, 0)
user := modelI.(*core.UserGroup)
Expand Down Expand Up @@ -622,7 +622,7 @@ func (b Blueprint) InitApp(app core.IApp) {
if cUsers == 0 {
return nil
}
return fmt.Errorf("user with name %s is already registered", i.(string))
return core.NewHTTPErrorResponse("user_name_already_registered", "user with name %s is already registered", i.(string))
})

core.UadminValidatorRegistry.AddValidator("email-unique", func(i interface{}, o interface{}) error {
Expand All @@ -634,26 +634,26 @@ func (b Blueprint) InitApp(app core.IApp) {
if cUsers == 0 {
return nil
}
return fmt.Errorf("user with email %s is already registered", i.(string))
return core.NewHTTPErrorResponse("user_email_already_registered", "user with email %s is already registered", i.(string))
})

core.UadminValidatorRegistry.AddValidator("username-uadmin", func(i interface{}, o interface{}) error {
minLength := core.CurrentConfig.D.Auth.MinUsernameLength
maxLength := core.CurrentConfig.D.Auth.MaxUsernameLength
currentUsername := i.(string)
if maxLength < len(currentUsername) || len(currentUsername) < minLength {
return fmt.Errorf("length of the username has to be between %d and %d symbols", minLength, maxLength)
return core.NewHTTPErrorResponse("username_length_error", "length of the username has to be between %s and %s symbols", strconv.Itoa(minLength), strconv.Itoa(maxLength))
}
return nil
})

core.UadminValidatorRegistry.AddValidator("password-uadmin", func(i interface{}, o interface{}) error {
passwordStruct := o.(PasswordValidationStruct)
if passwordStruct.Password != passwordStruct.ConfirmedPassword {
return fmt.Errorf("password doesn't equal to confirmed password")
return core.NewHTTPErrorResponse("password_not_equal", "password doesn't equal to confirmed password")
}
if len(passwordStruct.Password) < core.CurrentConfig.D.Auth.MinPasswordLength {
return fmt.Errorf("length of the password has to be at least %d symbols", core.CurrentConfig.D.Auth.MinPasswordLength)
return core.NewHTTPErrorResponse("password_length_error", "length of the password has to be at least %d symbols", strconv.Itoa(core.CurrentConfig.D.Auth.MinPasswordLength))
}
return nil
})
Expand Down
5 changes: 2 additions & 3 deletions core/admin_dashboard_panel.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package core

import (
"bytes"
"errors"
"fmt"
excelize1 "github.com/360EntSecGroup-Skylar/excelize/v2"
"github.com/gin-gonic/gin"
Expand Down Expand Up @@ -310,7 +309,7 @@ func (dap *DashboardAdminPanel) RegisterHTTPHandlers(router *gin.Engine) {
c.ListEditableFormsForInlines.AddForInlineWholeCollection(inline.Prefix, inlineListEditableCollection)
}
if !successfulInline {
return errors.New("error while submitting inlines")
return NewHTTPErrorResponse("error_inline_submit", "error while submitting inlines")
}
if ctx.Query("_popup") == "1" {
mID := GetID(reflect.ValueOf(modelToSave))
Expand All @@ -330,7 +329,7 @@ func (dap *DashboardAdminPanel) RegisterHTTPHandlers(router *gin.Engine) {
}
return nil
}
return errors.New("not successful form validation")
return NewHTTPErrorResponse("not_successful_form_validation", "not successful form validation")
})
if err != nil {
form.FormError.GeneralErrors = append(form.FormError.GeneralErrors, err)
Expand Down
2 changes: 1 addition & 1 deletion core/admin_model_actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func init() {
}
if removalConfirmed != "" {
query := ctx.Request.URL.Query()
query.Set("message", "Objects were removed succesfully")
query.Set("message", Tf(c.Language.Code, "Objects were removed succesfully"))
ctx.Redirect(http.StatusFound, fmt.Sprintf("%s/%s/%s/?%s", CurrentConfig.D.Uadmin.RootAdminURL, ap.ParentPage.Slug, ap.ModelName, query.Encode()))
return nil
}
Expand Down
4 changes: 2 additions & 2 deletions core/admin_page.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ func (ap *AdminPage) HandleModelAction(modelActionName string, ctx *gin.Context)
PopulateTemplateContextForAdminPanel(ctx, adminContext, adminRequestParams)
afo := ap.GetQueryset(adminContext, ap, adminRequestParams)
var json1 ModelActionRequestParams
if ctx.GetHeader("Content-Type") == "application/json" {
if strings.Contains(ctx.GetHeader("Content-Type"), "application/json") {
if err := ctx.ShouldBindJSON(&json1); err != nil {
ctx.JSON(http.StatusBadRequest, APIBadResponse(err.Error()))
return
Expand All @@ -241,7 +241,7 @@ func (ap *AdminPage) HandleModelAction(modelActionName string, ctx *gin.Context)
primaryKeyField, _ := ap.Form.FieldRegistry.GetPrimaryKey()
afo.FilterByMultipleIds(primaryKeyField, json1.RealObjectIds)
modelAction, _ := ap.ModelActionsRegistry.GetModelActionByName(modelActionName)
if ctx.GetHeader("Content-Type") == "application/json" {
if strings.Contains(ctx.GetHeader("Content-Type"), "application/json") {
_, affectedRows := modelAction.Handler(ap, afo, ctx)
ctx.JSON(http.StatusOK, gin.H{"Affected": strconv.Itoa(int(affectedRows))})
} else {
Expand Down
14 changes: 12 additions & 2 deletions core/admin_page_inlines.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package core

import (
"errors"
"fmt"
"html/template"
"mime/multipart"
Expand Down Expand Up @@ -56,6 +55,9 @@ func (api *AdminPageInline) RenderExampleForm(adminContext IAdminContext) templa
func1 := make(template.FuncMap)
path := "admin/inlineexampleform"
templateName := CurrentConfig.GetPathToTemplate(path)
templateRenderer.AddFuncMap("Translate", func(v interface{}) string {
return Tf(adminContext.GetLanguage().Code, v)
})
return templateRenderer.RenderAsString(
templateName,
c, FuncMap, func1,
Expand All @@ -65,6 +67,14 @@ func (api *AdminPageInline) RenderExampleForm(adminContext IAdminContext) templa
func (api *AdminPageInline) GetFormForExample(adminContext IAdminContext) *FormListEditable {
modelI, _ := api.GenerateModelI(nil)
form := api.ListDisplay.BuildListEditableFormForNewModel(adminContext, "toreplacewithid", modelI)
r := NewTemplateRenderer("")
r.AddFuncMap("Translate", func(v interface{}) string {
return Tf(adminContext.GetLanguage().Code, v)
})
for _, field := range form.FieldRegistry.GetAllFields() {
field.FieldConfig.Widget.RenderUsingRenderer(r)
}
// return r.RenderAsString(templateName, data, baseFuncMap)
return form
}

Expand Down Expand Up @@ -160,7 +170,7 @@ func (api *AdminPageInline) ProceedRequest(afo IAdminFilterObjects, f *multipart
}
}
if err {
return collection, errors.New("error while validating inlines")
return collection, NewHTTPErrorResponse("inline_validation_error", "error while validating inlines")
}
return collection, nil
}
Expand Down
13 changes: 13 additions & 0 deletions core/blueprint_interfaces.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package core

import (
"encoding/json"
"errors"
"fmt"
mapset "github.com/deckarep/golang-set"
Expand Down Expand Up @@ -288,6 +289,18 @@ func (r BlueprintRegistry) InitializeRouting(app IApp, router *gin.Engine) {
"message": "pong",
})
})
router.GET("/localization/", func(ctx *gin.Context) {
type Context struct {
AdminContext
}
c := &Context{}
adminRequestParams := NewAdminRequestParamsFromGinContext(ctx)
PopulateTemplateContextForAdminPanel(ctx, c, adminRequestParams)
langMap := ReadLocalization(c.GetLanguage().Code)
langMapB, _ := json.Marshal(langMap)
ctx.Header("Content-Type", "application/javascript")
ctx.String(200, fmt.Sprintf("setLocalization(%s)", string(langMapB)))
})
router.POST("/testcsrf/", func(c *gin.Context) {
c.String(200, "csrf token test passed")
})
Expand Down
1 change: 1 addition & 0 deletions core/config_interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ type UadminConfig struct {
TemplatesFS embed.FS
OverridenTemplatesFS *embed.FS
LocalizationFS embed.FS
CustomLocalizationFS *embed.FS
RequiresCsrfCheck func(c *gin.Context) bool
PatternsToIgnoreCsrfCheck *list.List
ErrorHandleFunc func(int, string, string)
Expand Down
Loading

0 comments on commit 47e783b

Please sign in to comment.