From 2bcd0ff8c3e03dc0e0a7efc67592fcdaaa2dd83e Mon Sep 17 00:00:00 2001 From: Yanislav Igonin Date: Thu, 27 Jan 2022 19:23:51 +0200 Subject: [PATCH] Feature- Bump limit (#9) * feat: add bump limit check on thread update * feat: add captcha is active env var --- .env.example | 4 ++- config/config.go | 6 ++++ controllers/threads_controller.go | 30 +++++++++++++++++-- repositories/posts_repository.go | 48 ++++++++++++++++++++----------- repositories/structs.go | 5 ++-- templates/post-form.html | 2 ++ 6 files changed, 72 insertions(+), 23 deletions(-) diff --git a/.env.example b/.env.example index f0f3468..62f348e 100644 --- a/.env.example +++ b/.env.example @@ -3,4 +3,6 @@ PORT=3000 SEED_DB=true IS_RATE_LIMITER_ENABLED=true THREADS_MAX_COUNT=50 -POSTGRES_URL=postgres://localhost/micrach?pool_max_conns=5 \ No newline at end of file +POSTGRES_URL=postgres://localhost/micrach?pool_max_conns=5 +THREAD_BUMP_LIMIT=500 +IS_CAPTCHA_ACTIVE=true \ No newline at end of file diff --git a/config/config.go b/config/config.go index 9d85ea5..1124423 100644 --- a/config/config.go +++ b/config/config.go @@ -13,6 +13,8 @@ type AppConfig struct { SeedDb bool IsRateLimiterEnabled bool ThreadsMaxCount int + ThreadBumpLimit int + IsCaptchaActive bool } type DbConfig struct { @@ -50,6 +52,8 @@ func getAppConfig() AppConfig { seedDb := getValueOrDefaultBoolean(os.Getenv("SEED_DB"), false) isRateLimiterEnabled := getValueOrDefaultBoolean(os.Getenv("IS_RATE_LIMITER_ENABLED"), true) threadsMaxCount := getValueOrDefaultInt(os.Getenv("THREADS_MAX_COUNT"), 50) + threadBumpLimit := getValueOrDefaultInt(os.Getenv("THREAD_BUMP_LIMIT"), 500) + isCaptchaActive := getValueOrDefaultBoolean(os.Getenv("IS_CAPTCHA_ACTIVE"), true) return AppConfig{ Env: env, @@ -57,6 +61,8 @@ func getAppConfig() AppConfig { SeedDb: seedDb, IsRateLimiterEnabled: isRateLimiterEnabled, ThreadsMaxCount: threadsMaxCount, + ThreadBumpLimit: threadBumpLimit, + IsCaptchaActive: isCaptchaActive, } } diff --git a/controllers/threads_controller.go b/controllers/threads_controller.go index e0f7b5f..a357da3 100644 --- a/controllers/threads_controller.go +++ b/controllers/threads_controller.go @@ -58,7 +58,8 @@ func GetThreads(c *gin.Context) { PagesCount: pagesCount, Page: page, FormData: Repositories.HtmlFormData{ - CaptchaID: captchaID, + CaptchaID: captchaID, + IsCaptchaActive: Config.App.IsCaptchaActive, }, } c.HTML(http.StatusOK, "index.html", htmlData) @@ -87,8 +88,9 @@ func GetThread(c *gin.Context) { htmlData := Repositories.GetThreadHtmlData{ Thread: thread, FormData: Repositories.HtmlFormData{ - FirstPostID: firstPost.ID, - CaptchaID: captchaID, + FirstPostID: firstPost.ID, + CaptchaID: captchaID, + IsCaptchaActive: Config.App.IsCaptchaActive, }, } c.HTML(http.StatusOK, "thread.html", htmlData) @@ -309,6 +311,11 @@ func UpdateThread(c *gin.Context) { } defer tx.Rollback(context.TODO()) + if err != nil { + log.Println("error:", err) + c.HTML(http.StatusInternalServerError, "500.html", nil) + return + } post := Repositories.Post{ IsParent: false, ParentID: threadID, @@ -323,6 +330,23 @@ func UpdateThread(c *gin.Context) { return } + postsCountInThread, err := Repositories.Posts.GetThreadPostsCount(threadID) + if err != nil { + log.Println("error:", err) + c.HTML(http.StatusInternalServerError, "500.html", nil) + return + } + isBumpLimit := postsCountInThread >= Config.App.ThreadBumpLimit + isThreadBumped := !isBumpLimit && !isSage && !post.IsParent + if isThreadBumped { + err = Repositories.Posts.BumpThreadInTx(tx, threadID) + if err != nil { + log.Println("error:", err) + c.HTML(http.StatusInternalServerError, "500.html", nil) + return + } + } + for _, fileInRequest := range filesInRequest { file := Repositories.File{ PostID: postID, diff --git a/repositories/posts_repository.go b/repositories/posts_repository.go index 8067740..9edebcd 100644 --- a/repositories/posts_repository.go +++ b/repositories/posts_repository.go @@ -208,23 +208,6 @@ func (r *PostsRepository) CreateInTx(tx pgx.Tx, p Post) (int, error) { return 0, err } - // updating parent post `updated_at` - if !p.IsParent && !p.IsSage { - sql = ` - UPDATE posts - SET updated_at = now() - WHERE id = $1 - ` - row := tx.QueryRow(context.TODO(), sql, p.ParentID) - var msg string - err = row.Scan(&msg) - // UPDATE always return `no rows` - // so we need to check this condition - if err != nil && err != pgx.ErrNoRows { - return 0, err - } - } - return createdPost.ID, nil } @@ -263,3 +246,34 @@ func (r *PostsRepository) ArchiveThreadsFrom(t time.Time) error { _, err := Db.Pool.Exec(context.TODO(), sql, t) return err } + +// Returns count of posts in thread by thread ID +func (r *PostsRepository) GetThreadPostsCount(id int) (int, error) { + sql := ` + SELECT COUNT(*) + FROM posts + WHERE + (id = $1 AND is_parent = true AND is_deleted != true) + OR (parent_id = $1 AND is_deleted != true) + ` + + row := Db.Pool.QueryRow(context.TODO(), sql, id) + var count int + err := row.Scan(&count) + if err != nil { + return 0, err + } + return count, nil +} + +// Updates threads updated at time by thread ID +func (r *PostsRepository) BumpThreadInTx(tx pgx.Tx, id int) error { + sql := ` + UPDATE posts + SET updated_at = now() + WHERE id = $1 + ` + + _, err := tx.Query(context.TODO(), sql, id) + return err +} diff --git a/repositories/structs.go b/repositories/structs.go index 66fee40..38cda47 100644 --- a/repositories/structs.go +++ b/repositories/structs.go @@ -34,8 +34,9 @@ type File struct { // post-form.html type HtmlFormData struct { - FirstPostID int - CaptchaID string + FirstPostID int + CaptchaID string + IsCaptchaActive bool } // thread.html diff --git a/templates/post-form.html b/templates/post-form.html index b40e232..8c7bea9 100644 --- a/templates/post-form.html +++ b/templates/post-form.html @@ -15,11 +15,13 @@ + {{ if .IsCaptchaActive }}
Captcha
+ {{ end }}
{{ if ne .FirstPostID 0 }}