mirror of
https://github.com/yanislav-igonin/micrach
synced 2024-12-22 14:22:33 +03:00
Feature- Bump limit (#9)
* feat: add bump limit check on thread update * feat: add captcha is active env var
This commit is contained in:
parent
868a7214d2
commit
2bcd0ff8c3
@ -3,4 +3,6 @@ PORT=3000
|
|||||||
SEED_DB=true
|
SEED_DB=true
|
||||||
IS_RATE_LIMITER_ENABLED=true
|
IS_RATE_LIMITER_ENABLED=true
|
||||||
THREADS_MAX_COUNT=50
|
THREADS_MAX_COUNT=50
|
||||||
POSTGRES_URL=postgres://localhost/micrach?pool_max_conns=5
|
POSTGRES_URL=postgres://localhost/micrach?pool_max_conns=5
|
||||||
|
THREAD_BUMP_LIMIT=500
|
||||||
|
IS_CAPTCHA_ACTIVE=true
|
@ -13,6 +13,8 @@ type AppConfig struct {
|
|||||||
SeedDb bool
|
SeedDb bool
|
||||||
IsRateLimiterEnabled bool
|
IsRateLimiterEnabled bool
|
||||||
ThreadsMaxCount int
|
ThreadsMaxCount int
|
||||||
|
ThreadBumpLimit int
|
||||||
|
IsCaptchaActive bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type DbConfig struct {
|
type DbConfig struct {
|
||||||
@ -50,6 +52,8 @@ func getAppConfig() AppConfig {
|
|||||||
seedDb := getValueOrDefaultBoolean(os.Getenv("SEED_DB"), false)
|
seedDb := getValueOrDefaultBoolean(os.Getenv("SEED_DB"), false)
|
||||||
isRateLimiterEnabled := getValueOrDefaultBoolean(os.Getenv("IS_RATE_LIMITER_ENABLED"), true)
|
isRateLimiterEnabled := getValueOrDefaultBoolean(os.Getenv("IS_RATE_LIMITER_ENABLED"), true)
|
||||||
threadsMaxCount := getValueOrDefaultInt(os.Getenv("THREADS_MAX_COUNT"), 50)
|
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{
|
return AppConfig{
|
||||||
Env: env,
|
Env: env,
|
||||||
@ -57,6 +61,8 @@ func getAppConfig() AppConfig {
|
|||||||
SeedDb: seedDb,
|
SeedDb: seedDb,
|
||||||
IsRateLimiterEnabled: isRateLimiterEnabled,
|
IsRateLimiterEnabled: isRateLimiterEnabled,
|
||||||
ThreadsMaxCount: threadsMaxCount,
|
ThreadsMaxCount: threadsMaxCount,
|
||||||
|
ThreadBumpLimit: threadBumpLimit,
|
||||||
|
IsCaptchaActive: isCaptchaActive,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +58,8 @@ func GetThreads(c *gin.Context) {
|
|||||||
PagesCount: pagesCount,
|
PagesCount: pagesCount,
|
||||||
Page: page,
|
Page: page,
|
||||||
FormData: Repositories.HtmlFormData{
|
FormData: Repositories.HtmlFormData{
|
||||||
CaptchaID: captchaID,
|
CaptchaID: captchaID,
|
||||||
|
IsCaptchaActive: Config.App.IsCaptchaActive,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
c.HTML(http.StatusOK, "index.html", htmlData)
|
c.HTML(http.StatusOK, "index.html", htmlData)
|
||||||
@ -87,8 +88,9 @@ func GetThread(c *gin.Context) {
|
|||||||
htmlData := Repositories.GetThreadHtmlData{
|
htmlData := Repositories.GetThreadHtmlData{
|
||||||
Thread: thread,
|
Thread: thread,
|
||||||
FormData: Repositories.HtmlFormData{
|
FormData: Repositories.HtmlFormData{
|
||||||
FirstPostID: firstPost.ID,
|
FirstPostID: firstPost.ID,
|
||||||
CaptchaID: captchaID,
|
CaptchaID: captchaID,
|
||||||
|
IsCaptchaActive: Config.App.IsCaptchaActive,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
c.HTML(http.StatusOK, "thread.html", htmlData)
|
c.HTML(http.StatusOK, "thread.html", htmlData)
|
||||||
@ -309,6 +311,11 @@ func UpdateThread(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
defer tx.Rollback(context.TODO())
|
defer tx.Rollback(context.TODO())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error:", err)
|
||||||
|
c.HTML(http.StatusInternalServerError, "500.html", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
post := Repositories.Post{
|
post := Repositories.Post{
|
||||||
IsParent: false,
|
IsParent: false,
|
||||||
ParentID: threadID,
|
ParentID: threadID,
|
||||||
@ -323,6 +330,23 @@ func UpdateThread(c *gin.Context) {
|
|||||||
return
|
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 {
|
for _, fileInRequest := range filesInRequest {
|
||||||
file := Repositories.File{
|
file := Repositories.File{
|
||||||
PostID: postID,
|
PostID: postID,
|
||||||
|
@ -208,23 +208,6 @@ func (r *PostsRepository) CreateInTx(tx pgx.Tx, p Post) (int, error) {
|
|||||||
return 0, err
|
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
|
return createdPost.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,3 +246,34 @@ func (r *PostsRepository) ArchiveThreadsFrom(t time.Time) error {
|
|||||||
_, err := Db.Pool.Exec(context.TODO(), sql, t)
|
_, err := Db.Pool.Exec(context.TODO(), sql, t)
|
||||||
return err
|
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
|
||||||
|
}
|
||||||
|
@ -34,8 +34,9 @@ type File struct {
|
|||||||
|
|
||||||
// post-form.html
|
// post-form.html
|
||||||
type HtmlFormData struct {
|
type HtmlFormData struct {
|
||||||
FirstPostID int
|
FirstPostID int
|
||||||
CaptchaID string
|
CaptchaID string
|
||||||
|
IsCaptchaActive bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// thread.html
|
// thread.html
|
||||||
|
@ -15,11 +15,13 @@
|
|||||||
<textarea class="form-control" id="postText" rows="5" placeholder="Text" name="text"></textarea>
|
<textarea class="form-control" id="postText" rows="5" placeholder="Text" name="text"></textarea>
|
||||||
<input class="form-control" type="file" id="postFiles" multiple name="files">
|
<input class="form-control" type="file" id="postFiles" multiple name="files">
|
||||||
|
|
||||||
|
{{ if .IsCaptchaActive }}
|
||||||
<div class="captcha-container text-center">
|
<div class="captcha-container text-center">
|
||||||
<img src="/captcha/{{ .CaptchaID }}" alt="Captcha">
|
<img src="/captcha/{{ .CaptchaID }}" alt="Captcha">
|
||||||
</div>
|
</div>
|
||||||
<input class="form-control" type="hidden" name="captchaId" value="{{ .CaptchaID }}">
|
<input class="form-control" type="hidden" name="captchaId" value="{{ .CaptchaID }}">
|
||||||
<input class="form-control" id="postCaptcha" type="tel" pattern="\d+" placeholder="Captcha" name="captcha">
|
<input class="form-control" id="postCaptcha" type="tel" pattern="\d+" placeholder="Captcha" name="captcha">
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{{ if ne .FirstPostID 0 }}
|
{{ if ne .FirstPostID 0 }}
|
||||||
|
Loading…
Reference in New Issue
Block a user