From e5c115b4003d804c56675672b2e504cced0459af Mon Sep 17 00:00:00 2001 From: akuma06 Date: Fri, 28 Jul 2017 20:43:22 +0200 Subject: [PATCH] Adding tag post controller --- config/default_config.yml | 3 +++ config/structs.go | 5 +++++ controllers/torrent/tag.go | 27 +++++++++++++++++++++++++++ controllers/torrent/view.go | 24 +++++++++++++++++++++++- models/gorm.go | 4 ++++ models/tag.go | 30 ++++++++++++++++++++++++++++++ models/torrent.go | 20 +++++++++++++++++--- models/user.go | 11 +++++++++++ models/users/find.go | 9 +++++++++ 9 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 controllers/torrent/tag.go diff --git a/config/default_config.yml b/config/default_config.yml index d6cd6c17..919e222b 100644 --- a/config/default_config.yml +++ b/config/default_config.yml @@ -126,6 +126,9 @@ torrents: order: torrent_id # TorrentSort : Default sorting order for torrents sort: DESC + tags: +# Torrent Tag Max weight for automatic system approval + max_weight: 100.00 users: default_notifications_settings: {"new_torrent": true, "new_torrent_email": false, "new_comment": true, "new_comment_email": false, "new_responses": false, "new_responses_email": false, "new_follower": false, "new_follower_email": false, "followed": false, "followed_email": false} navigation: diff --git a/config/structs.go b/config/structs.go index 8e1ead8a..1ca64932 100644 --- a/config/structs.go +++ b/config/structs.go @@ -46,6 +46,10 @@ type Config struct { Models ModelsConfig `yaml:"models,flow,omitempty"` } +type Tags struct { + MaxWeight float64 `yaml:"max_weight,omitempty"` +} + // WebAddressConfig : Config struct for web addresses type WebAddressConfig struct { Nyaa string `yaml:"nyaa,omitempty"` @@ -119,6 +123,7 @@ type TorrentsConfig struct { Trackers TrackersConfig `yaml:"trackers,flow,omitempty"` Order string `yaml:"order,omitempty"` Sort string `yaml:"sort,omitempty"` + Tags Tags `yaml:"tags,omitempty"` } // UsersConfig : Config struct for Users diff --git a/controllers/torrent/tag.go b/controllers/torrent/tag.go new file mode 100644 index 00000000..f1a87f6f --- /dev/null +++ b/controllers/torrent/tag.go @@ -0,0 +1,27 @@ +package torrentController + +import ( + "github.com/NyaaPantsu/nyaa/models" + "github.com/NyaaPantsu/nyaa/models/tags" + msg "github.com/NyaaPantsu/nyaa/utils/messages" + "github.com/NyaaPantsu/nyaa/utils/validator" + "github.com/NyaaPantsu/nyaa/utils/validator/tags" + "github.com/gin-gonic/gin" +) + +func postTag(c *gin.Context, torrent *models.Torrent, user *models.User) { + messages := msg.GetMessages(c) + tagForm := &tagsValidator.CreateForm{} + + c.Bind(tagForm) + validator.ValidateForm(tagForm, messages) + + for _, tag := range user.Tags { + if tag.Tag == tagForm.Tag { + return // already a tag by the user, don't add one more + } + } + + tags.Create(tagForm.Tag, tagForm.Type, torrent, user) // Add a tag to the db + tags.Filter(tagForm.Tag, tagForm.Type, torrent.ID) // Check if we have a tag reaching the maximum weight, if yes, deletes every tag and add only the one accepted +} diff --git a/controllers/torrent/view.go b/controllers/torrent/view.go index 2f23370a..ff52e2b3 100644 --- a/controllers/torrent/view.go +++ b/controllers/torrent/view.go @@ -20,35 +20,57 @@ func ViewHandler(c *gin.Context) { messages := msg.GetMessages(c) user := router.GetUser(c) + // Display success message on upload if c.Request.URL.Query()["success"] != nil { messages.AddInfoT("infos", "torrent_uploaded") } + // Display success message on edit if c.Request.URL.Query()["success_edit"] != nil { messages.AddInfoT("infos", "torrent_updated") } + // Display wrong captcha error message if c.Request.URL.Query()["badcaptcha"] != nil { messages.AddErrorT("errors", "bad_captcha") } + // Display reported successful message if c.Request.URL.Query()["reported"] != nil { messages.AddInfoTf("infos", "report_msg", id) } + // Retrieve the torrent torrent, err := torrents.FindByID(uint(id)) - if c.Request.URL.Query()["notif"] != nil { + // If come from notification, toggle the notification as read + if c.Request.URL.Query()["notif"] != nil && user.ID > 0 { notifications.ToggleReadNotification(torrent.Identifier(), user.ID) } + // If torrent not found, display 404 if err != nil { c.Status(http.StatusNotFound) return } + + // We load tags for user and torrents + user.LoadTags(torrent) + torrent.LoadTags() + + // We add a tag if posted + if c.PostForm("tag") != "" && user.ID > 0 { + postTag(c, torrent, user) + } + + // Convert torrent to the JSON Model used to display a torrent + // Since many datas need to be parsed from a simple torrent model to the actual display b := torrent.ToJSON() + // Get the folder root for the filelist view folder := filelist.FileListToFolder(torrent.FileList, "root") captchaID := "" + //Generate a captcha if user.NeedsCaptcha() { captchaID = captcha.GetID() } + // Display finally the view templates.Torrent(c, b, folder, captchaID) } diff --git a/models/gorm.go b/models/gorm.go index 8d906a87..506bbe7c 100644 --- a/models/gorm.go +++ b/models/gorm.go @@ -106,5 +106,9 @@ func GormInit(conf *config.Config, logger Logger) (*gorm.DB, error) { if db.Error != nil { return db, db.Error } + db.AutoMigrate(&Tag{}) + if db.Error != nil { + return db, db.Error + } return db, nil } diff --git a/models/tag.go b/models/tag.go index 0cf84658..b27abb81 100644 --- a/models/tag.go +++ b/models/tag.go @@ -1,5 +1,12 @@ package models +import ( + "errors" + "net/http" + + "github.com/fatih/structs" +) + // Tag model for a torrent vote system type Tag struct { TorrentID uint `gorm:"column:torrent_id"` @@ -8,4 +15,27 @@ type Tag struct { Type string `gorm:"column:type"` Weight float64 `gorm:"column:weight"` Accepted bool `gorm:"column:accepted"` + Total float64 `gorm:"-"` +} + +// Update a tag +func (ta *Tag) Update() (int, error) { + if ORM.Model(ta).UpdateColumn(ta.toMap()).Error != nil { + return http.StatusInternalServerError, errors.New("Tag was not updated") + } + return http.StatusOK, nil +} + +// Delete : delete a tag based on id +func (ta *Tag) Delete() (int, error) { + if ORM.Delete(ta).Error != nil { + return http.StatusInternalServerError, errors.New("tag_not_deleted") + } + + return http.StatusOK, nil +} + +// toMap : convert the model to a map of interface +func (ta *Tag) toMap() map[string]interface{} { + return structs.Map(ta) } diff --git a/models/torrent.go b/models/torrent.go index 3ef6fbed..82d18581 100644 --- a/models/torrent.go +++ b/models/torrent.go @@ -98,6 +98,7 @@ type TorrentJSON struct { Completed uint32 `json:"completed"` LastScrape time.Time `json:"last_scrape"` FileList []FileJSON `json:"file_list"` + Tags []Tag `json:"-"` // not needed in json to reduce db calls } // Size : Returns the total size of memory recursively allocated for this struct @@ -347,6 +348,7 @@ func (t *Torrent) ToJSON() TorrentJSON { Completed: scrape.Completed, LastScrape: scrape.LastScrape, FileList: fileListJSON, + Tags: t.Tags, } return res @@ -365,7 +367,6 @@ func TorrentsToJSON(t []Torrent) []TorrentJSON { // Update : Update a torrent based on model func (t *Torrent) Update(unscope bool) (int, error) { - cache.C.Delete(t.Identifier()) db := ORM if unscope { db = ORM.Unscoped() @@ -385,6 +386,8 @@ func (t *Torrent) Update(unscope bool) (int, error) { log.Errorf("Unable to update torrent to ES index: %s", err) } } + // We only flush cache after update + cache.C.Delete(t.Identifier()) return http.StatusOK, nil } @@ -394,9 +397,8 @@ func (t *Torrent) UpdateUnscope() (int, error) { return t.Update(true) } -// DeleteTorrent : delete a torrent based on id +// Delete : delete a torrent based on id func (t *Torrent) Delete(definitely bool) (*Torrent, int, error) { - cache.C.Flush() db := ORM if definitely { db = ORM.Unscoped() @@ -413,6 +415,8 @@ func (t *Torrent) Delete(definitely bool) (*Torrent, int, error) { log.Errorf("Unable to delete torrent to ES index: %s", err) } } + // We flush cache only after delete + cache.C.Flush() return t, http.StatusOK, nil } @@ -426,3 +430,13 @@ func (t *Torrent) DefinitelyDelete() (*Torrent, int, error) { func (t *Torrent) toMap() map[string]interface{} { return structs.Map(t) } + +// LoadTags : load all the unique tags with summed up weight from the database in torrent +func (t *Torrent) LoadTags() { + // Only load if necessary + if len(t.Tags) == 0 { + // Should output a query like this: SELECT tag, type, accepted, SUM(weight) as total FROM tags WHERE torrent_id=923000 GROUP BY type, tag ORDER BY type, total DESC + err := ORM.Select("tag, type, accepted, SUM(weight) as total").Where("torrent_id = ?", t.ID).Group("type, tag").Order("type ASC, total DESC").Find(&t.Tags).Error + log.CheckErrorWithMessage(err, "LOAD_TAGS_ERROR: Couldn't load tags!") + } +} diff --git a/models/user.go b/models/user.go index 854174db..8688b19c 100644 --- a/models/user.go +++ b/models/user.go @@ -59,6 +59,7 @@ type User struct { UnreadNotifications int `gorm:"-"` // We don't want to loop every notifications when accessing user unread notif Settings UserSettings `gorm:"-"` // We don't want to load settings everytime, stock it as a string, parse it when needed + Tags []Tag `gorm:"-"` // We load tags only when viewing a torrent } // UserJSON : User model conversion in JSON @@ -406,3 +407,13 @@ func (u *User) IncreasePantsu() { func (u *User) DecreasePantsu() { u.Pantsu = 0.8 * u.Pantsu // You lose 20% of your pantsu points each wrong vote } + +func (u *User) LoadTags(torrent *Torrent) { + if u.ID == 0 { + return + } + if err := ORM.Where("torrent_id = ? AND user_id = ?", torrent.ID, u.ID).Find(&u.Tags).Error; err != nil { + log.CheckErrorWithMessage(err, "LOAD_TAGS_ERROR: Couldn't load tags!") + return + } +} diff --git a/models/users/find.go b/models/users/find.go index a530b5d3..f480b7d6 100644 --- a/models/users/find.go +++ b/models/users/find.go @@ -105,6 +105,15 @@ func FindByID(id uint) (*models.User, int, error) { return user, http.StatusOK, nil } +// FindRawByID retrieves a user by ID without anything. +func FindRawByID(id uint) (*models.User, int, error) { + var user = &models.User{} + if models.ORM.Last(user, id).RecordNotFound() { + return user, http.StatusNotFound, errors.New("user_not_found") + } + return user, http.StatusOK, nil +} + func SessionByID(id uint) (*models.User, int, error) { var user = &models.User{} if models.ORM.Preload("Notifications").Where("user_id = ?", id).First(user).RecordNotFound() { // We only load unread notifications