mirror of
				https://github.com/yanislav-igonin/micrach
				synced 2025-10-30 18:07:02 +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 | ||||
| SEED_DB=true | ||||
| IS_RATE_LIMITER_ENABLED=true | ||||
| THREADS_MAX_COUNT=50 | ||||
| POSTGRES_URL=postgres://localhost/micrach?pool_max_conns=5 | ||||
| @ -2,6 +2,7 @@ package config | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"strconv" | ||||
| ) | ||||
| @ -11,6 +12,7 @@ type AppConfig struct { | ||||
| 	Port                 int | ||||
| 	SeedDb               bool | ||||
| 	IsRateLimiterEnabled bool | ||||
| 	ThreadsMaxCount      int | ||||
| } | ||||
| 
 | ||||
| type DbConfig struct { | ||||
| @ -29,7 +31,7 @@ func getAppConfig() AppConfig { | ||||
| 	} | ||||
| 	port, err := strconv.Atoi(portString) | ||||
| 	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") | ||||
| @ -38,11 +40,18 @@ func getAppConfig() AppConfig { | ||||
| 	isRateLimiterEnabledString := os.Getenv("IS_RATE_LIMITER_ENABLED") | ||||
| 	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{ | ||||
| 		Env:                  env, | ||||
| 		Port:                 port, | ||||
| 		SeedDb:               seedDb, | ||||
| 		IsRateLimiterEnabled: isRateLimiterEnabled, | ||||
| 		ThreadsMaxCount:      threadsMaxCount, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -12,6 +12,7 @@ import ( | ||||
| 	"github.com/dchest/captcha" | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 
 | ||||
| 	Config "micrach/config" | ||||
| 	Db "micrach/db" | ||||
| 	Repositories "micrach/repositories" | ||||
| 	Utils "micrach/utils" | ||||
| @ -133,6 +134,28 @@ func CreateThread(c *gin.Context) { | ||||
| 	} | ||||
| 	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()) | ||||
| 	if err != nil { | ||||
| 		log.Println("error:", err) | ||||
|  | ||||
| @ -33,4 +33,4 @@ CREATE TABLE migrations | ||||
|   id INT NOT NULL, | ||||
|   name VARCHAR 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 ( | ||||
| 	"context" | ||||
| 	Config "micrach/config" | ||||
| 	Db "micrach/db" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/jackc/pgx/v4" | ||||
| ) | ||||
| @ -66,6 +68,7 @@ func (r *PostsRepository) GetCount() (int, error) { | ||||
| 		WHERE  | ||||
| 			is_parent = true | ||||
| 			AND is_deleted != true | ||||
| 			AND is_archived != true | ||||
| 	` | ||||
| 
 | ||||
| 	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) { | ||||
| 	sql := ` | ||||
| 		INSERT INTO posts (is_parent, parent_id, title, text, is_sage) | ||||
| 		VALUES ($1, $2, $3, $4, $5) | ||||
| 		INSERT INTO posts (is_parent, parent_id, title, text, is_sage, updated_at) | ||||
| 		VALUES ($1, $2, $3, $4, $5, $6) | ||||
| 		RETURNING id | ||||
| 	` | ||||
| 
 | ||||
| 	var row pgx.Row | ||||
| 	if p.IsParent { | ||||
| 		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 { | ||||
| 		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) { | ||||
| 	sql := ` | ||||
| 		INSERT INTO posts (is_parent, parent_id, title, text, is_sage) | ||||
| 		VALUES ($1, $2, $3, $4, $5) | ||||
| 		INSERT INTO posts (is_parent, parent_id, title, text, is_sage, updated_at) | ||||
| 		VALUES ($1, $2, $3, $4, $5, $6) | ||||
| 		RETURNING id | ||||
| 	` | ||||
| 
 | ||||
| 	var row pgx.Row | ||||
| 	if p.IsParent { | ||||
| 		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 { | ||||
| 		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 | ||||
| } | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
| import ( | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Yanislav Igonin
						Yanislav Igonin