From 93364dac77d211e84c826f8375746f7120f76daa Mon Sep 17 00:00:00 2001 From: akuma06 Date: Thu, 15 Jun 2017 04:44:46 +0200 Subject: [PATCH] activity log for users (#1002) List Torrent delete log Torrent edit log Comment delete log And every other logged activities Can be filtered out by a filter tag ("edit" or "delete" supported) Pages navigation Can be accessed by /activities Added some translation string Fixed hidden username on api request Fixed comments username on modpanel New Activity model New Activity handler New Activity Service Fixed some updating issue for ES when moderating torrents Be aware deleting torrents and comments return the model now! --- config/default_config.yml | 2 + config/types.go | 1 + db/gorm.go | 2 +- model/activity.go | 27 ++++++++ router/activity_handler.go | 53 +++++++++++++++ router/api_handler.go | 2 +- router/modpanel.go | 50 ++++++++++---- router/router.go | 1 + router/template.go | 7 +- router/template_functions.go | 17 ++--- router/view_torrent_handler.go | 10 ++- service/activity/activity.go | 41 ++++++++++++ service/comment/comment.go | 12 ++-- service/report/report.go | 8 +-- .../metainfoFetcher/metainfo_fetcher.go | 10 +-- service/torrent/torrent.go | 67 ++++++++++++++++--- templates/activity_list.html | 22 ++++++ templates/admin/commentlist.html | 2 +- templates/admin/panelindex.html | 2 +- translations/en-us.all.json | 36 ++++++++++ 20 files changed, 319 insertions(+), 53 deletions(-) create mode 100644 model/activity.go create mode 100644 router/activity_handler.go create mode 100644 service/activity/activity.go create mode 100644 templates/activity_list.html diff --git a/config/default_config.yml b/config/default_config.yml index a7e61d17..e91ca538 100644 --- a/config/default_config.yml +++ b/config/default_config.yml @@ -181,6 +181,8 @@ models: files_table_name: files # NotificationTableName : Name of notifications table in DB notifications_table_name: notifications +# ActivitiesTableName : Name of activitis log table in DB + activities_table_name: activities # for sukebei: # LastOldTorrentID = 2303945 # TorrentsTableName = "sukebei_torrents" diff --git a/config/types.go b/config/types.go index 37c39016..5a31abbf 100644 --- a/config/types.go +++ b/config/types.go @@ -180,6 +180,7 @@ type ModelsConfig struct { UploadsOldTableName string `yaml:"uploads_old_table_name,omitempty"` FilesTableName string `yaml:"files_table_name,omitempty"` NotificationsTableName string `yaml:"notifications_table_name,omitempty"` + ActivityTableName string `yaml:"activities_table_name,omitempty"` } // SearchConfig : Config struct for search diff --git a/db/gorm.go b/db/gorm.go index fca134f5..df9aa1f0 100644 --- a/db/gorm.go +++ b/db/gorm.go @@ -82,7 +82,7 @@ func GormInit(conf *config.Config, logger Logger) (*gorm.DB, error) { db.SetLogger(logger) } - db.AutoMigrate(&model.User{}, &model.UserFollows{}, &model.UserUploadsOld{}, &model.Notification{}) + db.AutoMigrate(&model.User{}, &model.UserFollows{}, &model.UserUploadsOld{}, &model.Notification{}, &model.Activity{}) if db.Error != nil { return db, db.Error } diff --git a/model/activity.go b/model/activity.go new file mode 100644 index 00000000..cf4fb91d --- /dev/null +++ b/model/activity.go @@ -0,0 +1,27 @@ +package model + +import ( + "strings" + + "github.com/NyaaPantsu/nyaa/config" +) + +// Activity model +type Activity struct { + ID uint + Content string + Identifier string + Filter string + UserID uint + User *User +} + +// NewActivity : Create a new activity log +func NewActivity(identifier string, filter string, c ...string) Activity { + return Activity{Identifier: identifier, Content: strings.Join(c, ","), Filter: filter} +} + +// TableName : Return the name of activity table +func (a *Activity) TableName() string { + return config.Conf.Models.ActivityTableName +} diff --git a/router/activity_handler.go b/router/activity_handler.go new file mode 100644 index 00000000..5936a74c --- /dev/null +++ b/router/activity_handler.go @@ -0,0 +1,53 @@ +package router + +import ( + "html" + "net/http" + "strconv" + "strings" + + "github.com/NyaaPantsu/nyaa/service/activity" + "github.com/NyaaPantsu/nyaa/service/user/permission" + "github.com/NyaaPantsu/nyaa/util/log" + msg "github.com/NyaaPantsu/nyaa/util/messages" + + "github.com/gorilla/mux" +) + +// ActivityListHandler : Show a list of activity +func ActivityListHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + page := vars["page"] + pagenum := 1 + offset := 100 + userid := r.URL.Query().Get("userid") + filter := r.URL.Query().Get("filter") + defer r.Body.Close() + var err error + messages := msg.GetMessages(r) + currentUser := getUser(r) + if page != "" { + pagenum, err = strconv.Atoi(html.EscapeString(page)) + if !log.CheckError(err) { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } + var conditions []string + var values []interface{} + if userid != "" && userPermission.HasAdmin(currentUser) { + conditions = append(conditions, "user_id = ?") + values = append(values, userid) + } + if filter != "" { + conditions = append(conditions, "filter = ?") + values = append(values, filter) + } + + activities, nbActivities := activity.GetAllActivities(offset, (pagenum-1)*offset, strings.Join(conditions, " AND "), values...) + common := newCommonVariables(r) + common.Navigation = navigation{nbActivities, offset, pagenum, "activity_list"} + htv := modelListVbs{common, activities, messages.GetAllErrors(), messages.GetAllInfos()} + err = activityList.ExecuteTemplate(w, "index.html", htv) + log.CheckError(err) +} diff --git a/router/api_handler.go b/router/api_handler.go index 2111f7be..334a6324 100644 --- a/router/api_handler.go +++ b/router/api_handler.go @@ -269,7 +269,7 @@ func APIUpdateHandler(w http.ResponseWriter, r *http.Request) { messages.ImportFromError("errors", apiService.ErrRights) } update.UpdateTorrent(&torrent, user) - torrentService.UpdateTorrent(torrent) + torrentService.UpdateTorrent(&torrent) } apiResponseHandler(w, r) } diff --git a/router/modpanel.go b/router/modpanel.go index 70d9d53e..b5481ec2 100644 --- a/router/modpanel.go +++ b/router/modpanel.go @@ -12,6 +12,7 @@ import ( "github.com/NyaaPantsu/nyaa/db" "github.com/NyaaPantsu/nyaa/model" "github.com/NyaaPantsu/nyaa/service" + "github.com/NyaaPantsu/nyaa/service/activity" "github.com/NyaaPantsu/nyaa/service/api" "github.com/NyaaPantsu/nyaa/service/comment" "github.com/NyaaPantsu/nyaa/service/report" @@ -310,9 +311,12 @@ func TorrentPostEditModPanel(w http.ResponseWriter, r *http.Request) { torrent.WebsiteLink = uploadForm.WebsiteLink torrent.Description = uploadForm.Description torrent.Language = uploadForm.Language - // torrent.Uploader = nil // GORM will create a new user otherwise (wtf?!) - db.ORM.Model(&torrent).UpdateColumn(&torrent) + _, err := torrentService.UpdateUnscopeTorrent(&torrent) messages.AddInfoT("infos", "torrent_updated") + if err == nil { // We only log edit torrent for admins + _, username := torrentService.HideTorrentUser(torrent.UploaderID, torrent.Uploader.Username, torrent.Hidden) + activity.Log(&model.User{}, torrent.Identifier(), "edit", "torrent_edited_by", strconv.Itoa(int(torrent.ID)), username, getUser(r).Username) + } } } htv := formTemplateVariables{newPanelCommonVariables(r), uploadForm, messages.GetAllErrors(), messages.GetAllInfos()} @@ -325,7 +329,10 @@ func CommentDeleteModPanel(w http.ResponseWriter, r *http.Request) { id := r.URL.Query().Get("id") defer r.Body.Close() - _, _ = commentService.DeleteComment(id) + comment, _, err := commentService.DeleteComment(id) + if err == nil { + activity.Log(&model.User{}, comment.Identifier(), "delete", "comment_deleted_by", strconv.Itoa(int(comment.ID)), comment.User.Username, getUser(r).Username) + } url, _ := Router.Get("mod_clist").URL() http.Redirect(w, r, url.String()+"?deleted", http.StatusSeeOther) } @@ -336,8 +343,10 @@ func TorrentDeleteModPanel(w http.ResponseWriter, r *http.Request) { definitely := r.URL.Query()["definitely"] defer r.Body.Close() var returnRoute string + var err error + var torrent *model.Torrent if definitely != nil { - _, _ = torrentService.DefinitelyDeleteTorrent(id) + torrent, _, err = torrentService.DefinitelyDeleteTorrent(id) //delete reports of torrent whereParams := serviceBase.CreateWhereParams("torrent_id = ?", id) @@ -347,7 +356,7 @@ func TorrentDeleteModPanel(w http.ResponseWriter, r *http.Request) { } returnRoute = "mod_tlist_deleted" } else { - _, _ = torrentService.DeleteTorrent(id) + torrent, _, err = torrentService.DeleteTorrent(id) //delete reports of torrent whereParams := serviceBase.CreateWhereParams("torrent_id = ?", id) @@ -357,6 +366,10 @@ func TorrentDeleteModPanel(w http.ResponseWriter, r *http.Request) { } returnRoute = "mod_tlist" } + if err == nil { + _, username := torrentService.HideTorrentUser(torrent.UploaderID, torrent.Uploader.Username, torrent.Hidden) + activity.Log(&model.User{}, torrent.Identifier(), "delete", "torrent_deleted_by", strconv.Itoa(int(torrent.ID)), username, getUser(r).Username) + } url, _ := Router.Get(returnRoute).URL() http.Redirect(w, r, url.String()+"?deleted", http.StatusSeeOther) } @@ -367,8 +380,12 @@ func TorrentReportDeleteModPanel(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() fmt.Println(id) idNum, _ := strconv.ParseUint(id, 10, 64) - _, _ = reportService.DeleteTorrentReport(uint(idNum)) - + _, _, _ = reportService.DeleteTorrentReport(uint(idNum)) + /* If we need to log report delete activity + if err == nil { + activity.Log(&model.User{}, torrent.Identifier(), "delete", "torrent_report_deleted_by", strconv.Itoa(int(report.ID)), getUser(r).Username) + } + */ url, _ := Router.Get("mod_trlist").URL() http.Redirect(w, r, url.String()+"?deleted", http.StatusSeeOther) } @@ -500,8 +517,7 @@ func DeletedTorrentsPostPanel(w http.ResponseWriter, r *http.Request) { func TorrentBlockModPanel(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() id := r.URL.Query().Get("id") - torrent, _, _ := torrentService.ToggleBlockTorrent(id) - + torrent, _, err := torrentService.ToggleBlockTorrent(id) var returnRoute, action string if torrent.IsDeleted() { returnRoute = "mod_tlist_deleted" @@ -513,6 +529,10 @@ func TorrentBlockModPanel(w http.ResponseWriter, r *http.Request) { } else { action = "unblocked" } + if err == nil { + _, username := torrentService.HideTorrentUser(torrent.UploaderID, torrent.Uploader.Username, torrent.Hidden) + activity.Log(&model.User{}, torrent.Identifier(), action, "torrent_"+action+"_by", strconv.Itoa(int(torrent.ID)), username, getUser(r).Username) + } url, _ := Router.Get(returnRoute).URL() http.Redirect(w, r, url.String()+"?"+action, http.StatusSeeOther) } @@ -616,18 +636,24 @@ func torrentManyAction(r *http.Request) { } /* Changes are done, we save */ - db.ORM.Unscoped().Model(&torrent).UpdateColumn(&torrent) + _, err := torrentService.UpdateUnscopeTorrent(&torrent) + if err == nil { + _, username := torrentService.HideTorrentUser(torrent.UploaderID, torrent.Uploader.Username, torrent.Hidden) + activity.Log(&model.User{}, torrent.Identifier(), "edited", "torrent_edited_by", strconv.Itoa(int(torrent.ID)), username, getUser(r).Username) + } } else if action == "delete" { if status == model.TorrentStatusBlocked { // Then we should lock torrents before deleting them torrent.Status = status messages.AddInfoTf("infos", "torrent_moved", torrent.Name) - db.ORM.Unscoped().Model(&torrent).UpdateColumn(&torrent) // We must save it here and soft delete it after + torrentService.UpdateUnscopeTorrent(&torrent) } - _, err = torrentService.DeleteTorrent(torrentID) + _, _, err = torrentService.DeleteTorrent(torrentID) if err != nil { messages.ImportFromError("errors", err) } else { messages.AddInfoTf("infos", "torrent_deleted", torrent.Name) + _, username := torrentService.HideTorrentUser(torrent.UploaderID, torrent.Uploader.Username, torrent.Hidden) + activity.Log(&model.User{}, torrent.Identifier(), "deleted", "torrent_deleted_by", strconv.Itoa(int(torrent.ID)), username, getUser(r).Username) } } else { messages.AddErrorTf("errors", "no_action_exist", action) diff --git a/router/router.go b/router/router.go index e910a061..dc0ac2da 100755 --- a/router/router.go +++ b/router/router.go @@ -54,6 +54,7 @@ func init() { Router.HandleFunc("/search/{page}", SearchHandler).Name("search_page") Router.HandleFunc("/verify/email/{token}", UserVerifyEmailHandler).Name("user_verify").Methods("GET") Router.HandleFunc("/faq", FaqHandler).Name("faq") + Router.HandleFunc("/activities", ActivityListHandler).Name("activity_list") Router.HandleFunc("/feed", RSSHandler).Name("feed") Router.HandleFunc("/feed/{page}", RSSHandler).Name("feed_page") diff --git a/router/template.go b/router/template.go index f67cc522..c3844ba0 100644 --- a/router/template.go +++ b/router/template.go @@ -14,6 +14,7 @@ const ModeratorDir = "admin" var homeTemplate, searchTemplate, faqTemplate, + activityList, uploadTemplate, viewTemplate, viewRegisterTemplate, @@ -72,6 +73,11 @@ func ReloadTemplates() { name: "FAQ", file: "FAQ.html", }, + { + templ: &activityList, + name: "activityList", + file: "activity_list.html", + }, { templ: &viewTemplate, name: "view", @@ -132,7 +138,6 @@ func ReloadTemplates() { name: "change_settings", file: "public_settings.html", }, - } for idx := range pubTempls { pubTempls[idx].indexFile = filepath.Join(TemplateDir, "index.html") diff --git a/router/template_functions.go b/router/template_functions.go index 3c731b55..845dcb5f 100644 --- a/router/template_functions.go +++ b/router/template_functions.go @@ -9,6 +9,8 @@ import ( "github.com/NyaaPantsu/nyaa/config" "github.com/NyaaPantsu/nyaa/model" + "github.com/NyaaPantsu/nyaa/service/activity" + "github.com/NyaaPantsu/nyaa/service/torrent" "github.com/NyaaPantsu/nyaa/service/user/permission" "github.com/NyaaPantsu/nyaa/util" "github.com/NyaaPantsu/nyaa/util/categories" @@ -257,18 +259,17 @@ var FuncMap = template.FuncMap{ return string(T(d)) }, "genUploaderLink": func(uploaderID uint, uploaderName template.HTML, torrentHidden bool) template.HTML { - - if torrentHidden { - return template.HTML("れんちょん") - } + uploaderID, username := torrentService.HideTorrentUser(uploaderID, string(uploaderName), torrentHidden) if uploaderID == 0 { - return template.HTML("れんちょん") + return template.HTML(username) } - url, err := Router.Get("user_profile").URL("id", strconv.Itoa(int(uploaderID)), "username", string(uploaderName)) + url, err := Router.Get("user_profile").URL("id", strconv.Itoa(int(uploaderID)), "username", username) if err != nil { return "error" } - return template.HTML("" + string(uploaderName) + "") - + return template.HTML("" + username + "") + }, + "genActivityContent": func(a model.Activity, T publicSettings.TemplateTfunc) template.HTML { + return activity.ToLocale(&a, T) }, } diff --git a/router/view_torrent_handler.go b/router/view_torrent_handler.go index 8447402b..43841633 100644 --- a/router/view_torrent_handler.go +++ b/router/view_torrent_handler.go @@ -14,6 +14,7 @@ import ( "github.com/NyaaPantsu/nyaa/db" "github.com/NyaaPantsu/nyaa/model" "github.com/NyaaPantsu/nyaa/service" + "github.com/NyaaPantsu/nyaa/service/activity" "github.com/NyaaPantsu/nyaa/service/api" "github.com/NyaaPantsu/nyaa/service/captcha" "github.com/NyaaPantsu/nyaa/service/notifier" @@ -222,7 +223,6 @@ func TorrentPostEditUserPanel(w http.ResponseWriter, r *http.Request) { torrent.WebsiteLink = uploadForm.WebsiteLink torrent.Description = uploadForm.Description torrent.Language = uploadForm.Language - // torrent.Uploader = nil // GORM will create a new user otherwise (wtf?!) db.ORM.Model(&torrent).UpdateColumn(&torrent) messages.AddInfoT("infos", "torrent_updated") } @@ -241,8 +241,14 @@ func TorrentDeleteUserPanel(w http.ResponseWriter, r *http.Request) { currentUser := getUser(r) torrent, _ := torrentService.GetTorrentByID(id) if userPermission.CurrentOrAdmin(currentUser, torrent.UploaderID) { - _, err := torrentService.DeleteTorrent(id) + _, _, err := torrentService.DeleteTorrent(id) if err == nil { + _, username := torrentService.HideTorrentUser(torrent.UploaderID, torrent.Uploader.Username, torrent.Hidden) + if userPermission.HasAdmin(currentUser) { // We hide username on log activity if user is not admin and torrent is hidden + activity.Log(&model.User{}, torrent.Identifier(), "delete", "torrent_deleted_by", strconv.Itoa(int(torrent.ID)), username, currentUser.Username) + } else { + activity.Log(&model.User{}, torrent.Identifier(), "delete", "torrent_deleted_by", strconv.Itoa(int(torrent.ID)), username, username) + } //delete reports of torrent whereParams := serviceBase.CreateWhereParams("torrent_id = ?", id) reports, _, _ := reportService.GetTorrentReportsOrderBy(&whereParams, "", 0, 0) diff --git a/service/activity/activity.go b/service/activity/activity.go new file mode 100644 index 00000000..b0b45e3a --- /dev/null +++ b/service/activity/activity.go @@ -0,0 +1,41 @@ +package activity + +import ( + "html/template" + "strings" + + "github.com/NyaaPantsu/nyaa/db" + "github.com/NyaaPantsu/nyaa/model" + "github.com/NyaaPantsu/nyaa/util/publicSettings" +) + +// Log : log an activity from a user to his own id (System user id is 0) +func Log(user *model.User, name string, filter string, msg ...string) { + activity := model.NewActivity(name, filter, msg...) + activity.UserID = user.ID + db.ORM.Create(&activity) +} + +// DeleteAll : Erase aticities from a user (System user id is 0) +func DeleteAll(id uint) { + db.ORM.Where("user_id = ?", id).Delete(&model.Activity{}) +} + +// ToLocale : Convert list of parameters to message in local language +func ToLocale(a *model.Activity, T publicSettings.TemplateTfunc) template.HTML { + c := strings.Split(a.Content, ",") + d := make([]interface{}, len(c)-1) + for i, s := range c[1:] { + d[i] = s + } + return T(c[0], d...) +} + +// GetAllActivities : Get All activities +func GetAllActivities(limit int, offset int, conditions string, values ...interface{}) ([]model.Activity, int) { + var activities []model.Activity + var nbActivities int + db.ORM.Model(&activities).Where(conditions, values...).Count(&nbActivities) + db.ORM.Preload("User").Limit(limit).Offset(offset).Order("id DESC").Where(conditions, values...).Find(&activities) + return activities, nbActivities +} diff --git a/service/comment/comment.go b/service/comment/comment.go index 2e3ded9e..98ee280c 100644 --- a/service/comment/comment.go +++ b/service/comment/comment.go @@ -13,19 +13,19 @@ func GetAllComments(limit int, offset int, conditions string, values ...interfac var comments []model.Comment var nbComments int db.ORM.Model(&comments).Where(conditions, values...).Count(&nbComments) - db.ORM.Preload("User").Limit(limit).Offset(offset).Where(conditions, values...).Find(&comments) + db.ORM.Limit(limit).Offset(offset).Where(conditions, values...).Preload("User").Find(&comments) return comments, nbComments } // DeleteComment : Delete a comment // FIXME : move this to comment service -func DeleteComment(id string) (int, error) { +func DeleteComment(id string) (*model.Comment, int, error) { var comment model.Comment - if db.ORM.First(&comment, id).RecordNotFound() { - return http.StatusNotFound, errors.New("Comment is not found") + if db.ORM.Where("comment_id = ?", id).Preload("User").Preload("Torrent").Find(&comment).RecordNotFound() { + return &comment, http.StatusNotFound, errors.New("Comment is not found") } if db.ORM.Delete(&comment).Error != nil { - return http.StatusInternalServerError, errors.New("Comment is not deleted") + return &comment, http.StatusInternalServerError, errors.New("Comment is not deleted") } - return http.StatusOK, nil + return &comment, http.StatusOK, nil } diff --git a/service/report/report.go b/service/report/report.go index 1f9b72fe..99ed671d 100644 --- a/service/report/report.go +++ b/service/report/report.go @@ -20,15 +20,15 @@ func CreateTorrentReport(torrentReport model.TorrentReport) error { } // DeleteTorrentReport : Delete a torrent report by id -func DeleteTorrentReport(id uint) (error, int) { +func DeleteTorrentReport(id uint) (*model.TorrentReport, error, int) { var torrentReport model.TorrentReport if db.ORM.First(&torrentReport, id).RecordNotFound() { - return errors.New("Trying to delete a torrent report that does not exists"), http.StatusNotFound + return &torrentReport, errors.New("Trying to delete a torrent report that does not exists"), http.StatusNotFound } if err := db.ORM.Delete(&torrentReport).Error; err != nil { - return err, http.StatusInternalServerError + return &torrentReport, err, http.StatusInternalServerError } - return nil, http.StatusOK + return &torrentReport, nil, http.StatusOK } // DeleteDefinitelyTorrentReport : Delete definitely a torrent report by id diff --git a/service/torrent/metainfoFetcher/metainfo_fetcher.go b/service/torrent/metainfoFetcher/metainfo_fetcher.go index ba13c435..067ddb75 100644 --- a/service/torrent/metainfoFetcher/metainfo_fetcher.go +++ b/service/torrent/metainfoFetcher/metainfo_fetcher.go @@ -41,10 +41,10 @@ func New(fetcherConfig *config.MetainfoFetcherConfig) (*MetainfoFetcher, error) // Well, it seems this is the right way to convert speed -> rate.Limiter // https://github.com/anacrolix/torrent/blob/master/cmd/torrent/main.go - const uploadBurst = 0x40000 // 256K + const uploadBurst = 0x40000 // 256K const downloadBurst = 0x100000 // 1M - uploadLimit := fetcherConfig.UploadRateLimitKiB*1024 - downloadLimit := fetcherConfig.DownloadRateLimitKiB*1024 + uploadLimit := fetcherConfig.UploadRateLimitKiB * 1024 + downloadLimit := fetcherConfig.DownloadRateLimitKiB * 1024 if uploadLimit > 0 { limit := rate.Limit(uploadLimit) limiter := rate.NewLimiter(limit, uploadBurst) @@ -170,7 +170,7 @@ func (fetcher *MetainfoFetcher) gotResult(r Result) { if r.operation.torrent.Filesize != r.info.TotalLength() { log.Infof("Got length %d for torrent TID: %d. Updating.", r.info.TotalLength(), r.operation.torrent.ID) r.operation.torrent.Filesize = r.info.TotalLength() - _, err := torrentService.UpdateTorrent(r.operation.torrent) + _, err := torrentService.UpdateTorrent(&r.operation.torrent) if err != nil { log.Infof("Failed to update torrent TID: %d with new filesize", r.operation.torrent.ID) lengthOK = false @@ -217,7 +217,7 @@ func (fetcher *MetainfoFetcher) removeOldFailures() { // | 3 | 8 * base // integers inside fetcher.numFails are never less than or equal to zero mul := 1 << uint(fetcher.numFails[id]-1) - cd := time.Duration(mul * fetcher.baseFailCooldown) * time.Second + cd := time.Duration(mul*fetcher.baseFailCooldown) * time.Second if cd > max { cd = max } diff --git a/service/torrent/torrent.go b/service/torrent/torrent.go index 728b5e3f..432fdbb2 100644 --- a/service/torrent/torrent.go +++ b/service/torrent/torrent.go @@ -3,6 +3,7 @@ package torrentService import ( "errors" "fmt" + "html/template" "net/http" "strconv" "strings" @@ -11,6 +12,7 @@ import ( "github.com/NyaaPantsu/nyaa/db" "github.com/NyaaPantsu/nyaa/model" "github.com/NyaaPantsu/nyaa/service" + "github.com/NyaaPantsu/nyaa/service/activity" "github.com/NyaaPantsu/nyaa/service/notifier" "github.com/NyaaPantsu/nyaa/service/user" "github.com/NyaaPantsu/nyaa/service/user/permission" @@ -192,13 +194,13 @@ func GetAllTorrentsDB() ([]model.Torrent, int, error) { } // DeleteTorrent : delete a torrent based on id -func DeleteTorrent(id string) (int, error) { +func DeleteTorrent(id string) (*model.Torrent, int, error) { var torrent model.Torrent if db.ORM.First(&torrent, id).RecordNotFound() { - return http.StatusNotFound, errors.New("Torrent is not found") + return &torrent, http.StatusNotFound, errors.New("Torrent is not found") } if db.ORM.Delete(&torrent).Error != nil { - return http.StatusInternalServerError, errors.New("Torrent was not deleted") + return &torrent, http.StatusInternalServerError, errors.New("Torrent was not deleted") } if db.ElasticSearchClient != nil { @@ -209,17 +211,17 @@ func DeleteTorrent(id string) (int, error) { log.Errorf("Unable to delete torrent to ES index: %s", err) } } - return http.StatusOK, nil + return &torrent, http.StatusOK, nil } // DefinitelyDeleteTorrent : deletes definitely a torrent based on id -func DefinitelyDeleteTorrent(id string) (int, error) { +func DefinitelyDeleteTorrent(id string) (*model.Torrent, int, error) { var torrent model.Torrent if db.ORM.Unscoped().Model(&torrent).First(&torrent, id).RecordNotFound() { - return http.StatusNotFound, errors.New("Torrent is not found") + return &torrent, http.StatusNotFound, errors.New("Torrent is not found") } if db.ORM.Unscoped().Model(&torrent).Delete(&torrent).Error != nil { - return http.StatusInternalServerError, errors.New("Torrent was not deleted") + return &torrent, http.StatusInternalServerError, errors.New("Torrent was not deleted") } if db.ElasticSearchClient != nil { @@ -230,7 +232,7 @@ func DefinitelyDeleteTorrent(id string) (int, error) { log.Errorf("Unable to delete torrent to ES index: %s", err) } } - return http.StatusOK, nil + return &torrent, http.StatusOK, nil } // ToggleBlockTorrent ; Lock/Unlock a torrent based on id @@ -251,8 +253,27 @@ func ToggleBlockTorrent(id string) (model.Torrent, int, error) { } // UpdateTorrent : Update a torrent based on model -func UpdateTorrent(torrent model.Torrent) (int, error) { - if db.ORM.Model(&torrent).UpdateColumn(&torrent).Error != nil { +func UpdateTorrent(torrent *model.Torrent) (int, error) { + if db.ORM.Model(torrent).UpdateColumn(torrent).Error != nil { + return http.StatusInternalServerError, errors.New("Torrent was not updated") + } + + // TODO Don't create a new client for each request + if db.ElasticSearchClient != nil { + err := torrent.AddToESIndex(db.ElasticSearchClient) + if err == nil { + log.Infof("Successfully updated torrent to ES index.") + } else { + log.Errorf("Unable to update torrent to ES index: %s", err) + } + } + + return http.StatusOK, nil +} + +// UpdateUnscopeTorrent : Update a torrent based on model +func UpdateUnscopeTorrent(torrent *model.Torrent) (int, error) { + if db.ORM.Unscoped().Model(torrent).UpdateColumn(torrent).Error != nil { return http.StatusInternalServerError, errors.New("Torrent was not updated") } @@ -281,10 +302,11 @@ func ExistOrDelete(hash string, user *model.User) error { db.ORM.Unscoped().Model(&model.Torrent{}).Where("torrent_hash = ?", hash).First(&torrentIndb) if torrentIndb.ID > 0 { if userPermission.CurrentUserIdentical(user, torrentIndb.UploaderID) && torrentIndb.IsDeleted() && !torrentIndb.IsBlocked() { // if torrent is not locked and is deleted and the user is the actual owner - _, err := DefinitelyDeleteTorrent(strconv.Itoa(int(torrentIndb.ID))) + torrent, _, err := DefinitelyDeleteTorrent(strconv.Itoa(int(torrentIndb.ID))) if err != nil { return err } + activity.Log(&model.User{}, torrent.Identifier(), "delete", "torrent_deleted_by", strconv.Itoa(int(torrent.ID)), torrent.Uploader.Username, user.Username) } else { return errors.New("Torrent already in database") } @@ -312,3 +334,26 @@ func NewTorrentEvent(router *mux.Router, user *model.User, torrent *model.Torren } return nil } + +// HideTorrentUser : hides a torrent user for hidden torrents +func HideTorrentUser(uploaderID uint, uploaderName string, torrentHidden bool) (uint, string) { + if torrentHidden { + return 0, "れんちょん" + } + if uploaderID == 0 { + return 0, "れんちょん" + } + return uploaderID, uploaderName +} + +// TorrentsToAPI : Map Torrents for API usage without reallocations +func TorrentsToAPI(t []model.Torrent) []model.TorrentJSON { + json := make([]model.TorrentJSON, len(t)) + for i := range t { + json[i] = t[i].ToJSON() + uploaderID, username := HideTorrentUser(json[i].UploaderID, string(json[i].UploaderName), json[i].Hidden) + json[i].UploaderName = template.HTML(username) + json[i].UploaderID = uploaderID + } + return json +} diff --git a/templates/activity_list.html b/templates/activity_list.html new file mode 100644 index 00000000..606bee02 --- /dev/null +++ b/templates/activity_list.html @@ -0,0 +1,22 @@ +{{define "title"}}{{ call $.T "activity_list" }}{{end}} +{{define "contclass"}}cont-home{{end}} +{{define "content"}} +
+ + + + + + + + + {{ range .Models}} + + + + + {{end}} + +
{{ call $.T "activities" }}{{ call $.T "filter" }}
{{ genActivityContent . $.T }}{{ call $.T .Filter }}
+
+{{end}} \ No newline at end of file diff --git a/templates/admin/commentlist.html b/templates/admin/commentlist.html index 4097a455..c65d18a6 100644 --- a/templates/admin/commentlist.html +++ b/templates/admin/commentlist.html @@ -17,7 +17,7 @@ {{ .Content }} {{ .TorrentID }} - {{ .UserID }} + {{if .User }}{{ .User.Username }}{{else}}れんちょん{{end}} {{ call $.T "delete" }} {{end}} diff --git a/templates/admin/panelindex.html b/templates/admin/panelindex.html index 60141223..ca5592b4 100644 --- a/templates/admin/panelindex.html +++ b/templates/admin/panelindex.html @@ -92,7 +92,7 @@ {{range .Comments}} {{ .Content }} - {{.UserID}} + {{if .User }}{{ .User.Username }}{{else}}れんちょん{{end}} {{ call $.T "delete" }} {{end}} diff --git a/translations/en-us.all.json b/translations/en-us.all.json index 1658e064..0eb4e97a 100644 --- a/translations/en-us.all.json +++ b/translations/en-us.all.json @@ -859,6 +859,22 @@ "id": "torrent_deleted", "translation": "Torrent %s deleted!" }, + { + "id": "torrent_deleted_by", + "translation": "Torrent #%s from %s has been deleted by %s." + }, + { + "id": "torrent_edited_by", + "translation": "Torrent #%s from %s has been edited by %s." + }, + { + "id": "torrent_blocked_by", + "translation": "Torrent #%s from %s has been locked by %s." + }, + { + "id": "torrent_blocked_by", + "translation": "Torrent #%s from %s has been unlocked by %s." + }, { "id": "torrents_deleted", "translation": "Torrents Deleted" @@ -871,6 +887,14 @@ "id": "delete_report", "translation": "Delete Report" }, + { + "id": "comment_deleted_by", + "translation": "Comment #%s from %s has been deleted by %s." + }, + { + "id": "comment_edited_by", + "translation": "Comment #%s from %s has been edited by %s." + }, { "id": "no_action_exist", "translation": "No such action %s exist!" @@ -1334,5 +1358,17 @@ { "id": "language_multiple_name", "translation": "Multiple Languages" + }, + { + "id": "activity_list", + "translation": "Activity List" + }, + { + "id": "activities", + "translation": "Activities" + }, + { + "id": "filter", + "translation": "Filter" } ]