diff --git a/.gitignore b/.gitignore index 0ce14950..719edda7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ nyaa.exe nyaa-master.exe *.zip *.swp +.idea .vscode templates/*.html.go *.bat diff --git a/config/config.go b/config/config.go index b5a19cf2..931a8e74 100644 --- a/config/config.go +++ b/config/config.go @@ -102,4 +102,4 @@ func (config *Config) Pretty(output io.Writer) error { data = append(data, []byte("\n")...) _, err = output.Write(data) return err -} \ No newline at end of file +} diff --git a/db/gorm.go b/db/gorm.go index cb38b775..bbef9270 100644 --- a/db/gorm.go +++ b/db/gorm.go @@ -31,28 +31,12 @@ func GormInit(conf *config.Config) (*gorm.DB, error) { if config.Environment == "DEVELOPMENT" { db.LogMode(true) // db.DropTable(&model.User{}, "UserFollower") - db.AutoMigrate(&model.Torrents{}) - // db.AutoMigrate(&model.User{}, &model.Role{}, &model.Connection{}, &model.Language{}, &model.Article{}, &model.Location{}, &model.Comment{}, &model.File{}) + db.AutoMigrate(&model.Torrents{}, &model.User{}, &model.Role{}, &model.Connection{}, &model.Language{}) + // db.AutoMigrate(&model.Article{}, &model.Location{}, &model.Comment{}, &model.File{}) // db.Model(&model.User{}).AddIndex("idx_user_token", "token") } log.CheckError(err) - // relation := gorm.Relationship{} - // relation.Kind = "many2many" - // relation.ForeignFieldNames = []string{"id"} //(M1 pkey) - // relation.ForeignDBNames = []string{"user_id"} //(M1 fkey in m1m2join) - // relation.AssociationForeignFieldNames = []string{"id"} //(M2 pkey) - // // relation.AssociationForeignStructFieldNames = []string{"id", "ID"} //(m2 pkey name in m2 struct?) - // relation.AssociationForeignDBNames = []string{"follower_id"} //(m2 fkey in m1m2join) - // m1Type := reflect.TypeOf(model.User{}) - // m2Type := reflect.TypeOf(model.User{}) - // handler := gorm.JoinTableHandler{} - // // ORDER BELOW MATTERS - // // Install handler - // db.SetJoinTableHandler(&model.User{}, "Likings", &handler) - // // Configure handler to use the relation that we've defined - // handler.Setup(&relation, "users_followers", m1Type, m2Type) - return db, err } diff --git a/main.go b/main.go index 756b0a9f..c4e719d4 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "github.com/ewhal/nyaa/db" "github.com/ewhal/nyaa/router" "github.com/ewhal/nyaa/util/log" + "github.com/ewhal/nyaa/util/search" // super hacky fix "github.com/ewhal/nyaa/util/signals" "net/http" @@ -59,6 +60,7 @@ func main() { if len(config.TorrentFileStorage) > 0 { os.MkdirAll(config.TorrentFileStorage, 0755) } + search.Init(conf.DBType) // super hacky fix RunServer(conf) } } diff --git a/public/css/style-night.css b/public/css/style-night.css index a303976e..ec5f2263 100644 --- a/public/css/style-night.css +++ b/public/css/style-night.css @@ -1,10 +1,13 @@ -/* Night mode image */ -#sunmoon { - position: fixed; - top: 10px; - height: 30px; - +/* Night mode switcher */ +.nightswitch { + position: fixed; + top: 12px; + right: 48px; } +.nightswitch > a > img { + width: 24px; +} + /* Torrent status colors */ .remake { background-color: #795c46; @@ -55,12 +58,8 @@ body { text-align: center; } -.pagination { - background-color: #264040; -} - .pagination > .active > a { - background-color: #ececec; + background: #ececec; border-color: #ececec; color: #337ab7; /* restore usual text color */ } @@ -115,6 +114,12 @@ a { padding: 4px; } +.captcha-container { + display: grid; + grid-template-rows: auto; + grid-template-columns: 240px; +} + tr.torrent-info td.date { white-space: nowrap; } @@ -154,11 +159,25 @@ div.container div.blockBody:nth-of-type(2) table tr:first-of-type th:last-of-typ .table > tbody > tr > td { border: none; } - #container.cont-home { - background: #29363d url(/img/megumin.png) no-repeat; - } - #container.cont-view { - background: #29363d url(/img/megumin.png) no-repeat; - background-size: 75px, 100px; - } } + +/* Credit to bootsnipp.com for the css for the color graph */ +.colorgraph { + height: 5px; + border-top: 0; + background: #c4e17f; + border-radius: 5px; + background-image: -webkit-linear-gradient(left, #c4e17f, #c4e17f 12.5%, #f7fdca 12.5%, #f7fdca 25%, #fecf71 25%, #fecf71 37.5%, #f0776c 37.5%, #f0776c 50%, #db9dbe 50%, #db9dbe 62.5%, #c49cde 62.5%, #c49cde 75%, #669ae1 75%, #669ae1 87.5%, #62c2e4 87.5%, #62c2e4); + background-image: -moz-linear-gradient(left, #c4e17f, #c4e17f 12.5%, #f7fdca 12.5%, #f7fdca 25%, #fecf71 25%, #fecf71 37.5%, #f0776c 37.5%, #f0776c 50%, #db9dbe 50%, #db9dbe 62.5%, #c49cde 62.5%, #c49cde 75%, #669ae1 75%, #669ae1 87.5%, #62c2e4 87.5%, #62c2e4); + background-image: -o-linear-gradient(left, #c4e17f, #c4e17f 12.5%, #f7fdca 12.5%, #f7fdca 25%, #fecf71 25%, #fecf71 37.5%, #f0776c 37.5%, #f0776c 50%, #db9dbe 50%, #db9dbe 62.5%, #c49cde 62.5%, #c49cde 75%, #669ae1 75%, #669ae1 87.5%, #62c2e4 87.5%, #62c2e4); + background-image: linear-gradient(to right, #c4e17f, #c4e17f 12.5%, #f7fdca 12.5%, #f7fdca 25%, #fecf71 25%, #fecf71 37.5%, #f0776c 37.5%, #f0776c 50%, #db9dbe 50%, #db9dbe 62.5%, #c49cde 62.5%, #c49cde 75%, #669ae1 75%, #669ae1 87.5%, #62c2e4 87.5%, #62c2e4); +} + +.center-image { + max-width: 100%; + max-height: 80vh; +} + +.navbar { + border-radius: 0px; +} \ No newline at end of file diff --git a/public/css/style.css b/public/css/style.css index 338b38ae..7877c119 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -1,9 +1,13 @@ -/* Night mode image */ -#sunmoon { - position: fixed; - top: 10px; - height: 30px; +/* Night mode switcher */ +.nightswitch { + position: fixed; + top: 12px; + right: 48px; } +.nightswitch > a > img { + width: 24px; +} + /* Torrent status colors */ .remake { background-color: rgb(240, 176, 128); @@ -173,3 +177,8 @@ div.container div.blockBody:nth-of-type(2) table tr:first-of-type th:last-of-typ max-width: 100%; max-height: 80vh; } + +/* the curved edges triggered my autism */ +.navbar { + border-radius: 0px; +} diff --git a/public/js/main.js b/public/js/main.js index f34c0abe..657a4909 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -1,25 +1,20 @@ // Night mode -// also sorry that this code is soo bad, literally nothing else worked.. ima remake it in a near future var night = localStorage.getItem("night"); -if (night=="true") { - document.getElementById("style").href = "/css/style-night.css"; - document.getElementById("nightbutton").innerHTML = ""; +if (night == "true") { + $("#style")[0].href = "/css/style-night.css"; + $("#nighticon")[0].src = "/img/sun.png"; } function toggleNightMode() { - var styleshieeet = document.getElementById("style").href; - var styleshieet = new RegExp("style.css"); - var stylesheet = styleshieet.test(styleshieeet); - if (stylesheet==true) { - document.getElementById("style").href = "/css/style-night.css"; - document.getElementById("nightbutton").innerHTML = ""; - localStorage.setItem("night", "true"); - } - else { - document.getElementById("style").href = "/css/style.css"; - document.getElementById("nightbutton").innerHTML = ""; - localStorage.setItem("night", "false"); + var night = localStorage.getItem("night"); + if(night == "true") { + $("#style")[0].href = "/css/style.css"; + $("#nighticon")[0].src = "/img/moon.png"; + } else { + $("#style")[0].href = "/css/style-night.css"; + $("#nighticon")[0].src = "/img/sun.png"; } + localStorage.setItem("night", (night == "true") ? "false" : "true"); } // Used by spoiler tags @@ -30,6 +25,7 @@ function toggleLayer(elem) { elem.classList.add("hide"); } +// Date formatting function formatDate(date) { // thanks stackoverflow var monthNames = [ "January", "February", "March", diff --git a/router/router.go b/router/router.go index 8799123d..9187fc65 100644 --- a/router/router.go +++ b/router/router.go @@ -32,6 +32,7 @@ func init() { Router.HandleFunc("/upload", UploadHandler).Name("upload") Router.HandleFunc("/user/register", UserRegisterFormHandler).Name("user_register").Methods("GET") Router.HandleFunc("/user/login", UserLoginFormHandler).Name("user_login").Methods("GET") + Router.HandleFunc("/verify/email/{token}", UserVerifyEmailHandler).Name("user_verify").Methods("GET") Router.HandleFunc("/user/register", UserRegisterPostHandler).Name("user_register").Methods("POST") Router.PathPrefix("/captcha").Methods("GET").HandlerFunc(captcha.ServeFiles) diff --git a/router/template.go b/router/template.go index 4cd8db08..678a4ab2 100644 --- a/router/template.go +++ b/router/template.go @@ -7,7 +7,7 @@ import ( var TemplateDir = "templates" -var homeTemplate, searchTemplate, faqTemplate, uploadTemplate, viewTemplate, viewRegisterTemplate, viewLoginTemplate, viewRegisterSuccessTemplate *template.Template +var homeTemplate, searchTemplate, faqTemplate, uploadTemplate, viewTemplate, viewRegisterTemplate, viewLoginTemplate, viewRegisterSuccessTemplate, viewVerifySuccessTemplate *template.Template type templateLoader struct { templ **template.Template @@ -53,6 +53,11 @@ func ReloadTemplates() { name: "user_register_success", file: "user/signup_success.html", }, + templateLoader{ + templ: &viewVerifySuccessTemplate, + name: "user_verify_success", + file: "user/verify_success.html", + }, templateLoader{ templ: &viewLoginTemplate, name: "user_login", diff --git a/router/templateVariables.go b/router/templateVariables.go index 44be10e5..25306280 100644 --- a/router/templateVariables.go +++ b/router/templateVariables.go @@ -38,6 +38,15 @@ type ViewTemplateVariables struct { type UserRegisterTemplateVariables struct { RegistrationForm userForms.RegistrationForm + FormErrors map[string][]string + Search SearchForm + Navigation Navigation + URL *url.URL // For parsing Url in templates + Route *mux.Route // For getting current route in templates +} + +type UserVerifyTemplateVariables struct { + FormErrors map[string][]string Search SearchForm Navigation Navigation URL *url.URL // For parsing Url in templates diff --git a/router/userHandler.go b/router/userHandler.go index 9559f843..5661e9ca 100644 --- a/router/userHandler.go +++ b/router/userHandler.go @@ -16,14 +16,19 @@ import ( // Getting View User Registration func UserRegisterFormHandler(w http.ResponseWriter, r *http.Request) { - b := form.RegistrationForm{} - modelHelper.BindValueForm(&b, r) - b.CaptchaID = captcha.GetID() - languages.SetTranslation("en-us", viewRegisterTemplate) - htv := UserRegisterTemplateVariables{b, NewSearchForm(), Navigation{}, r.URL, mux.CurrentRoute(r)} - err := viewRegisterTemplate.ExecuteTemplate(w, "index.html", htv) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + _, errorUser := userService.CurrentUser(r) + if (errorUser != nil) { + b := form.RegistrationForm{} + modelHelper.BindValueForm(&b, r) + b.CaptchaID = captcha.GetID() + languages.SetTranslation("en-us", viewRegisterTemplate) + htv := UserRegisterTemplateVariables{b, form.NewErrors(), NewSearchForm(), Navigation{}, r.URL, mux.CurrentRoute(r)} + err := viewRegisterTemplate.ExecuteTemplate(w, "index.html", htv) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } else { + HomeHandler(w, r) } } @@ -52,31 +57,58 @@ 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)) { - // TODO: Prettier passing of mistyoed captcha errors - http.Error(w, captcha.ErrInvalidCaptcha.Error(), 403) - return + err["errors"] = append(err["errors"], "Wrong captcha!") } - if (r.PostFormValue("password") == r.PostFormValue("password_confirm")) && (r.PostFormValue("password") != "") { - if (form.EmailValidation(r.PostFormValue("email"))) && - (form.ValidateUsername(r.PostFormValue("username"))) && - (form.IsAgreed(r.PostFormValue("t_and_c"))) { - _, err := userService.CreateUser(w, r) - if err == nil { - b := form.RegistrationForm{} - htv := UserRegisterTemplateVariables{b, NewSearchForm(), Navigation{}, r.URL, mux.CurrentRoute(r)} - err = viewRegisterSuccessTemplate.ExecuteTemplate(w, "index.html", htv) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + if (len(err) == 0) { + _, err = form.EmailValidation(r.PostFormValue("email"), err) + _, err = form.ValidateUsername(r.PostFormValue("username"), err) + if (len(err) == 0) { + modelHelper.BindValueForm(&b, r) + err = modelHelper.ValidateForm(&b, err) + if (len(err) == 0) { + _, errorUser := userService.CreateUser(w, r) + if (errorUser != nil) { + err["errors"] = append(err["errors"], errorUser.Error()) } - } else { - UserRegisterFormHandler(w, r) - } - } else { - UserRegisterFormHandler(w, r) + if (len(err) == 0) { + b := form.RegistrationForm{} + languages.SetTranslation("en-us", viewRegisterSuccessTemplate) + htv := UserRegisterTemplateVariables{b, err, NewSearchForm(), Navigation{}, r.URL, mux.CurrentRoute(r)} + errorTmpl := viewRegisterSuccessTemplate.ExecuteTemplate(w, "index.html", htv) + if errorTmpl != nil { + http.Error(w, errorTmpl.Error(), http.StatusInternalServerError) + } + } + } + } + } + if (len(err) > 0) { + b.CaptchaID = captcha.GetID() + languages.SetTranslation("en-us", viewRegisterTemplate) + htv := UserRegisterTemplateVariables{b, err, NewSearchForm(), Navigation{}, r.URL, mux.CurrentRoute(r)} + errorTmpl := viewRegisterTemplate.ExecuteTemplate(w, "index.html", htv) + if errorTmpl != nil { + http.Error(w, errorTmpl.Error(), http.StatusInternalServerError) } - } else { - UserRegisterFormHandler(w, r) + } +} + +func UserVerifyEmailHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + token := vars["token"] + err := form.NewErrors() + _, errEmail := userService.EmailVerification(token, w) + if (errEmail != nil) { + err["errors"] = append(err["errors"], errEmail.Error()) + } + languages.SetTranslation("en-us", viewVerifySuccessTemplate) + htv := UserVerifyTemplateVariables{err, NewSearchForm(), Navigation{}, r.URL, mux.CurrentRoute(r)} + errorTmpl := viewVerifySuccessTemplate.ExecuteTemplate(w, "index.html", htv) + if errorTmpl != nil { + http.Error(w, errorTmpl.Error(), http.StatusInternalServerError) } } diff --git a/service/torrent/torrent.go b/service/torrent/torrent.go index b461a66b..096b9861 100644 --- a/service/torrent/torrent.go +++ b/service/torrent/torrent.go @@ -65,6 +65,7 @@ func getTorrentsOrderBy(parameters *WhereParams, orderBy string, limit int, offs var torrents []model.Torrents var dbQuery *gorm.DB var count int + conditions := "torrent_hash IS NOT NULL AND filesize > 0" //filter out broken entries var params []interface{} if parameters != nil { // if there is where parameters @@ -76,9 +77,9 @@ func getTorrentsOrderBy(parameters *WhereParams, orderBy string, limit int, offs } dbQuery = db.ORM.Model(&torrents).Where(conditions, params...) - if orderBy == "" { + if orderBy == "" { // default OrderBy orderBy = "torrent_id DESC" - } // Default OrderBy + } if limit != 0 || offset != 0 { // if limits provided dbQuery = dbQuery.Limit(limit).Offset(offset) } diff --git a/service/user/cookieHelper.go b/service/user/cookieHelper.go index 9ec85646..0974fc83 100644 --- a/service/user/cookieHelper.go +++ b/service/user/cookieHelper.go @@ -89,7 +89,7 @@ func SetCookieHandler(w http.ResponseWriter, email string, pass string) (int, er if email != "" && pass != "" { log.Debugf("User email : %s , password : %s", email, pass) var user model.User - isValidEmail := formStruct.EmailValidation(email) + isValidEmail, _ := formStruct.EmailValidation(email, formStruct.NewErrors()) if isValidEmail { log.Debug("User entered valid email.") if db.ORM.Where("email = ?", email).First(&user).RecordNotFound() { diff --git a/service/user/form/formValidator.go b/service/user/form/formValidator.go index 9cc5dfb1..7a0a5783 100644 --- a/service/user/form/formValidator.go +++ b/service/user/form/formValidator.go @@ -9,34 +9,32 @@ import ( const EMAIL_REGEX = `(\w[-._\w]*\w@\w[-._\w]*\w\.\w{2,3})` const USERNAME_REGEX = `(\W)` -func EmailValidation(email string) bool { - exp, err := regexp.Compile(EMAIL_REGEX) - if regexpCompiled := log.CheckError(err); regexpCompiled { +func EmailValidation(email string, err map[string][]string) (bool, map[string][]string) { + exp, errorRegex := regexp.Compile(EMAIL_REGEX) + if regexpCompiled := log.CheckError(errorRegex); regexpCompiled { if exp.MatchString(email) { - return true + return true, err } - } - return false + } + err["email"] = append(err["email"], "Email Address is not valid") + return false, err } -func ValidateUsername(username string) bool { - exp, err := regexp.Compile(USERNAME_REGEX) - - if username == "" { - return false - - } - if (len(username) < 3) || (len(username) > 15) { - return false - - } - if regexpCompiled := log.CheckError(err); regexpCompiled { +func ValidateUsername(username string, err map[string][]string) (bool, map[string][]string) { + exp, errorRegex := regexp.Compile(USERNAME_REGEX) + if regexpCompiled := log.CheckError(errorRegex); regexpCompiled { if exp.MatchString(username) { - return false + err["username"] = append(err["username"], "Username contains illegal characters") + return false, err } } else { - return false + return false, err } - return true + return true, err +} + +func NewErrors() map[string][]string { + err := make(map[string][]string) + return err } func IsAgreed(t_and_c string) bool { if t_and_c == "1" { @@ -47,10 +45,12 @@ func IsAgreed(t_and_c string) bool { // RegistrationForm is used when creating a user. type RegistrationForm struct { - Username string `form:"registrationUsername"` - Email string `form:"registrationEmail"` - Password string `form:"registrationPassword"` - CaptchaID string `form:"captchaID" inmodel:"false"` + Username string `form:"username" needed:"true" len_min:"3" len_max:"20"` + Email string `form:"email" needed:"true"` + Password string `form:"password" needed:"true" len_min:"6" len_max:"25" equalInput:"Confirm_Password"` + Confirm_Password string `form:"password_confirmation" omit:"true" needed:"true"` + CaptchaID string `form:"captchaID" omit:"true" needed:"true"` + T_and_C bool `form:"t_and_c" omit:"true" needed:"true" equal:"true" hum_name:"Terms and Conditions"` } // RegistrationForm is used when creating a user authentication. diff --git a/service/user/locale/en-us.all.json b/service/user/locale/en-us.all.json index 933c25a0..c554ec18 100644 --- a/service/user/locale/en-us.all.json +++ b/service/user/locale/en-us.all.json @@ -91,6 +91,10 @@ "id":"sign_up_success", "translation": "Almost Signed Up!" }, + { + "id":"verify_success", + "translation": "Your account is now activated!" + }, { "id":"signup_verification_email", "translation": "Now, you are one step left before being part of our community! Check your emails (inbox or spam folder) and click the link provided for activating your account!" diff --git a/service/user/user.go b/service/user/user.go index 91638a8a..65ab1929 100644 --- a/service/user/user.go +++ b/service/user/user.go @@ -15,6 +15,7 @@ import ( "github.com/ewhal/nyaa/util/log" "github.com/ewhal/nyaa/util/modelHelper" "github.com/ewhal/nyaa/util/timeHelper" + "fmt" ) var userFields []string = []string{"name", "email", "createdAt", "updatedAt"} @@ -42,7 +43,14 @@ func SuggestUsername(username string) string { } return usernameCandidate } - +func CheckEmail(email string) bool { + var count int + db.ORM.Model(model.User{}).Where(&model.User{Email: email}).Count(&count) + if count == 0 { + return false + } + return true +} // CreateUserFromForm creates a user from a registration form. func CreateUserFromForm(registrationForm formStruct.RegistrationForm) (model.User, error) { var user model.User @@ -70,7 +78,13 @@ func CreateUser(w http.ResponseWriter, r *http.Request) (int, error) { var err error modelHelper.BindValueForm(®istrationForm, r) - + usernameCandidate := SuggestUsername(registrationForm.Username) + if (usernameCandidate != registrationForm.Username) { + return http.StatusInternalServerError, fmt.Errorf("Username already taken, you can choose: %s", usernameCandidate) + } + if CheckEmail(registrationForm.Email) { + return http.StatusInternalServerError, errors.New("Email Address already in our database!") + } password, err := bcrypt.GenerateFromPassword([]byte(registrationForm.Password), 10) if err != nil { return http.StatusInternalServerError, err diff --git a/service/user/verification.go b/service/user/verification.go index 895c2ae2..37672cb5 100644 --- a/service/user/verification.go +++ b/service/user/verification.go @@ -8,12 +8,10 @@ import ( "github.com/ewhal/nyaa/config" "github.com/ewhal/nyaa/db" "github.com/ewhal/nyaa/model" - "github.com/ewhal/nyaa/util/modelHelper" "github.com/ewhal/nyaa/util/crypto" "github.com/ewhal/nyaa/util/email" "github.com/ewhal/nyaa/util/log" "github.com/ewhal/nyaa/util/timeHelper" - formStruct "github.com/ewhal/nyaa/service/user/form" "github.com/nicksnyder/go-i18n/i18n" ) @@ -66,12 +64,10 @@ func SendVerification(r *http.Request) (int, error) { } // EmailVerification verifies an email of user. -func EmailVerification(w http.ResponseWriter, r *http.Request) (int, error) { +func EmailVerification(token string,w http.ResponseWriter) (int, error) { var user model.User - var verifyEmailForm formStruct.VerifyEmailForm - modelHelper.BindValueForm(&verifyEmailForm, r) - log.Debugf("verifyEmailForm.ActivationToken : %s", verifyEmailForm.ActivationToken) - if db.ORM.Where(&model.User{ActivationToken: verifyEmailForm.ActivationToken}).First(&user).RecordNotFound() { + log.Debugf("verifyEmailForm.ActivationToken : %s", token) + if db.ORM.Where(&model.User{ActivationToken: token}).First(&user).RecordNotFound() { return http.StatusNotFound, errors.New("User is not found.") } isExpired := timeHelper.IsExpired(user.ActivateUntil) diff --git a/templates/index.html b/templates/index.html index bf00535a..112d8143 100755 --- a/templates/index.html +++ b/templates/index.html @@ -47,7 +47,6 @@