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
15 changes: 15 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM golang:1.23.4

WORKDIR /app

COPY go.mod go.sum ./

RUN go mod tidy

COPY . .

RUN go build -o notesApp .

EXPOSE 8080

CMD ["./notesApp"]
15 changes: 15 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,20 @@ services:
- "${APP_DB_PORT}:5432"
volumes:
- postgres_data:/var/lib/postgresql/data

app:
build: .
container_name: notes_app
depends_on:
- db
environment:
DB_HOST: db
DB_PORT: 5432
DB_USER: ${APP_DB_USER}
DB_PASSWORD: ${APP_DB_PASSWORD}
DB_NAME: ${APP_DB_NAME}
ports:
- "8080:8080"

volumes:
postgres_data:
9 changes: 8 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@ module NotesWebApp
go 1.23.4

require (
github.com/DATA-DOG/go-sqlmock v1.5.2
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
github.com/stretchr/testify v1.10.0
golang.org/x/crypto v0.32.0
)

require github.com/gorilla/securecookie v1.1.2 // indirect
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
13 changes: 13 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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=
Expand All @@ -14,9 +18,18 @@ 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/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
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=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
1 change: 1 addition & 0 deletions handlers/auth_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func (ah *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
user, err := models.GetUserByEmail(ah.DB, email)
if err != nil {
http.Error(w, "User not found: invalid credentials", http.StatusUnauthorized)
return
}

if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
Expand Down
1 change: 0 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ func main() {
IdleTimeout: 30 * time.Second,
}

log.Println("Server is running on port 8080...")
if err := runServer(db, server); err != nil {
log.Fatal("Server error:", err)
}
Expand Down
209 changes: 209 additions & 0 deletions models/note_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
package models

import (
"database/sql"
"errors"
"regexp"
"testing"
"time"

"github.com/DATA-DOG/go-sqlmock"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/assert"
)

func TestNote_CreateNote(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("Failed to create sqlmock: %v", err)
}
defer db.Close()

sqlxDB := sqlx.NewDb(db, "sqlmock")

note := &Note{
Title: "Test Title",
Content: "Test Content",
UserID: 1,
}

rows := sqlmock.NewRows([]string{"id", "created_at", "updated_at"}).
AddRow(1, time.Now(), time.Now())

mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO notes (title, content, user_id)
VALUES ($1, $2, $3) RETURNING id, created_at, updated_at`)).
WithArgs(note.Title, note.Content, note.UserID).
WillReturnRows(rows)

err = note.CreateNote(sqlxDB)
assert.NoError(t, err)

assert.NoError(t, mock.ExpectationsWereMet())
}

func TestNote_UpdateNote(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("Faild to create sqlmock: %v", err)
}
defer db.Close()

sqlxDB := sqlx.NewDb(db, "sqlmock")

note := &Note{
ID: 1,
Title: "Updated Title",
Content: "Updated Content",
UpdatedAt: time.Now(),
}

mock.ExpectExec(regexp.QuoteMeta(`UPDATE notes SET title=?, content=?, updated_at=?
WHERE id=?`)).
WithArgs(note.Title, note.Content, sqlmock.AnyArg(), note.ID).
WillReturnResult(sqlmock.NewResult(1, 1))

err = note.UpdateNote(sqlxDB)
assert.NoError(t, err)
assert.NoError(t, mock.ExpectationsWereMet())
}

func TestNote_DeleteNote(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("Failed to create sqlmock: %v", err)
}
defer db.Close()

sqlxDB := sqlx.NewDb(db, "sqlmock")

note := &Note{
ID: 1,
}

mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM notes WHERE id=?`)).WithArgs(note.ID).
WillReturnResult(sqlmock.NewResult(1, 1))

err = note.DeleteNote(sqlxDB)
assert.NoError(t, err)
assert.NoError(t, mock.ExpectationsWereMet())
}

func TestGetNotesByUser(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("Failed to create sqlmock: %v", err)
}
defer db.Close()

sqlxDB := sqlx.NewDb(db, "sqlmock")

userID := 1
expectedNotes := []Note{
{ID: 1, Title: "Note 1", Content: "Content 1", CreatedAt: time.Now(), UpdatedAt: time.Now()},
{ID: 2, Title: "Note 2", Content: "Content 2", CreatedAt: time.Now(), UpdatedAt: time.Now()},
}

rows := sqlmock.NewRows([]string{"id", "title", "content", "created_at", "updated_at"}).
AddRow(expectedNotes[0].ID, expectedNotes[0].Title, expectedNotes[0].Content, expectedNotes[0].CreatedAt,
expectedNotes[0].UpdatedAt).
AddRow(expectedNotes[1].ID, expectedNotes[1].Title, expectedNotes[1].Content, expectedNotes[1].CreatedAt,
expectedNotes[1].UpdatedAt)

mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, title, content, created_at, updated_at
FROM notes WHERE user_id=$1`)).
WithArgs(userID).
WillReturnRows(rows)

note := &Note{}

notes, err := note.GetNotesByUser(sqlxDB, userID)

assert.NoError(t, err)
assert.Equal(t, expectedNotes, notes)
assert.NoError(t, mock.ExpectationsWereMet())
}

func TestGetNoteByID(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("Failed to create sqlmock: %v", err)
}
defer db.Close()

sqlxDB := sqlx.NewDb(db, "sqlmock")

noteID := 1
expectedNote := &Note{
ID: noteID,
Title: "Test Note",
Content: "Test Content",
UserID: 1,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}

rows := sqlmock.NewRows([]string{"id", "title", "content", "user_id", "created_at", "updated_at"}).
AddRow(expectedNote.ID, expectedNote.Title, expectedNote.Content, expectedNote.UserID,
time.Now(), time.Now())

mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, title, content, user_id, created_at, updated_at
FROM notes WHERE id=$1`)).
WithArgs(noteID).
WillReturnRows(rows)

note, err := GetNoteByID(sqlxDB, noteID)
assert.NoError(t, err)
assert.NotNil(t, note)

assert.Equal(t, expectedNote.ID, noteID)
assert.Equal(t, expectedNote.Title, note.Title)
assert.Equal(t, expectedNote.Content, note.Content)
assert.Equal(t, expectedNote.UserID, note.UserID)
assert.NoError(t, mock.ExpectationsWereMet())
}

func TestGetNoteByID_Error(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("Failed to create sqlmock: %v", err)
}
defer db.Close()

sqlxDB := sqlx.NewDb(db, "sqlmock")

noteID := 1
expectedError := errors.New("database connection error")
mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, title, content, user_id, created_at, updated_at
FROM notes WHERE id=$1`)).
WithArgs(noteID).
WillReturnError(expectedError)

note, err := GetNoteByID(sqlxDB, noteID)
assert.Error(t, err)
assert.Nil(t, note)
assert.Equal(t, expectedError, err)

assert.NoError(t, mock.ExpectationsWereMet())
}

func TestGetNoteByID_NotFound(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("Failed to create sqlmock: %v", err)
}
defer db.Close()

sqlxDB := sqlx.NewDb(db, "sqlmock")

userID := 1
mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, title, content, user_id, created_at, updated_at
FROM notes WHERE id=$1`)).
WithArgs(userID).
WillReturnError(sql.ErrNoRows)

note, err := GetNoteByID(sqlxDB, userID)
assert.NoError(t, err)
assert.Nil(t, note)

assert.NoError(t, mock.ExpectationsWereMet())
}
2 changes: 1 addition & 1 deletion models/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type User struct {
}

func (u *User) CreateUser(db *sqlx.DB) error {
query := `INSERT into users (email, password) VALUES ($1, $2) RETURNING id`
query := `INSERT INTO users (email, password) VALUES ($1, $2) RETURNING id`
return db.QueryRowx(query, u.Email, u.Password).Scan(&u.ID)
}

Expand Down
63 changes: 63 additions & 0 deletions models/user_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package models

import (
"github.com/DATA-DOG/go-sqlmock"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/assert"
"regexp"
"testing"
)

func TestUser_CreateUser(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("Failed to create sqlmock: %v", err)
}
defer db.Close()

sqlxDB := sqlx.NewDb(db, "sqlmock")

user := &User{
Email: "test@example.com",
Password: "hashedpassword",
}

rows := sqlmock.NewRows([]string{"id"}).AddRow(1)

mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO users (email, password) VALUES ($1, $2) RETURNING id`)).
WithArgs(user.Email, user.Password).
WillReturnRows(rows)

err = user.CreateUser(sqlxDB)
assert.NoError(t, err)
assert.NoError(t, mock.ExpectationsWereMet())
}

func TestGetUserByEmail(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("Failed to create sqlmock: %v", err)
}
defer db.Close()

sqlxDB := sqlx.NewDb(db, "sqlmock")

email := "test@example.com"
expectedUser := &User{
ID: 1,
Email: email,
Password: "hashedpassword",
}

rows := sqlmock.NewRows([]string{"id", "email", "password"}).
AddRow(expectedUser.ID, expectedUser.Email, expectedUser.Password)

mock.ExpectQuery(`SELECT id, email, password FROM users WHERE email=\$1`).
WithArgs(email).
WillReturnRows(rows)

user, err := GetUserByEmail(sqlxDB, email)
assert.NoError(t, err)
assert.Equal(t, expectedUser, user)
assert.NoError(t, mock.ExpectationsWereMet())
}
Binary file added notesApp
Binary file not shown.
Loading