From 1845d5b3e9e0f643b577d43bd9e3cce0ed0ef51e Mon Sep 17 00:00:00 2001 From: Krovaldo Date: Wed, 15 Jan 2025 01:19:02 +0300 Subject: [PATCH 01/14] initialized db and connection --- database/db.go | 24 ++++++++++++++++++++++++ docker-compose.yml | 14 ++++++++++++++ go.mod | 6 ++++++ go.sum | 12 ++++++++++++ main.go | 11 +++++++++++ 5 files changed, 67 insertions(+) create mode 100644 database/db.go create mode 100644 docker-compose.yml create mode 100644 go.sum create mode 100644 main.go diff --git a/database/db.go b/database/db.go new file mode 100644 index 0000000..ec9479b --- /dev/null +++ b/database/db.go @@ -0,0 +1,24 @@ +package database + +import ( + "fmt" + "github.com/jmoiron/sqlx" + "github.com/joho/godotenv" + _ "github.com/lib/pq" + "os" +) + +func InitDB() (*sqlx.DB, error) { + err := godotenv.Load("./.env") + if err != nil { + return nil, fmt.Errorf("error loading .env file: %v", err) + } + + db, err := sqlx.Connect("postgres", os.Getenv("DATABASE_URL")) + if err != nil { + return nil, fmt.Errorf("error connecting to database: %v", err) + } + + fmt.Println("Successfully connected to the database") + return db, nil +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1de4ab0 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +services: + db: + image: postgres:latest + container_name: notes_app_db + environment: + POSTGRES_USER: ${APP_DB_USER} + POSTGRES_PASSWORD: ${APP_DB_PASSWORD} + POSTGRES_DB: ${APP_DB_NAME} + ports: + - "${APP_DB_PORT}:5432" + volumes: + - postgres_data:/var/lib/postgresql/data +volumes: + postgres_data: \ No newline at end of file diff --git a/go.mod b/go.mod index bfc07e3..a626a6e 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,9 @@ module NotesWebApp go 1.23.4 + +require ( + github.com/jmoiron/sqlx v1.4.0 + github.com/joho/godotenv v1.5.1 + github.com/lib/pq v1.10.9 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..36c8479 --- /dev/null +++ b/go.sum @@ -0,0 +1,12 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= diff --git a/main.go b/main.go new file mode 100644 index 0000000..6ef6e30 --- /dev/null +++ b/main.go @@ -0,0 +1,11 @@ +package main + +import "NotesWebApp/database" + +func main() { + pgdb, err := database.InitDB() + if err != nil { + panic(err) + } + defer pgdb.Close() +} From 1345e6a065dd34f6c554299a56a6060a2eddcf9b Mon Sep 17 00:00:00 2001 From: Krovaldo Date: Wed, 15 Jan 2025 01:30:25 +0300 Subject: [PATCH 02/14] lint fix --- .golangci.yml | 1 - database/db.go | 9 +++++---- main.go | 7 ------- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index f8198cf..e3692fd 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -12,7 +12,6 @@ linters: - asciicheck - bodyclose - deadcode - - depguard - dogsled - dupl - durationcheck diff --git a/database/db.go b/database/db.go index ec9479b..5888a8e 100644 --- a/database/db.go +++ b/database/db.go @@ -2,21 +2,22 @@ package database import ( "fmt" + "os" + "github.com/jmoiron/sqlx" "github.com/joho/godotenv" - _ "github.com/lib/pq" - "os" + _ "github.com/lib/pq" // Регистрация драйвера PostgreSQL ) func InitDB() (*sqlx.DB, error) { err := godotenv.Load("./.env") if err != nil { - return nil, fmt.Errorf("error loading .env file: %v", err) + return nil, fmt.Errorf("error loading .env file: %w", err) } db, err := sqlx.Connect("postgres", os.Getenv("DATABASE_URL")) if err != nil { - return nil, fmt.Errorf("error connecting to database: %v", err) + return nil, fmt.Errorf("error connecting to database: %w", err) } fmt.Println("Successfully connected to the database") diff --git a/main.go b/main.go index 6ef6e30..da29a2c 100644 --- a/main.go +++ b/main.go @@ -1,11 +1,4 @@ package main -import "NotesWebApp/database" - func main() { - pgdb, err := database.InitDB() - if err != nil { - panic(err) - } - defer pgdb.Close() } From 841f24415212c40ddc5d3ff60359277ab6c0e145 Mon Sep 17 00:00:00 2001 From: Krovaldo Date: Wed, 15 Jan 2025 01:34:33 +0300 Subject: [PATCH 03/14] lint fix --- .golangci.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index e3692fd..ee6f80a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -11,7 +11,6 @@ linters: enable: - asciicheck - bodyclose - - deadcode - dogsled - dupl - durationcheck @@ -34,7 +33,6 @@ linters: - gosec - gosimple - govet - - ifshort - importas - ineffassign - lll @@ -50,7 +48,6 @@ linters: - rowserrcheck - sqlclosecheck - staticcheck - - structcheck - stylecheck - tagliatelle - thelper @@ -59,6 +56,5 @@ linters: - unconvert - unparam - unused - - varcheck - wastedassign - whitespace \ No newline at end of file From 4321e7bec3707bd8a34d0a5332ef0f8a6e27044a Mon Sep 17 00:00:00 2001 From: Krovaldo Date: Wed, 15 Jan 2025 23:35:36 +0300 Subject: [PATCH 04/14] added models --- go.mod | 1 + go.sum | 2 ++ models/note.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ models/user.go | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+) create mode 100644 models/note.go create mode 100644 models/user.go diff --git a/go.mod b/go.mod index a626a6e..90e5c6d 100644 --- a/go.mod +++ b/go.mod @@ -6,4 +6,5 @@ require ( github.com/jmoiron/sqlx v1.4.0 github.com/joho/godotenv v1.5.1 github.com/lib/pq v1.10.9 + golang.org/x/crypto v0.32.0 ) diff --git a/go.sum b/go.sum index 36c8479..4637fce 100644 --- a/go.sum +++ b/go.sum @@ -10,3 +10,5 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= diff --git a/models/note.go b/models/note.go new file mode 100644 index 0000000..3a90ac5 --- /dev/null +++ b/models/note.go @@ -0,0 +1,48 @@ +package models + +import ( + "github.com/jmoiron/sqlx" + "time" +) + +type Note struct { + ID int `db:"id"` + Title string `db:"title"` + Content string `db:"content"` + UserID int `db:"user_id"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} + +func (n *Note) CreateNote(db *sqlx.DB) error { + query := `INSERT INTO notes (title, content, user_id) +VALUES (:title, :content, :user_id) RETURNING id, created_at, updated_at` + return db.QueryRowx(query, n).Scan(&n.ID, &n.CreatedAt, &n.UpdatedAt) +} + +func (n *Note) UpdateNote(db *sqlx.DB) error { + query := `UPDATE notes SET title=:title, content=:content, updated_at=:updated_at + WHERE id=:id` + _, err := db.NamedExec(query, n) + return err +} + +func (n *Note) DeleteNote(db *sqlx.DB) error { + query := `DELETE FROM notes WHERE id=:id` + _, err := db.NamedExec(query, n) + return err +} +func (n *Note) GetNotesByUser(db *sqlx.DB, userId int) ([]Note, error) { + var notes []Note + query := `SELECT id, title, content, created_at, updated_at FROM notes WHERE user_id=:user_id` + err := db.Select(¬es, query, userId) + return notes, err +} + +func GetNoteByID(db *sqlx.DB, id int) (*Note, error) { + var note Note + query := `SELECT id, title, content, created_at, updated_at FROM notes WHERE id=$1` + + err := db.Get(¬e, query, id) + return ¬e, err +} diff --git a/models/user.go b/models/user.go new file mode 100644 index 0000000..169d834 --- /dev/null +++ b/models/user.go @@ -0,0 +1,32 @@ +package models + +import ( + "github.com/jmoiron/sqlx" + "golang.org/x/crypto/bcrypt" +) + +type User struct { + ID int `db:"id"` + Email string `db:"email"` + Password string `db:"password"` +} + +func (u *User) CreateUser(db *sqlx.DB) error { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost) + if err != nil { + return err + } + + query := `INSERT into users (email, password) VALUES (:email, :password) RETURNING id` + return db.QueryRowx(query, map[string]interface{}{ + "email": u.Email, + "password": string(hashedPassword), + }).Scan(&u.ID) +} + +func GetUserByEmail(db *sqlx.DB, email string) (*User, error) { + var user User + query := `SELECT id, email, password FROM users WHERE email=$1` + err := db.Get(&user, query, email) + return &user, err +} From dbbedc5a9b0d94a38e2c4fa50a8f3d0726563cce Mon Sep 17 00:00:00 2001 From: Krovaldo Date: Wed, 15 Jan 2025 23:41:51 +0300 Subject: [PATCH 05/14] fixed lint for models --- models/note.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/models/note.go b/models/note.go index 3a90ac5..c802259 100644 --- a/models/note.go +++ b/models/note.go @@ -1,8 +1,9 @@ package models import ( - "github.com/jmoiron/sqlx" "time" + + "github.com/jmoiron/sqlx" ) type Note struct { @@ -32,6 +33,7 @@ func (n *Note) DeleteNote(db *sqlx.DB) error { _, err := db.NamedExec(query, n) return err } + func (n *Note) GetNotesByUser(db *sqlx.DB, userId int) ([]Note, error) { var notes []Note query := `SELECT id, title, content, created_at, updated_at FROM notes WHERE user_id=:user_id` From d273b8edcc7b496fa2eae66110ba465b426cd826 Mon Sep 17 00:00:00 2001 From: Krovaldo Date: Thu, 16 Jan 2025 00:04:46 +0300 Subject: [PATCH 06/14] fixed lint naming models --- models/note.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/note.go b/models/note.go index c802259..5b3b5ac 100644 --- a/models/note.go +++ b/models/note.go @@ -34,10 +34,10 @@ func (n *Note) DeleteNote(db *sqlx.DB) error { return err } -func (n *Note) GetNotesByUser(db *sqlx.DB, userId int) ([]Note, error) { +func (n *Note) GetNotesByUser(db *sqlx.DB, userID int) ([]Note, error) { var notes []Note query := `SELECT id, title, content, created_at, updated_at FROM notes WHERE user_id=:user_id` - err := db.Select(¬es, query, userId) + err := db.Select(¬es, query, userID) return notes, err } From b290fbbfd01a3bb471fdaeb667fd493df8b5fba7 Mon Sep 17 00:00:00 2001 From: Krovaldo Date: Thu, 16 Jan 2025 23:51:40 +0300 Subject: [PATCH 07/14] changed some func in models --- models/note.go | 7 ++++--- models/user.go | 7 ++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/models/note.go b/models/note.go index 5b3b5ac..61a8e95 100644 --- a/models/note.go +++ b/models/note.go @@ -17,11 +17,12 @@ type Note struct { func (n *Note) CreateNote(db *sqlx.DB) error { query := `INSERT INTO notes (title, content, user_id) -VALUES (:title, :content, :user_id) RETURNING id, created_at, updated_at` - return db.QueryRowx(query, n).Scan(&n.ID, &n.CreatedAt, &n.UpdatedAt) +VALUES ($1, $2, $3) RETURNING id, created_at, updated_at` + return db.QueryRowx(query, n.Title, n.Content, n.UserID).Scan(&n.ID, &n.CreatedAt, &n.UpdatedAt) } func (n *Note) UpdateNote(db *sqlx.DB) error { + n.UpdatedAt = time.Now() query := `UPDATE notes SET title=:title, content=:content, updated_at=:updated_at WHERE id=:id` _, err := db.NamedExec(query, n) @@ -36,7 +37,7 @@ func (n *Note) DeleteNote(db *sqlx.DB) error { func (n *Note) GetNotesByUser(db *sqlx.DB, userID int) ([]Note, error) { var notes []Note - query := `SELECT id, title, content, created_at, updated_at FROM notes WHERE user_id=:user_id` + query := `SELECT id, title, content, created_at, updated_at FROM notes WHERE user_id=$1` err := db.Select(¬es, query, userID) return notes, err } diff --git a/models/user.go b/models/user.go index 169d834..e6e34a6 100644 --- a/models/user.go +++ b/models/user.go @@ -17,11 +17,8 @@ func (u *User) CreateUser(db *sqlx.DB) error { return err } - query := `INSERT into users (email, password) VALUES (:email, :password) RETURNING id` - return db.QueryRowx(query, map[string]interface{}{ - "email": u.Email, - "password": string(hashedPassword), - }).Scan(&u.ID) + query := `INSERT into users (email, password) VALUES ($1, $2) RETURNING id` + return db.QueryRowx(query, u.Email, string(hashedPassword)).Scan(&u.ID) } func GetUserByEmail(db *sqlx.DB, email string) (*User, error) { From 8135f3fc2c8dd3ebb610bdc12cd69b50685c2cc8 Mon Sep 17 00:00:00 2001 From: Krovaldo Date: Wed, 22 Jan 2025 00:42:04 +0300 Subject: [PATCH 08/14] working project --- go.mod | 6 ++ go.sum | 8 ++ handlers/auth_handler.go | 129 +++++++++++++++++++++++ handlers/note_handler.go | 217 +++++++++++++++++++++++++++++++++++++++ handlers/session.go | 34 ++++++ main.go | 48 +++++++++ models/note.go | 10 +- models/user.go | 8 +- static/styles.css | 47 +++++++++ templates/create.html | 21 ++++ templates/edit.html | 21 ++++ templates/index.html | 28 +++++ templates/login.html | 22 ++++ templates/register.html | 22 ++++ 14 files changed, 613 insertions(+), 8 deletions(-) create mode 100644 handlers/auth_handler.go create mode 100644 handlers/note_handler.go create mode 100644 handlers/session.go create mode 100644 static/styles.css create mode 100644 templates/create.html create mode 100644 templates/edit.html create mode 100644 templates/index.html create mode 100644 templates/login.html create mode 100644 templates/register.html diff --git a/go.mod b/go.mod index 90e5c6d..4abc18d 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,14 @@ module NotesWebApp go 1.23.4 require ( + github.com/gorilla/sessions v1.4.0 github.com/jmoiron/sqlx v1.4.0 github.com/joho/godotenv v1.5.1 github.com/lib/pq v1.10.9 golang.org/x/crypto v0.32.0 ) + +require ( + github.com/gorilla/mux v1.8.1 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect +) diff --git a/go.sum b/go.sum index 4637fce..74eb695 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,14 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= +github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= diff --git a/handlers/auth_handler.go b/handlers/auth_handler.go new file mode 100644 index 0000000..14b7966 --- /dev/null +++ b/handlers/auth_handler.go @@ -0,0 +1,129 @@ +package handlers + +import ( + "NotesWebApp/models" + "github.com/jmoiron/sqlx" + "golang.org/x/crypto/bcrypt" + "html/template" + "log" + "net/http" +) + +type AuthHandler struct { + DB *sqlx.DB +} + +func NewAuthHandler(db *sqlx.DB) *AuthHandler { + return &AuthHandler{DB: db} +} + +func (ah *AuthHandler) Index(w http.ResponseWriter, r *http.Request) { + session, err := store.Get(r, sessionName) + if err != nil { + http.Error(w, "Failed to get session", http.StatusBadRequest) + } + _, ok := session.Values["userID"].(int) + if !ok { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + + http.Redirect(w, r, "/notes", http.StatusFound) +} + +func (ah *AuthHandler) LoginForm(w http.ResponseWriter, r *http.Request) { + tmpl := template.Must(template.ParseFiles("templates/login.html")) + err := tmpl.Execute(w, nil) + if err != nil { + log.Println("Error while executing login.html template:", err) + return + } +} + +func (ah *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { + email := r.FormValue("email") + password := r.FormValue("password") + + user, err := models.GetUserByEmail(ah.DB, email) + if err != nil { + http.Error(w, "User not found: invalid credentials", http.StatusUnauthorized) + } + + if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { + http.Error(w, "Wrong password", http.StatusUnauthorized) + return + } + + session, err := store.Get(r, sessionName) + if err != nil { + log.Printf("Failed to get session: %v", err) + + http.Error(w, "Failed to get session", http.StatusBadRequest) + return + } + session.Values["userID"] = user.ID + err = session.Save(r, w) + if err != nil { + log.Println("Can't save session:", err) + return + } + log.Println("User logged in successfully:", user.ID) //123 + http.Redirect(w, r, "/notes", http.StatusFound) +} + +func (ah *AuthHandler) RegisterForm(w http.ResponseWriter, r *http.Request) { + tmpl := template.Must(template.ParseFiles("templates/register.html")) + err := tmpl.Execute(w, nil) + if err != nil { + log.Println("Error while executing register.html template:", err) + return + } +} + +func (ah *AuthHandler) Register(w http.ResponseWriter, r *http.Request) { + email := r.FormValue("email") + password := r.FormValue("password") + + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + log.Printf("Failed to hash password for user %s: %v", email, err) + + http.Error(w, "Internal server error: failed to process password", http.StatusInternalServerError) + return + } + + user := models.User{ + Email: email, + Password: string(hashedPassword), + } + + if err := user.CreateUser(ah.DB); err != nil { + log.Printf("Failed to create user %s: %v", email, err) + + http.Error(w, "Internal server error: failed to create user", http.StatusInternalServerError) + return + } + + log.Printf("user created %+v", user) + + http.Redirect(w, r, "/login", http.StatusSeeOther) +} + +func (ah *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) { + session, err := store.Get(r, sessionName) + if err != nil { + http.Error(w, "Failed to get session", http.StatusInternalServerError) + return + } + + session.Values = make(map[interface{}]interface{}) // удаляем все + session.Options.MaxAge = -1 // ставим срок действия сессии в прошлое (удаление сессии) + + err = session.Save(r, w) + if err != nil { + http.Error(w, "Failed to save session", http.StatusInternalServerError) + return + } + + http.Redirect(w, r, "/", http.StatusFound) +} diff --git a/handlers/note_handler.go b/handlers/note_handler.go new file mode 100644 index 0000000..7474e0f --- /dev/null +++ b/handlers/note_handler.go @@ -0,0 +1,217 @@ +package handlers + +import ( + "NotesWebApp/models" + "github.com/gorilla/mux" + "github.com/jmoiron/sqlx" + "html/template" + "log" + "net/http" + "strconv" +) + +type NoteHandler struct { + DB *sqlx.DB +} + +func NewNoteHandler(db *sqlx.DB) *NoteHandler { + return &NoteHandler{DB: db} +} + +func (nh *NoteHandler) GetNotes(w http.ResponseWriter, r *http.Request) { + session, err := store.Get(r, sessionName) + if err != nil { + http.Error(w, "Failed to get session", http.StatusBadRequest) + return + } + + userID, ok := session.Values["userID"].(int) + if !ok { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + + note := models.Note{} + + notes, err := note.GetNotesByUser(nh.DB, userID) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + tmpl := template.Must(template.ParseFiles("templates/index.html")) + err = tmpl.Execute(w, notes) + if err != nil { + log.Println("Error while executing index.html:", err) + return + } +} + +func (nh *NoteHandler) CreateNoteForm(w http.ResponseWriter, r *http.Request) { + tmpl := template.Must(template.ParseFiles("templates/create.html")) + err := tmpl.Execute(w, nil) + if err != nil { + log.Println("Error while executing create.html:", err) + return + } +} + +func (nh *NoteHandler) CreateNote(w http.ResponseWriter, r *http.Request) { + session, err := store.Get(r, sessionName) + if err != nil { + http.Error(w, "Failed to get session", http.StatusBadRequest) + return + } + userID, ok := session.Values["userID"].(int) + if !ok { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + + title := r.FormValue("title") + content := r.FormValue("content") + + note := &models.Note{ + Title: title, + Content: content, + UserID: userID, + } + + if err := note.CreateNote(nh.DB); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + http.Redirect(w, r, "/notes", http.StatusFound) +} + +func (nh *NoteHandler) EditNoteForm(w http.ResponseWriter, r *http.Request) { + session, err := store.Get(r, sessionName) + if err != nil { + http.Error(w, "Failed to get session", http.StatusBadRequest) + return + } + + userID, ok := session.Values["userID"].(int) + if !ok { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + + vars := mux.Vars(r) + id, _ := strconv.Atoi(vars["id"]) + + note, err := models.GetNoteByID(nh.DB, id) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + log.Printf("полученная заметка GetNoteByID: %+v", note) + + if note.UserID != userID { + log.Printf("User %d tried to edit note %d belonging to user %d", userID, note.ID, note.UserID) + http.Error(w, "You do not have permission to edit this note", http.StatusForbidden) + return + } + + tmpl := template.Must(template.ParseFiles("templates/edit.html")) + err = tmpl.Execute(w, note) + if err != nil { + log.Println("Error while executing edit.html:", err) + return + } +} + +func (nh *NoteHandler) EditNote(w http.ResponseWriter, r *http.Request) { + session, err := store.Get(r, sessionName) + if err != nil { + http.Error(w, "Failed to get session", http.StatusBadRequest) + return + } + + userID, ok := session.Values["userID"].(int) + if !ok { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + + vars := mux.Vars(r) + id, err := strconv.Atoi(vars["id"]) + if err != nil { + http.Error(w, "Invalid note ID", http.StatusBadRequest) + return + } + + note, err := models.GetNoteByID(nh.DB, id) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + if note == nil { + http.Error(w, "Note not found", http.StatusNotFound) + return + } else { + if note.UserID != userID { + log.Printf("User %d tried to edit note %d belonging to user %d", userID, note.ID, note.UserID) + http.Error(w, "You do not have permission to edit this note", http.StatusForbidden) + return + } + } + + title := r.FormValue("title") + content := r.FormValue("content") + + note.Title = title + note.Content = content + + if err := note.UpdateNote(nh.DB); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + http.Redirect(w, r, "/notes", http.StatusFound) +} + +func (nh *NoteHandler) DeleteNote(w http.ResponseWriter, r *http.Request) { + session, err := store.Get(r, sessionName) + if err != nil { + http.Error(w, "Failed to get session", http.StatusBadRequest) + return + } + + userID, ok := session.Values["userID"].(int) + if !ok { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + + vars := mux.Vars(r) + id, err := strconv.Atoi(vars["id"]) + if err != nil { + http.Error(w, "Invalid note ID", http.StatusBadRequest) + return + } + + note, err := models.GetNoteByID(nh.DB, id) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + if note == nil { + http.Error(w, "Note not found", http.StatusNotFound) + return + } else { + if note.UserID != userID { + log.Printf("User %d tried to edit note %d belonging to user %d", userID, note.ID, note.UserID) + http.Error(w, "You do not have permission to edit this note", http.StatusForbidden) + return + } + } + + if err := note.DeleteNote(nh.DB); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + http.Redirect(w, r, "/notes", http.StatusSeeOther) +} diff --git a/handlers/session.go b/handlers/session.go new file mode 100644 index 0000000..30bacd0 --- /dev/null +++ b/handlers/session.go @@ -0,0 +1,34 @@ +package handlers + +import ( + "github.com/gorilla/sessions" + "log" + "os" +) + +var ( + store *sessions.CookieStore + sessionName string +) + +func InitSession() { + sessionSecret := os.Getenv("SESSION_SECRET") + if sessionSecret == "" { + log.Fatal("SESSION_SECRET is not set in .env file") + } + + sessionName = os.Getenv("SESSION_NAME") + if sessionName == "" { + log.Fatal("SESSION_NAME is not set in .env file") + } + + store = sessions.NewCookieStore([]byte(sessionSecret)) +} + +func GetStore() *sessions.CookieStore { + return store +} + +func GetSessionName() string { + return sessionName +} diff --git a/main.go b/main.go index da29a2c..9f76e52 100644 --- a/main.go +++ b/main.go @@ -1,4 +1,52 @@ package main +import ( + "NotesWebApp/database" + "NotesWebApp/handlers" + "github.com/gorilla/mux" + "github.com/joho/godotenv" + "log" + "net/http" +) + func main() { + err := godotenv.Load(".env") // Загружаем переменные окружения + if err != nil { + log.Fatal("Error loading .env file") + } + + handlers.InitSession() // Инициализация сессии + + db, err := database.InitDB() // инициализация базы + if err != nil { + log.Fatal(err) + } + defer db.Close() + + router := mux.NewRouter() // инициализация роутера + + // инициализация обработчиков + noteHandler := handlers.NewNoteHandler(db) + authHandler := handlers.NewAuthHandler(db) + + // маршруты заметок + router.HandleFunc("/notes", noteHandler.GetNotes).Methods("GET") + router.HandleFunc("/notes/create", noteHandler.CreateNoteForm).Methods("GET") + router.HandleFunc("/notes/create", noteHandler.CreateNote).Methods("POST") + router.HandleFunc("/notes/edit/{id}", noteHandler.EditNoteForm).Methods("GET") + router.HandleFunc("/notes/edit/{id}", noteHandler.EditNote).Methods("POST") + router.HandleFunc("/notes/delete/{id}", noteHandler.DeleteNote).Methods("POST") + + // маршруты аутентификации + router.HandleFunc("/", authHandler.Index).Methods("GET") + router.HandleFunc("/login", authHandler.LoginForm).Methods("GET") + router.HandleFunc("/login", authHandler.Login).Methods("POST") + router.HandleFunc("/register", authHandler.RegisterForm).Methods("GET") + router.HandleFunc("/register", authHandler.Register).Methods("POST") + router.HandleFunc("/logout", authHandler.Logout).Methods("POST") + + router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) + + log.Println("Server is running on port 8080...") + log.Fatal(http.ListenAndServe(":8080", router)) } diff --git a/models/note.go b/models/note.go index 61a8e95..f86179d 100644 --- a/models/note.go +++ b/models/note.go @@ -1,6 +1,8 @@ package models import ( + "database/sql" + "errors" "time" "github.com/jmoiron/sqlx" @@ -44,8 +46,14 @@ func (n *Note) GetNotesByUser(db *sqlx.DB, userID int) ([]Note, error) { func GetNoteByID(db *sqlx.DB, id int) (*Note, error) { var note Note - query := `SELECT id, title, content, created_at, updated_at FROM notes WHERE id=$1` + query := `SELECT id, title, content, user_id, created_at, updated_at FROM notes WHERE id=$1` err := db.Get(¬e, query, id) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, nil + } + return nil, err + } return ¬e, err } diff --git a/models/user.go b/models/user.go index e6e34a6..aeff2db 100644 --- a/models/user.go +++ b/models/user.go @@ -2,7 +2,6 @@ package models import ( "github.com/jmoiron/sqlx" - "golang.org/x/crypto/bcrypt" ) type User struct { @@ -12,13 +11,8 @@ type User struct { } func (u *User) CreateUser(db *sqlx.DB) error { - hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost) - if err != nil { - return err - } - query := `INSERT into users (email, password) VALUES ($1, $2) RETURNING id` - return db.QueryRowx(query, u.Email, string(hashedPassword)).Scan(&u.ID) + return db.QueryRowx(query, u.Email, u.Password).Scan(&u.ID) } func GetUserByEmail(db *sqlx.DB, email string) (*User, error) { diff --git a/static/styles.css b/static/styles.css new file mode 100644 index 0000000..9d239d8 --- /dev/null +++ b/static/styles.css @@ -0,0 +1,47 @@ +body { + font-family: Arial, sans-serif; + margin: 20px; +} + +h1 { + color: #333; +} + +form { + margin-bottom: 20px; +} + +label { + display: block; + margin-bottom: 5px; +} + +input, textarea { + width: 100%; + padding: 8px; + margin-bottom: 10px; + border: 1px solid #ccc; + border-radius: 4px; +} + +button { + padding: 10px 15px; + background-color: #28a745; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; +} + +button:hover { + background-color: #218838; +} + +a { + color: #007bff; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} \ No newline at end of file diff --git a/templates/create.html b/templates/create.html new file mode 100644 index 0000000..abe8031 --- /dev/null +++ b/templates/create.html @@ -0,0 +1,21 @@ + + + + + + Create Note + + + +

Create New Note

+
+ + +
+ + +
+ +
+ + \ No newline at end of file diff --git a/templates/edit.html b/templates/edit.html new file mode 100644 index 0000000..d9efb03 --- /dev/null +++ b/templates/edit.html @@ -0,0 +1,21 @@ + + + + + + Edit Note + + + +

Edit Note

+
+ + +
+ + +
+ +
+ + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..29e942c --- /dev/null +++ b/templates/index.html @@ -0,0 +1,28 @@ + + + + + + Notes App + + + +

My Notes

+ Create New Note +
    + {{range .}} +
  • +

    {{.Title}}

    +

    {{.Content}}

    + Edit +
    + +
    +
  • + {{end}} +
+
+ +
+ + \ No newline at end of file diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..d6a52a4 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,22 @@ + + + + + + Login + + + +

Login

+
+ + +
+ + +
+ +
+ Register + + \ No newline at end of file diff --git a/templates/register.html b/templates/register.html new file mode 100644 index 0000000..cab4517 --- /dev/null +++ b/templates/register.html @@ -0,0 +1,22 @@ + + + + + + Register + + + +

Register

+
+ + +
+ + +
+ +
+ Login + + \ No newline at end of file From d3a98605d2ee9e0b80b7b8192c8ae5a2f6e66f0c Mon Sep 17 00:00:00 2001 From: Krovaldo Date: Wed, 22 Jan 2025 01:10:45 +0300 Subject: [PATCH 09/14] fixed imports --- handlers/auth_handler.go | 8 +++++--- handlers/note_handler.go | 8 +++++--- handlers/session.go | 3 ++- main.go | 10 ++++++---- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/handlers/auth_handler.go b/handlers/auth_handler.go index 14b7966..a91a7e6 100644 --- a/handlers/auth_handler.go +++ b/handlers/auth_handler.go @@ -1,12 +1,14 @@ package handlers import ( - "NotesWebApp/models" - "github.com/jmoiron/sqlx" - "golang.org/x/crypto/bcrypt" "html/template" "log" "net/http" + + "github.com/jmoiron/sqlx" + "golang.org/x/crypto/bcrypt" + + "NotesWebApp/models" ) type AuthHandler struct { diff --git a/handlers/note_handler.go b/handlers/note_handler.go index 7474e0f..b22ad17 100644 --- a/handlers/note_handler.go +++ b/handlers/note_handler.go @@ -1,13 +1,15 @@ package handlers import ( - "NotesWebApp/models" - "github.com/gorilla/mux" - "github.com/jmoiron/sqlx" "html/template" "log" "net/http" "strconv" + + "github.com/gorilla/mux" + "github.com/jmoiron/sqlx" + + "NotesWebApp/models" ) type NoteHandler struct { diff --git a/handlers/session.go b/handlers/session.go index 30bacd0..a1afa56 100644 --- a/handlers/session.go +++ b/handlers/session.go @@ -1,9 +1,10 @@ package handlers import ( - "github.com/gorilla/sessions" "log" "os" + + "github.com/gorilla/sessions" ) var ( diff --git a/main.go b/main.go index 9f76e52..6713e79 100644 --- a/main.go +++ b/main.go @@ -1,12 +1,14 @@ package main import ( - "NotesWebApp/database" - "NotesWebApp/handlers" - "github.com/gorilla/mux" - "github.com/joho/godotenv" "log" "net/http" + + "github.com/gorilla/mux" + "github.com/joho/godotenv" + + "NotesWebApp/database" + "NotesWebApp/handlers" ) func main() { From cf76c0e48f4a3e82233fd75b1bce19120818aa80 Mon Sep 17 00:00:00 2001 From: Krovaldo Date: Wed, 22 Jan 2025 01:29:58 +0300 Subject: [PATCH 10/14] lint fix --- go.mod | 6 ++---- handlers/auth_handler.go | 3 +-- handlers/note_handler.go | 3 +-- main.go | 5 ++--- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 4abc18d..a85ff72 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module NotesWebApp go 1.23.4 require ( + github.com/gorilla/mux v1.8.1 github.com/gorilla/sessions v1.4.0 github.com/jmoiron/sqlx v1.4.0 github.com/joho/godotenv v1.5.1 @@ -10,7 +11,4 @@ require ( golang.org/x/crypto v0.32.0 ) -require ( - github.com/gorilla/mux v1.8.1 // indirect - github.com/gorilla/securecookie v1.1.2 // indirect -) +require github.com/gorilla/securecookie v1.1.2 // indirect diff --git a/handlers/auth_handler.go b/handlers/auth_handler.go index a91a7e6..5c9bbbd 100644 --- a/handlers/auth_handler.go +++ b/handlers/auth_handler.go @@ -5,10 +5,9 @@ import ( "log" "net/http" + "NotesWebApp/models" "github.com/jmoiron/sqlx" "golang.org/x/crypto/bcrypt" - - "NotesWebApp/models" ) type AuthHandler struct { diff --git a/handlers/note_handler.go b/handlers/note_handler.go index b22ad17..24ae2fb 100644 --- a/handlers/note_handler.go +++ b/handlers/note_handler.go @@ -6,10 +6,9 @@ import ( "net/http" "strconv" + "NotesWebApp/models" "github.com/gorilla/mux" "github.com/jmoiron/sqlx" - - "NotesWebApp/models" ) type NoteHandler struct { diff --git a/main.go b/main.go index 6713e79..5a34728 100644 --- a/main.go +++ b/main.go @@ -4,11 +4,10 @@ import ( "log" "net/http" - "github.com/gorilla/mux" - "github.com/joho/godotenv" - "NotesWebApp/database" "NotesWebApp/handlers" + "github.com/gorilla/mux" + "github.com/joho/godotenv" ) func main() { From 9920726efbf26026188dce1e364e123809906452 Mon Sep 17 00:00:00 2001 From: Krovaldo Date: Thu, 23 Jan 2025 20:35:53 +0300 Subject: [PATCH 11/14] migrations and linter fix --- .github/workflows/ci.yml | 2 +- .golangci.yml | 1 - handlers/auth_handler.go | 9 +++++---- handlers/note_handler.go | 26 +++++++++++++------------- main.go | 23 +++++++++++++++++++++-- migrations/20250123095246_initial.sql | 19 +++++++++++++++++++ 6 files changed, 59 insertions(+), 21 deletions(-) create mode 100644 migrations/20250123095246_initial.sql diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12e5024..c4b6ab2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: uses: golangci/golangci-lint-action@v2 with: version: latest - args: --config=.golangci.yml + args: --config=.765golangci.yml test: runs-on: ubuntu-latest diff --git a/.golangci.yml b/.golangci.yml index ee6f80a..9242408 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -16,7 +16,6 @@ linters: - durationcheck - errorlint - exhaustive - - exportloopref - funlen - gci - gocognit diff --git a/handlers/auth_handler.go b/handlers/auth_handler.go index 5c9bbbd..1627cf0 100644 --- a/handlers/auth_handler.go +++ b/handlers/auth_handler.go @@ -5,9 +5,10 @@ import ( "log" "net/http" - "NotesWebApp/models" "github.com/jmoiron/sqlx" "golang.org/x/crypto/bcrypt" + + "NotesWebApp/models" ) type AuthHandler struct { @@ -32,7 +33,7 @@ func (ah *AuthHandler) Index(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/notes", http.StatusFound) } -func (ah *AuthHandler) LoginForm(w http.ResponseWriter, r *http.Request) { +func (ah *AuthHandler) LoginForm(w http.ResponseWriter, _ *http.Request) { tmpl := template.Must(template.ParseFiles("templates/login.html")) err := tmpl.Execute(w, nil) if err != nil { @@ -68,11 +69,11 @@ func (ah *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { log.Println("Can't save session:", err) return } - log.Println("User logged in successfully:", user.ID) //123 + log.Println("User logged in successfully:", user.ID) http.Redirect(w, r, "/notes", http.StatusFound) } -func (ah *AuthHandler) RegisterForm(w http.ResponseWriter, r *http.Request) { +func (ah *AuthHandler) RegisterForm(w http.ResponseWriter, _ *http.Request) { tmpl := template.Must(template.ParseFiles("templates/register.html")) err := tmpl.Execute(w, nil) if err != nil { diff --git a/handlers/note_handler.go b/handlers/note_handler.go index 24ae2fb..b5737f7 100644 --- a/handlers/note_handler.go +++ b/handlers/note_handler.go @@ -47,7 +47,7 @@ func (nh *NoteHandler) GetNotes(w http.ResponseWriter, r *http.Request) { } } -func (nh *NoteHandler) CreateNoteForm(w http.ResponseWriter, r *http.Request) { +func (nh *NoteHandler) CreateNoteForm(w http.ResponseWriter, _ *http.Request) { tmpl := template.Must(template.ParseFiles("templates/create.html")) err := tmpl.Execute(w, nil) if err != nil { @@ -151,12 +151,12 @@ func (nh *NoteHandler) EditNote(w http.ResponseWriter, r *http.Request) { if note == nil { http.Error(w, "Note not found", http.StatusNotFound) return - } else { - if note.UserID != userID { - log.Printf("User %d tried to edit note %d belonging to user %d", userID, note.ID, note.UserID) - http.Error(w, "You do not have permission to edit this note", http.StatusForbidden) - return - } + } + + if note.UserID != userID { + log.Printf("User %d tried to edit note %d belonging to user %d", userID, note.ID, note.UserID) + http.Error(w, "You do not have permission to edit this note", http.StatusForbidden) + return } title := r.FormValue("title") @@ -201,12 +201,12 @@ func (nh *NoteHandler) DeleteNote(w http.ResponseWriter, r *http.Request) { if note == nil { http.Error(w, "Note not found", http.StatusNotFound) return - } else { - if note.UserID != userID { - log.Printf("User %d tried to edit note %d belonging to user %d", userID, note.ID, note.UserID) - http.Error(w, "You do not have permission to edit this note", http.StatusForbidden) - return - } + } + + if note.UserID != userID { + log.Printf("User %d tried to edit note %d belonging to user %d", userID, note.ID, note.UserID) + http.Error(w, "You do not have permission to edit this note", http.StatusForbidden) + return } if err := note.DeleteNote(nh.DB); err != nil { diff --git a/main.go b/main.go index 5a34728..11db717 100644 --- a/main.go +++ b/main.go @@ -3,13 +3,23 @@ package main import ( "log" "net/http" + "time" "NotesWebApp/database" "NotesWebApp/handlers" + "github.com/gorilla/mux" + "github.com/jmoiron/sqlx" "github.com/joho/godotenv" ) +func runServer(db *sqlx.DB, server *http.Server) error { + defer db.Close() + + log.Println("Server is running on port 8080...") + return server.ListenAndServe() +} + func main() { err := godotenv.Load(".env") // Загружаем переменные окружения if err != nil { @@ -22,7 +32,6 @@ func main() { if err != nil { log.Fatal(err) } - defer db.Close() router := mux.NewRouter() // инициализация роутера @@ -48,6 +57,16 @@ func main() { router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) + server := &http.Server{ + Addr: ":8080", + Handler: router, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 30 * time.Second, + } + log.Println("Server is running on port 8080...") - log.Fatal(http.ListenAndServe(":8080", router)) + if err := runServer(db, server); err != nil { + log.Fatal("Server error:", err) + } } diff --git a/migrations/20250123095246_initial.sql b/migrations/20250123095246_initial.sql new file mode 100644 index 0000000..1a86573 --- /dev/null +++ b/migrations/20250123095246_initial.sql @@ -0,0 +1,19 @@ +-- +goose Up +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + email VARCHAR(255) UNIQUE NOT NULL, + password VARCHAR(255) NOT NULL +); + +CREATE TABLE notes ( + id SERIAL PRIMARY KEY, + title VARCHAR(255) NOT NULL, + content TEXT NOT NULL, + user_id INT REFERENCES users(id) ON DELETE CASCADE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- +goose Down +DROP TABLE notes; +DROP TABLE users; \ No newline at end of file From aa51527ad6b6d2f4de1d8e42dd4e44d94e6f9d6a Mon Sep 17 00:00:00 2001 From: Krovaldo Date: Thu, 23 Jan 2025 20:41:51 +0300 Subject: [PATCH 12/14] ci config --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c4b6ab2..12e5024 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: uses: golangci/golangci-lint-action@v2 with: version: latest - args: --config=.765golangci.yml + args: --config=.golangci.yml test: runs-on: ubuntu-latest From 33f1486aa79f25b5da5052996a9132cd1c384090 Mon Sep 17 00:00:00 2001 From: Krovaldo Date: Thu, 23 Jan 2025 20:46:05 +0300 Subject: [PATCH 13/14] ci config --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12e5024..9deef61 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: uses: golangci/golangci-lint-action@v2 with: version: latest - args: --config=.golangci.yml + args: --out-format=colored-line-number --config=.golangci.yml --fix test: runs-on: ubuntu-latest From bf0572803cc5acb4987bc7ca2438ee926b89ddbb Mon Sep 17 00:00:00 2001 From: Krovaldo Date: Thu, 23 Jan 2025 20:47:32 +0300 Subject: [PATCH 14/14] ci config --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9deef61..a9640f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: uses: golangci/golangci-lint-action@v2 with: version: latest - args: --out-format=colored-line-number --config=.golangci.yml --fix + args: --config=.golangci.yml --fix test: runs-on: ubuntu-latest