Albirew/nyaa-pantsu
Archivé
1
0
Bifurcation 0
Ce dépôt a été archivé le 2022-05-07. Vous pouvez voir ses fichiers ou le cloner, mais pas ouvrir de ticket ou de demandes d'ajout, ni soumettre de changements.
nyaa-pantsu/controllers/modpanel.go
akuma06 5991a21818 First batch of changes for the refactor (#1078)
* First batch of changes for the refactor

Added the support of gin in routes and other services/utils
Begining implementation of JetHTML

* Remove os folder

* Move scrapers to own repo

* Second batch of changes

All .jet.html are the working templates.
You can now test this PR, the index Page and upload works. If you want to complete the other html templates, you're welcome

* Move captcha to util

* Move uploadService to utils

* Use govalidator instead of regex

* Third batch of changes

All the front end should as previously.
I also fixed some minor things unrelated to the refactor (mostly style issues on static pages)
Now errors can be accessed by importing the "errors" helpers and using the `yield errors(name="xxx")` command in templates.
Same for infos.
Templates are now more hierarchized with a base template "base.jet.html" which is extended depending on the context in "index_site" or "index_admin" layouts. Those layouts are extended than in every pages.
Other helpers are captcha to render a captcha `yield captcha(captchaid="xxx")`
And also csrf, with the command `yield csrf_field()`
To translate, you don't have anymore to do `call $.T "xxx"`, you just have to do `T("xxx")`.

Pages for the website part are in folders in the folder "templates/site". Pages for the admin part are in "templates/admin". Layouts are separated in "templates/layouts". Helpers and menu are in "templates/layouts/helpers" and "templates/layouts/menu". Error pages should be put in "templates/errors"

* Added test on templates

When adding a new template, you have to tell to template_test.go, the context of the new template (if it doesn't use the common context)

* Panel admin works

Now the templating part should work. The PR can now be fully tested.

I think we should push the templating PR  and do the routes/controllers/removal of services in another branch. So we know that this one is functional

* Updated dependencies

* Fixed test for modelhelper

* Fix testing for commentlist

* Fix travis :')

* Just renamed router and removed network

* Applying same SEO fix

* Update form_validator.go

* Added back regexp package
2017-06-28 21:42:38 +10:00

612 lignes
20 Kio
Go

package controllers
import (
"fmt"
"html"
"net/http"
"strconv"
"strings"
"github.com/NyaaPantsu/nyaa/config"
"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"
"github.com/NyaaPantsu/nyaa/service/torrent"
"github.com/NyaaPantsu/nyaa/service/user"
"github.com/NyaaPantsu/nyaa/service/user/permission"
"github.com/NyaaPantsu/nyaa/util/categories"
"github.com/NyaaPantsu/nyaa/util/log"
msg "github.com/NyaaPantsu/nyaa/util/messages"
"github.com/NyaaPantsu/nyaa/util/search"
"github.com/gin-gonic/gin"
)
// ReassignForm : Structure for reassign Form used by the reassign page
type ReassignForm struct {
AssignTo uint
By string
Data string
Torrents []uint
}
// ExtractInfo : Function to assign values from request to ReassignForm
func (f *ReassignForm) ExtractInfo(c *gin.Context) bool {
f.By = c.PostForm("by")
messages := msg.GetMessages(c)
if f.By != "olduser" && f.By != "torrentid" {
messages.AddErrorTf("errors", "no_action_exist", f.By)
return false
}
f.Data = strings.Trim(c.PostForm("data"), " \r\n")
if f.By == "olduser" {
if f.Data == "" {
messages.AddErrorT("errors", "user_not_found")
return false
} else if strings.Contains(f.Data, "\n") {
messages.AddErrorT("errors", "multiple_username_error")
return false
}
} else if f.By == "torrentid" {
if f.Data == "" {
messages.AddErrorT("errors", "no_id_given")
return false
}
splitData := strings.Split(f.Data, "\n")
for i, tmp := range splitData {
tmp = strings.Trim(tmp, " \r")
torrentID, err := strconv.ParseUint(tmp, 10, 0)
if err != nil {
messages.AddErrorTf("errors", "parse_error_line", i+1)
return false // TODO: Shouldn't it continue to parse the rest and display the errored lines?
}
f.Torrents = append(f.Torrents, uint(torrentID))
}
}
tmpID := c.PostForm("to")
parsed, err := strconv.ParseUint(tmpID, 10, 0)
if err != nil {
messages.Error(err)
return false
}
f.AssignTo = uint(parsed)
_, _, _, _, err = userService.RetrieveUser(c, tmpID)
if err != nil {
messages.AddErrorTf("errors", "no_user_found_id", int(parsed))
return false
}
return true
}
// ExecuteAction : Function for applying the changes from ReassignForm
func (f *ReassignForm) ExecuteAction() (int, error) {
var toBeChanged []uint
var err error
if f.By == "olduser" {
toBeChanged, err = userService.RetrieveOldUploadsByUsername(f.Data)
if err != nil {
return 0, err
}
} else if f.By == "torrentid" {
toBeChanged = f.Torrents
}
num := 0
for _, torrentID := range toBeChanged {
torrent, err2 := torrentService.GetRawTorrentByID(torrentID)
if err2 == nil {
torrent.UploaderID = f.AssignTo
db.ORM.Model(&torrent).UpdateColumn(&torrent)
num++
}
}
return num, nil
}
// IndexModPanel : Controller for showing index page of Mod Panel
func IndexModPanel(c *gin.Context) {
offset := 10
torrents, _, _ := torrentService.GetAllTorrents(offset, 0)
users, _ := userService.RetrieveUsersForAdmin(offset, 0)
comments, _ := commentService.GetAllComments(offset, 0, "", "")
torrentReports, _, _ := reportService.GetAllTorrentReports(offset, 0)
panelAdminTemplate(c, torrents, model.TorrentReportsToJSON(torrentReports), users, comments)
}
// TorrentsListPanel : Controller for listing torrents, can accept common search arguments
func TorrentsListPanel(c *gin.Context) {
page := c.Param("page")
messages := msg.GetMessages(c)
deleted := c.Request.URL.Query()["deleted"]
unblocked := c.Request.URL.Query()["unblocked"]
blocked := c.Request.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
if page != "" {
pagenum, err = strconv.Atoi(html.EscapeString(page))
if !log.CheckError(err) {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
}
searchParam, torrents, count, err := search.SearchByQueryWithUser(c, pagenum)
if err != nil {
messages.Error(err)
}
category := ""
if len(searchParam.Category) > 0 {
category = searchParam.Category[0].String()
}
searchForm := searchForm{
SearchParam: searchParam,
Category: category,
ShowItemsPerPage: true,
}
nav := navigation{count, int(searchParam.Max), pagenum, "mod_tlist_page"}
modelList(c, "admin/torrentlist.jet.html", torrents, nav, searchForm)
}
// TorrentReportListPanel : Controller for listing torrent reports, can accept pages
func TorrentReportListPanel(c *gin.Context) {
page := c.Param("page")
pagenum := 1
offset := 100
var err error
if page != "" {
pagenum, err = strconv.Atoi(html.EscapeString(page))
if !log.CheckError(err) {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
}
torrentReports, nbReports, _ := reportService.GetAllTorrentReports(offset, (pagenum-1)*offset)
reportJSON := model.TorrentReportsToJSON(torrentReports)
nav := navigation{nbReports, offset, pagenum, "mod_trlist_page"}
modelList(c, "admin/torrent_report.jet.html", reportJSON, nav, newSearchForm(c))
}
// UsersListPanel : Controller for listing users, can accept pages
func UsersListPanel(c *gin.Context) {
page := c.Param("page")
pagenum := 1
offset := 100
var err error
if page != "" {
pagenum, err = strconv.Atoi(html.EscapeString(page))
if !log.CheckError(err) {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
}
users, nbUsers := userService.RetrieveUsersForAdmin(offset, (pagenum-1)*offset)
nav := navigation{nbUsers, offset, pagenum, "mod_ulist_page"}
modelList(c, "admin/userlist.jet.html", users, nav, newSearchForm(c))
}
// CommentsListPanel : Controller for listing comments, can accept pages and userID
func CommentsListPanel(c *gin.Context) {
page := c.Param("page")
pagenum := 1
offset := 100
userid := c.Query("userid")
var err error
if page != "" {
pagenum, err = strconv.Atoi(html.EscapeString(page))
if !log.CheckError(err) {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
}
var conditions string
var values []interface{}
if userid != "" {
conditions = "user_id = ?"
values = append(values, userid)
}
comments, nbComments := commentService.GetAllComments(offset, (pagenum-1)*offset, conditions, values...)
nav := navigation{nbComments, offset, pagenum, "mod_clist_page"}
modelList(c, "admin/commentlist.jet.html", comments, nav, newSearchForm(c))
}
// TorrentEditModPanel : Controller for editing a torrent after GET request
func TorrentEditModPanel(c *gin.Context) {
id := c.Query("id")
torrent, _ := torrentService.GetTorrentByID(id)
torrentJSON := torrent.ToJSON()
uploadForm := apiService.NewTorrentRequest()
uploadForm.Name = torrentJSON.Name
uploadForm.Category = torrentJSON.Category + "_" + torrentJSON.SubCategory
uploadForm.Status = torrentJSON.Status
uploadForm.Hidden = torrent.Hidden
uploadForm.WebsiteLink = string(torrentJSON.WebsiteLink)
uploadForm.Description = string(torrentJSON.Description)
uploadForm.Language = torrent.Language
formTemplate(c, "admin/paneltorrentedit.jet.html", uploadForm)
}
// TorrentPostEditModPanel : Controller for editing a torrent after POST request
func TorrentPostEditModPanel(c *gin.Context) {
var uploadForm apiService.TorrentRequest
id := c.Query("id")
messages := msg.GetMessages(c)
torrent, _ := torrentService.GetTorrentByID(id)
if torrent.ID > 0 {
errUp := uploadForm.ExtractEditInfo(c)
if errUp != nil {
messages.AddErrorT("errors", "fail_torrent_update")
}
if !messages.HasErrors() {
// update some (but not all!) values
torrent.Name = uploadForm.Name
torrent.Category = uploadForm.CategoryID
torrent.SubCategory = uploadForm.SubCategoryID
torrent.Status = uploadForm.Status
torrent.Hidden = uploadForm.Hidden
torrent.WebsiteLink = uploadForm.WebsiteLink
torrent.Description = uploadForm.Description
torrent.Language = uploadForm.Language
_, 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(c).Username)
}
}
}
formTemplate(c, "admin/paneltorrentedit.jet.html", uploadForm)
}
// CommentDeleteModPanel : Controller for deleting a comment
func CommentDeleteModPanel(c *gin.Context) {
id := c.Query("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(c).Username)
}
c.Redirect(http.StatusSeeOther, "/mod/comments?deleted")
}
// TorrentDeleteModPanel : Controller for deleting a torrent
func TorrentDeleteModPanel(c *gin.Context) {
id := c.Query("id")
definitely := c.Request.URL.Query()["definitely"]
var returnRoute string
var err error
var torrent *model.Torrent
if definitely != nil {
torrent, _, err = torrentService.DefinitelyDeleteTorrent(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/torrents/deleted"
} else {
torrent, _, err = 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/torrents"
}
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(c).Username)
}
c.Redirect(http.StatusSeeOther, returnRoute+"?deleted")
}
// TorrentReportDeleteModPanel : Controller for deleting a torrent report
func TorrentReportDeleteModPanel(c *gin.Context) {
id := c.Query("id")
fmt.Println(id)
idNum, _ := strconv.ParseUint(id, 10, 64)
_, _, _ = 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(c).Username)
}
*/
c.Redirect(http.StatusSeeOther, "/mod/reports?deleted")
}
// TorrentReassignModPanel : Controller for reassigning a torrent, after GET request
func TorrentReassignModPanel(c *gin.Context) {
formTemplate(c, "admin/reassign.jet.html", ReassignForm{})
}
// TorrentPostReassignModPanel : Controller for reassigning a torrent, after POST request
func TorrentPostReassignModPanel(c *gin.Context) {
var rForm ReassignForm
messages := msg.GetMessages(c)
if rForm.ExtractInfo(c) {
count, err2 := rForm.ExecuteAction()
if err2 != nil {
messages.AddErrorT("errors", "something_went_wrong")
} else {
messages.AddInfoTf("infos", "nb_torrents_updated", count)
}
}
formTemplate(c, "admin/reassign.jet.html", rForm)
}
// TorrentsPostListPanel : Controller for listing torrents, after POST request when mass update
func TorrentsPostListPanel(c *gin.Context) {
torrentManyAction(c)
TorrentsListPanel(c)
}
// APIMassMod : This function is used on the frontend for the mass
/* Query is: action=status|delete|owner|category|multiple
* Needed: torrent_id[] Ids of torrents in checkboxes of name torrent_id
*
* Needed on context:
* status=0|1|2|3|4 according to config/torrent.go (can be omitted if action=delete|owner|category|multiple)
* owner is the User ID of the new owner of the torrents (can be omitted if action=delete|status|category|multiple)
* category is the category string (eg. 1_3) of the new category of the torrents (can be omitted if action=delete|status|owner|multiple)
*
* withreport is the bool to enable torrent reports deletion (can be omitted)
*
* In case of action=multiple, torrents can be at the same time changed status, owner and category
*/
func APIMassMod(c *gin.Context) {
torrentManyAction(c)
messages := msg.GetMessages(c) // new util for errors and infos
c.Header("Content-Type", "application/json")
var mapOk map[string]interface{}
if !messages.HasErrors() {
mapOk = map[string]interface{}{"ok": true, "infos": messages.GetAllInfos()["infos"]}
} else { // We need to show error messages
mapOk = map[string]interface{}{"ok": false, "errors": messages.GetAllErrors()["errors"]}
}
c.JSON(http.StatusOK, mapOk)
}
// DeletedTorrentsModPanel : Controller for viewing deleted torrents, accept common search arguments
func DeletedTorrentsModPanel(c *gin.Context) {
page := c.Param("page")
messages := msg.GetMessages(c) // new util for errors and infos
deleted := c.Request.URL.Query()["deleted"]
unblocked := c.Request.URL.Query()["unblocked"]
blocked := c.Request.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) {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
}
searchParam, torrents, count, err := search.SearchByQueryDeleted(c, pagenum)
if err != nil {
messages.Error(err)
}
category := ""
if len(searchParam.Category) > 0 {
category = searchParam.Category[0].String()
}
searchForm := searchForm{
SearchParam: searchParam,
Category: category,
ShowItemsPerPage: true,
}
nav := navigation{count, int(searchParam.Max), pagenum, "mod_tlist_page"}
search := searchForm
modelList(c, "admin/torrentlist.jet.html", torrents, nav, search)
}
// DeletedTorrentsPostPanel : Controller for viewing deleted torrents after a mass update, accept common search arguments
func DeletedTorrentsPostPanel(c *gin.Context) {
torrentManyAction(c)
DeletedTorrentsModPanel(c)
}
// TorrentBlockModPanel : Controller to lock torrents, redirecting to previous page
func TorrentBlockModPanel(c *gin.Context) {
id := c.Query("id")
torrent, _, err := torrentService.ToggleBlockTorrent(id)
var returnRoute, action string
if torrent.IsDeleted() {
returnRoute = "/mod/torrents/deleted"
} else {
returnRoute = "/mod/torrents"
}
if torrent.IsBlocked() {
action = "blocked"
} 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(c).Username)
}
c.Redirect(http.StatusSeeOther, returnRoute+"?"+action)
}
/*
* Controller to modify multiple torrents and can be used by the owner of the torrent or admin
*/
func torrentManyAction(c *gin.Context) {
currentUser := getUser(c)
torrentsSelected := c.PostFormArray("torrent_id") // should be []string
action := c.PostForm("action")
status, _ := strconv.Atoi(c.PostForm("status"))
owner, _ := strconv.Atoi(c.PostForm("owner"))
category := c.PostForm("category")
withReport, _ := strconv.ParseBool(c.DefaultPostForm("withreport", "false"))
messages := msg.GetMessages(c) // new util for errors and infos
catID, subCatID := -1, -1
var err error
if action == "" {
messages.AddErrorT("errors", "no_action_selected")
}
if action == "status" && c.PostForm("status") == "" { // We need to check the form value, not the int one because hidden is 0
messages.AddErrorT("errors", "no_move_location_selected")
}
if action == "owner" && c.PostForm("owner") == "" { // We need to check the form value, not the int one because renchon is 0
messages.AddErrorT("errors", "no_owner_selected")
}
if action == "category" && category == "" {
messages.AddErrorT("errors", "no_category_selected")
}
if len(torrentsSelected) == 0 {
messages.AddErrorT("errors", "select_one_element")
}
if !config.Conf.Torrents.Status[status] { // Check if the status exist
messages.AddErrorTf("errors", "no_status_exist", status)
status = -1
}
if !userPermission.HasAdmin(currentUser) {
if c.PostForm("status") != "" { // Condition to check if a user try to change torrent status without having the right permission
if (status == model.TorrentStatusTrusted && !currentUser.IsTrusted()) || status == model.TorrentStatusAPlus || status == 0 {
status = model.TorrentStatusNormal
}
}
if c.PostForm("owner") != "" { // Only admins can change owner of torrents
owner = -1
}
withReport = false // Users should not be able to remove reports
}
if c.PostForm("owner") != "" && userPermission.HasAdmin(currentUser) { // We check that the user given exist and if not we return an error
_, _, errorUser := userService.RetrieveUserForAdmin(strconv.Itoa(owner))
if errorUser != nil {
messages.AddErrorTf("errors", "no_user_found_id", owner)
owner = -1
}
}
if category != "" {
catsSplit := strings.Split(category, "_")
// need this to prevent out of index panics
if len(catsSplit) == 2 {
catID, err = strconv.Atoi(catsSplit[0])
if err != nil {
messages.AddErrorT("errors", "invalid_torrent_category")
}
subCatID, err = strconv.Atoi(catsSplit[1])
if err != nil {
messages.AddErrorT("errors", "invalid_torrent_category")
}
if !categories.CategoryExists(category) {
messages.AddErrorT("errors", "invalid_torrent_category")
}
}
}
if !messages.HasErrors() {
for _, torrentID := range torrentsSelected {
torrent, _ := torrentService.GetTorrentByID(torrentID)
if torrent.ID > 0 && userPermission.CurrentOrAdmin(currentUser, torrent.UploaderID) {
if action == "status" || action == "multiple" || action == "category" || action == "owner" {
/* If we don't delete, we make changes according to the form posted and we save at the end */
if c.PostForm("status") != "" && status != -1 {
torrent.Status = status
messages.AddInfoTf("infos", "torrent_moved", torrent.Name)
}
if c.PostForm("owner") != "" && owner != -1 {
torrent.UploaderID = uint(owner)
messages.AddInfoTf("infos", "torrent_owner_changed", torrent.Name)
}
if category != "" && catID != -1 && subCatID != -1 {
torrent.Category = catID
torrent.SubCategory = subCatID
messages.AddInfoTf("infos", "torrent_category_changed", torrent.Name)
}
/* Changes are done, we save */
_, 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(c).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)
torrentService.UpdateUnscopeTorrent(&torrent)
}
_, _, 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(c).Username)
}
} else {
messages.AddErrorTf("errors", "no_action_exist", action)
}
if withReport {
whereParams := serviceBase.CreateWhereParams("torrent_id = ?", torrentID)
reports, _, _ := reportService.GetTorrentReportsOrderBy(&whereParams, "", 0, 0)
for _, report := range reports {
reportService.DeleteTorrentReport(report.ID)
}
messages.AddInfoTf("infos", "torrent_reports_deleted", torrent.Name)
}
} else {
messages.AddErrorTf("errors", "torrent_not_exist", torrentID)
}
}
}
}