Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
uses: golangci/golangci-lint-action@v2
with:
version: latest
args: --config=.golangci.yml
args: --config=.golangci.yml --fix

test:
runs-on: ubuntu-latest
Expand Down
6 changes: 0 additions & 6 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,11 @@ linters:
enable:
- asciicheck
- bodyclose
- deadcode
- depguard
- dogsled
- dupl
- durationcheck
- errorlint
- exhaustive
- exportloopref
- funlen
- gci
- gocognit
Expand All @@ -35,7 +32,6 @@ linters:
- gosec
- gosimple
- govet
- ifshort
- importas
- ineffassign
- lll
Expand All @@ -51,7 +47,6 @@ linters:
- rowserrcheck
- sqlclosecheck
- staticcheck
- structcheck
- stylecheck
- tagliatelle
- thelper
Expand All @@ -60,6 +55,5 @@ linters:
- unconvert
- unparam
- unused
- varcheck
- wastedassign
- whitespace
25 changes: 25 additions & 0 deletions database/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package database

import (
"fmt"
"os"

"github.com/jmoiron/sqlx"
"github.com/joho/godotenv"
_ "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: %w", err)
}

db, err := sqlx.Connect("postgres", os.Getenv("DATABASE_URL"))
if err != nil {
return nil, fmt.Errorf("error connecting to database: %w", err)
}

fmt.Println("Successfully connected to the database")
return db, nil
}
14 changes: 14 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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:
11 changes: 11 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
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
github.com/lib/pq v1.10.9
golang.org/x/crypto v0.32.0
)

require github.com/gorilla/securecookie v1.1.2 // indirect
22 changes: 22 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
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=
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=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
131 changes: 131 additions & 0 deletions handlers/auth_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package handlers

import (
"html/template"
"log"
"net/http"

"github.com/jmoiron/sqlx"
"golang.org/x/crypto/bcrypt"

"NotesWebApp/models"
)

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, _ *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)
http.Redirect(w, r, "/notes", http.StatusFound)
}

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 {
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)
}
Loading
Loading