micrach/controllers/threads_controller.go

387 lines
11 KiB
Go
Raw Normal View History

2021-10-03 11:48:59 +03:00
package controllers
2021-08-26 20:23:55 +03:00
import (
"errors"
2021-09-05 00:36:55 +03:00
"log"
2021-09-10 02:17:45 +03:00
"math"
2021-09-10 19:16:53 +03:00
"path/filepath"
2021-09-05 00:36:55 +03:00
"strconv"
2021-09-11 16:05:31 +03:00
"strings"
2021-08-26 20:23:55 +03:00
2021-10-03 11:48:59 +03:00
"github.com/dchest/captcha"
2026-07-16 15:25:23 +03:00
"github.com/gofiber/fiber/v3"
"github.com/jackc/pgx/v5"
"micrach/config"
"micrach/db"
"micrach/repositories"
"micrach/utils"
2021-08-26 20:23:55 +03:00
)
2026-07-16 15:25:23 +03:00
func GetThreads(c fiber.Ctx) error {
pageString := c.Query("page", "1")
2021-09-10 02:17:45 +03:00
page, err := strconv.Atoi(pageString)
2021-08-30 12:09:27 +03:00
if err != nil {
return c.Status(fiber.StatusNotFound).Render("pages/404", nil)
2021-09-10 17:09:15 +03:00
}
if page <= 0 {
return c.Status(fiber.StatusNotFound).Render("pages/404", nil)
2021-08-30 12:09:27 +03:00
}
2021-09-10 17:09:15 +03:00
2021-09-10 02:17:45 +03:00
limit := 10
offset := limit * (page - 1)
2026-07-16 15:25:23 +03:00
threads, err := repositories.Posts.Get(c, limit, offset)
2021-09-10 02:17:45 +03:00
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
2021-09-10 02:17:45 +03:00
}
2026-07-16 15:25:23 +03:00
count, err := repositories.Posts.GetThreadsCount(c)
2021-09-10 02:17:45 +03:00
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
2021-09-10 02:17:45 +03:00
}
2021-09-10 17:09:15 +03:00
pagesCount := int(math.Ceil(float64(count) / 10))
2021-09-11 01:20:56 +03:00
if page > pagesCount && count != 0 {
return c.Status(fiber.StatusNotFound).Render("pages/404", nil)
2021-09-10 17:09:15 +03:00
}
2021-10-03 11:48:59 +03:00
captchaID := captcha.New()
htmlData := repositories.GetThreadsHtmlData{
Threads: threads,
Pagination: repositories.HtmlPaginationData{
PagesCount: pagesCount,
Page: page,
},
FormData: repositories.HtmlFormData{
CaptchaID: captchaID,
IsCaptchaActive: config.App.IsCaptchaActive,
},
}
return c.Render("pages/index", htmlData)
2021-08-26 20:23:55 +03:00
}
2026-07-16 15:25:23 +03:00
func GetThread(c fiber.Ctx) error {
threadID, err := strconv.Atoi(c.Params("threadID"))
2021-09-05 00:36:55 +03:00
if err != nil {
return c.Status(fiber.StatusNotFound).Render("pages/404", nil)
2021-09-05 00:36:55 +03:00
}
2026-07-16 15:25:23 +03:00
thread, err := repositories.Posts.GetThreadByPostID(c, threadID)
2021-09-05 00:36:55 +03:00
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
2021-09-05 00:36:55 +03:00
}
2021-09-05 13:50:22 +03:00
if thread == nil {
return c.Status(fiber.StatusNotFound).Render("pages/404", nil)
2021-09-05 13:50:22 +03:00
}
firstPost := thread[0]
captchaID := captcha.New()
htmlData := repositories.GetThreadHtmlData{
Thread: thread,
FormData: repositories.HtmlFormData{
FirstPostID: firstPost.ID,
CaptchaID: captchaID,
IsCaptchaActive: config.App.IsCaptchaActive,
},
}
return c.Render("pages/thread", htmlData)
2021-08-26 20:23:55 +03:00
}
2026-07-16 15:25:23 +03:00
func CreateThread(c fiber.Ctx) error {
2021-09-05 13:50:22 +03:00
form, err := c.MultipartForm()
if err != nil {
2021-09-10 19:16:53 +03:00
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
2021-09-05 13:50:22 +03:00
}
// TODO: dat shit crashes if no fields in request
2021-09-10 19:16:53 +03:00
text := form.Value["text"][0]
title := form.Value["title"][0]
filesInRequest := form.File["files"]
validationErrorMessage := utils.ValidatePost(title, text, filesInRequest)
if validationErrorMessage != "" {
errorHtmlData := repositories.BadRequestHtmlData{
Message: validationErrorMessage,
2021-10-10 10:41:36 +03:00
}
return c.Status(fiber.StatusBadRequest).Render("pages/400", errorHtmlData)
2021-10-10 10:41:36 +03:00
}
2021-09-10 19:16:53 +03:00
if config.App.IsCaptchaActive {
2022-02-09 10:18:51 +03:00
captchaID := form.Value["captchaId"][0]
captchaString := form.Value["captcha"][0]
isCaptchaValid := captcha.VerifyString(captchaID, captchaString)
if !isCaptchaValid {
errorHtmlData := repositories.BadRequestHtmlData{
Message: repositories.InvalidCaptchaErrorMessage,
2022-02-09 10:18:51 +03:00
}
return c.Status(fiber.StatusBadRequest).Render("pages/400", errorHtmlData)
}
}
2026-07-16 15:25:23 +03:00
conn, err := db.Pool.Acquire(c)
2021-09-10 19:16:53 +03:00
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
2021-09-10 19:16:53 +03:00
}
defer conn.Release()
2026-07-16 15:25:23 +03:00
threadsCount, err := repositories.Posts.GetThreadsCount(c)
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
}
if threadsCount >= config.App.ThreadsMaxCount {
2026-07-16 15:25:23 +03:00
oldestThreadUpdatedAt, err := repositories.Posts.GetOldestThreadUpdatedAt(c)
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
}
2026-07-16 15:25:23 +03:00
err = repositories.Posts.ArchiveThreadsFrom(c, oldestThreadUpdatedAt)
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
}
}
2026-07-16 15:25:23 +03:00
tx, err := conn.Begin(c)
2021-09-10 19:16:53 +03:00
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
2021-09-10 19:16:53 +03:00
}
defer func() {
2026-07-16 15:25:23 +03:00
rollbackErr := tx.Rollback(c)
if rollbackErr != nil && !errors.Is(rollbackErr, pgx.ErrTxClosed) {
log.Println("error:", rollbackErr)
}
}()
2021-09-10 19:16:53 +03:00
post := repositories.Post{
2021-09-10 19:16:53 +03:00
IsParent: true,
Title: title,
Text: text,
IsSage: false,
}
2026-07-16 15:25:23 +03:00
threadID, err := repositories.Posts.CreateInTx(c, tx, post)
2021-09-10 19:16:53 +03:00
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
2021-09-10 19:16:53 +03:00
}
err = utils.CreateThreadFolder(threadID)
2021-09-10 19:16:53 +03:00
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
2021-09-10 19:16:53 +03:00
}
for _, fileInRequest := range filesInRequest {
file := repositories.File{
2021-09-12 14:06:52 +03:00
PostID: threadID,
2021-09-10 19:16:53 +03:00
Name: fileInRequest.Filename,
2021-09-11 16:05:31 +03:00
// image/jpeg -> jpeg
Ext: strings.Split(fileInRequest.Header["Content-Type"][0], "/")[1],
Size: int(fileInRequest.Size),
2021-09-10 19:16:53 +03:00
}
2026-07-16 15:25:23 +03:00
fileID, err := repositories.Files.CreateInTx(c, tx, file)
2021-09-10 19:16:53 +03:00
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
2021-09-10 19:16:53 +03:00
}
2021-09-11 16:05:31 +03:00
path := filepath.Join(
utils.UPLOADS_DIR_PATH,
2021-09-12 14:06:52 +03:00
strconv.Itoa(threadID),
"o",
2021-09-11 16:05:31 +03:00
strconv.Itoa(fileID)+"."+file.Ext,
)
err = c.SaveFile(fileInRequest, path)
2021-09-10 19:16:53 +03:00
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
2021-09-10 19:16:53 +03:00
}
2021-09-12 14:06:52 +03:00
// creating thumbnail
thumbImg, err := utils.MakeImageThumbnail(path, file.Ext, threadID, fileID)
2021-09-12 14:06:52 +03:00
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
2021-09-12 14:06:52 +03:00
}
// saving thumbnail
err = utils.SaveImageThumbnail(thumbImg, threadID, fileID, file.Ext)
2021-09-12 14:06:52 +03:00
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
2021-09-12 14:06:52 +03:00
}
2021-09-10 19:16:53 +03:00
}
2026-07-16 15:25:23 +03:00
if err := tx.Commit(c); err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
}
2021-09-10 19:16:53 +03:00
path := "/" + strconv.Itoa(threadID)
2026-07-16 15:25:23 +03:00
return c.Redirect().Status(fiber.StatusFound).To(path)
2021-08-26 20:23:55 +03:00
}
// Add new post in thread
2026-07-16 15:25:23 +03:00
func UpdateThread(c fiber.Ctx) error {
threadID, err := strconv.Atoi(c.Params("threadID"))
2021-09-11 14:59:16 +03:00
if err != nil {
return c.Status(fiber.StatusNotFound).Render("pages/404", nil)
2021-09-11 14:59:16 +03:00
}
2026-07-16 15:25:23 +03:00
isArchived, err := repositories.Posts.GetIfThreadIsArchived(c, threadID)
if isArchived {
errorHtmlData := repositories.BadRequestHtmlData{
Message: repositories.ThreadIsArchivedErrorMessage,
}
return c.Status(fiber.StatusBadRequest).Render("pages/400", errorHtmlData)
}
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
}
2021-09-11 14:59:16 +03:00
form, err := c.MultipartForm()
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
2021-09-11 14:59:16 +03:00
}
// TODO: dat shit crashes if no fields in request
text := form.Value["text"][0]
filesInRequest := form.File["files"]
validationErrorMessage := utils.ValidatePost("", text, filesInRequest)
if validationErrorMessage != "" {
errorHtmlData := repositories.BadRequestHtmlData{
Message: validationErrorMessage,
2021-10-10 10:41:36 +03:00
}
return c.Status(fiber.StatusBadRequest).Render("pages/400", errorHtmlData)
2021-10-10 10:41:36 +03:00
}
if config.App.IsCaptchaActive {
2022-02-09 10:18:51 +03:00
captchaID := form.Value["captchaId"][0]
captchaString := form.Value["captcha"][0]
isCaptchaValid := captcha.VerifyString(captchaID, captchaString)
if !isCaptchaValid {
errorHtmlData := repositories.BadRequestHtmlData{
Message: repositories.InvalidCaptchaErrorMessage,
2022-02-09 10:18:51 +03:00
}
return c.Status(fiber.StatusBadRequest).Render("pages/400", errorHtmlData)
}
}
2021-09-11 14:59:16 +03:00
isSageField := form.Value["sage"]
var isSageString string
if len(isSageField) != 0 {
isSageString = isSageField[0]
}
2021-10-07 10:51:18 +03:00
isSage := isSageString == "on"
2021-09-11 14:59:16 +03:00
2026-07-16 15:25:23 +03:00
conn, err := db.Pool.Acquire(c)
2021-09-11 14:59:16 +03:00
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
2021-09-11 14:59:16 +03:00
}
defer conn.Release()
2026-07-16 15:25:23 +03:00
tx, err := conn.Begin(c)
2021-09-11 14:59:16 +03:00
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
2021-09-11 14:59:16 +03:00
}
defer func() {
2026-07-16 15:25:23 +03:00
rollbackErr := tx.Rollback(c)
if rollbackErr != nil && !errors.Is(rollbackErr, pgx.ErrTxClosed) {
log.Println("error:", rollbackErr)
}
}()
2021-09-11 14:59:16 +03:00
post := repositories.Post{
2021-09-11 14:59:16 +03:00
IsParent: false,
2022-02-03 18:22:46 +03:00
ParentID: &threadID,
2021-09-12 14:06:52 +03:00
Title: "",
2021-09-11 14:59:16 +03:00
Text: text,
IsSage: isSage,
}
2026-07-16 15:25:23 +03:00
postID, err := repositories.Posts.CreateInTx(c, tx, post)
2021-09-11 14:59:16 +03:00
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
2021-09-11 14:59:16 +03:00
}
2026-07-16 15:25:23 +03:00
postsCountInThread, err := repositories.Posts.GetThreadPostsCount(c, threadID)
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
}
isBumpLimit := postsCountInThread >= config.App.ThreadBumpLimit
isThreadBumped := !isBumpLimit && !isSage && !post.IsParent
if isThreadBumped {
2026-07-16 15:25:23 +03:00
err = repositories.Posts.BumpThreadInTx(c, tx, threadID)
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
}
}
2021-09-11 14:59:16 +03:00
for _, fileInRequest := range filesInRequest {
file := repositories.File{
2021-09-11 14:59:16 +03:00
PostID: postID,
Name: fileInRequest.Filename,
2021-09-11 16:05:31 +03:00
// image/jpeg -> jpeg
Ext: strings.Split(fileInRequest.Header["Content-Type"][0], "/")[1],
Size: int(fileInRequest.Size),
2021-09-11 14:59:16 +03:00
}
2026-07-16 15:25:23 +03:00
fileID, err := repositories.Files.CreateInTx(c, tx, file)
2021-09-11 14:59:16 +03:00
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
2021-09-11 14:59:16 +03:00
}
2021-09-11 16:05:31 +03:00
path := filepath.Join(
utils.UPLOADS_DIR_PATH,
2021-09-11 16:05:31 +03:00
strconv.Itoa(threadID),
"o",
2021-09-11 16:05:31 +03:00
strconv.Itoa(fileID)+"."+file.Ext,
)
err = c.SaveFile(fileInRequest, path)
2021-09-11 14:59:16 +03:00
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
2021-09-11 14:59:16 +03:00
}
2021-09-12 14:06:52 +03:00
// creating thumbnail
thumbImg, err := utils.MakeImageThumbnail(path, file.Ext, threadID, fileID)
2021-09-12 14:06:52 +03:00
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
2021-09-12 14:06:52 +03:00
}
// saving thumbnail
err = utils.SaveImageThumbnail(thumbImg, threadID, fileID, file.Ext)
2021-09-12 14:06:52 +03:00
if err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
2021-09-12 14:06:52 +03:00
}
2021-09-11 14:59:16 +03:00
}
2026-07-16 15:25:23 +03:00
if err := tx.Commit(c); err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
}
2021-09-11 14:59:16 +03:00
path := "/" + strconv.Itoa(threadID) + "#" + strconv.Itoa(postID)
2026-07-16 15:25:23 +03:00
return c.Redirect().Status(fiber.StatusFound).To(path)
2021-08-26 20:23:55 +03:00
}