Feature - Threads archivation (#4)

* todo

* update env var

* add new var to config

* update GetCount (not archived threads)

* wip on threads count check

* feat: add get oldest updated at thread

* feat: add archivation method for oldest threads

* feat: add migrations for posts timefields

* feat: now child posts created without updated_at

* feat: add threads archivation
This commit is contained in:
Yanislav Igonin 2021-11-20 19:13:05 +02:00 committed by GitHub
parent 9d2703f35a
commit 783e577311
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 96 additions and 10 deletions

View File

@ -2,4 +2,5 @@ ENV=debug
PORT=3000 PORT=3000
SEED_DB=true SEED_DB=true
IS_RATE_LIMITER_ENABLED=true IS_RATE_LIMITER_ENABLED=true
THREADS_MAX_COUNT=50
POSTGRES_URL=postgres://localhost/micrach?pool_max_conns=5 POSTGRES_URL=postgres://localhost/micrach?pool_max_conns=5

View File

@ -2,6 +2,7 @@ package config
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"strconv" "strconv"
) )
@ -11,6 +12,7 @@ type AppConfig struct {
Port int Port int
SeedDb bool SeedDb bool
IsRateLimiterEnabled bool IsRateLimiterEnabled bool
ThreadsMaxCount int
} }
type DbConfig struct { type DbConfig struct {
@ -29,7 +31,7 @@ func getAppConfig() AppConfig {
} }
port, err := strconv.Atoi(portString) port, err := strconv.Atoi(portString)
if err != nil { if err != nil {
panic(fmt.Sprintf("Could not parse %s to int", portString)) log.Panicln(fmt.Sprintf("Could not parse %s to int", portString))
} }
seedDbString := os.Getenv("SEED_DB") seedDbString := os.Getenv("SEED_DB")
@ -38,11 +40,18 @@ func getAppConfig() AppConfig {
isRateLimiterEnabledString := os.Getenv("IS_RATE_LIMITER_ENABLED") isRateLimiterEnabledString := os.Getenv("IS_RATE_LIMITER_ENABLED")
isRateLimiterEnabled := isRateLimiterEnabledString == "true" isRateLimiterEnabled := isRateLimiterEnabledString == "true"
threadsMaxCountString := os.Getenv("THREADS_MAX_COUNT")
threadsMaxCount, err := strconv.Atoi(threadsMaxCountString)
if err != nil {
log.Panicln(fmt.Sprintf("Could not parse %s to int", threadsMaxCountString))
}
return AppConfig{ return AppConfig{
Env: env, Env: env,
Port: port, Port: port,
SeedDb: seedDb, SeedDb: seedDb,
IsRateLimiterEnabled: isRateLimiterEnabled, IsRateLimiterEnabled: isRateLimiterEnabled,
ThreadsMaxCount: threadsMaxCount,
} }
} }

View File

@ -12,6 +12,7 @@ import (
"github.com/dchest/captcha" "github.com/dchest/captcha"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
Config "micrach/config"
Db "micrach/db" Db "micrach/db"
Repositories "micrach/repositories" Repositories "micrach/repositories"
Utils "micrach/utils" Utils "micrach/utils"
@ -133,6 +134,28 @@ func CreateThread(c *gin.Context) {
} }
defer conn.Release() defer conn.Release()
threadsCount, err := Repositories.Posts.GetCount()
if err != nil {
log.Println("error:", err)
c.HTML(http.StatusInternalServerError, "500.html", nil)
return
}
if threadsCount >= Config.App.ThreadsMaxCount {
oldestThreadUpdatedAt, err := Repositories.Posts.GetOldestThreadUpdatedAt()
if err != nil {
log.Println("error:", err)
c.HTML(http.StatusInternalServerError, "500.html", nil)
return
}
err = Repositories.Posts.ArchiveThreadsFrom(oldestThreadUpdatedAt)
if err != nil {
log.Println("error:", err)
c.HTML(http.StatusInternalServerError, "500.html", nil)
return
}
}
tx, err := conn.Begin(context.TODO()) tx, err := conn.Begin(context.TODO())
if err != nil { if err != nil {
log.Println("error:", err) log.Println("error:", err)

View File

@ -0,0 +1,9 @@
ALTER TABLE posts
ALTER COLUMN updated_at DROP NOT NULL;
ALTER TABLE posts
ALTER COLUMN updated_at DROP DEFAULT;
UPDATE posts
SET updated_at = null
WHERE is_parent != true;

View File

@ -0,0 +1,5 @@
ALTER TABLE posts
ALTER COLUMN created_at TYPE timestamptz;
ALTER TABLE posts
ALTER COLUMN updated_at TYPE timestamptz;

View File

@ -2,7 +2,9 @@ package repositories
import ( import (
"context" "context"
Config "micrach/config"
Db "micrach/db" Db "micrach/db"
"time"
"github.com/jackc/pgx/v4" "github.com/jackc/pgx/v4"
) )
@ -66,6 +68,7 @@ func (r *PostsRepository) GetCount() (int, error) {
WHERE WHERE
is_parent = true is_parent = true
AND is_deleted != true AND is_deleted != true
AND is_archived != true
` `
row := Db.Pool.QueryRow(context.TODO(), sql) row := Db.Pool.QueryRow(context.TODO(), sql)
@ -79,19 +82,19 @@ func (r *PostsRepository) GetCount() (int, error) {
func (r *PostsRepository) Create(p Post) (int, error) { func (r *PostsRepository) Create(p Post) (int, error) {
sql := ` sql := `
INSERT INTO posts (is_parent, parent_id, title, text, is_sage) INSERT INTO posts (is_parent, parent_id, title, text, is_sage, updated_at)
VALUES ($1, $2, $3, $4, $5) VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id RETURNING id
` `
var row pgx.Row var row pgx.Row
if p.IsParent { if p.IsParent {
row = Db.Pool.QueryRow( row = Db.Pool.QueryRow(
context.TODO(), sql, p.IsParent, nil, p.Title, p.Text, p.IsSage, context.TODO(), sql, p.IsParent, nil, p.Title, p.Text, p.IsSage, time.Now(),
) )
} else { } else {
row = Db.Pool.QueryRow( row = Db.Pool.QueryRow(
context.TODO(), sql, p.IsParent, p.ParentID, p.Title, p.Text, p.IsSage, context.TODO(), sql, p.IsParent, p.ParentID, p.Title, p.Text, p.IsSage, nil,
) )
} }
@ -165,19 +168,19 @@ func (r *PostsRepository) GetThreadByPostID(ID int) ([]Post, error) {
func (r *PostsRepository) CreateInTx(tx pgx.Tx, p Post) (int, error) { func (r *PostsRepository) CreateInTx(tx pgx.Tx, p Post) (int, error) {
sql := ` sql := `
INSERT INTO posts (is_parent, parent_id, title, text, is_sage) INSERT INTO posts (is_parent, parent_id, title, text, is_sage, updated_at)
VALUES ($1, $2, $3, $4, $5) VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id RETURNING id
` `
var row pgx.Row var row pgx.Row
if p.IsParent { if p.IsParent {
row = tx.QueryRow( row = tx.QueryRow(
context.TODO(), sql, p.IsParent, nil, p.Title, p.Text, p.IsSage, context.TODO(), sql, p.IsParent, nil, p.Title, p.Text, p.IsSage, time.Now(),
) )
} else { } else {
row = tx.QueryRow( row = tx.QueryRow(
context.TODO(), sql, p.IsParent, p.ParentID, p.Title, p.Text, p.IsSage, context.TODO(), sql, p.IsParent, p.ParentID, p.Title, p.Text, p.IsSage, nil,
) )
} }
@ -206,3 +209,38 @@ func (r *PostsRepository) CreateInTx(tx pgx.Tx, p Post) (int, error) {
return createdPost.ID, nil return createdPost.ID, nil
} }
func (r *PostsRepository) GetOldestThreadUpdatedAt() (time.Time, error) {
sql := `
SELECT updated_at
FROM posts
WHERE
is_parent = true
AND is_deleted != true
AND is_archived != true
ORDER BY updated_at DESC
OFFSET $1 - 1
LIMIT 1
`
row := Db.Pool.QueryRow(context.TODO(), sql, Config.App.ThreadsMaxCount)
var updatedAt time.Time
err := row.Scan(&updatedAt)
if err != nil {
return time.Time{}, err
}
return updatedAt, nil
}
func (r *PostsRepository) ArchiveThreadsFrom(t time.Time) error {
sql := `
UPDATE posts
SET is_archived = true
WHERE
is_archived != true
AND updated_at <= $1
`
_, err := Db.Pool.Exec(context.TODO(), sql, t)
return err
}

View File

@ -1,3 +1,4 @@
// TODO: move all functions to different packages
package utils package utils
import ( import (