diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index fd8169c1..9b1847a5 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.sqlite +*.db-journal *.db *.sql main @@ -14,3 +15,7 @@ templates/*.html.go *.backup tags *.retry + +# emacs temp files +*\#* +*~ \ No newline at end of file diff --git a/db/gorm.go b/db/gorm.go index b2c57d93..8872bd17 100644 --- a/db/gorm.go +++ b/db/gorm.go @@ -30,7 +30,9 @@ func GormInit(conf *config.Config) (*gorm.DB, error) { // TODO: Enable Gorm initialization for non-development builds if config.Environment == "DEVELOPMENT" { db.LogMode(true) - db.AutoMigrate(&model.Torrent{}, &model.UserFollows{}, &model.User{}, &model.Comment{}, &model.OldComment{}, &model.TorrentReport{}) + db.AutoMigrate(&model.User{}, &model.UserFollows{}, &model.UserUploadsOld{}) + db.AutoMigrate(&model.Torrent{}, &model.TorrentReport{}) + db.AutoMigrate(&model.Comment{}, &model.OldComment{}) } return db, nil diff --git a/main.go b/main.go index a0c8a3a4..3c89c7a8 100644 --- a/main.go +++ b/main.go @@ -40,9 +40,17 @@ func RunServer(conf *config.Config) { l, err := network.CreateHTTPListener(conf) log.CheckError(err) if err == nil { + // add http server to be closed gracefully + signals.RegisterCloser(&network.GracefulHttpCloser{ + Server: srv, + Listener: l, + }) log.Infof("listening on %s", l.Addr()) err := srv.Serve(l) - log.CheckError(err) + if err != nil && err != network.ErrListenerStopped { + log.CheckError(err) + } + } } diff --git a/model/report.go b/model/report.go new file mode 100644 index 00000000..ae158448 --- /dev/null +++ b/model/report.go @@ -0,0 +1,37 @@ +package model + +// TODO Add field to specify kind of reports +// TODO Add CreatedAt field +// INFO User can be null (anonymous reports) +// FIXME can't preload field Torrents for model.TorrentReport +type TorrentReport struct { + ID uint `gorm:"column:torrent_report_id;primary_key"` + Description string `gorm:"column:type"` + TorrentID uint `gorm:"column:torrent_id"` + UserID uint `gorm:"column:user_id"` + + Torrent Torrent `gorm:"AssociationForeignKey:TorrentID;ForeignKey:torrent_id"` + User User `gorm:"AssociationForeignKey:UserID;ForeignKey:ID"` +} + +type TorrentReportJson struct { + ID uint `json:"id"` + Description string `json:"description"` + Torrent TorrentJSON `json:"torrent"` + User UserJSON `json:"user"` +} + +/* Model Conversion to Json */ + +func (report *TorrentReport) ToJson() TorrentReportJson { + json := TorrentReportJson{report.ID, report.Description, report.Torrent.ToJSON(), report.User.ToJSON()} + return json +} + +func TorrentReportsToJSON(reports []TorrentReport) []TorrentReportJson { + json := make([]TorrentReportJson, len(reports)) + for i := range reports { + json[i] = reports[i].ToJson() + } + return json +} diff --git a/model/torrent.go b/model/torrent.go index 55eb2105..ef8024d6 100644 --- a/model/torrent.go +++ b/model/torrent.go @@ -5,7 +5,6 @@ import ( "github.com/ewhal/nyaa/util" "fmt" - "html" "html/template" "strconv" "strings" @@ -36,12 +35,14 @@ type Torrent struct { WebsiteLink string `gorm:"column:website_link"` DeletedAt *time.Time - Uploader *User `gorm:"ForeignKey:UploaderId"` + Uploader *User `gorm:"ForeignKey:uploader"` + OldUploader string `gorm:"-"` // ??????? OldComments []OldComment `gorm:"ForeignKey:torrent_id"` Comments []Comment `gorm:"ForeignKey:torrent_id"` } // Returns the total size of memory recursively allocated for this struct +// FIXME: doesn't go have sizeof or something nicer for this? func (t Torrent) Size() (s int) { s += 8 + // ints 2*3 + // time.Time @@ -66,19 +67,6 @@ func (t Torrent) Size() (s int) { } -// TODO Add field to specify kind of reports -// TODO Add CreatedAt field -// INFO User can be null (anonymous reports) -// FIXME can't preload field Torrents for model.TorrentReport -type TorrentReport struct { - ID uint `gorm:"column:torrent_report_id;primary_key"` - Description string `gorm:"column:type"` - TorrentID uint - UserID uint - Torrent Torrent `gorm:"AssociationForeignKey:TorrentID;ForeignKey:ID"` - User User `gorm:"AssociationForeignKey:UserID;ForeignKey:ID"` -} - /* We need a JSON object instead of a Gorm structure because magnet URLs are not in the database and have to be generated dynamically */ @@ -108,32 +96,18 @@ type TorrentJSON struct { Downloads int `json:"downloads"` UploaderID uint `json:"uploader_id"` UploaderName template.HTML `json:"uploader_name"` + OldUploader template.HTML `json:"uploader_old"` WebsiteLink template.URL `json:"website_link"` Magnet template.URL `json:"magnet"` TorrentLink template.URL `json:"torrent"` } -type TorrentReportJson struct { - ID uint `json:"id"` - Description string `json:"description"` - Torrent TorrentJSON `json:"torrent"` - User string -} - -/* Model Conversion to Json */ - -func (report *TorrentReport) ToJson() TorrentReportJson { - json := TorrentReportJson{report.ID, report.Description, report.Torrent.ToJSON(), report.User.Username} - return json -} - // ToJSON converts a model.Torrent to its equivalent JSON structure func (t *Torrent) ToJSON() TorrentJSON { magnet := util.InfoHashToMagnet(strings.TrimSpace(t.Hash), t.Name, config.Trackers...) commentsJSON := make([]CommentJSON, 0, len(t.OldComments)+len(t.Comments)) for _, c := range t.OldComments { - escapedContent := template.HTML(html.EscapeString(c.Content)) - commentsJSON = append(commentsJSON, CommentJSON{Username: c.Username, Content: escapedContent, Date: c.Date}) + commentsJSON = append(commentsJSON, CommentJSON{Username: c.Username, Content: template.HTML(c.Content), Date: c.Date}) } for _, c := range t.Comments { commentsJSON = append(commentsJSON, CommentJSON{Username: c.User.Username, Content: util.MarkdownToHTML(c.Content), Date: c.CreatedAt}) @@ -162,6 +136,7 @@ func (t *Torrent) ToJSON() TorrentJSON { Downloads: t.Downloads, UploaderID: t.UploaderID, UploaderName: util.SafeText(uploader), + OldUploader: util.SafeText(t.OldUploader), WebsiteLink: util.Safe(t.WebsiteLink), Magnet: util.Safe(magnet), TorrentLink: util.Safe(torrentlink)} @@ -179,11 +154,3 @@ func TorrentsToJSON(t []Torrent) []TorrentJSON { // TODO: Convert to singular ve } return json } - -func TorrentReportsToJSON(reports []TorrentReport) []TorrentReportJson { - json := make([]TorrentReportJson, len(reports)) - for i := range reports { - json[i] = reports[i].ToJson() - } - return json -} diff --git a/model/user.go b/model/user.go index 4a01c168..1e1e3ca7 100644 --- a/model/user.go +++ b/model/user.go @@ -22,10 +22,19 @@ type User struct { Likings []User // Don't work `gorm:"foreignkey:user_id;associationforeignkey:follower_id;many2many:user_follows"` Liked []User // Don't work `gorm:"foreignkey:follower_id;associationforeignkey:user_id;many2many:user_follows"` - MD5 string `json:"md5"` // Hash of email address, used for Gravatar + MD5 string `json:"md5" gorm:"column:md5"` // Hash of email address, used for Gravatar Torrents []Torrent `gorm:"ForeignKey:UploaderID"` } +type UserJSON struct { + ID uint `json:"user_id"` + Username string `json:"username"` + Status int `json:"status"` + CreatedAt string `json:"created_at"` + LikingCount int `json:"liking_count"` + LikedCount int `json:"liked_count"` +} + // Returns the total size of memory recursively allocated for this struct func (u User) Size() (s int) { s += 4 + // ints @@ -50,3 +59,25 @@ type UserFollows struct { UserID uint `gorm:"column:user_id"` FollowerID uint `gorm:"column:following"` } + +type UserUploadsOld struct { + Username string `gorm:"column:username"` + TorrentId uint `gorm:"column:torrent_id"` +} + +func (c UserUploadsOld) TableName() string { + // TODO: rename this in db + return "user_uploads_old" +} + +func (u *User) ToJSON() UserJSON { + json := UserJSON{ + ID: u.ID, + Username: u.Username, + Status: u.Status, + CreatedAt: u.CreatedAt.Format(time.RFC3339), + LikingCount: u.LikingCount, + LikedCount: u.LikedCount, + } + return json +} diff --git a/network/closer.go b/network/closer.go new file mode 100644 index 00000000..267db4b7 --- /dev/null +++ b/network/closer.go @@ -0,0 +1,17 @@ +package network + +import ( + "net" + "net/http" +) + +// implements io.Closer that gracefully closes an http server +type GracefulHttpCloser struct { + Server *http.Server + Listener net.Listener +} + +func (c *GracefulHttpCloser) Close() error { + c.Listener.Close() + return c.Server.Shutdown(nil) +} diff --git a/network/graceful.go b/network/graceful.go new file mode 100644 index 00000000..bcf4555a --- /dev/null +++ b/network/graceful.go @@ -0,0 +1,58 @@ +package network + +import ( + "errors" + "net" +) + +var ErrListenerStopped = errors.New("listener was stopped") + +// GracefulListener provides safe and graceful net.Listener wrapper that prevents error on graceful shutdown +type GracefulListener struct { + listener net.Listener + stop chan int +} + +func (l *GracefulListener) Accept() (net.Conn, error) { + for { + c, err := l.listener.Accept() + select { + case <-l.stop: + if c != nil { + c.Close() + } + close(l.stop) + l.stop = nil + return nil, ErrListenerStopped + default: + + } + if err != nil { + neterr, ok := err.(net.Error) + if ok && neterr.Timeout() && neterr.Temporary() { + continue + } + } + return c, err + } +} + +func (l *GracefulListener) Close() (err error) { + l.listener.Close() + if l.stop != nil { + l.stop <- 0 + } + return +} + +func (l *GracefulListener) Addr() net.Addr { + return l.listener.Addr() +} + +// WrapListener wraps a net.Listener such that it can be closed gracefully +func WrapListener(l net.Listener) net.Listener { + return &GracefulListener{ + listener: l, + stop: make(chan int), + } +} diff --git a/network/network.go b/network/network.go index 89b249b6..d851da0a 100644 --- a/network/network.go +++ b/network/network.go @@ -20,5 +20,8 @@ func CreateHTTPListener(conf *config.Config) (l net.Listener, err error) { l = s } } + if l != nil { + l = WrapListener(l) + } return } diff --git a/public/css/style.css b/public/css/style.css index c39c0238..02cec37b 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -1,5 +1,3 @@ - - /* Torrent status colors */ .remake { background-color: rgb(240, 176, 128); @@ -323,7 +321,8 @@ div.container div.blockBody:nth-of-type(2) table tr:first-of-type th:last-of-typ background: #fff; min-height: 460px; } -/* Night mode switcher */ + +/* Night mode switcher */ #mainmenu a.nightswitch { background: transparent url(/img/sun.png) no-repeat; background-size: 24px; @@ -331,11 +330,12 @@ div.container div.blockBody:nth-of-type(2) table tr:first-of-type th:last-of-typ margin-left: 3px; width: 24px; } + footer { text-align: center; - padding: 2rem; + padding-bottom: 2rem; font-size: 2rem; font-family: cursive; - color: #CCC; - text-shadow: 0px -1px #999999; -} \ No newline at end of file + color: #616161; + text-shadow: -1px -1px #999999; +} diff --git a/router/apiHandler.go b/router/apiHandler.go index a01fb58e..2939ea50 100644 --- a/router/apiHandler.go +++ b/router/apiHandler.go @@ -11,6 +11,7 @@ import ( "github.com/ewhal/nyaa/config" "github.com/ewhal/nyaa/db" "github.com/ewhal/nyaa/model" + "github.com/ewhal/nyaa/service" "github.com/ewhal/nyaa/service/api" "github.com/ewhal/nyaa/service/torrent" "github.com/ewhal/nyaa/util" @@ -21,7 +22,7 @@ import ( func ApiHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) page := vars["page"] - whereParams := torrentService.WhereParams{} + whereParams := serviceBase.WhereParams{} req := apiService.TorrentsRequest{} contentType := r.Header.Get("Content-Type") diff --git a/router/modpanel.go b/router/modpanel.go index 6b2ad5af..d48c5b5c 100644 --- a/router/modpanel.go +++ b/router/modpanel.go @@ -3,30 +3,42 @@ package router import ( + "html" "html/template" "net/http" "path/filepath" "strconv" - "fmt" + "github.com/ewhal/nyaa/model" "github.com/ewhal/nyaa/service/comment" + "github.com/ewhal/nyaa/service/report" "github.com/ewhal/nyaa/service/torrent" "github.com/ewhal/nyaa/service/torrent/form" "github.com/ewhal/nyaa/service/user" form "github.com/ewhal/nyaa/service/user/form" "github.com/ewhal/nyaa/service/user/permission" "github.com/ewhal/nyaa/util/languages" + "github.com/ewhal/nyaa/util/log" "github.com/ewhal/nyaa/util/modelHelper" + "github.com/ewhal/nyaa/util/search" + "github.com/gorilla/mux" ) -var panelIndex, panelTorrentList, panelUserList, panelCommentList, panelTorrentEd *template.Template +var panelIndex, panelTorrentList, panelUserList, panelCommentList, panelTorrentEd, panelTorrentReportList *template.Template func init() { panelTorrentList = template.Must(template.New("torrentlist").Funcs(FuncMap).ParseFiles(filepath.Join(TemplateDir, "admin_index.html"), filepath.Join(TemplateDir, "admin/torrentlist.html"))) + panelTorrentList = template.Must(panelTorrentList.ParseGlob(filepath.Join("templates", "_*.html"))) panelUserList = template.Must(template.New("userlist").Funcs(FuncMap).ParseFiles(filepath.Join(TemplateDir, "admin_index.html"), filepath.Join(TemplateDir, "admin/userlist.html"))) + panelUserList = template.Must(panelUserList.ParseGlob(filepath.Join("templates", "_*.html"))) panelCommentList = template.Must(template.New("commentlist").Funcs(FuncMap).ParseFiles(filepath.Join(TemplateDir, "admin_index.html"), filepath.Join(TemplateDir, "admin/commentlist.html"))) + panelCommentList = template.Must(panelCommentList.ParseGlob(filepath.Join("templates", "_*.html"))) panelIndex = template.Must(template.New("indexPanel").Funcs(FuncMap).ParseFiles(filepath.Join(TemplateDir, "admin_index.html"), filepath.Join(TemplateDir, "admin/panelindex.html"))) - panelTorrentEd = template.Must(template.New("indexPanel").Funcs(FuncMap).ParseFiles(filepath.Join(TemplateDir, "admin_index.html"), filepath.Join(TemplateDir, "admin/paneltorrentedit.html"))) + panelIndex = template.Must(panelIndex.ParseGlob(filepath.Join("templates", "_*.html"))) + panelTorrentEd = template.Must(template.New("torrent_ed").Funcs(FuncMap).ParseFiles(filepath.Join(TemplateDir, "admin_index.html"), filepath.Join(TemplateDir, "admin/paneltorrentedit.html"))) + panelTorrentEd = template.Must(panelTorrentEd.ParseGlob(filepath.Join("templates", "_*.html"))) + panelTorrentReportList = template.Must(template.New("torrent_report").Funcs(FuncMap).ParseFiles(filepath.Join(TemplateDir, "admin_index.html"), filepath.Join(TemplateDir, "admin/torrent_report.html"))) + panelTorrentReportList = template.Must(panelTorrentReportList.ParseGlob(filepath.Join("templates", "_*.html"))) } func IndexModPanel(w http.ResponseWriter, r *http.Request) { @@ -34,58 +46,138 @@ func IndexModPanel(w http.ResponseWriter, r *http.Request) { if userPermission.HasAdmin(currentUser) { offset := 10 - torrents, _, _ := torrentService.GetAllTorrents(0, offset) - users := userService.RetrieveUsersForAdmin(0, offset) - comments := commentService.GetAllComments(0, offset) + torrents, _, _ := torrentService.GetAllTorrents(offset, 0) + users, _ := userService.RetrieveUsersForAdmin(offset, 0) + comments, _ := commentService.GetAllComments(offset, 0, "", "") + torrentReports, _, _ := reportService.GetAllTorrentReports(offset, 0) + languages.SetTranslationFromRequest(panelIndex, r, "en-us") - htv := PanelIndexVbs{torrents, users, comments} + htv := PanelIndexVbs{torrents, torrentReports, users, comments, NewSearchForm(), currentUser, r.URL} _ = panelIndex.ExecuteTemplate(w, "admin_index.html", htv) } else { http.Error(w, "admins only", http.StatusForbidden) } } + func TorrentsListPanel(w http.ResponseWriter, r *http.Request) { currentUser := GetUser(r) if userPermission.HasAdmin(currentUser) { - page, _ := strconv.Atoi(r.URL.Query().Get("p")) + vars := mux.Vars(r) + page := vars["page"] + + 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 + } + } offset := 100 - torrents, _, _ := torrentService.GetAllTorrents(offset, page * offset) + searchParam, torrents, _, err := search.SearchByQuery(r, pagenum) + searchForm := SearchForm{ + SearchParam: searchParam, + Category: searchParam.Category.String(), + HideAdvancedSearch: false, + } + languages.SetTranslationFromRequest(panelTorrentList, r, "en-us") - htv := PanelTorrentListVbs{torrents} - err := panelTorrentList.ExecuteTemplate(w, "admin_index.html", htv) - fmt.Println(err) + htv := PanelTorrentListVbs{torrents, searchForm, Navigation{int(searchParam.Max), offset, pagenum, "mod_tlist_page"}, currentUser, r.URL} + err = panelTorrentList.ExecuteTemplate(w, "admin_index.html", htv) + log.CheckError(err) } else { http.Error(w, "admins only", http.StatusForbidden) } } + +func TorrentReportListPanel(w http.ResponseWriter, r *http.Request) { + currentUser := GetUser(r) + if userPermission.HasAdmin(currentUser) { + vars := mux.Vars(r) + page := vars["page"] + + 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 + } + } + offset := 100 + + torrentReports, nbReports, _ := reportService.GetAllTorrentReports(offset, (pagenum-1)*offset) + + reportJSON := model.TorrentReportsToJSON(torrentReports) + languages.SetTranslationFromRequest(panelTorrentReportList, r, "en-us") + htv := PanelTorrentReportListVbs{reportJSON, NewSearchForm(), Navigation{nbReports, offset, pagenum, "mod_trlist_page"}, currentUser, r.URL} + err = panelTorrentReportList.ExecuteTemplate(w, "admin_index.html", htv) + log.CheckError(err) + } else { + http.Error(w, "admins only", http.StatusForbidden) + } +} + func UsersListPanel(w http.ResponseWriter, r *http.Request) { currentUser := GetUser(r) if userPermission.HasAdmin(currentUser) { - page, _ := strconv.Atoi(r.URL.Query().Get("p")) + vars := mux.Vars(r) + page := vars["page"] + + 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 + } + } offset := 100 - users := userService.RetrieveUsersForAdmin(offset, page*offset) + users, nbUsers := userService.RetrieveUsersForAdmin(offset, (pagenum-1)*offset) languages.SetTranslationFromRequest(panelUserList, r, "en-us") - htv := PanelUserListVbs{users} - err := panelUserList.ExecuteTemplate(w, "admin_index.html", htv) - fmt.Println(err) + htv := PanelUserListVbs{users, NewSearchForm(), Navigation{nbUsers, offset, pagenum, "mod_ulist_page"}, currentUser, r.URL} + err = panelUserList.ExecuteTemplate(w, "admin_index.html", htv) + log.CheckError(err) } else { http.Error(w, "admins only", http.StatusForbidden) } } + func CommentsListPanel(w http.ResponseWriter, r *http.Request) { currentUser := GetUser(r) if userPermission.HasAdmin(currentUser) { - page, _ := strconv.Atoi(r.URL.Query().Get("p")) - offset := 100 + vars := mux.Vars(r) + page := vars["page"] - comments := commentService.GetAllComments(offset, page * offset) + 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 + } + } + offset := 100 + userid := r.URL.Query().Get("userid") + 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...) languages.SetTranslationFromRequest(panelCommentList, r, "en-us") - htv := PanelCommentListVbs{comments} - err := panelCommentList.ExecuteTemplate(w, "admin_index.html", htv) - fmt.Println(err) + htv := PanelCommentListVbs{comments, NewSearchForm(), Navigation{nbComments, offset, pagenum, "mod_clist_page"}, currentUser, r.URL} + err = panelCommentList.ExecuteTemplate(w, "admin_index.html", htv) + log.CheckError(err) } else { http.Error(w, "admins only", http.StatusForbidden) } @@ -97,9 +189,9 @@ func TorrentEditModPanel(w http.ResponseWriter, r *http.Request) { id := r.URL.Query().Get("id") torrent, _ := torrentService.GetTorrentById(id) languages.SetTranslationFromRequest(panelTorrentEd, r, "en-us") - htv := PanelTorrentEdVbs{torrent} + htv := PanelTorrentEdVbs{torrent, NewSearchForm(), currentUser} err := panelTorrentEd.ExecuteTemplate(w, "admin_index.html", htv) - fmt.Println(err) + log.CheckError(err) } else { http.Error(w, "admins only", http.StatusForbidden) } @@ -128,7 +220,7 @@ func TorrentPostEditModPanel(w http.ResponseWriter, r *http.Request) { } } languages.SetTranslationFromRequest(panelTorrentEd, r, "en-us") - htv := PanelTorrentEdVbs{torrent} + htv := PanelTorrentEdVbs{torrent, NewSearchForm(), currentUser} _ = panelTorrentEd.ExecuteTemplate(w, "admin_index.html", htv) } else { http.Error(w, "admins only", http.StatusForbidden) diff --git a/router/router.go b/router/router.go old mode 100644 new mode 100755 index 998c2a0b..da317cec --- a/router/router.go +++ b/router/router.go @@ -37,10 +37,12 @@ func init() { gzipUserLogoutHandler := handlers.CompressHandler(http.HandlerFunc(UserLogoutHandler)) gzipUserProfileHandler := handlers.CompressHandler(http.HandlerFunc(UserProfileHandler)) gzipUserFollowHandler := handlers.CompressHandler(http.HandlerFunc(UserFollowHandler)) + gzipUserDetailsHandler := handlers.CompressHandler(http.HandlerFunc(UserDetailsHandler)) gzipUserProfileFormHandler := handlers.CompressHandler(http.HandlerFunc(UserProfileFormHandler)) gzipIndexModPanel := handlers.CompressHandler(http.HandlerFunc(IndexModPanel)) gzipTorrentsListPanel := handlers.CompressHandler(http.HandlerFunc(TorrentsListPanel)) + gzipTorrentReportListPanel := handlers.CompressHandler(http.HandlerFunc(TorrentReportListPanel)) gzipUsersListPanel := handlers.CompressHandler(http.HandlerFunc(UsersListPanel)) gzipCommentsListPanel := handlers.CompressHandler(http.HandlerFunc(CommentsListPanel)) gzipTorrentEditModPanel := handlers.CompressHandler(http.HandlerFunc(TorrentEditModPanel)) @@ -48,7 +50,6 @@ func init() { gzipCommentDeleteModPanel := handlers.CompressHandler(http.HandlerFunc(CommentDeleteModPanel)) gzipTorrentDeleteModPanel := handlers.CompressHandler(http.HandlerFunc(TorrentDeleteModPanel)) - gzipGetTorrentReportHandler := handlers.CompressHandler(http.HandlerFunc(GetTorrentReportHandler)) //gzipTorrentReportCreateHandler := handlers.CompressHandler(http.HandlerFunc(CreateTorrentReportHandler)) //gzipTorrentReportDeleteHandler := handlers.CompressHandler(http.HandlerFunc(DeleteTorrentReportHandler)) //gzipTorrentDeleteHandler := handlers.CompressHandler(http.HandlerFunc(DeleteTorrentHandler)) @@ -81,13 +82,17 @@ func init() { Router.Handle("/user/logout", gzipUserLogoutHandler).Name("user_logout") Router.Handle("/user/{id}/{username}", wrapHandler(gzipUserProfileHandler)).Name("user_profile").Methods("GET") Router.Handle("/user/{id}/{username}/follow", gzipUserFollowHandler).Name("user_follow").Methods("GET") - Router.Handle("/user/{id}/{username}", wrapHandler(gzipUserProfileFormHandler)).Name("user_profile").Methods("POST") + Router.Handle("/user/{id}/{username}/edit", wrapHandler(gzipUserDetailsHandler)).Name("user_profile_details").Methods("GET") + Router.Handle("/user/{id}/{username}/edit", wrapHandler(gzipUserProfileFormHandler)).Name("user_profile_edit").Methods("POST") - Router.Handle("/mod/", gzipIndexModPanel).Name("mod_index") + Router.Handle("/mod", gzipIndexModPanel).Name("mod_index") Router.Handle("/mod/torrents", gzipTorrentsListPanel).Name("mod_tlist") + Router.Handle("/mod/torrents/{page}", gzipTorrentsListPanel).Name("mod_tlist_page") Router.Handle("/mod/users", gzipUsersListPanel).Name("mod_ulist") + Router.Handle("/mod/users/{page}", gzipUsersListPanel).Name("mod_ulist_page") Router.Handle("/mod/comments", gzipCommentsListPanel).Name("mod_clist") - Router.Handle("/mod/comments", gzipCommentsListPanel).Name("mod_cedit") // TODO + Router.Handle("/mod/comments/{page}", gzipCommentsListPanel).Name("mod_clist_page") + Router.Handle("/mod/comment", gzipCommentsListPanel).Name("mod_cedit") // TODO Router.Handle("/mod/torrent/", gzipTorrentEditModPanel).Name("mod_tedit") Router.Handle("/mod/torrent/", gzipTorrentPostEditModPanel).Name("mod_ptedit") Router.Handle("/mod/torrent/delete", gzipTorrentDeleteModPanel).Name("mod_tdelete") @@ -102,7 +107,8 @@ func init() { // TODO Allow only moderators to access /moderation/* //Router.Handle("/moderation/report/delete", gzipTorrentReportDeleteHandler).Name("torrent_report_delete").Methods("POST") //Router.Handle("/moderation/torrent/delete", gzipTorrentDeleteHandler).Name("torrent_delete").Methods("POST") - Router.Handle("/moderation/report", gzipGetTorrentReportHandler ).Name("torrent_report").Methods("GET") + Router.Handle("/mod/reports", gzipTorrentReportListPanel).Name("mod_trlist").Methods("GET") + Router.Handle("/mod/reports/{page}", gzipTorrentReportListPanel).Name("mod_trlist_page").Methods("GET") Router.NotFoundHandler = http.HandlerFunc(NotFoundHandler) } diff --git a/router/template.go b/router/template.go index 60940c48..e206ffac 100644 --- a/router/template.go +++ b/router/template.go @@ -7,7 +7,7 @@ import ( var TemplateDir = "templates" -var torrentReportTemplate, homeTemplate, searchTemplate, faqTemplate, uploadTemplate, viewTemplate, viewRegisterTemplate, viewLoginTemplate, viewRegisterSuccessTemplate, viewVerifySuccessTemplate, viewProfileTemplate, viewProfileEditTemplate, viewUserDeleteTemplate, notFoundTemplate *template.Template +var homeTemplate, searchTemplate, faqTemplate, uploadTemplate, viewTemplate, viewRegisterTemplate, viewLoginTemplate, viewRegisterSuccessTemplate, viewVerifySuccessTemplate, viewProfileTemplate, viewProfileEditTemplate, viewUserDeleteTemplate, notFoundTemplate *template.Template type templateLoader struct { templ **template.Template @@ -18,11 +18,6 @@ type templateLoader struct { // ReloadTemplates reloads templates on runtime func ReloadTemplates() { templs := []templateLoader{ - templateLoader{ - templ: &torrentReportTemplate, - name: "torrent_report", - file: "torrent_report.html", - }, templateLoader{ templ: &homeTemplate, name: "home", diff --git a/router/templateFunctions.go b/router/templateFunctions.go index 9f974f71..51e497ec 100644 --- a/router/templateFunctions.go +++ b/router/templateFunctions.go @@ -27,9 +27,10 @@ var FuncMap = template.FuncMap{ return "error" }, "genNav": func(nav Navigation, currentUrl *url.URL, pagesSelectable int) template.HTML { + var ret = "" + if (nav.TotalItem > 0) { maxPages := math.Ceil(float64(nav.TotalItem) / float64(nav.MaxItemPerPage)) - var ret = "" if nav.CurrentPage-1 > 0 { url, _ := Router.Get(nav.Route).URL("page", "1") ret = ret + "
  • «
  • " @@ -57,6 +58,7 @@ var FuncMap = template.FuncMap{ url, _ := Router.Get(nav.Route).URL("page", strconv.Itoa(nav.CurrentPage+1)) ret = ret + "
  • »
  • " } + } return template.HTML(ret) }, "T": i18n.IdentityTfunc, diff --git a/router/templateVariables.go b/router/templateVariables.go index 126b012d..c1907ca6 100644 --- a/router/templateVariables.go +++ b/router/templateVariables.go @@ -113,27 +113,51 @@ type UploadTemplateVariables struct { Route *mux.Route } +/* MODERATION Variables */ + type PanelIndexVbs struct { - Torrents []model.Torrent - Users []model.User - Comments []model.Comment + Torrents []model.Torrent + TorrentReports []model.TorrentReport + Users []model.User + Comments []model.Comment + Search SearchForm + User *model.User + URL *url.URL // For parsing Url in templates } type PanelTorrentListVbs struct { - Torrents []model.Torrent + Torrents []model.Torrent + Search SearchForm + Navigation Navigation + User *model.User + URL *url.URL // For parsing Url in templates } type PanelUserListVbs struct { - Users []model.User + Users []model.User + Search SearchForm + Navigation Navigation + User *model.User + URL *url.URL // For parsing Url in templates } type PanelCommentListVbs struct { - Comments []model.Comment + Comments []model.Comment + Search SearchForm + Navigation Navigation + User *model.User + URL *url.URL // For parsing Url in templates } type PanelTorrentEdVbs struct { Torrent model.Torrent + Search SearchForm + User *model.User } -type ViewTorrentReportsVariables struct { - Torrents []model.TorrentReportJson +type PanelTorrentReportListVbs struct { + TorrentReports []model.TorrentReportJson + Search SearchForm + Navigation Navigation + User *model.User + URL *url.URL // For parsing Url in templates } /* diff --git a/router/torrentReportHandler.go b/router/torrentReportHandler.go index 74907c0d..92f61d58 100644 --- a/router/torrentReportHandler.go +++ b/router/torrentReportHandler.go @@ -1,12 +1,15 @@ package router -import ( +/*import ( "net/http" + "strconv" "github.com/ewhal/nyaa/model" "github.com/ewhal/nyaa/service/moderation" "github.com/ewhal/nyaa/service/user/permission" -) + "github.com/gorilla/mux" +)*/ + /* func SanitizeTorrentReport(torrentReport *model.TorrentReport) { // TODO unescape html ? @@ -47,24 +50,7 @@ func DeleteTorrentReportHandler(w http.ResponseWriter, r *http.Request) { } } */ -func GetTorrentReportHandler(w http.ResponseWriter, r *http.Request) { - currentUser := GetUser(r) - if userPermission.HasAdmin(currentUser) { - torrentReports, err := moderationService.GetTorrentReports() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - err = torrentReportTemplate.ExecuteTemplate(w, "torrent_report.html", ViewTorrentReportsVariables{model.TorrentReportsToJSON(torrentReports)}) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - } else { - http.Error(w, "admins only", http.StatusForbidden) - } -} /* func DeleteTorrentHandler(w http.ResponseWriter, r *http.Request) { @@ -76,3 +62,32 @@ func DeleteTorrentHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) } }*/ + +/*func GetTorrentReportHandler(w http.ResponseWriter, r *http.Request) { + currentUser := GetUser(r) + if userPermission.HasAdmin(currentUser) { + vars := mux.Vars(r) + page, _ := strconv.Atoi(vars["page"]) + offset := 100 + userid := r.URL.Query().Get("userid") + var conditions string + var values []interface{} + if (userid != "") { + conditions = "user_id = ?" + values = append(values, userid) + } + + torrentReports, nbReports, err := moderationService.GetTorrentReports(offset, page * offset, conditions, values...) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + err = torrentReportTemplate.ExecuteTemplate(w, "admin_index.html", ViewTorrentReportsVariables{model.TorrentReportsToJSON(torrentReports), NewSearchForm(), Navigation{nbReports, offset, page, "mod_trlist_page"}, currentUser, r.URL}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } else { + http.Error(w, "admins only", http.StatusForbidden) + } +}*/ diff --git a/router/upload.go b/router/upload.go index c0ba05bf..2e7f89a5 100644 --- a/router/upload.go +++ b/router/upload.go @@ -14,6 +14,7 @@ import ( "strconv" "strings" + "github.com/ewhal/nyaa/cache" "github.com/ewhal/nyaa/config" "github.com/ewhal/nyaa/service/captcha" "github.com/ewhal/nyaa/util" @@ -94,6 +95,7 @@ func (f *UploadForm) ExtractInfo(r *http.Request) error { f.Name = util.TrimWhitespaces(f.Name) f.Description = p.Sanitize(util.TrimWhitespaces(f.Description)) f.Magnet = util.TrimWhitespaces(f.Magnet) + cache.Clear() catsSplit := strings.Split(f.Category, "_") // need this to prevent out of index panics diff --git a/router/userHandler.go b/router/userHandler.go old mode 100644 new mode 100755 index 03290e84..2e74b855 --- a/router/userHandler.go +++ b/router/userHandler.go @@ -10,6 +10,7 @@ import ( "github.com/ewhal/nyaa/service/user" "github.com/ewhal/nyaa/service/user/form" "github.com/ewhal/nyaa/service/user/permission" + "github.com/ewhal/nyaa/util/log" "github.com/ewhal/nyaa/util/languages" "github.com/ewhal/nyaa/util/modelHelper" "github.com/gorilla/mux" @@ -54,30 +55,21 @@ func UserProfileHandler(w http.ResponseWriter, r *http.Request) { userProfile, _, errorUser := userService.RetrieveUserForAdmin(id) if errorUser == nil { currentUser := GetUser(r) - view := r.URL.Query()["edit"] follow := r.URL.Query()["followed"] unfollow := r.URL.Query()["unfollowed"] infosForm := form.NewInfos() deleteVar := r.URL.Query()["delete"] - if (view != nil) && (userPermission.CurrentOrAdmin(currentUser, userProfile.ID)) { - b := form.UserForm{} - modelHelper.BindValueForm(&b, r) - languages.SetTranslationFromRequest(viewProfileEditTemplate, r, "en-us") - htv := UserProfileEditVariables{&userProfile, b, form.NewErrors(), form.NewInfos(), NewSearchForm(), Navigation{}, currentUser, r.URL, mux.CurrentRoute(r)} - - err := viewProfileEditTemplate.ExecuteTemplate(w, "index.html", htv) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - } else if (deleteVar != nil) && (userPermission.CurrentOrAdmin(currentUser, userProfile.ID)) { + if (deleteVar != nil) && (userPermission.CurrentOrAdmin(currentUser, userProfile.ID)) { err := form.NewErrors() _, errUser := userService.DeleteUser(w, currentUser, id) if errUser != nil { err["errors"] = append(err["errors"], errUser.Error()) } languages.SetTranslationFromRequest(viewUserDeleteTemplate, r, "en-us") - htv := UserVerifyTemplateVariables{err, NewSearchForm(), Navigation{}, GetUser(r), r.URL, mux.CurrentRoute(r)} + searchForm := NewSearchForm() + searchForm.HideAdvancedSearch = true + htv := UserVerifyTemplateVariables{err, searchForm, Navigation{}, GetUser(r), r.URL, mux.CurrentRoute(r)} errorTmpl := viewUserDeleteTemplate.ExecuteTemplate(w, "index.html", htv) if errorTmpl != nil { http.Error(w, errorTmpl.Error(), http.StatusInternalServerError) @@ -90,7 +82,9 @@ func UserProfileHandler(w http.ResponseWriter, r *http.Request) { if unfollow != nil { infosForm["infos"] = append(infosForm["infos"], fmt.Sprintf(T("user_unfollowed_msg"), userProfile.Username)) } - htv := UserProfileVariables{&userProfile, infosForm, NewSearchForm(), Navigation{}, currentUser, r.URL, mux.CurrentRoute(r)} + searchForm := NewSearchForm() + searchForm.HideAdvancedSearch = true + htv := UserProfileVariables{&userProfile, infosForm, searchForm, Navigation{}, currentUser, r.URL, mux.CurrentRoute(r)} err := viewProfileTemplate.ExecuteTemplate(w, "index.html", htv) if err != nil { @@ -109,6 +103,25 @@ func UserProfileHandler(w http.ResponseWriter, r *http.Request) { } } +//Getting User Profile Details View +func UserDetailsHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id := vars["id"] + b := form.UserForm{} + userProfile, _, errorUser := userService.RetrieveUserForAdmin(id) + if errorUser == nil { + currentUser := GetUser(r) + modelHelper.BindValueForm(&b, r) + languages.SetTranslationFromRequest(viewProfileEditTemplate, r, "en-us") + searchForm := NewSearchForm() + searchForm.HideAdvancedSearch = true + htv := UserProfileEditVariables{&userProfile, b, form.NewErrors(), form.NewInfos(), searchForm, Navigation{}, currentUser, r.URL, mux.CurrentRoute(r)} + err := viewProfileEditTemplate.ExecuteTemplate(w, "index.html", htv) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } +} // Getting View User Profile Update func UserProfileFormHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) @@ -129,9 +142,14 @@ func UserProfileFormHandler(w http.ResponseWriter, r *http.Request) { } if len(err) == 0 { modelHelper.BindValueForm(&b, r) + if (!userPermission.HasAdmin(currentUser)) { + b.Username = currentUser.Username + } err = modelHelper.ValidateForm(&b, err) + log.Info("lol") if len(err) == 0 { userProfile, _, errorUser = userService.UpdateUser(w, &b, currentUser, id) + log.Infof("xD2") if errorUser != nil { err["errors"] = append(err["errors"], errorUser.Error()) } @@ -169,7 +187,6 @@ func UserProfileFormHandler(w http.ResponseWriter, r *http.Request) { // Post Registration controller, we do some check on the form here, the rest on user service func UserRegisterPostHandler(w http.ResponseWriter, r *http.Request) { - // Check same Password b := form.RegistrationForm{} err := form.NewErrors() if !captcha.Authenticate(captcha.Extract(r)) { diff --git a/router/viewTorrentHandler.go b/router/viewTorrentHandler.go index ccc51edb..e9fab112 100644 --- a/router/viewTorrentHandler.go +++ b/router/viewTorrentHandler.go @@ -13,6 +13,8 @@ import ( "github.com/ewhal/nyaa/util/languages" "github.com/ewhal/nyaa/util/log" "github.com/gorilla/mux" + + "fmt" ) func ViewHandler(w http.ResponseWriter, r *http.Request) { @@ -49,8 +51,8 @@ func PostCommentHandler(w http.ResponseWriter, r *http.Request) { userID := currentUser.ID comment := model.Comment{TorrentID: uint(idNum), UserID: userID, Content: content, CreatedAt: time.Now()} - -err = db.ORM.Create(&comment).Error + + err = db.ORM.Create(&comment).Error if err != nil { util.SendError(w, err, 500) return @@ -75,9 +77,18 @@ func ReportTorrentHandler(w http.ResponseWriter, r *http.Request) { currentUser := GetUser(r) idNum, err := strconv.Atoi(id) - userID := currentUser.ID - report := model.TorrentReport{Description: r.FormValue("report_type"), TorrentID: uint(idNum), UserID: userID} + + torrent, _ := torrentService.GetTorrentById(id) + + report := model.TorrentReport{ + Description: r.FormValue("report_type"), + TorrentID: uint(idNum), + UserID: userID, + Torrent: torrent, + User: *currentUser, + } + fmt.Println(report) err = db.ORM.Create(&report).Error if err != nil { diff --git a/service/api/api.go b/service/api/api.go index e3806c05..3c5a3547 100644 --- a/service/api/api.go +++ b/service/api/api.go @@ -8,7 +8,7 @@ import ( "strings" "github.com/ewhal/nyaa/model" - "github.com/ewhal/nyaa/service/torrent" + "github.com/ewhal/nyaa/service" ) type torrentsQuery struct { @@ -40,8 +40,8 @@ type UpdateRequest struct { Update TorrentRequest `json:"update"` } -func (r *TorrentsRequest) ToParams() torrentService.WhereParams { - res := torrentService.WhereParams{} +func (r *TorrentsRequest) ToParams() serviceBase.WhereParams { + res := serviceBase.WhereParams{} conditions := "" v := reflect.ValueOf(r.Query) diff --git a/service/comment/comment.go b/service/comment/comment.go index f24c4fb0..19497c19 100644 --- a/service/comment/comment.go +++ b/service/comment/comment.go @@ -6,8 +6,10 @@ import ( ) -func GetAllComments(limit int, offset int) []model.Comment{ +func GetAllComments(limit int, offset int, conditions string, values ...interface{}) ([]model.Comment, int){ var comments []model.Comment - db.ORM.Limit(limit).Offset(offset).Preload("User").Find(&comments) - return comments + 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) + return comments, nbComments } diff --git a/service/moderation/torrent_report.go b/service/moderation/torrent_report.go deleted file mode 100644 index ebce2555..00000000 --- a/service/moderation/torrent_report.go +++ /dev/null @@ -1,38 +0,0 @@ -package moderationService - -import ( - "errors" - "net/http" - - "github.com/ewhal/nyaa/db" - "github.com/ewhal/nyaa/model" -) - -// Return torrentReport in case we did modified it (ie: CreatedAt field) -func CreateTorrentReport(torrentReport model.TorrentReport) (model.TorrentReport, error) { - if db.ORM.Create(&torrentReport).Error != nil { - return torrentReport, errors.New("TorrentReport was not created") - } - return torrentReport, nil -} - -func DeleteTorrentReport(id int) (int, error) { - var torrentReport model.TorrentReport - if db.ORM.First(&torrentReport, id).RecordNotFound() { - return http.StatusNotFound, errors.New("Trying to delete a torrent report that does not exists.") - } - if db.ORM.Delete(&torrentReport).Error != nil { - return http.StatusInternalServerError, errors.New("User is not deleted.") - } - return http.StatusOK, nil -} - -// TODO Add WhereParams to filter the torrent reports (ie: searching description) -// TODO Use limit, offset -func GetTorrentReports() ([]model.TorrentReport, error) { - var torrentReports []model.TorrentReport - if db.ORM.Preload("User").Preload("Torrent").Find(&torrentReports).Error != nil { - return nil, errors.New("Problem finding all torrent reports.") - } - return torrentReports, nil -} diff --git a/service/report/report.go b/service/report/report.go new file mode 100644 index 00000000..caf2e958 --- /dev/null +++ b/service/report/report.go @@ -0,0 +1,76 @@ +package reportService + +import ( + "errors" + "net/http" + "strconv" + "strings" + + "github.com/ewhal/nyaa/db" + "github.com/ewhal/nyaa/model" + "github.com/ewhal/nyaa/service" +) + +// Return torrentReport in case we did modified it (ie: CreatedAt field) +func CreateTorrentReport(torrentReport model.TorrentReport) error { + if db.ORM.Create(&torrentReport).Error != nil { + return errors.New("TorrentReport was not created") + } + return nil +} + +func DeleteTorrentReport(id int) (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 + } + if err := db.ORM.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, +) { + var conditionArray []string + var params []interface{} + if parameters != nil { // if there is where parameters + if len(parameters.Conditions) > 0 { + conditionArray = append(conditionArray, parameters.Conditions) + } + params = parameters.Params + } + conditions := strings.Join(conditionArray, " AND ") + if countAll { + err = db.ORM.Model(&torrentReports).Where(conditions, params...).Count(&count).Error + if err != nil { + return + } + } + // TODO: Vulnerable to injections. Use query builder. (is it?) + + // build custom db query for performance reasons + dbQuery := "SELECT * FROM torrent_reports" + if conditions != "" { + dbQuery = dbQuery + " WHERE " + conditions + } + + if orderBy == "" { // default OrderBy + orderBy = "torrent_report_id DESC" + } + dbQuery = dbQuery + " ORDER BY " + orderBy + if limit != 0 || offset != 0 { // if limits provided + dbQuery = dbQuery + " LIMIT " + strconv.Itoa(limit) + " OFFSET " + strconv.Itoa(offset) + } + err = db.ORM.Preload("Torrent").Preload("User").Raw(dbQuery, params...).Find(&torrentReports).Error //fixed !!!! + return +} + +func GetTorrentReportsOrderBy(parameters *serviceBase.WhereParams, orderBy string, limit int, offset int) ([]model.TorrentReport, int, error) { + return getTorrentReportsOrderBy(parameters, orderBy, limit, offset, true) +} + +func GetAllTorrentReports(limit int, offset int) ([]model.TorrentReport, int, error) { + return GetTorrentReportsOrderBy(nil, "", limit, offset) +} diff --git a/service/service.go b/service/service.go new file mode 100644 index 00000000..1d21b15c --- /dev/null +++ b/service/service.go @@ -0,0 +1,17 @@ +package serviceBase + +type WhereParams struct { + Conditions string // Ex : name LIKE ? AND category_id LIKE ? + Params []interface{} +} + +func CreateWhereParams(conditions string, params ...string) WhereParams { + whereParams := WhereParams{ + Conditions: conditions, + Params: make([]interface{}, len(params)), + } + for i := range params { + whereParams.Params[i] = params[i] + } + return whereParams +} diff --git a/service/torrent/torrent.go b/service/torrent/torrent.go index 8667ff6f..caa776f7 100644 --- a/service/torrent/torrent.go +++ b/service/torrent/torrent.go @@ -2,21 +2,17 @@ package torrentService import ( "errors" + "net/http" "strconv" "strings" - "net/http" "github.com/ewhal/nyaa/config" "github.com/ewhal/nyaa/db" "github.com/ewhal/nyaa/model" + "github.com/ewhal/nyaa/service" "github.com/ewhal/nyaa/util" ) -type WhereParams struct { - Conditions string // Ex : name LIKE ? AND category_id LIKE ? - Params []interface{} -} - /* Function to interact with Models * * Get the torrents with where clause @@ -51,7 +47,8 @@ func GetFeeds() (result []model.Feed, err error) { } func GetTorrentById(id string) (torrent model.Torrent, err error) { - id_int, err := strconv.Atoi(id) + // Postgres DB integer size is 32-bit + id_int, err := strconv.ParseInt(id, 10, 32) if err != nil { return } @@ -73,6 +70,13 @@ func GetTorrentById(id string) (torrent model.Torrent, err error) { // (or maybe I'm just retarded) torrent.Uploader = new(model.User) db.ORM.Where("user_id = ?", torrent.UploaderID).Find(torrent.Uploader) + torrent.OldUploader = "" + if torrent.ID <= config.LastOldTorrentID { + var tmp model.UserUploadsOld + if !db.ORM.Where("torrent_id = ?", torrent.ID).Find(&tmp).RecordNotFound() { + torrent.OldUploader = tmp.Username + } + } for i := range torrent.Comments { torrent.Comments[i].User = new(model.User) err = db.ORM.Where("user_id = ?", torrent.Comments[i].UserID).Find(torrent.Comments[i].User).Error @@ -84,21 +88,21 @@ func GetTorrentById(id string) (torrent model.Torrent, err error) { return } -func GetTorrentsOrderByNoCount(parameters *WhereParams, orderBy string, limit int, offset int) (torrents []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) return } -func GetTorrentsOrderBy(parameters *WhereParams, orderBy string, limit int, offset int) (torrents []model.Torrent, count int, err error) { +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) return } -func getTorrentsOrderBy(parameters *WhereParams, orderBy string, limit int, offset int, countAll bool) ( +func getTorrentsOrderBy(parameters *serviceBase.WhereParams, orderBy string, limit int, offset int, countAll bool) ( torrents []model.Torrent, count int, err error, ) { var conditionArray []string - conditionArray = append(conditionArray, "deleted_at IS NULL") + conditionArray = append(conditionArray, "deleted_at IS NULL") if strings.HasPrefix(orderBy, "filesize") { // torrents w/ NULL filesize fuck up the sorting on Postgres conditionArray = append(conditionArray, "filesize IS NOT NULL") @@ -112,12 +116,13 @@ func getTorrentsOrderBy(parameters *WhereParams, orderBy string, limit int, offs } 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 if err != nil { return } } - // TODO: Vulnerable to injections. Use query builder. + // TODO: Vulnerable to injections. Use query builder. (is it?) // build custom db query for performance reasons dbQuery := "SELECT * FROM torrents" @@ -143,12 +148,12 @@ func getTorrentsOrderBy(parameters *WhereParams, orderBy string, limit int, offs // database. The list will be of length 'limit' and in default order. // GetTorrents returns the first records found. Later records may be retrieved // by providing a positive 'offset' -func GetTorrents(parameters WhereParams, limit int, offset int) ([]model.Torrent, int, error) { +func GetTorrents(parameters serviceBase.WhereParams, limit int, offset int) ([]model.Torrent, int, error) { return GetTorrentsOrderBy(¶meters, "", limit, offset) } // Get Torrents with where parameters but no limit and order by default (get all the torrents corresponding in the db) -func GetTorrentsDB(parameters WhereParams) ([]model.Torrent, int, error) { +func GetTorrentsDB(parameters serviceBase.WhereParams) ([]model.Torrent, int, error) { return GetTorrentsOrderBy(¶meters, "", 0, 0) } @@ -164,17 +169,6 @@ func GetAllTorrentsDB() ([]model.Torrent, int, error) { return GetTorrentsOrderBy(nil, "", 0, 0) } -func CreateWhereParams(conditions string, params ...string) WhereParams { - whereParams := WhereParams{ - Conditions: conditions, - Params: make([]interface{}, len(params)), - } - for i := range params { - whereParams.Params[i] = params[i] - } - return whereParams -} - func DeleteTorrent(id string) (int, error) { var torrent model.Torrent if db.ORM.First(&torrent, id).RecordNotFound() { diff --git a/service/user/cookieHelper.go b/service/user/cookieHelper.go index d63cc762..95730971 100644 --- a/service/user/cookieHelper.go +++ b/service/user/cookieHelper.go @@ -116,6 +116,9 @@ func SetCookieHandler(w http.ResponseWriter, email string, pass string) (int, er // RegisterHanderFromForm sets cookie from a RegistrationForm. func RegisterHanderFromForm(w http.ResponseWriter, registrationForm formStruct.RegistrationForm) (int, error) { email := registrationForm.Email + if email == "" { + email = registrationForm.Username + } pass := registrationForm.Password log.Debugf("RegisterHandler UserEmail : %s", email) log.Debugf("RegisterHandler UserPassword : %s", pass) diff --git a/service/user/form/formValidator.go b/service/user/form/formValidator.go index a24cf84b..9f93ce7b 100644 --- a/service/user/form/formValidator.go +++ b/service/user/form/formValidator.go @@ -49,7 +49,7 @@ func IsAgreed(termsAndConditions string) bool { // TODO: Inline function // RegistrationForm is used when creating a user. type RegistrationForm struct { Username string `form:"username" needed:"true" len_min:"3" len_max:"20"` - Email string `form:"email" needed:"true"` + Email string `form:"email"` Password string `form:"password" needed:"true" len_min:"6" len_max:"25" equalInput:"ConfirmPassword"` ConfirmPassword string `form:"password_confirmation" omit:"true" needed:"true"` CaptchaID string `form:"captchaID" omit:"true" needed:"true"` @@ -64,13 +64,13 @@ type LoginForm struct { // UserForm is used when updating a user. type UserForm struct { - Username string `form:"username" needed:"true" len_min:"3" len_max:"20"` - Email string `form:"email" needed:"true"` - Language string `form:"language" default:"en-us"` - CurrentPassword string `form:"password" len_min:"6" len_max:"25" omit:"true"` - Password string `form:"password" len_min:"6" len_max:"25" equalInput:"ConfirmPassword"` - ConfirmPassword string `form:"password_confirmation" omit:"true"` - Status int `form:"language" default:"0"` + Username string `form:"username" needed:"true" len_min:"3" len_max:"20"` + Email string `form:"email" needed:"true"` + Language string `form:"language" default:"en-us"` + CurrentPassword string `form:"current_password" len_min:"6" len_max:"25" omit:"true"` + Password string `form:"password" len_min:"6" len_max:"25" equalInput:"Confirm_Password"` + Confirm_Password string `form:"password_confirmation" omit:"true"` + Status int `form:"status" default:"0"` } // PasswordForm is used when updating a user password. diff --git a/service/user/permission/permission.go b/service/user/permission/permission.go index b7dcae2b..320388c8 100644 --- a/service/user/permission/permission.go +++ b/service/user/permission/permission.go @@ -20,7 +20,7 @@ func CurrentOrAdmin(user *model.User, userID uint) bool { // CurrentUserIdentical check that userID is same as current user's ID. // TODO: Inline this func CurrentUserIdentical(user *model.User, userID uint) bool { - return user.ID != userID + return user.ID == userID } func GetRole(user *model.User) string { diff --git a/service/user/user.go b/service/user/user.go index 085bb1de..5932df53 100644 --- a/service/user/user.go +++ b/service/user/user.go @@ -173,7 +173,7 @@ func UpdateUser(w http.ResponseWriter, form *formStruct.UserForm, currentUser *m if db.ORM.First(&user, id).RecordNotFound() { return user, http.StatusNotFound, errors.New("user not found") } - + log.Infof("updateUser") if form.Password != "" { err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(form.CurrentPassword)) if err != nil && !userPermission.HasAdmin(currentUser) { @@ -273,10 +273,12 @@ func RetrieveUserForAdmin(id string) (model.User, int, error) { } // RetrieveUsersForAdmin retrieves users for an administrator. -func RetrieveUsersForAdmin(limit int, offset int) []model.User { +func RetrieveUsersForAdmin(limit int, offset int) ([]model.User, int) { var users []model.User - db.ORM.Preload("Torrents").Find(&users).Limit(limit).Offset(offset) - return users + var nbUsers int + db.ORM.Model(&users).Count(&nbUsers) + db.ORM.Preload("Torrents").Limit(limit).Offset(offset).Find(&users) + return users, nbUsers } // CreateUserAuthentication creates user authentication. diff --git a/templates/_badgemenu.html b/templates/_badgemenu.html index 7697bc70..bb6ed667 100644 --- a/templates/_badgemenu.html +++ b/templates/_badgemenu.html @@ -7,6 +7,7 @@ {{ .Username }} diff --git a/templates/_profile_edit.html b/templates/_profile_edit.html index 99a5893f..a3496f45 100644 --- a/templates/_profile_edit.html +++ b/templates/_profile_edit.html @@ -10,7 +10,7 @@
    - +
    {{ range (index $.FormErrors "email")}} diff --git a/templates/_user_list_torrents.html b/templates/_user_list_torrents.html index c82ce511..4b50ef6c 100644 --- a/templates/_user_list_torrents.html +++ b/templates/_user_list_torrents.html @@ -11,7 +11,7 @@ {{T "links"}} {{ range .Torrents }} - {{ with .ToJson }} + {{ with .ToJSON }} + {{ range .Comments}} - + + {{end}}
    {{ .Content }}Delete
    {{ .Content }}{{ .User.Username }}{{ T "delete" }}
    + {{end}} diff --git a/templates/admin/panelindex.html b/templates/admin/panelindex.html index 3fe40750..651feb23 100644 --- a/templates/admin/panelindex.html +++ b/templates/admin/panelindex.html @@ -1,7 +1,57 @@ +{{define "title"}}Moderation Overview{{end}} {{define "content"}} -nigga this just be some links -torrent list -user list -comments list +

    Last Torrents

    + +{{ range .Torrents}} -{{end}} \ No newline at end of file + + +{{end}} +
    {{ .Name }}{{ .UploaderID }}{{ T "delete" }}
    + +

    Last Torrents Report

    + +{{ range .TorrentReports}} + + + +{{end}} +
    {{ .Torrent.Name }}{{.User.Username}}{{.Description}}{{ T "delete" }}
    + +

    Last Users

    + +{{ range .Users}} + + + + + +{{end}} +
    {{ .Username }}{{ T "delete" }}
    + +

    Last Comments

    + +{{ range .Comments}} + + + +{{end}} +
    {{ .Content }}{{ .User.Username }}{{ T "delete" }}
    + +{{end}} diff --git a/templates/admin/torrent_report.html b/templates/admin/torrent_report.html new file mode 100644 index 00000000..2ff7c881 --- /dev/null +++ b/templates/admin/torrent_report.html @@ -0,0 +1,8 @@ +{{define "title"}}Torrents Report{{end}} +{{define "content"}} + + {{ range .TorrentReports}} + + {{end}} +
    {{.Torrent.Name}}{{.User.Username}}{{.Description}}Delete
    +{{end}} diff --git a/templates/admin/torrentlist.html b/templates/admin/torrentlist.html index d67290ac..62fd365d 100644 --- a/templates/admin/torrentlist.html +++ b/templates/admin/torrentlist.html @@ -1,8 +1,15 @@ +{{define "title"}}Torrents List{{end}} {{define "content"}} - +
    {{ range .Torrents}} - + + {{end}}
    {{ .Name }}Delete
    {{ .Name }}{{ .UploaderID }}{{ T "delete" }}
    + {{end}} diff --git a/templates/admin/userlist.html b/templates/admin/userlist.html index 5f473ba9..db67a2d8 100644 --- a/templates/admin/userlist.html +++ b/templates/admin/userlist.html @@ -1,8 +1,17 @@ +{{define "title"}}Users List{{end}} {{define "content"}} - +
    {{ range .Users}} - + + + + {{end}}
    {{ .Username }}Delete
    {{ .Username }}{{ T "delete" }}
    + {{end}} diff --git a/templates/admin_index.html b/templates/admin_index.html index 4ff1b519..78b6d9e8 100644 --- a/templates/admin_index.html +++ b/templates/admin_index.html @@ -27,10 +27,46 @@ + +
    {{block "content" .}}{{end}}
    - +
    + Powered by NyaaPantsu +
    diff --git a/templates/home.html b/templates/home.html index e580247b..d5caa277 100644 --- a/templates/home.html +++ b/templates/home.html @@ -34,8 +34,8 @@ {{.Name}} - {{.Date}} - {{.Filesize}} + {{.Date}} + {{.Filesize}} diff --git a/templates/torrent_report.html b/templates/torrent_report.html deleted file mode 100755 index ff93de86..00000000 --- a/templates/torrent_report.html +++ /dev/null @@ -1,8 +0,0 @@ - - - - {{ range .Torrents}} - - {{end}} -
    {{ .Torrent.Name }}{{.User}}{{.Description}}Delete
    - diff --git a/templates/user/profile.html b/templates/user/profile.html old mode 100644 new mode 100755 index 79dbf7f4..b9b31fad --- a/templates/user/profile.html +++ b/templates/user/profile.html @@ -26,13 +26,13 @@
    {{if gt $.User.ID 0 }} - {{if not (CurrentUserIdentical $.User .ID) }} - {{if not (IsFollower . $.User)}} - {{ T "follow"}} - {{else}} - {{ T "unfollow"}} - {{end}} - {{end}} + {{if not (CurrentUserIdentical $.User .ID) }} + {{if not (IsFollower . $.User)}} + {{ T "follow"}} + {{else}} + {{ T "unfollow"}} + {{end}} + {{end}} {{end}}
    @@ -41,16 +41,13 @@