mirror of
https://github.com/yanislav-igonin/micrach
synced 2024-12-22 06:12:33 +03:00
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:
parent
9d2703f35a
commit
783e577311
@ -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
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -33,4 +33,4 @@ CREATE TABLE migrations
|
|||||||
id INT NOT NULL,
|
id INT NOT NULL,
|
||||||
name VARCHAR NOT NULL,
|
name VARCHAR NOT NULL,
|
||||||
created_at TIMESTAMP DEFAULT NOW() NOT NULL
|
created_at TIMESTAMP DEFAULT NOW() NOT NULL
|
||||||
)
|
)
|
||||||
|
9
migrations/3-remove_posts_updated_at_default.sql
Normal file
9
migrations/3-remove_posts_updated_at_default.sql
Normal 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;
|
5
migrations/4-posts_timefields_now_with_timezone.sql
Normal file
5
migrations/4-posts_timefields_now_with_timezone.sql
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
ALTER TABLE posts
|
||||||
|
ALTER COLUMN created_at TYPE timestamptz;
|
||||||
|
|
||||||
|
ALTER TABLE posts
|
||||||
|
ALTER COLUMN updated_at TYPE timestamptz;
|
@ -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
|
||||||
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// TODO: move all functions to different packages
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
Loading…
Reference in New Issue
Block a user