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" } ]