diff --git a/model/torrent.go b/model/torrent.go index 7cb64ea5..e86daa28 100644 --- a/model/torrent.go +++ b/model/torrent.go @@ -14,10 +14,11 @@ import ( ) const ( - TorrentStatusNormal = 1 - TorrentStatusRemake = 2 - TorrentStatusTrusted = 3 - TorrentStatusAPlus = 4 + TorrentStatusNormal = 1 + TorrentStatusRemake = 2 + TorrentStatusTrusted = 3 + TorrentStatusAPlus = 4 + TorrentStatusBlocked = 5 ) type Feed struct { @@ -106,6 +107,14 @@ func (t Torrent) IsAPlus() bool { return t.Status == TorrentStatusAPlus } +func (t *Torrent) IsBlocked() bool { + return t.Status == TorrentStatusBlocked +} + +func (t *Torrent) IsDeleted() bool { + return t.DeletedAt != nil +} + /* We need a JSON object instead of a Gorm structure because magnet URLs are not in the database and have to be generated dynamically */ diff --git a/router/api_handler.go b/router/api_handler.go index 34eae5e0..06f22fa6 100644 --- a/router/api_handler.go +++ b/router/api_handler.go @@ -253,7 +253,7 @@ func ApiUpdateHandler(w http.ResponseWriter, r *http.Request) { } update.UpdateTorrent(&torrent) - db.ORM.Save(&torrent) + db.ORM.Model(&torrent).UpdateColumn(&torrent) if err != nil { util.SendError(w, err, 500) return diff --git a/router/modpanel.go b/router/modpanel.go index 4b6914d2..09a787b2 100644 --- a/router/modpanel.go +++ b/router/modpanel.go @@ -91,7 +91,7 @@ func (f *ReassignForm) ExecuteAction() (int, error) { torrent, err2 := torrentService.GetRawTorrentById(torrent_id) if err2 == nil { torrent.UploaderID = f.AssignTo - db.ORM.Save(&torrent) + db.ORM.Model(&torrent).UpdateColumn(&torrent) num += 1 } } @@ -128,6 +128,20 @@ func IndexModPanel(w http.ResponseWriter, r *http.Request) { func TorrentsListPanel(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) page := vars["page"] + messages := msg.GetMessages(r) + + deleted := r.URL.Query()["deleted"] + unblocked := r.URL.Query()["unblocked"] + blocked := r.URL.Query()["blocked"] + if deleted != nil { + messages.AddInfoTf("infos", "torrent_deleted", "") + } + if blocked != nil { + messages.AddInfoT("infos", "torrent_blocked") + } + if unblocked != nil { + messages.AddInfoT("infos", "torrent_unblocked") + } var err error pagenum := 1 @@ -146,7 +160,6 @@ func TorrentsListPanel(w http.ResponseWriter, r *http.Request) { ShowItemsPerPage: true, } - messages := msg.GetMessages(r) common := NewCommonVariables(r) common.Navigation = Navigation{ count, int(searchParam.Max), pagenum, "mod_tlist_page"} common.Search = searchForm @@ -268,8 +281,8 @@ func TorrentPostEditModPanel(w http.ResponseWriter, r *http.Request) { torrent.Status = uploadForm.Status torrent.WebsiteLink = uploadForm.WebsiteLink torrent.Description = uploadForm.Description - torrent.Uploader = nil // GORM will create a new user otherwise (wtf?!) - db.ORM.Save(&torrent) + // torrent.Uploader = nil // GORM will create a new user otherwise (wtf?!) + db.ORM.Model(&torrent).UpdateColumn(&torrent) messages.AddInfoT("infos", "torrent_updated") } } @@ -288,15 +301,30 @@ func CommentDeleteModPanel(w http.ResponseWriter, r *http.Request) { func TorrentDeleteModPanel(w http.ResponseWriter, r *http.Request) { id := r.URL.Query().Get("id") - _, _ = torrentService.DeleteTorrent(id) + definitely := r.URL.Query()["definitely"] + var returnRoute string + if definitely != nil { + _, _ = torrentService.DefinitelyDeleteTorrent(id) - //delete reports of torrent - whereParams := serviceBase.CreateWhereParams("torrent_id = ?", id) - reports, _, _ := reportService.GetTorrentReportsOrderBy(&whereParams, "", 0, 0) - for _, report := range reports { - reportService.DeleteTorrentReport(report.ID) + //delete reports of torrent + whereParams := serviceBase.CreateWhereParams("torrent_id = ?", id) + reports, _, _ := reportService.GetTorrentReportsOrderBy(&whereParams, "", 0, 0) + for _, report := range reports { + reportService.DeleteDefinitelyTorrentReport(report.ID) + } + returnRoute = "mod_tlist_deleted" + } else { + _, _ = torrentService.DeleteTorrent(id) + + //delete reports of torrent + whereParams := serviceBase.CreateWhereParams("torrent_id = ?", id) + reports, _, _ := reportService.GetTorrentReportsOrderBy(&whereParams, "", 0, 0) + for _, report := range reports { + reportService.DeleteTorrentReport(report.ID) + } + returnRoute = "mod_tlist" } - url, _ := Router.Get("mod_tlist").URL() + url, _ := Router.Get(returnRoute).URL() http.Redirect(w, r, url.String()+"?deleted", http.StatusSeeOther) } @@ -375,6 +403,75 @@ func ApiMassMod(w http.ResponseWriter, r *http.Request) { w.Write(apiJson) } +/* + * Manage deleted torrents + */ + +func DeletedTorrentsModPanel(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + page := vars["page"] + messages := msg.GetMessages(r) // new util for errors and infos + deleted := r.URL.Query()["deleted"] + unblocked := r.URL.Query()["unblocked"] + blocked := r.URL.Query()["blocked"] + if deleted != nil { + messages.AddInfoT("infos", "torrent_deleted_definitely") + } + if blocked != nil { + messages.AddInfoT("infos", "torrent_blocked") + } + if unblocked != nil { + messages.AddInfoT("infos", "torrent_unblocked") + } + var err error + pagenum := 1 + if page != "" { + pagenum, err = strconv.Atoi(html.EscapeString(page)) + if !log.CheckError(err) { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } + + searchParam, torrents, count, err := search.SearchByQueryDeleted(r, pagenum) + searchForm := SearchForm{ + SearchParam: searchParam, + Category: searchParam.Category.String(), + ShowItemsPerPage: true, + } + + common := NewCommonVariables(r) + common.Navigation = Navigation{ count, int(searchParam.Max), pagenum, "mod_tlist_page"} + common.Search = searchForm + ptlv := PanelTorrentListVbs{common, torrents, messages.GetAllErrors(), messages.GetAllInfos()} + err = panelTorrentList.ExecuteTemplate(w, "admin_index.html", ptlv) + log.CheckError(err) +} + +func DeletedTorrentsPostPanel(w http.ResponseWriter, r *http.Request) { + torrentManyAction(r) + DeletedTorrentsModPanel(w, r) +} + +func TorrentBlockModPanel(w http.ResponseWriter, r *http.Request) { + id := r.URL.Query().Get("id") + torrent, _, _ := torrentService.ToggleBlockTorrent(id) + + var returnRoute, action string + if torrent.IsDeleted() { + returnRoute = "mod_tlist_deleted" + } else { + returnRoute = "mod_tlist" + } + if torrent.IsBlocked() { + action = "blocked" + } else { + action = "unblocked" + } + url, _ := Router.Get(returnRoute).URL() + http.Redirect(w, r, url.String()+"?"+action, http.StatusSeeOther) +} + /* * Controller to modify multiple torrents and can be used by the owner of the torrent or admin */ @@ -474,7 +571,7 @@ func torrentManyAction(r *http.Request) { } /* Changes are done, we save */ - db.ORM.Model(&torrent).UpdateColumn(&torrent) + db.ORM.Unscoped().Model(&torrent).UpdateColumn(&torrent) } else if action == "delete" { _, err = torrentService.DeleteTorrent(torrent_id) if err != nil { diff --git a/router/router.go b/router/router.go index 9729bb08..2f8c1a51 100755 --- a/router/router.go +++ b/router/router.go @@ -78,9 +78,13 @@ func init() { // TODO Find a native mux way to add a 'prehook' for route /mod Router.HandleFunc("/mod", WrapModHandler(IndexModPanel)).Name("mod_index") Router.HandleFunc("/mod/torrents", WrapModHandler(TorrentsListPanel)).Name("mod_tlist").Methods("GET") - Router.HandleFunc("/mod/torrents/{page}", WrapModHandler(TorrentsListPanel)).Name("mod_tlist_page").Methods("GET") + Router.HandleFunc("/mod/torrents/{page:[0-9]+}", WrapModHandler(TorrentsListPanel)).Name("mod_tlist_page").Methods("GET") Router.HandleFunc("/mod/torrents", WrapModHandler(TorrentsPostListPanel)).Methods("POST") - Router.HandleFunc("/mod/torrents/{page}", WrapModHandler(TorrentsPostListPanel)).Methods("POST") + Router.HandleFunc("/mod/torrents/{page:[0-9]+}", WrapModHandler(TorrentsPostListPanel)).Methods("POST") + Router.HandleFunc("/mod/torrents/deleted", WrapModHandler(DeletedTorrentsModPanel)).Name("mod_tlist_deleted").Methods("GET") + Router.HandleFunc("/mod/torrents/deleted/{page:[0-9]+}", WrapModHandler(DeletedTorrentsModPanel)).Name("mod_tlist_deleted_page").Methods("GET") + Router.HandleFunc("/mod/torrents/deleted", WrapModHandler(DeletedTorrentsPostPanel)).Name("mod_tlist_deleted").Methods("POST") + Router.HandleFunc("/mod/torrents/deleted/{page:[0-9]+}", WrapModHandler(DeletedTorrentsPostPanel)).Name("mod_tlist_deleted_page").Methods("POST") Router.HandleFunc("/mod/reports", WrapModHandler(TorrentReportListPanel)).Name("mod_trlist") Router.HandleFunc("/mod/reports/{page}", WrapModHandler(TorrentReportListPanel)).Name("mod_trlist_page") Router.HandleFunc("/mod/users", WrapModHandler(UsersListPanel)).Name("mod_ulist") @@ -91,6 +95,7 @@ func init() { Router.HandleFunc("/mod/torrent/", WrapModHandler(TorrentEditModPanel)).Name("mod_tedit").Methods("GET") Router.HandleFunc("/mod/torrent/", WrapModHandler(TorrentPostEditModPanel)).Name("mod_ptedit").Methods("POST") Router.HandleFunc("/mod/torrent/delete", WrapModHandler(TorrentDeleteModPanel)).Name("mod_tdelete") + Router.HandleFunc("/mod/torrent/block", WrapModHandler(TorrentBlockModPanel)).Name("mod_tblock") Router.HandleFunc("/mod/report/delete", WrapModHandler(TorrentReportDeleteModPanel)).Name("mod_trdelete") Router.HandleFunc("/mod/comment/delete", WrapModHandler(CommentDeleteModPanel)).Name("mod_cdelete") Router.HandleFunc("/mod/reassign", WrapModHandler(TorrentReassignModPanel)).Name("mod_treassign").Methods("GET") diff --git a/router/upload_handler.go b/router/upload_handler.go index ab146018..8ff2dfde 100644 --- a/router/upload_handler.go +++ b/router/upload_handler.go @@ -11,6 +11,7 @@ import ( "github.com/NyaaPantsu/nyaa/model" "github.com/NyaaPantsu/nyaa/service/captcha" "github.com/NyaaPantsu/nyaa/service/notifier" + "github.com/NyaaPantsu/nyaa/service/torrent" "github.com/NyaaPantsu/nyaa/service/upload" "github.com/NyaaPantsu/nyaa/service/user" "github.com/NyaaPantsu/nyaa/service/user/permission" @@ -57,10 +58,14 @@ func UploadPostHandler(w http.ResponseWriter, r *http.Request) { status = model.TorrentStatusTrusted } - var sameTorrents int - db.ORM.Model(&model.Torrent{}).Where("torrent_hash = ?", uploadForm.Infohash).Count(&sameTorrents) - if sameTorrents > 0 { - messages.AddError("errors", "Torrent already in database !") + torrentIndb := model.Torrent{} + db.ORM.Unscoped().Model(&model.Torrent{}).Where("torrent_hash = ?", uploadForm.Infohash).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 + torrentService.DefinitelyDeleteTorrent(strconv.Itoa(int(torrentIndb.ID))) + } else { + messages.AddError("errors", "Torrent already in database !") + } } if !messages.HasErrors() { diff --git a/router/view_torrent_handler.go b/router/view_torrent_handler.go index 531ec344..5b632a3b 100644 --- a/router/view_torrent_handler.go +++ b/router/view_torrent_handler.go @@ -194,8 +194,8 @@ func TorrentPostEditUserPanel(w http.ResponseWriter, r *http.Request) { torrent.Status = status torrent.WebsiteLink = uploadForm.WebsiteLink torrent.Description = uploadForm.Description - torrent.Uploader = nil // GORM will create a new user otherwise (wtf?!) - db.ORM.Save(&torrent) + // torrent.Uploader = nil // GORM will create a new user otherwise (wtf?!) + db.ORM.Model(&torrent).UpdateColumn(&torrent) messages.AddInfoT("infos", "torrent_updated") } htv := UserTorrentEdVbs{NewCommonVariables(r), uploadForm, messages.GetAllErrors(), messages.GetAllInfos()} diff --git a/service/report/report.go b/service/report/report.go index 4d16eb6c..e9dff533 100644 --- a/service/report/report.go +++ b/service/report/report.go @@ -30,6 +30,17 @@ func DeleteTorrentReport(id uint) (error, int) { return nil, http.StatusOK } +func DeleteDefinitelyTorrentReport(id uint) (error, int) { + var torrentReport model.TorrentReport + if db.ORM.Unscoped().First(&torrentReport, id).RecordNotFound() { + return errors.New("Trying to delete a torrent report that does not exists."), http.StatusNotFound + } + if err := db.ORM.Unscoped().Delete(&torrentReport).Error; err != nil { + return err, http.StatusInternalServerError + } + return nil, http.StatusOK +} + func getTorrentReportsOrderBy(parameters *serviceBase.WhereParams, orderBy string, limit int, offset int, countAll bool) ( torrentReports []model.TorrentReport, count int, err error, ) { diff --git a/service/torrent/torrent.go b/service/torrent/torrent.go index 6ea18579..85317094 100644 --- a/service/torrent/torrent.go +++ b/service/torrent/torrent.go @@ -102,25 +102,24 @@ func GetRawTorrentById(id uint) (torrent model.Torrent, err error) { } func GetTorrentsOrderByNoCount(parameters *serviceBase.WhereParams, orderBy string, limit int, offset int) (torrents []model.Torrent, err error) { - torrents, _, err = getTorrentsOrderBy(parameters, orderBy, limit, offset, false, false) + torrents, _, err = getTorrentsOrderBy(parameters, orderBy, limit, offset, false, false, false) return } func GetTorrentsOrderBy(parameters *serviceBase.WhereParams, orderBy string, limit int, offset int) (torrents []model.Torrent, count int, err error) { - torrents, count, err = getTorrentsOrderBy(parameters, orderBy, limit, offset, true, false) + torrents, count, err = getTorrentsOrderBy(parameters, orderBy, limit, offset, true, false, false) return } func GetTorrentsWithUserOrderBy(parameters *serviceBase.WhereParams, orderBy string, limit int, offset int) (torrents []model.Torrent, count int, err error) { - torrents, count, err = getTorrentsOrderBy(parameters, orderBy, limit, offset, true, true) + torrents, count, err = getTorrentsOrderBy(parameters, orderBy, limit, offset, true, true, false) return } -func getTorrentsOrderBy(parameters *serviceBase.WhereParams, orderBy string, limit int, offset int, countAll bool, withUser bool) ( +func getTorrentsOrderBy(parameters *serviceBase.WhereParams, orderBy string, limit int, offset int, countAll bool, withUser bool, deleted bool) ( torrents []model.Torrent, count int, err error, ) { var conditionArray []string - conditionArray = append(conditionArray, "deleted_at IS NULL") var params []interface{} if parameters != nil { // if there is where parameters if len(parameters.Conditions) > 0 { @@ -128,10 +127,16 @@ func getTorrentsOrderBy(parameters *serviceBase.WhereParams, orderBy string, lim } params = parameters.Params } + if !deleted { + conditionArray = append(conditionArray, "deleted_at IS NULL") + } else { + conditionArray = append(conditionArray, "deleted_at NOT NULL") + } + conditions := strings.Join(conditionArray, " AND ") + if countAll { - // FIXME: `deleted_at IS NULL` is duplicate in here because GORM handles this for us - err = db.ORM.Model(&torrents).Where(conditions, params...).Count(&count).Error + err = db.ORM.Unscoped().Model(&torrents).Where(conditions, params...).Count(&count).Error if err != nil { return } @@ -201,10 +206,42 @@ func DeleteTorrent(id string) (int, error) { return http.StatusOK, nil } +func DefinitelyDeleteTorrent(id string) (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.") + } + if db.ORM.Unscoped().Model(&torrent).Delete(&torrent).Error != nil { + return http.StatusInternalServerError, errors.New("Torrent was not deleted.") + } + return http.StatusOK, nil +} + +func ToggleBlockTorrent(id string) (model.Torrent, int, error) { + var torrent model.Torrent + if db.ORM.Unscoped().Model(&torrent).First(&torrent, id).RecordNotFound() { + return torrent, http.StatusNotFound, errors.New("Torrent is not found.") + } + if torrent.Status == model.TorrentStatusBlocked { + torrent.Status = model.TorrentStatusNormal + } else { + torrent.Status = model.TorrentStatusBlocked + } + if db.ORM.Unscoped().Model(&torrent).UpdateColumn(&torrent).Error != nil { + return torrent, http.StatusInternalServerError, errors.New("Torrent was not updated.") + } + return torrent, http.StatusOK, nil +} + func UpdateTorrent(torrent model.Torrent) (int, error) { - if db.ORM.Save(torrent).Error != nil { + if db.ORM.Model(&torrent).UpdateColumn(&torrent).Error != nil { return http.StatusInternalServerError, errors.New("Torrent was not updated.") } return http.StatusOK, nil } + +func GetDeletedTorrents(parameters *serviceBase.WhereParams, orderBy string, limit int, offset int) (torrents []model.Torrent, count int, err error) { + torrents, count, err = getTorrentsOrderBy(parameters, orderBy, limit, offset, true, true, true) + return +} \ No newline at end of file diff --git a/templates/admin/paneltorrentedit.html b/templates/admin/paneltorrentedit.html index fb74a35d..1eac7b1e 100644 --- a/templates/admin/paneltorrentedit.html +++ b/templates/admin/paneltorrentedit.html @@ -24,6 +24,7 @@
+
+ Not Deleted + Deleted +
@@ -42,9 +47,16 @@ {{ range .Torrents}} - {{ .Name }} (Edit) - {{ if gt .UploaderID 0 }}{{ .Uploader.Username }}{{ else }}れんちょん{{end}} - {{ call $.T "delete" }} + {{ .Name }} {{ if not .IsDeleted }}({{ call $.T "edit"}}){{end}} + {{ if .Uploader }}{{ .Uploader.Username }}{{ else }}れんちょん{{end}} + + {{ if .IsBlocked }}{{ call $.T "torrent_unblock" }}{{else}}{{ call $.T "torrent_block" }}{{end}} + {{ if .IsDeleted }} + {{ call $.T "delete_definitely" }} + {{ else }} + {{ call $.T "delete" }} + {{ end }} + {{end}} diff --git a/translations/en-us.all.json b/translations/en-us.all.json index e073704f..3c9faf9b 100644 --- a/translations/en-us.all.json +++ b/translations/en-us.all.json @@ -922,5 +922,45 @@ { "id": "torrent_reports_deleted", "translation": "Reports of torrent \"%s\" were deleted!" + }, + { + "id": "edit", + "translation": "Edit" + }, + { + "id": "delete_definitely_torrent_warning", + "translation": "You will not be able to recover the file, neither stop someone to reupload it!" + }, + { + "id": "delete_definitely", + "translation": "Delete definitely" + }, + { + "id": "torrent_unblock", + "translation": "Unlock" + }, + { + "id": "torrent_block", + "translation": "Lock" + }, + { + "id": "torrent_deleted_definitely", + "translation": "Torrent has been erased from the database!" + }, + { + "id": "torrent_unblocked", + "translation": "Torrent has been unlocked!" + }, + { + "id": "torrent_blocked", + "translation": "Torrent has been locked!" + }, + { + "id": "torrent_nav_notdeleted", + "translation": "Torrents not deleted" + }, + { + "id": "torrent_nav_deleted", + "translation": "Torrents deleted" } ] diff --git a/util/search/search.go b/util/search/search.go index 4a65cde6..100b1bad 100644 --- a/util/search/search.go +++ b/util/search/search.go @@ -44,21 +44,26 @@ func stringIsAscii(input string) bool { } func SearchByQuery(r *http.Request, pagenum int) (search common.SearchParam, tor []model.Torrent, count int, err error) { - search, tor, count, err = searchByQuery(r, pagenum, true, false) + search, tor, count, err = searchByQuery(r, pagenum, true, false, false) return } func SearchByQueryWithUser(r *http.Request, pagenum int) (search common.SearchParam, tor []model.Torrent, count int, err error) { - search, tor, count, err = searchByQuery(r, pagenum, true, true) + search, tor, count, err = searchByQuery(r, pagenum, true, true, false) return } func SearchByQueryNoCount(r *http.Request, pagenum int) (search common.SearchParam, tor []model.Torrent, err error) { - search, tor, _, err = searchByQuery(r, pagenum, false, false) + search, tor, _, err = searchByQuery(r, pagenum, false, false, false) return } -func searchByQuery(r *http.Request, pagenum int, countAll bool, withUser bool) ( +func SearchByQueryDeleted(r *http.Request, pagenum int) (search common.SearchParam, tor []model.Torrent, count int, err error) { + search, tor, count, err = searchByQuery(r, pagenum, true, true, true) + return +} + +func searchByQuery(r *http.Request, pagenum int, countAll bool, withUser bool, deleted bool) ( search common.SearchParam, tor []model.Torrent, count int, err error, ) { max, err := strconv.ParseUint(r.URL.Query().Get("max"), 10, 32) @@ -215,8 +220,9 @@ func searchByQuery(r *http.Request, pagenum int, countAll bool, withUser bool) ( log.Infof("SQL query is :: %s\n", parameters.Conditions) tor, count, err = cache.Impl.Get(search, func() (tor []model.Torrent, count int, err error) { - - if countAll && !withUser { + if deleted { + tor, count, err = torrentService.GetDeletedTorrents(¶meters, orderBy, int(search.Max), int(search.Max)*(search.Page-1)) + } else if countAll && !withUser { tor, count, err = torrentService.GetTorrentsOrderBy(¶meters, orderBy, int(search.Max), int(search.Max)*(search.Page-1)) } else if withUser { tor, count, err = torrentService.GetTorrentsWithUserOrderBy(¶meters, orderBy, int(search.Max), int(search.Max)*(search.Page-1))