Feature - Migrator (#3)

* feat: rename init migration

* feat: add migration for is_archived field

* fix: thread title wrap

* fix: captcha checks after post validation now

* fix

* wip on migrate method

* feat: decided to write own migrator

* feat: add get files on folder func

* wip

* feat: add migrations table

* feat: add migration method

* doc

* feat: query -> exec
This commit is contained in:
Yanislav Igonin 2021-11-17 10:44:12 +02:00 committed by GitHub
parent c1f632ee03
commit 242abb8645
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 151 additions and 37 deletions

View File

@ -101,17 +101,6 @@ func CreateThread(c *gin.Context) {
return return
} }
captchaID := form.Value["captchaId"][0]
captchaString := form.Value["captcha"][0]
isCaptchaValid := captcha.VerifyString(captchaID, captchaString)
if !isCaptchaValid {
errorHtmlData := Repositories.BadRequestHtmlData{
Message: Repositories.InvalidCaptchaErrorMessage,
}
c.HTML(http.StatusInternalServerError, "400.html", errorHtmlData)
return
}
// TODO: dat shit crashes if no fields in request // TODO: dat shit crashes if no fields in request
text := form.Value["text"][0] text := form.Value["text"][0]
title := form.Value["title"][0] title := form.Value["title"][0]
@ -125,6 +114,17 @@ func CreateThread(c *gin.Context) {
return return
} }
captchaID := form.Value["captchaId"][0]
captchaString := form.Value["captcha"][0]
isCaptchaValid := captcha.VerifyString(captchaID, captchaString)
if !isCaptchaValid {
errorHtmlData := Repositories.BadRequestHtmlData{
Message: Repositories.InvalidCaptchaErrorMessage,
}
c.HTML(http.StatusInternalServerError, "400.html", errorHtmlData)
return
}
conn, err := Db.Pool.Acquire(context.TODO()) conn, err := Db.Pool.Acquire(context.TODO())
if err != nil { if err != nil {
log.Println("error:", err) log.Println("error:", err)
@ -225,17 +225,6 @@ func UpdateThread(c *gin.Context) {
return return
} }
captchaID := form.Value["captchaId"][0]
captchaString := form.Value["captcha"][0]
isCaptchaValid := captcha.VerifyString(captchaID, captchaString)
if !isCaptchaValid {
errorHtmlData := Repositories.BadRequestHtmlData{
Message: Repositories.InvalidCaptchaErrorMessage,
}
c.HTML(http.StatusInternalServerError, "400.html", errorHtmlData)
return
}
// TODO: dat shit crashes if no fields in request // TODO: dat shit crashes if no fields in request
text := form.Value["text"][0] text := form.Value["text"][0]
filesInRequest := form.File["files"] filesInRequest := form.File["files"]
@ -248,6 +237,17 @@ func UpdateThread(c *gin.Context) {
return return
} }
captchaID := form.Value["captchaId"][0]
captchaString := form.Value["captcha"][0]
isCaptchaValid := captcha.VerifyString(captchaID, captchaString)
if !isCaptchaValid {
errorHtmlData := Repositories.BadRequestHtmlData{
Message: Repositories.InvalidCaptchaErrorMessage,
}
c.HTML(http.StatusInternalServerError, "400.html", errorHtmlData)
return
}
isSageField := form.Value["sage"] isSageField := form.Value["sage"]
var isSageString string var isSageString string
if len(isSageField) != 0 { if len(isSageField) != 0 {

View File

@ -3,14 +3,24 @@ package db
import ( import (
"context" "context"
"log" "log"
"path/filepath"
"strconv"
"strings"
Config "micrach/config" Config "micrach/config"
Files "micrach/files"
"github.com/jackc/pgx/v4/pgxpool" "github.com/jackc/pgx/v4/pgxpool"
) )
var Pool *pgxpool.Pool var Pool *pgxpool.Pool
type MigrationsMap map[int]string
type Migration struct {
ID int
Name string
}
func Init() { func Init() {
var err error var err error
Pool, err = pgxpool.Connect(context.TODO(), Config.Db.Url) Pool, err = pgxpool.Connect(context.TODO(), Config.Db.Url)
@ -21,3 +31,60 @@ func Init() {
log.Println("database - online") log.Println("database - online")
} }
func Migrate() {
dbMigrations := getDbMigrations()
sqlMigrations := Files.GetFullFilePathsInFolder("migrations")
for _, m := range sqlMigrations {
filename := filepath.Base(m)
splitted := strings.Split(filename, "-")
id, err := strconv.Atoi(splitted[0])
if err != nil {
log.Panicln(err)
}
// Get name without extension
name := strings.Split(splitted[1], ".")[0]
_, isMigrationInDb := dbMigrations[id]
if !isMigrationInDb {
_, err := Pool.Exec(context.TODO(), Files.ReadFileText(m))
if err != nil {
log.Panicln(err)
}
sql := `INSERT INTO migrations (id, name) VALUES ($1, $2)`
_, err = Pool.Query(context.TODO(), sql, id, name)
if err != nil {
log.Panicln(err)
}
log.Println("database migration - " + name + " - online")
}
}
log.Println("database migrations - online")
}
func getDbMigrations() MigrationsMap {
sql := `SELECT id, name FROM migrations`
rows, err := Pool.Query(context.TODO(), sql)
if err != nil {
log.Panicln(err)
}
if rows.Err() != nil {
log.Panicln(rows.Err())
}
migrationsMap := make(MigrationsMap)
for rows.Next() {
var m Migration
err = rows.Scan(&m.ID, &m.Name)
if err != nil {
log.Panicln(err)
}
migrationsMap[m.ID] = m.Name
}
return migrationsMap
}

View File

@ -0,0 +1,40 @@
package files
import (
"io/ioutil"
"log"
"os"
"path"
)
// Reads folder and returns full file paths slice
func GetFullFilePathsInFolder(folder string) []string {
currentPath, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
fullFolderPath := path.Join(currentPath, folder)
files, err := ioutil.ReadDir(fullFolderPath)
if err != nil {
log.Fatal(err)
}
var paths []string
for _, file := range files {
paths = append(paths, path.Join(fullFolderPath, file.Name()))
}
return paths
}
// Reads file contents by full path and returns string
func ReadFileText(fullFilePath string) string {
file, err := ioutil.ReadFile(fullFilePath)
if err != nil {
log.Fatal(err)
}
return string(file)
}

4
go.mod
View File

@ -7,18 +7,18 @@ require (
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/gin-gonic/gin v1.7.4 github.com/gin-gonic/gin v1.7.4
github.com/go-playground/validator/v10 v10.9.0 // indirect github.com/go-playground/validator/v10 v10.9.0 // indirect
github.com/golang-migrate/migrate v3.5.4+incompatible // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/jackc/pgtype v1.8.1 github.com/jackc/pgtype v1.8.1
github.com/jackc/pgx/v4 v4.13.0 github.com/jackc/pgx/v4 v4.13.0
github.com/jackc/puddle v1.1.4 // indirect github.com/jackc/puddle v1.1.4 // indirect
github.com/joho/godotenv v1.3.0 github.com/joho/godotenv v1.3.0
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/lib/pq v1.10.3 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-isatty v0.0.14 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/ugorji/go v1.2.6 // indirect github.com/ugorji/go v1.2.6 // indirect
github.com/ulule/limiter/v3 v3.8.0 github.com/ulule/limiter/v3 v3.8.0
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 // indirect golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect google.golang.org/protobuf v1.27.1 // indirect

9
go.sum
View File

@ -42,8 +42,6 @@ github.com/go-redis/redis/v8 v8.4.2/go.mod h1:A1tbYoHSa1fXwN+//ljcCYYJeLmVrwL9hb
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA=
github.com/golang-migrate/migrate v3.5.4+incompatible/go.mod h1:IsVUlFN5puWOmXrqjgGUfIRIbU7mr8oNBE2tyERd9Wk=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
@ -137,8 +135,9 @@ github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ic
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
@ -221,8 +220,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=

View File

@ -22,6 +22,7 @@ import (
func main() { func main() {
Config.Init() Config.Init()
Db.Init() Db.Init()
Db.Migrate()
defer Db.Pool.Close() defer Db.Pool.Close()
gin.SetMode(Config.App.Env) gin.SetMode(Config.App.Env)
if Config.App.SeedDb { if Config.App.SeedDb {

View File

@ -1,2 +0,0 @@
DROP TABLE files;
DROP TABLE posts;

View File

@ -1,5 +1,3 @@
-- UP
-- Posts
CREATE TABLE posts CREATE TABLE posts
( (
id SERIAL NOT NULL, id SERIAL NOT NULL,
@ -19,8 +17,6 @@ CREATE TABLE posts
PRIMARY KEY (id) PRIMARY KEY (id)
); );
-- Files
CREATE TABLE files CREATE TABLE files
( (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
@ -31,3 +27,10 @@ CREATE TABLE files
created_at TIMESTAMP DEFAULT NOW() NOT NULL, created_at TIMESTAMP DEFAULT NOW() NOT NULL,
FOREIGN KEY (post_id) REFERENCES posts (id) FOREIGN KEY (post_id) REFERENCES posts (id)
); );
CREATE TABLE migrations
(
id INT NOT NULL,
name VARCHAR NOT NULL,
created_at TIMESTAMP DEFAULT NOW() NOT NULL
)

View File

@ -0,0 +1,2 @@
ALTER TABLE posts
ADD COLUMN is_archived BOOLEAN DEFAULT false;

View File

@ -4,3 +4,7 @@
.thread-title-link:hover { .thread-title-link:hover {
text-decoration: underline; text-decoration: underline;
} }
.thread-title {
overflow-wrap: break-word;
}

View File

@ -11,7 +11,7 @@
<body> <body>
<a href="/" class="thread-title-link"> <a href="/" class="thread-title-link">
<h1 class="display-1 text-center"> <h1 class="display-1 text-center thread-title">
{{ if ne $FirstPost.Title "" }} {{ if ne $FirstPost.Title "" }}
{{$FirstPost.Title}} {{$FirstPost.Title}}