diff options
| author | Bobby <[email protected]> | 2025-07-18 18:37:28 +0530 |
|---|---|---|
| committer | Bobby <[email protected]> | 2025-07-18 18:37:28 +0530 |
| commit | 7feec60d6ec8b2bb5eaa34dc59c15de38a647e12 (patch) | |
| tree | 8300eb67918eb36b07fed882071583c2cdee9f48 | |
| parent | aa0405ee98c45a9bb25dd9959d899bbd56bc1b02 (diff) | |
| download | imageboard-7feec60d6ec8b2bb5eaa34dc59c15de38a647e12.tar.xz imageboard-7feec60d6ec8b2bb5eaa34dc59c15de38a647e12.zip | |
better error handlers
| -rw-r--r-- | config/constants.go | 8 | ||||
| -rw-r--r-- | controllers/404.go | 26 | ||||
| -rw-r--r-- | controllers/account.go | 30 | ||||
| -rw-r--r-- | controllers/errors.go | 61 | ||||
| -rw-r--r-- | controllers/login.go | 31 | ||||
| -rw-r--r-- | controllers/posts.go | 64 | ||||
| -rw-r--r-- | controllers/register.go | 27 |
7 files changed, 165 insertions, 82 deletions
diff --git a/config/constants.go b/config/constants.go index 05d6f43..96c5851 100644 --- a/config/constants.go +++ b/config/constants.go @@ -6,11 +6,12 @@ const ( PT_LOGIN = "Login" PT_POST_LIST = "Posts" PT_POST_NEW = "Upload New Post" + PT_POST_SINGLE = "Post" + PT_POST_EDIT = "Edit Post" PT_PREFERENCES = "Preferences" PT_REGISTER = "Register" PT_404 = "Page Not Found" PT_VERIFY_EMAIL = "Verify Email" - PT_POST_SINGLE = "Post" // Template names TEMPLATE_HOME = "home" @@ -18,9 +19,10 @@ const ( TEMPLATE_POST_LIST = "posts/list" TEMPLATE_POST_NEW = "posts/new" TEMPLATE_POST_SINGLE = "posts/single" + TEMPLATE_POST_EDIT = "posts/edit" TEMPLATE_PREFERENCES = "preferences" TEMPLATE_REGISTER = "register" - TEMPLATE_404 = "404" + TEMPLATE_ERROR = "error" TEMPLATE_VERIFY_EMAIL = "account/verify_email" // URL constants for various routes @@ -44,6 +46,8 @@ const ( ERR_PASSWORD_MISMATCH = "Entered passwords do not match. Ensure both fields are identical." ERR_SESSION_FAILED_TO_CREATE = "Server failed to create a session. If this issue persists, contact support." ERR_SESSION_FAILED_TO_SAVE = "Server failed to save session data. If this issue persists, contact support." + ERR_REGISTER_FAILED_TO_CREATE_USER = "Failed to create user account: " + ERR_REGISTER_USER_CREATED_EMAIL_FAILED = "User account created, but failed to send verification email." ERR_VERIFY_EMAIL_MISSING_TOKEN = `Verification token is missing. Check the link you clicked or request a <a href="` + URL_RESEND_VERIFICATION + `">new verification email</a>.` ERR_VERIFY_EMAIL_INVALID_OR_EXPIRED_TOKEN = `The verification token is either invalid or has expired. Try requesting a <a href="` + URL_RESEND_VERIFICATION + `">new verification email</a>.` ERR_VERIFY_EMAIL_USER_NOT_FOUND = `User not found for the provided verification token. If you think this is an error, contact support.` diff --git a/controllers/404.go b/controllers/404.go deleted file mode 100644 index 670f230..0000000 --- a/controllers/404.go +++ /dev/null @@ -1,26 +0,0 @@ -package controllers - -import ( - "imageboard/utils/shortcuts" - "strings" - - "github.com/gofiber/fiber/v2" -) - -func NotFoundController(ctx *fiber.Ctx) error { - ctx.Locals("Title", "Page Not Found") - - path := ctx.Path() - - if strings.HasSuffix(path, ".json") { - return ctx.Status(fiber.StatusNotFound).JSON(fiber.Map{ - "error": "Not Found", - }) - } - - if len(path) > 1 && strings.Contains(path[1:], ".") && !strings.HasSuffix(path, ".html") { - return ctx.SendStatus(fiber.StatusNotFound) - } - - return shortcuts.Render(ctx, "404", nil) -} diff --git a/controllers/account.go b/controllers/account.go index 06e29d5..d59da8e 100644 --- a/controllers/account.go +++ b/controllers/account.go @@ -9,35 +9,45 @@ import ( "github.com/gofiber/fiber/v2" ) -func renderVerifyEmailError(ctx *fiber.Ctx, errorMsg string, statusCode int) error { - return shortcuts.RenderWithStatus(ctx, config.TEMPLATE_VERIFY_EMAIL, fiber.Map{ - "Error": errorMsg, - }, statusCode) -} - func VerifyEmailController(ctx *fiber.Ctx) error { ctx.Locals("Title", config.PT_VERIFY_EMAIL) if auth.IsAuthenticated(ctx) { return ctx.Redirect(auth.GetRedirectURL(ctx), fiber.StatusSeeOther) } token := ctx.Query("token") + handleVerifyEmailError := func(errorMessage string, statusCode int) error { + return TemplateErrorController(ctx, TemplateError{ + Template: config.TEMPLATE_VERIFY_EMAIL, + ErrorMessage: errorMessage, + StatusCode: statusCode, + }, nil) + } + if token == "" { - return renderVerifyEmailError(ctx, config.ERR_VERIFY_EMAIL_MISSING_TOKEN, fiber.StatusBadRequest) + return handleVerifyEmailError(config.ERR_VERIFY_EMAIL_MISSING_TOKEN, fiber.StatusBadRequest) } emailToken, err := database.VerifyToken(token, config.EmailTokenTypeVerification) if err != nil { - return renderVerifyEmailError(ctx, config.ERR_VERIFY_EMAIL_INVALID_OR_EXPIRED_TOKEN, fiber.StatusBadRequest) + return handleVerifyEmailError(config.ERR_VERIFY_EMAIL_INVALID_OR_EXPIRED_TOKEN, fiber.StatusBadRequest) } user, err := database.GetUserByID(emailToken.UserID) if err != nil { - return renderVerifyEmailError(ctx, config.ERR_VERIFY_EMAIL_USER_NOT_FOUND, fiber.StatusInternalServerError) + if err.Error() == "record not found" { + return handleVerifyEmailError(config.ERR_VERIFY_EMAIL_USER_NOT_FOUND, fiber.StatusBadRequest) + } + + return handleVerifyEmailError(config.ERR_VERIFY_EMAIL_ACTIVATION_FAILED, fiber.StatusInternalServerError) } user.Activate() if err := database.DB.Save(user).Error; err != nil { - return renderVerifyEmailError(ctx, config.ERR_VERIFY_EMAIL_ACTIVATION_FAILED, fiber.StatusInternalServerError) + if err.Error() == "record not found" { + return handleVerifyEmailError(config.ERR_VERIFY_EMAIL_USER_NOT_FOUND, fiber.StatusBadRequest) + } + + return handleVerifyEmailError(config.ERR_VERIFY_EMAIL_ACTIVATION_FAILED, fiber.StatusInternalServerError) } return shortcuts.Render(ctx, config.TEMPLATE_VERIFY_EMAIL, fiber.Map{ diff --git a/controllers/errors.go b/controllers/errors.go new file mode 100644 index 0000000..472a5a8 --- /dev/null +++ b/controllers/errors.go @@ -0,0 +1,61 @@ +package controllers + +import ( + "errors" + "imageboard/config" + "imageboard/utils/shortcuts" + "strings" + + "github.com/gofiber/fiber/v2" +) + +type TemplateError struct { + Template string + ErrorMessage string + StatusCode int +} + +func TemplateErrorController(ctx *fiber.Ctx, err TemplateError, bind fiber.Map) error { + bind["Error"] = err.ErrorMessage + return shortcuts.RenderWithStatus(ctx, err.Template, bind, err.StatusCode) +} + +func GenericErrorController(ctx *fiber.Ctx, title string, err error, statusCode int) error { + ctx.Locals("Title", title) + + if strings.HasSuffix(ctx.Path(), ".json") { + return ctx.Status(statusCode).JSON(fiber.Map{ + "error": err.Error(), + }) + } + + if len(ctx.Path()) > 1 && strings.Contains(ctx.Path()[1:], ".") && !strings.HasSuffix(ctx.Path(), ".html") { + return ctx.SendStatus(statusCode) + } + + return shortcuts.RenderWithStatus(ctx, config.TEMPLATE_ERROR, fiber.Map{ + "Title": title, + "Error": err.Error(), + }, statusCode) +} + +func NotFoundController(ctx *fiber.Ctx) error { + error := errors.New("The page you are looking for does not exist.") + return GenericErrorController(ctx, "Page Not Found", error, fiber.StatusNotFound) +} + +func InternalServerErrorController(ctx *fiber.Ctx, err error) error { + return GenericErrorController(ctx, "Internal Server Error", err, fiber.StatusInternalServerError) +} + +func BadRequestController(ctx *fiber.Ctx, err error) error { + return GenericErrorController(ctx, "Bad Request", err, fiber.StatusBadRequest) +} + +func UnauthorizedController(ctx *fiber.Ctx, err error) error { + return GenericErrorController(ctx, "Unauthorized", err, fiber.StatusUnauthorized) +} + +func ForbiddenController(ctx *fiber.Ctx, err error) error { + return GenericErrorController(ctx, "Forbidden", err, fiber.StatusForbidden) +} diff --git a/controllers/login.go b/controllers/login.go index aa02e0c..64ad047 100644 --- a/controllers/login.go +++ b/controllers/login.go @@ -15,13 +15,6 @@ type LoginForm struct { Password string `json:"password" form:"password"` } -func renderLoginError(ctx *fiber.Ctx, errorMsg string, statusCode int) error { - return shortcuts.RenderWithStatus(ctx, config.TEMPLATE_LOGIN, fiber.Map{ - "Error": errorMsg, - "Username": ctx.FormValue("username"), // Preserve username in form - }, statusCode) -} - func LoginPageController(ctx *fiber.Ctx) error { ctx.Locals("Title", config.PT_LOGIN) @@ -40,35 +33,45 @@ func LoginPostController(ctx *fiber.Ctx) error { var form LoginForm var err error + handleLoginError := func(errorMessage string, statusCode int) error { + return TemplateErrorController(ctx, TemplateError{ + Template: config.TEMPLATE_LOGIN, + ErrorMessage: errorMessage, + StatusCode: statusCode, + }, fiber.Map{ + "Username": form.Username, + }) + } + if err = ctx.BodyParser(&form); err != nil { - return renderLoginError(ctx, config.ERR_INVALID_FORM_DATA, fiber.StatusBadRequest) + return handleLoginError(config.ERR_INVALID_FORM_DATA, fiber.StatusBadRequest) } user, err := database.GetUserByUsername(form.Username) if err != nil { - return renderLoginError(ctx, config.ERR_USER_NOT_FOUND, fiber.StatusUnauthorized) + return handleLoginError(config.ERR_USER_NOT_FOUND, fiber.StatusUnauthorized) } if !user.CheckPassword(form.Password) { - return renderLoginError(ctx, config.ERR_LOGIN_INVALID_CREDENTIALS, fiber.StatusUnauthorized) + return handleLoginError(config.ERR_LOGIN_INVALID_CREDENTIALS, fiber.StatusUnauthorized) } if !user.IsActive() { - return renderLoginError(ctx, config.ERR_ACCOUNT_DISABLED, fiber.StatusForbidden) + return handleLoginError(config.ERR_ACCOUNT_DISABLED, fiber.StatusForbidden) } if !user.CanLogin() { - return renderLoginError(ctx, config.ERR_ACCOUNT_UNABLE_TO_LOGIN, fiber.StatusForbidden) + return handleLoginError(config.ERR_ACCOUNT_UNABLE_TO_LOGIN, fiber.StatusForbidden) } sess, err := session.Store.Get(ctx) if err != nil { - return renderLoginError(ctx, config.ERR_SESSION_FAILED_TO_CREATE, fiber.StatusInternalServerError) + return handleLoginError(config.ERR_SESSION_FAILED_TO_CREATE, fiber.StatusInternalServerError) } sess.Set("user_id", user.ID) sess.Set("username", user.Username) if err := sess.Save(); err != nil { - return renderLoginError(ctx, config.ERR_SESSION_FAILED_TO_SAVE, fiber.StatusInternalServerError) + return handleLoginError(config.ERR_SESSION_FAILED_TO_SAVE, fiber.StatusInternalServerError) } user.UpdateLastUserLogin(database.DB) diff --git a/controllers/posts.go b/controllers/posts.go index 41bbc86..038ca1f 100644 --- a/controllers/posts.go +++ b/controllers/posts.go @@ -1,6 +1,7 @@ package controllers import ( + "errors" "imageboard/config" "imageboard/database" "imageboard/utils/auth" @@ -10,7 +11,6 @@ import ( "imageboard/utils/shortcuts" "imageboard/utils/transformers" "io" - "log" "net/http" "strings" @@ -39,7 +39,7 @@ func PostsPageController(ctx *fiber.Ctx) error { posts, err := database.GetPosts(preferences.PostsPerPage, queryRatings, queryTagsList) if err != nil { - log.Println(err) + return InternalServerErrorController(ctx, err) } return shortcuts.Render(ctx, config.TEMPLATE_POST_LIST, fiber.Map{ @@ -272,31 +272,25 @@ func PostsUploadImageLinkProxyController(ctx *fiber.Ctx) error { return ctx.Send(buf) } -func renderSinglePostError(ctx *fiber.Ctx, errorMsg string, statusCode int) error { - return shortcuts.RenderWithStatus(ctx, config.TEMPLATE_POST_SINGLE, fiber.Map{ - "Error": errorMsg, - }, statusCode) -} - func PostsSinglePostPageController(ctx *fiber.Ctx) error { ctx.Locals("Title", config.PT_POST_SINGLE) postID := ctx.Params("id") if postID == "" { - return renderSinglePostError(ctx, "Post ID is required", fiber.StatusBadRequest) + return NotFoundController(ctx) } uintPostID, err := format.StringToUint(postID) if err != nil { - return renderSinglePostError(ctx, "Invalid Post ID", fiber.StatusBadRequest) + return NotFoundController(ctx) } post, err := database.GetPostByID(uintPostID) if err != nil { if err.Error() == "record not found" { - return renderSinglePostError(ctx, "Post not found", fiber.StatusNotFound) + return NotFoundController(ctx) } - return renderSinglePostError(ctx, "Failed to retrieve post. "+err.Error(), fiber.StatusInternalServerError) + return InternalServerErrorController(ctx, err) } currentUser := auth.GetCurrentUser(ctx) @@ -320,30 +314,64 @@ func PostsSinglePostFavouriteController(ctx *fiber.Ctx) error { postID := ctx.Params("id") if postID == "" { - return renderSinglePostError(ctx, "Post ID is required", fiber.StatusBadRequest) + return NotFoundController(ctx) } uintPostID, err := format.StringToUint(postID) if err != nil { - return renderSinglePostError(ctx, "Invalid Post ID", fiber.StatusBadRequest) + return NotFoundController(ctx) } post, err := database.GetPostByID(uintPostID) if err != nil { if err.Error() == "record not found" { - return renderSinglePostError(ctx, "Post not found", fiber.StatusNotFound) + return NotFoundController(ctx) } - return renderSinglePostError(ctx, "Failed to retrieve post. "+err.Error(), fiber.StatusInternalServerError) + return InternalServerErrorController(ctx, err) } currentUser := auth.GetCurrentUser(ctx) if currentUser == nil { - return renderSinglePostError(ctx, "User not found", fiber.StatusUnauthorized) + return UnauthorizedController(ctx, errors.New("User not found")) } if err := post.ToggleFavourite(database.DB, currentUser); err != nil { - return renderSinglePostError(ctx, "Failed to toggle favourite. "+err.Error(), fiber.StatusInternalServerError) + return InternalServerErrorController(ctx, err) } return ctx.Redirect("/posts/" + postID) } + +func PostsSinglePostEditController(ctx *fiber.Ctx) error { + if !auth.IsAuthenticated(ctx) { + return ctx.Redirect(auth.GetLoginURLWithNextField(ctx), fiber.StatusFound) + } + + postID := ctx.Params("id") + if postID == "" { + return NotFoundController(ctx) + } + + uintPostID, err := format.StringToUint(postID) + if err != nil { + return NotFoundController(ctx) + } + + post, err := database.GetPostByID(uintPostID) + if err != nil { + if err.Error() == "record not found" { + return NotFoundController(ctx) + } + return InternalServerErrorController(ctx, err) + } + + if post.Uploader.Username != auth.GetCurrentUser(ctx).Username || !auth.GetCurrentUser(ctx).CanEditPosts() { + return ForbiddenController(ctx, errors.New("You do not have permission to edit this post")) + } + + ctx.Locals("Title", config.PT_POST_EDIT+" #"+format.Int64ToString(int64(post.ID))) + return shortcuts.Render(ctx, config.TEMPLATE_POST_EDIT, fiber.Map{ + "Post": post, + "CDNURL": format.GetCDNURL(), + }) +} diff --git a/controllers/register.go b/controllers/register.go index c52d6e5..8952c91 100644 --- a/controllers/register.go +++ b/controllers/register.go @@ -20,14 +20,6 @@ type RegisterForm struct { ConfirmPassword string `json:"confirm_password" form:"confirm_password"` } -func renderRegisterError(ctx *fiber.Ctx, errorMsg string, statusCode int) error { - return shortcuts.RenderWithStatus(ctx, config.TEMPLATE_REGISTER, fiber.Map{ - "Error": errorMsg, - "Username": ctx.FormValue("username"), - "Email": ctx.FormValue("email"), - }, statusCode) -} - func RegisterPageController(ctx *fiber.Ctx) error { ctx.Locals("Title", config.PT_REGISTER) @@ -46,12 +38,23 @@ func RegisterPostController(ctx *fiber.Ctx) error { } var form RegisterForm + handleRegisterError := func(errorMessage string, statusCode int) error { + return TemplateErrorController(ctx, TemplateError{ + Template: config.TEMPLATE_REGISTER, + ErrorMessage: errorMessage, + StatusCode: statusCode, + }, fiber.Map{ + "Username": form.Username, + "Email": form.Email, + }) + } + if err := ctx.BodyParser(&form); err != nil { - return renderRegisterError(ctx, config.ERR_INVALID_FORM_DATA, fiber.StatusBadRequest) + return handleRegisterError(config.ERR_INVALID_FORM_DATA, fiber.StatusBadRequest) } if form.Password != form.ConfirmPassword { - return renderRegisterError(ctx, config.ERR_PASSWORD_MISMATCH, fiber.StatusBadRequest) + return handleRegisterError(config.ERR_PASSWORD_MISMATCH, fiber.StatusBadRequest) } user := &models.User{ @@ -70,12 +73,12 @@ func RegisterPostController(ctx *fiber.Ctx) error { statusCode = fiber.StatusInternalServerError } - return renderRegisterError(ctx, "Failed to create user: "+err.Error(), statusCode) + return handleRegisterError(config.ERR_REGISTER_FAILED_TO_CREATE_USER+err.Error(), statusCode) } if err := email.SendVerificationEmail(user); err != nil { log.Printf("Failed to send verification email: %v", err) - return renderRegisterError(ctx, "User created but failed to send verification email", fiber.StatusInternalServerError) + return handleRegisterError(config.ERR_REGISTER_USER_CREATED_EMAIL_FAILED, fiber.StatusInternalServerError) } return shortcuts.Render(ctx, config.TEMPLATE_REGISTER, fiber.Map{ |
