diff --git a/config/config.go b/config/config.go index 98798f01..ae7e4472 100644 --- a/config/config.go +++ b/config/config.go @@ -18,6 +18,7 @@ const ( CommentsTableName = "comments" UploadsOldTableName = "user_uploads_old" FilesTableName = "files" + NotificationTableName = "notifications" // for sukebei: //LastOldTorrentID = 2303945 diff --git a/db/gorm.go b/db/gorm.go index ebe2fd43..185c26f1 100644 --- a/db/gorm.go +++ b/db/gorm.go @@ -54,7 +54,7 @@ func GormInit(conf *config.Config, logger Logger) (*gorm.DB, error) { db.SetLogger(logger) } - db.AutoMigrate(&model.User{}, &model.UserFollows{}, &model.UserUploadsOld{}) + db.AutoMigrate(&model.User{}, &model.UserFollows{}, &model.UserUploadsOld{}, &model.Notification{}) if db.Error != nil { return db, db.Error } diff --git a/model/notification.go b/model/notification.go new file mode 100644 index 00000000..a2c1e5e9 --- /dev/null +++ b/model/notification.go @@ -0,0 +1,23 @@ +package model + +import ( + "github.com/NyaaPantsu/nyaa/config" +) + +type Notification struct { + ID uint + Content string + Read bool + Identifier string + UserID uint +// User *User `gorm:"AssociationForeignKey:UserID;ForeignKey:user_id"` // Don't think that we need it here +} + +func NewNotification(identifier string, c string) Notification { + return Notification{Identifier: identifier, Content: c} +} + +func (n *Notification) TableName() string { + return config.NotificationTableName +} + diff --git a/model/torrent.go b/model/torrent.go index 4358a0ab..361991e3 100644 --- a/model/torrent.go +++ b/model/torrent.go @@ -86,6 +86,10 @@ func (t Torrent) TableName() string { return config.TorrentsTableName } +func (t Torrent) Identifier() string { + return "torrent_"+strconv.Itoa(int(t.ID)) +} + func (t Torrent) IsNormal() bool { return t.Status == TorrentStatusNormal } diff --git a/model/user.go b/model/user.go index 6d98ca21..8782873d 100644 --- a/model/user.go +++ b/model/user.go @@ -26,13 +26,14 @@ type User struct { Language string `gorm:"column:language"` // TODO: move this to PublicUser - LikingCount int `json:"likingCount" gorm:"-"` - LikedCount int `json:"likedCount" gorm:"-"` Likings []User // Don't work `gorm:"foreignkey:user_id;associationforeignkey:follower_id;many2many:user_follows"` Liked []User // Don't work `gorm:"foreignkey:follower_id;associationforeignkey:user_id;many2many:user_follows"` MD5 string `json:"md5" gorm:"column:md5"` // Hash of email address, used for Gravatar Torrents []Torrent `gorm:"ForeignKey:UploaderID"` + + UnreadNotifications int // We don't want to loop every notifications when accessing user unread notif + Notifications []Notification `gorm:"ForeignKey:UserID"` } type UserJSON struct { @@ -72,6 +73,17 @@ func (u User) IsModerator() bool { return u.Status == UserStatusModerator } +func (u User) GetUnreadNotifications() int { + if u.UnreadNotifications == 0 { + for _, notif := range u.Notifications { + if !notif.Read { + u.UnreadNotifications++ + } + } + } + return u.UnreadNotifications +} + type PublicUser struct { User *User } @@ -98,8 +110,8 @@ func (u *User) ToJSON() UserJSON { Username: u.Username, Status: u.Status, CreatedAt: u.CreatedAt.Format(time.RFC3339), - LikingCount: u.LikingCount, - LikedCount: u.LikedCount, + LikingCount: len(u.Likings), + LikedCount: len(u.Liked), } return json } diff --git a/router/upload_handler.go b/router/upload_handler.go index 5677ab9c..f857474f 100644 --- a/router/upload_handler.go +++ b/router/upload_handler.go @@ -8,7 +8,9 @@ import ( "github.com/NyaaPantsu/nyaa/db" "github.com/NyaaPantsu/nyaa/model" "github.com/NyaaPantsu/nyaa/service/captcha" + "github.com/NyaaPantsu/nyaa/service/notifier" "github.com/NyaaPantsu/nyaa/service/upload" + "github.com/NyaaPantsu/nyaa/service/user" "github.com/NyaaPantsu/nyaa/service/user/permission" "github.com/NyaaPantsu/nyaa/util/languages" msg "github.com/NyaaPantsu/nyaa/util/messages" @@ -75,6 +77,18 @@ func UploadPostHandler(w http.ResponseWriter, r *http.Request) { UploaderID: user.ID} db.ORM.Create(&torrent) + if (user.ID > 0) { // If we are a member + userService.GetLiked(user) // We populate the liked field for users + if len(user.Liked) > 0 { // If we are followed by at least someone + for _, follower := range user.Liked { + T, _, _ := languages.TfuncAndLanguageWithFallback(user.Language, user.Language) // We need to send the notification to every user in their language + + notifierService.NotifyUser(&follower, torrent.Identifier(), T("new_torrent_uploaded", torrent.Name, user.Username)) + + } + } + } + // add filelist to files db, if we have one if len(uploadForm.FileList) > 0 { for _, uploadedFile := range uploadForm.FileList { diff --git a/service/notifier/notifier.go b/service/notifier/notifier.go new file mode 100644 index 00000000..0ea1fa01 --- /dev/null +++ b/service/notifier/notifier.go @@ -0,0 +1,18 @@ +package notifierService + +import ( + "github.com/NyaaPantsu/nyaa/db" + "github.com/NyaaPantsu/nyaa/model" +) + + +func NotifyUser(user *model.User, name string, msg string) { + if (user.ID > 0) { + user.Notifications = append(user.Notifications, model.NewNotification(name, msg)) + // TODO: Email notification + } +} + +func ToggleReadNotification(identifier string, id uint) { // + db.ORM.Model(&model.Notification{}).Where("identifier = ? AND user_id = ?", identifier, id).Updates(model.Notification{Read: true}) +} \ No newline at end of file diff --git a/service/user/cookie_helper.go b/service/user/cookie_helper.go index 3c092b36..b4d7f640 100644 --- a/service/user/cookie_helper.go +++ b/service/user/cookie_helper.go @@ -141,7 +141,7 @@ func CurrentUser(r *http.Request) (model.User, error) { if userFromContext.ID > 0 && user_id == userFromContext.ID { user = userFromContext } else { - if db.ORM.Where("user_id = ?", user_id).First(&user).RecordNotFound() { + if db.ORM.Preload("Notifications").Where("user_id = ?", user_id).First(&user).RecordNotFound() { // We only load unread notifications return user, errors.New("User not found") } else { setUserToContext(r, user) diff --git a/service/user/user.go b/service/user/user.go index dd8e445c..54b7b058 100644 --- a/service/user/user.go +++ b/service/user/user.go @@ -280,7 +280,7 @@ func RetrieveOldUploadsByUsername(username string) ([]uint, error) { // RetrieveUserForAdmin retrieves a user for an administrator. func RetrieveUserForAdmin(id string) (model.User, int, error) { var user model.User - if db.ORM.Preload("Torrents").Last(&user, id).RecordNotFound() { + if db.ORM.Preload("Notifications").Preload("Torrents").Last(&user, id).RecordNotFound() { return user, http.StatusNotFound, errors.New("user not found") } var liked, likings []model.User @@ -300,6 +300,19 @@ func RetrieveUsersForAdmin(limit int, offset int) ([]model.User, int) { return users, nbUsers } +func GetLiked(user *model.User) *model.User { + var liked []model.User + db.ORM.Joins("JOIN user_follows on user_follows.following=?", user.ID).Where("users.user_id = user_follows.user_id").Group("users.user_id").Find(&liked) + user.Liked = liked + return user +} +func GetLikings(user *model.User) *model.User { + var likings []model.User + db.ORM.Joins("JOIN user_follows on user_follows.user_id=?", user.ID).Where("users.user_id = user_follows.following").Group("users.user_id").Find(&likings) + user.Likings = likings + return user +} + // CreateUserAuthentication creates user authentication. func CreateUserAuthentication(w http.ResponseWriter, r *http.Request) (int, error) { var form formStruct.LoginForm