mirror of
https://github.com/yanislav-igonin/micrach
synced 2024-12-22 06:12:33 +03:00
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:
parent
c1f632ee03
commit
242abb8645
@ -101,17 +101,6 @@ func CreateThread(c *gin.Context) {
|
||||
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
|
||||
text := form.Value["text"][0]
|
||||
title := form.Value["title"][0]
|
||||
@ -125,6 +114,17 @@ func CreateThread(c *gin.Context) {
|
||||
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())
|
||||
if err != nil {
|
||||
log.Println("error:", err)
|
||||
@ -225,17 +225,6 @@ func UpdateThread(c *gin.Context) {
|
||||
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
|
||||
text := form.Value["text"][0]
|
||||
filesInRequest := form.File["files"]
|
||||
@ -248,6 +237,17 @@ func UpdateThread(c *gin.Context) {
|
||||
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"]
|
||||
var isSageString string
|
||||
if len(isSageField) != 0 {
|
||||
|
67
db/db.go
67
db/db.go
@ -3,14 +3,24 @@ package db
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
Config "micrach/config"
|
||||
Files "micrach/files"
|
||||
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
)
|
||||
|
||||
var Pool *pgxpool.Pool
|
||||
|
||||
type MigrationsMap map[int]string
|
||||
type Migration struct {
|
||||
ID int
|
||||
Name string
|
||||
}
|
||||
|
||||
func Init() {
|
||||
var err error
|
||||
Pool, err = pgxpool.Connect(context.TODO(), Config.Db.Url)
|
||||
@ -21,3 +31,60 @@ func Init() {
|
||||
|
||||
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
|
||||
}
|
||||
|
40
files/get_files_in_folder.go
Normal file
40
files/get_files_in_folder.go
Normal 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
4
go.mod
@ -7,18 +7,18 @@ require (
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/gin-gonic/gin v1.7.4
|
||||
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/jackc/pgtype v1.8.1
|
||||
github.com/jackc/pgx/v4 v4.13.0
|
||||
github.com/jackc/puddle v1.1.4 // indirect
|
||||
github.com/joho/godotenv v1.3.0
|
||||
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/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/ugorji/go v1.2.6 // indirect
|
||||
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/text v0.3.7 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
|
9
go.sum
9
go.sum
@ -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/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/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.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
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.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.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
|
||||
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.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
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-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-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
||||
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/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
|
1
main.go
1
main.go
@ -22,6 +22,7 @@ import (
|
||||
func main() {
|
||||
Config.Init()
|
||||
Db.Init()
|
||||
Db.Migrate()
|
||||
defer Db.Pool.Close()
|
||||
gin.SetMode(Config.App.Env)
|
||||
if Config.App.SeedDb {
|
||||
|
@ -1,2 +0,0 @@
|
||||
DROP TABLE files;
|
||||
DROP TABLE posts;
|
@ -1,5 +1,3 @@
|
||||
-- UP
|
||||
-- Posts
|
||||
CREATE TABLE posts
|
||||
(
|
||||
id SERIAL NOT NULL,
|
||||
@ -19,8 +17,6 @@ CREATE TABLE posts
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
|
||||
-- Files
|
||||
CREATE TABLE files
|
||||
(
|
||||
id SERIAL PRIMARY KEY,
|
||||
@ -31,3 +27,10 @@ CREATE TABLE files
|
||||
created_at TIMESTAMP DEFAULT NOW() NOT NULL,
|
||||
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
|
||||
)
|
2
migrations/2-posts_archivation.sql
Normal file
2
migrations/2-posts_archivation.sql
Normal file
@ -0,0 +1,2 @@
|
||||
ALTER TABLE posts
|
||||
ADD COLUMN is_archived BOOLEAN DEFAULT false;
|
@ -3,4 +3,8 @@
|
||||
}
|
||||
.thread-title-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.thread-title {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
<body>
|
||||
<a href="/" class="thread-title-link">
|
||||
<h1 class="display-1 text-center">
|
||||
<h1 class="display-1 text-center thread-title">
|
||||
⤶
|
||||
{{ if ne $FirstPost.Title "" }}
|
||||
{{$FirstPost.Title}}
|
||||
|
Loading…
Reference in New Issue
Block a user