From 32c51a57cb7acea481846c13a747b5923a5d6fa4 Mon Sep 17 00:00:00 2001 From: akuma06 Date: Sun, 21 May 2017 18:13:28 +0200 Subject: [PATCH] User Settings Notification (WIP) --- config/notifications.go | 13 ++++++++++++ model/user.go | 31 ++++++++++++++++++++++++++- router/upload_handler.go | 10 +++++---- router/user_handler.go | 30 ++++++++++++++------------ service/user/form/form_validator.go | 33 +++++++++++++++++++++-------- util/modelHelper/model_helper.go | 28 +++++++++++++----------- 6 files changed, 105 insertions(+), 40 deletions(-) create mode 100644 config/notifications.go diff --git a/config/notifications.go b/config/notifications.go new file mode 100644 index 00000000..05cc4b94 --- /dev/null +++ b/config/notifications.go @@ -0,0 +1,13 @@ +package config + + +/* + * Here we config the notifications options + */ +var EnableNotifications = map[string]bool { + "new_torrent": true, + "new_comment_owner": true, + "new_comment_all": true, + "new_follower": false, + "followed": false, +} \ No newline at end of file diff --git a/model/user.go b/model/user.go index d6dc565c..c15b4f30 100644 --- a/model/user.go +++ b/model/user.go @@ -24,6 +24,7 @@ type User struct { ApiToken string `gorm:"column:api_token"` ApiTokenExpiry time.Time `gorm:"column:api_token_expiry"` Language string `gorm:"column:language"` + UserSettings string `gorm:"column:settings"` // TODO: move this to PublicUser Likings []User // Don't work `gorm:"foreignkey:user_id;associationforeignkey:follower_id;many2many:user_follows"` @@ -31,9 +32,10 @@ type User struct { MD5 string `json:"md5" gorm:"column:md5"` // Hash of email address, used for Gravatar Torrents []Torrent `gorm:"ForeignKey:UploaderID"` + Notifications []Notification `gorm:"ForeignKey:UserID"` UnreadNotifications int `gorm:"-"` // We don't want to loop every notifications when accessing user unread notif - Notifications []Notification `gorm:"ForeignKey:UserID"` + Settings *UserSettings `gorm:"-"` // We don't want to loop every notifications when accessing user unread notif } type UserJSON struct { @@ -99,6 +101,10 @@ type UserUploadsOld struct { TorrentId uint `gorm:"column:torrent_id"` } +type UserSettings struct { + settings map[string]interface{} `json:"settings"` +} + func (c UserUploadsOld) TableName() string { // is this needed here? return config.UploadsOldTableName @@ -115,3 +121,26 @@ func (u *User) ToJSON() UserJSON { } return json } + +/* User Settings */ + +func(s *UserSettings) Get(key string) interface{} { + return s.settings[key] +} + +func (s *UserSettings) Set(key string, val interface{}) { + s.settings[key] = val +} + +func (s *UserSettings) GetSettings() { + return s.settings +} + +func (u *User) SaveSettings() { + u.UserSettings , _ = json.Marshal(u.Settings.GetSettings()) +} + +func (u *User) ParseSettings() { + if len(u.Settings.GetSettings()) == 0 + json.Unmarshal([]byte(u.UserSettings), u.Settings) +} \ No newline at end of file diff --git a/router/upload_handler.go b/router/upload_handler.go index fadd73f4..717be98b 100644 --- a/router/upload_handler.go +++ b/router/upload_handler.go @@ -79,14 +79,16 @@ func UploadPostHandler(w http.ResponseWriter, r *http.Request) { url, err := Router.Get("view_torrent").URL("id", strconv.FormatUint(uint64(torrent.ID), 10)) - if (user.ID > 0) { // If we are a member + if (user.ID > 0 && config.EnableNotifications["new_torrent"]) { // If we are a member and notifications for new torrents are enabled userService.GetLikings(user) // We populate the liked field for users if len(user.Likings) > 0 { // If we are followed by at least someone for _, follower := range user.Likings { - T, _, _ := languages.TfuncAndLanguageWithFallback(user.Language, user.Language) // We need to send the notification to every user in their language + follower.ParseSettings() // We need to call it before checking settings + if follower.Settings.Get("notifications.new_torrent"] { + T, _, _ := languages.TfuncAndLanguageWithFallback(user.Language, user.Language) // We need to send the notification to every user in their language - notifierService.NotifyUser(&follower, torrent.Identifier(), fmt.Sprintf(T("new_torrent_uploaded"), torrent.Name, user.Username), url.String()) - + notifierService.NotifyUser(&follower, torrent.Identifier(), fmt.Sprintf(T("new_torrent_uploaded"), torrent.Name, user.Username), url.String()) + } } } } diff --git a/router/user_handler.go b/router/user_handler.go index 3ef4a324..65e083e6 100644 --- a/router/user_handler.go +++ b/router/user_handler.go @@ -137,40 +137,42 @@ func UserProfileFormHandler(w http.ResponseWriter, r *http.Request) { NotFoundHandler(w, r) return } - + messages := msg.GetMessages(r) userForm := form.UserForm{} - err := form.NewErrors() - infos := form.NewInfos() + userSettingsForm := form.UserSettingsForm{} + + T := languages.GetTfuncFromRequest(r) if len(r.PostFormValue("email")) > 0 { - _, err = form.EmailValidation(r.PostFormValue("email"), err) + form.EmailValidation(r.PostFormValue("email"), &messages) } if len(r.PostFormValue("username")) > 0 { - _, err = form.ValidateUsername(r.PostFormValue("username"), err) + form.ValidateUsername(r.PostFormValue("username"), &messages) } - if len(err) == 0 { + if !messages.HasErrors() { modelHelper.BindValueForm(&userForm, r) + modelHelper.BindValueForm(&userSettingsForm, r) if !userPermission.HasAdmin(currentUser) { userForm.Username = userProfile.Username userForm.Status = userProfile.Status } else { if userProfile.Status != userForm.Status && userForm.Status == 2 { - err["errors"] = append(err["errors"], "Elevating status to moderator is prohibited") + messages.AddError("errors", "Elevating status to moderator is prohibited") } } - err = modelHelper.ValidateForm(&userForm, err) - if len(err) == 0 { + modelHelper.ValidateForm(&userForm, &messages) + if !messages.HasErrors() { if userForm.Email != userProfile.Email { userService.SendVerificationToUser(*currentUser, userForm.Email) - infos["infos"] = append(infos["infos"], fmt.Sprintf(string(T("email_changed")), userForm.Email)) + messages.AddInfof("infos", string(T("email_changed")), userForm.Email) userForm.Email = userProfile.Email // reset, it will be set when user clicks verification } userProfile, _, errorUser = userService.UpdateUser(w, &userForm, currentUser, id) if errorUser != nil { - err["errors"] = append(err["errors"], errorUser.Error()) + messages.ImportFromError("errors", errorUser) } else { - infos["infos"] = append(infos["infos"], string(T("profile_updated"))) + messages.AddInfo("infos", string(T("profile_updated"))) } } } @@ -179,8 +181,8 @@ func UserProfileFormHandler(w http.ResponseWriter, r *http.Request) { CommonTemplateVariables: NewCommonVariables(r), UserProfile: &userProfile, UserForm: userForm, - FormErrors: err, - FormInfos: infos, + FormErrors: messages.GetAllErrors(), + FormInfos: messages.GetAllInfos(), Languages: availableLanguages, } errorTmpl := viewProfileEditTemplate.ExecuteTemplate(w, "index.html", upev) diff --git a/service/user/form/form_validator.go b/service/user/form/form_validator.go index 16dcc0a1..9d07a043 100644 --- a/service/user/form/form_validator.go +++ b/service/user/form/form_validator.go @@ -4,33 +4,34 @@ import ( "regexp" "github.com/NyaaPantsu/nyaa/util/log" + msg "github.com/NyaaPantsu/nyaa/util/messages" ) const EMAIL_REGEX = `(\w[-._\w]*\w@\w[-._\w]*\w\.\w{2,3})` const USERNAME_REGEX = `(\W)` -func EmailValidation(email string, err map[string][]string) (bool, map[string][]string) { +func EmailValidation(email string, mes *msg.Messages) bool { exp, errorRegex := regexp.Compile(EMAIL_REGEX) if regexpCompiled := log.CheckError(errorRegex); regexpCompiled { if exp.MatchString(email) { - return true, err + return true } } - err["email"] = append(err["email"], "Email Address is not valid") - return false, err + mes.AddError("email", "Email Address is not valid") + return false } -func ValidateUsername(username string, err map[string][]string) (bool, map[string][]string) { +func ValidateUsername(username string, mes *msg.Messages) bool { exp, errorRegex := regexp.Compile(USERNAME_REGEX) if regexpCompiled := log.CheckError(errorRegex); regexpCompiled { if exp.MatchString(username) { - err["username"] = append(err["username"], "Username contains illegal characters") - return false, err + mes.AddError("username", "Username contains illegal characters") + return false } } else { - return false, err + return false } - return true, err + return true } func NewErrors() map[string][]string { @@ -73,6 +74,20 @@ type UserForm struct { Status int `form:"status" default:"0"` } +// UserSettingsForm is used when updating a user. +type UserSettingsForm struct { + NewTorrent bool `form:"new_torrent" default:"true"` + NewTorrentEmail bool `form:"new_torrent_email" default:"true"` + NewComment bool `form:"new_comment" default:"true"` + NewCommentEmail bool `form:"new_comment_email" default:"false"` + NewResponses bool `form:"new_responses" default:"true"` + NewResponsesEmail bool `form:"new_responses_email" default:"false"` + NewFollower bool `form:"new_follower" default:"true"` + NewFollowerEmail bool `form:"new_follower_email" default:"true"` + Followed bool `form:"followed" default:"false"` + FollowedEmail bool `form:"followed_email" default:"false"` +} + // PasswordForm is used when updating a user password. type PasswordForm struct { CurrentPassword string `form:"currentPassword"` diff --git a/util/modelHelper/model_helper.go b/util/modelHelper/model_helper.go index 9d42f5cd..a22fb649 100644 --- a/util/modelHelper/model_helper.go +++ b/util/modelHelper/model_helper.go @@ -3,6 +3,7 @@ package modelHelper import ( "fmt" "github.com/NyaaPantsu/nyaa/util/log" + msg "github.com/NyaaPantsu/nyaa/util/messages" "net/http" "reflect" "strconv" @@ -57,7 +58,7 @@ func BindValueForm(form interface{}, r *http.Request) { } } -func ValidateForm(form interface{}, errorForm map[string][]string) map[string][]string { +func ValidateForm(form interface{}, mes *msg.Messages) { formElem := reflect.ValueOf(form).Elem() for i := 0; i < formElem.NumField(); i++ { typeField := formElem.Type().Field(i) @@ -69,28 +70,28 @@ func ValidateForm(form interface{}, errorForm map[string][]string) map[string][] if tag.Get("len_min") != "" && (tag.Get("needed") != "" || formElem.Field(i).Len() > 0) { // Check minimum length lenMin, _ := strconv.Atoi(tag.Get("len_min")) if formElem.Field(i).Len() < lenMin { - errorForm[tag.Get("form")] = append(errorForm[tag.Get("form")], fmt.Sprintf("Minimal length of %s required for the input: %s", strconv.Itoa(lenMin), inputName)) + mes.AddErrorf(tag.Get("form"), "Minimal length of %s required for the input: %s", strconv.Itoa(lenMin), inputName) } } if tag.Get("len_max") != "" && (tag.Get("needed") != "" || formElem.Field(i).Len() > 0) { // Check maximum length lenMax, _ := strconv.Atoi(tag.Get("len_max")) if formElem.Field(i).Len() > lenMax { - errorForm[tag.Get("form")] = append(errorForm[tag.Get("form")], fmt.Sprintf("Maximal length of %s required for the input: %s", strconv.Itoa(lenMax), inputName)) + mes.AddErrorf(tag.Get("form"), "Maximal length of %s required for the input: %s", strconv.Itoa(lenMax), inputName) } } if tag.Get("equalInput") != "" && (tag.Get("needed") != "" || formElem.Field(i).Len() > 0) { otherInput := formElem.FieldByName(tag.Get("equalInput")) if formElem.Field(i).Interface() != otherInput.Interface() { - errorForm[tag.Get("form")] = append(errorForm[tag.Get("form")], fmt.Sprintf("Must be same %s", inputName)) + mes.AddErrorf(tag.Get("form"), "Must be same %s", inputName) } } switch typeField.Type.Name() { case "string": if tag.Get("equal") != "" && formElem.Field(i).String() != tag.Get("equal") { - errorForm[tag.Get("form")] = append(errorForm[tag.Get("form")], fmt.Sprintf("Wrong value for the input: %s", inputName)) + mes.AddErrorf(tag.Get("form"), "Wrong value for the input: %s", inputName) } if tag.Get("needed") != "" && formElem.Field(i).String() == "" { - errorForm[tag.Get("form")] = append(errorForm[tag.Get("form")], fmt.Sprintf("Field needed: %s", inputName)) + mes.AddErrorf(tag.Get("form"), "Field needed: %s", inputName) } if formElem.Field(i).String() == "" && tag.Get("default") != "" { formElem.Field(i).SetString(tag.Get("default")) @@ -99,11 +100,11 @@ func ValidateForm(form interface{}, errorForm map[string][]string) map[string][] if tag.Get("equal") != "" { // Check minimum length equal, _ := strconv.Atoi(tag.Get("equal")) if formElem.Field(i).Int() > int64(equal) { - errorForm[tag.Get("form")] = append(errorForm[tag.Get("form")], fmt.Sprintf("Wrong value for the input: %s", inputName)) + mes.AddErrorf(tag.Get("form"), "Wrong value for the input: %s", inputName) } } if tag.Get("needed") != "" && formElem.Field(i).Int() == 0 { - errorForm[tag.Get("form")] = append(errorForm[tag.Get("form")], fmt.Sprintf("Field needed: %s", inputName)) + mes.AddErrorf(tag.Get("form"), "Field needed: %s", inputName) } if formElem.Field(i).Interface == nil && tag.Get("default") != "" { defaultValue, _ := strconv.Atoi(tag.Get("default")) @@ -113,11 +114,11 @@ func ValidateForm(form interface{}, errorForm map[string][]string) map[string][] if tag.Get("equal") != "" { // Check minimum length equal, _ := strconv.Atoi(tag.Get("equal")) if formElem.Field(i).Float() != float64(equal) { - errorForm[tag.Get("form")] = append(errorForm[tag.Get("form")], fmt.Sprintf("Wrong value for the input: %s", inputName)) + mes.AddErrorf(tag.Get("form"), "Wrong value for the input: %s", inputName) } } if tag.Get("needed") != "" && formElem.Field(i).Float() == 0 { - errorForm[tag.Get("form")] = append(errorForm[tag.Get("form")], fmt.Sprintf("Field needed: %s", inputName)) + mes.AddErrorf(tag.Get("form"), "Field needed: %s", inputName) } if formElem.Field(i).Interface == nil && tag.Get("default") != "" { defaultValue, _ := strconv.Atoi(tag.Get("default")) @@ -127,10 +128,13 @@ func ValidateForm(form interface{}, errorForm map[string][]string) map[string][] if tag.Get("equal") != "" { // Check minimum length equal, _ := strconv.ParseBool(tag.Get("equal")) if formElem.Field(i).Bool() != equal { - errorForm[tag.Get("form")] = append(errorForm[tag.Get("form")], fmt.Sprintf("Wrong value for the input: %s", inputName)) + mes.AddErrorf(tag.Get("form"), "Wrong value for the input: %s", inputName) } } + if formElem.Field(i).Interface == nil && tag.Get("default") != "" { + defaultValue, _ := strconv.ParseBool(tag.Get("default")) + formElem.Field(i).SetBool(defaultValue) + } } } - return errorForm }