From 10e4ba0c967b72f9d7e240521b2d078bce128788 Mon Sep 17 00:00:00 2001 From: Taehoya Date: Tue, 31 Oct 2023 18:15:02 -0500 Subject: [PATCH 01/11] implement save trip repository --- server/go.mod | 4 +-- server/go.sum | 4 +++ .../pkg/repositories/trip/repository.go | 35 +++++++++++++++++-- .../pkg/repositories/trip/repository_test.go | 34 ++++++++++++++++++ .../pkg/repositories/trip/setup_test.sql | 4 +++ .../pkg/repositories/trip/teardown_test.sql | 2 ++ .../000007_add_user_id_attribute.down.sql | 2 ++ .../000007_add_user_id_attribute.up.sql | 2 ++ 8 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 server/internal/pkg/repositories/trip/setup_test.sql create mode 100644 server/internal/pkg/repositories/trip/teardown_test.sql create mode 100644 server/migration/000007_add_user_id_attribute.down.sql create mode 100644 server/migration/000007_add_user_id_attribute.up.sql diff --git a/server/go.mod b/server/go.mod index 0d276f5..283bd06 100644 --- a/server/go.mod +++ b/server/go.mod @@ -3,7 +3,8 @@ module github.com/Taehoya/pocket-mate go 1.19 require ( - github.com/Taehoya/go-utils v0.0.0-20231024000027-f8dd9bc6aa30 + github.com/Taehoya/go-utils v0.0.2 + github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/golang-migrate/migrate/v4 v4.16.2 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.4 @@ -11,7 +12,6 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/server/go.sum b/server/go.sum index b9ed711..42940d4 100644 --- a/server/go.sum +++ b/server/go.sum @@ -4,6 +4,10 @@ github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6Xge github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Taehoya/go-utils v0.0.0-20231024000027-f8dd9bc6aa30 h1:525HkzKPW858IGjAhNqynIzlUmDV0ZM4+Q4K04O964o= github.com/Taehoya/go-utils v0.0.0-20231024000027-f8dd9bc6aa30/go.mod h1:YhqoNnC/v7vQSWa39AGi90f2/l9zAEAwXG2r+kWoAr4= +github.com/Taehoya/go-utils v0.0.1 h1:Om3Rdd2oFFA3XF2YyUy3HdvrzbHvbi18v8VflZT4Lkw= +github.com/Taehoya/go-utils v0.0.1/go.mod h1:YhqoNnC/v7vQSWa39AGi90f2/l9zAEAwXG2r+kWoAr4= +github.com/Taehoya/go-utils v0.0.2 h1:6K3JkXdD3ZQuz8t41c0+EF5PKEyoAI6RMjDM6BhCiIw= +github.com/Taehoya/go-utils v0.0.2/go.mod h1:YhqoNnC/v7vQSWa39AGi90f2/l9zAEAwXG2r+kWoAr4= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.10.0 h1:qtNZduETEIWJVIyDl01BeNxur2rW9OwTQ/yBqFRkKEk= diff --git a/server/internal/pkg/repositories/trip/repository.go b/server/internal/pkg/repositories/trip/repository.go index 7f2e9ac..2a8001a 100644 --- a/server/internal/pkg/repositories/trip/repository.go +++ b/server/internal/pkg/repositories/trip/repository.go @@ -1,6 +1,7 @@ package repository import ( + "context" "database/sql" "fmt" "log" @@ -18,8 +19,8 @@ func NewTripRepository(db *sql.DB) *TripRepository { } } -func (r *TripRepository) GetTripAll() ([]entities.Trip, error) { - var trips []entities.Trip +func (r *TripRepository) GetTripAll() ([]*entities.Trip, error) { + var trips []*entities.Trip rows, err := r.db.Query("SELECT * FROM pm.trip;") if err != nil { @@ -29,7 +30,7 @@ func (r *TripRepository) GetTripAll() ([]entities.Trip, error) { defer rows.Close() for rows.Next() { - var trip entities.Trip + var trip *entities.Trip if err := rows.Scan(&trip.ID, &trip.Name, &trip.Budget, &trip.CountryId, &trip.Description, &trip.StartDateTime, &trip.EndDateTime); err != nil { log.Printf("failed to scan row: %v\n", err) return nil, fmt.Errorf("internal server error") @@ -44,3 +45,31 @@ func (r *TripRepository) GetTripAll() ([]entities.Trip, error) { return trips, nil } + +func (r *TripRepository) SaveTrip(ctx context.Context, name string, userId int, budget float64, countryId int, description string, startDateTime string, endDateTime string) error { + query := ` + INSERT INTO trips + (name, user_id, budget, country_id, description, start_date_time, end_date_time) + VALUES + (?, ?, ?, ?, ?, ?, ?); + ` + + result, err := r.db.ExecContext(ctx, query, name, userId, budget, countryId, description, startDateTime, endDateTime) + if err != nil { + log.Printf("failed to execute query: %v\n", err) + return fmt.Errorf("internal Server Error") + } + + rows, err := result.RowsAffected() + if err != nil { + log.Printf("failed to get affected rows: %\nv", err) + return fmt.Errorf("internal Server Error") + } + + if rows != 1 { + log.Printf("expected 1 affected row, got %d\n", rows) + return fmt.Errorf("internal Server Error") + } + + return nil +} diff --git a/server/internal/pkg/repositories/trip/repository_test.go b/server/internal/pkg/repositories/trip/repository_test.go index 50a4378..d4bcd40 100644 --- a/server/internal/pkg/repositories/trip/repository_test.go +++ b/server/internal/pkg/repositories/trip/repository_test.go @@ -1 +1,35 @@ package repository + +import ( + "context" + "testing" + + "github.com/Taehoya/go-utils/mysqltest" + "github.com/stretchr/testify/assert" +) + +func TestSaveTrip(t *testing.T) { + db, err := mysqltest.InitDB() + assert.NoError(t, err) + defer db.Close() + + repository := NewTripRepository(db) + + t.Run("Successfully save trip", func(t *testing.T) { + defer mysqltest.SetUp(db, "./teardown_test.sql") + mysqltest.SetUp(db, "./teardown_test.sql") + mysqltest.SetUp(db, "./setup_test.sql") + + ctx := context.TODO() + name := "test-name" + userId := 1 + budget := 1000.0 + countryId := 1 + description := "test-description" + startDateTime := "2023-10-31 00:00:00" + endDateTime := "2023-10-31 00:00:00" + + err := repository.SaveTrip(ctx, name, userId, budget, countryId, description, startDateTime, endDateTime) + assert.NoError(t, err) + }) +} diff --git a/server/internal/pkg/repositories/trip/setup_test.sql b/server/internal/pkg/repositories/trip/setup_test.sql new file mode 100644 index 0000000..148c278 --- /dev/null +++ b/server/internal/pkg/repositories/trip/setup_test.sql @@ -0,0 +1,4 @@ +INSERT INTO users + (id, nickname, email, password) +VALUES + (1, "test-nickname", "test-email", "test-password"); \ No newline at end of file diff --git a/server/internal/pkg/repositories/trip/teardown_test.sql b/server/internal/pkg/repositories/trip/teardown_test.sql new file mode 100644 index 0000000..3fe9593 --- /dev/null +++ b/server/internal/pkg/repositories/trip/teardown_test.sql @@ -0,0 +1,2 @@ +DELETE FROM trips; +DELETE FROM users; diff --git a/server/migration/000007_add_user_id_attribute.down.sql b/server/migration/000007_add_user_id_attribute.down.sql new file mode 100644 index 0000000..d5226e0 --- /dev/null +++ b/server/migration/000007_add_user_id_attribute.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE `trips` DROP FOREIGN KEY `fk_trip_user`; +ALTER TABLE `trips` DROP COLUMN `user_id`; \ No newline at end of file diff --git a/server/migration/000007_add_user_id_attribute.up.sql b/server/migration/000007_add_user_id_attribute.up.sql new file mode 100644 index 0000000..4694f50 --- /dev/null +++ b/server/migration/000007_add_user_id_attribute.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE `trips` ADD COLUMN `user_id` BIGINT NOT NULL AFTER `name`; +ALTER TABLE `trips` ADD CONSTRAINT `fk_trip_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`); \ No newline at end of file From 40e1cbbdec6ca4644c569e56ab75a49b419ca83f Mon Sep 17 00:00:00 2001 From: Taehoya Date: Tue, 31 Oct 2023 18:15:38 -0500 Subject: [PATCH 02/11] update taehoya/go-util package --- .../repositories/country/repository_test.go | 10 ++++---- .../pkg/repositories/user/repository_test.go | 24 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/server/internal/pkg/repositories/country/repository_test.go b/server/internal/pkg/repositories/country/repository_test.go index 734574c..6323b08 100644 --- a/server/internal/pkg/repositories/country/repository_test.go +++ b/server/internal/pkg/repositories/country/repository_test.go @@ -4,22 +4,22 @@ import ( "context" "testing" - "github.com/Taehoya/go-utils/mysql" + "github.com/Taehoya/go-utils/mysqltest" "github.com/Taehoya/pocket-mate/internal/pkg/entities" "github.com/stretchr/testify/assert" ) func TestGetCountryAll(t *testing.T) { - db, err := mysql.InitTestDB() + db, err := mysqltest.InitDB() assert.NoError(t, err) defer db.Close() repository := NewCountryRepository(db) t.Run("successfully get list of countries", func(t *testing.T) { - defer mysql.SetUp(db, "./teardown_test.sql") - mysql.SetUp(db, "./teardown_test.sql") - mysql.SetUp(db, "./setup_test.sql") + defer mysqltest.SetUp(db, "./teardown_test.sql") + mysqltest.SetUp(db, "./teardown_test.sql") + mysqltest.SetUp(db, "./setup_test.sql") expected := []*entities.Country{ entities.NewCountry(1, "AF", "Afghanistan", "؋"), diff --git a/server/internal/pkg/repositories/user/repository_test.go b/server/internal/pkg/repositories/user/repository_test.go index e2f3b00..f469337 100644 --- a/server/internal/pkg/repositories/user/repository_test.go +++ b/server/internal/pkg/repositories/user/repository_test.go @@ -4,20 +4,20 @@ import ( "context" "testing" - "github.com/Taehoya/go-utils/mysql" + "github.com/Taehoya/go-utils/mysqltest" "github.com/stretchr/testify/assert" ) func TestSaveUser(t *testing.T) { - db, err := mysql.InitTestDB() + db, err := mysqltest.InitDB() assert.NoError(t, err) defer db.Close() repository := NewUserRepository(db) t.Run("Successfully save user", func(t *testing.T) { - defer mysql.SetUp(db, "./teardown_test.sql") - mysql.SetUp(db, "./teardown_test.sql") + defer mysqltest.SetUp(db, "./teardown_test.sql") + mysqltest.SetUp(db, "./teardown_test.sql") nickname := "test-nickname" email := "test-email" @@ -36,16 +36,16 @@ func TestSaveUser(t *testing.T) { } func TestGetUser(t *testing.T) { - db, err := mysql.InitTestDB() + db, err := mysqltest.InitDB() assert.NoError(t, err) defer db.Close() repository := NewUserRepository(db) t.Run("Successfully get user", func(t *testing.T) { - defer mysql.SetUp(db, "./teardown_test.sql") - mysql.SetUp(db, "./teardown_test.sql") - mysql.SetUp(db, "setup_test.sql") + defer mysqltest.SetUp(db, "./teardown_test.sql") + mysqltest.SetUp(db, "./teardown_test.sql") + mysqltest.SetUp(db, "setup_test.sql") email := "test-email" @@ -59,16 +59,16 @@ func TestGetUser(t *testing.T) { } func TestGetUserById(t *testing.T) { - db, err := mysql.InitTestDB() + db, err := mysqltest.InitDB() assert.NoError(t, err) defer db.Close() repository := NewUserRepository(db) t.Run("Successfully get user by id", func(t *testing.T) { - defer mysql.SetUp(db, "./teardown_test.sql") - mysql.SetUp(db, "./teardown_test.sql") - mysql.SetUp(db, "setup_test.sql") + defer mysqltest.SetUp(db, "./teardown_test.sql") + mysqltest.SetUp(db, "./teardown_test.sql") + mysqltest.SetUp(db, "setup_test.sql") id := 1 expectedNickName := "test-nickname" From 84484c50b966b85cd79169b6d531d490ec0de99c Mon Sep 17 00:00:00 2001 From: Taehoya Date: Mon, 13 Nov 2023 21:32:16 -0600 Subject: [PATCH 03/11] implement trip repository layer --- server/internal/pkg/entities/trip.go | 61 ++++++-- .../pkg/repositories/trip/repository.go | 130 ++++++++++++++++-- .../pkg/repositories/trip/repository_test.go | 88 +++++++++++- .../pkg/repositories/trip/setup_test.sql | 7 +- 4 files changed, 259 insertions(+), 27 deletions(-) diff --git a/server/internal/pkg/entities/trip.go b/server/internal/pkg/entities/trip.go index 18cf309..f6bfa7f 100644 --- a/server/internal/pkg/entities/trip.go +++ b/server/internal/pkg/entities/trip.go @@ -5,14 +5,55 @@ import ( ) type Trip struct { - ID int `json:"ID" example:"1"` - Name string `json:"Name" example:"sample-name"` - Budget float64 `json:"Budget" example:"2000.12"` - CountryId int `json:"CountryId" example:"1"` - Description string `json:"Description" example:"sample-description"` - StartDateTime time.Time `json:"StartDateTime" example:"2023-05-29"` - EndDateTime time.Time `json:"EndDateTime" example:"2023-08-29"` - CreatedAt time.Time `json:"CreatedAt" example:"2023-05-29"` - UpdatedAt time.Time `json:"UpdatedAt" example:"2023-05-29"` - DeletedAt time.Time `json:"DeletedAt" example:"2023-05-29"` + id int + name string + budget float64 + countryId int + description string + startDateTime time.Time + endDateTime time.Time + createdAt time.Time + updatedAt time.Time +} + +func NewTrip(id int, name string, budget float64, countryId int, description string, startDateTime time.Time, endDateTime time.Time, createdAt time.Time, updatedAt time.Time) *Trip { + return &Trip{ + id: id, + name: name, + budget: budget, + countryId: countryId, + description: description, + startDateTime: startDateTime, + endDateTime: endDateTime, + createdAt: createdAt, + updatedAt: updatedAt, + } +} + +func (t *Trip) ID() int { + return t.id +} + +func (t *Trip) Name() string { + return t.name +} + +func (t *Trip) Budget() float64 { + return t.budget +} + +func (t *Trip) CountryID() int { + return t.countryId +} + +func (t *Trip) Description() string { + return t.description +} + +func (t *Trip) StartDateTime() time.Time { + return t.startDateTime +} + +func (t *Trip) EndDateTime() time.Time { + return t.endDateTime } diff --git a/server/internal/pkg/repositories/trip/repository.go b/server/internal/pkg/repositories/trip/repository.go index 2a8001a..a2fd1c8 100644 --- a/server/internal/pkg/repositories/trip/repository.go +++ b/server/internal/pkg/repositories/trip/repository.go @@ -5,6 +5,7 @@ import ( "database/sql" "fmt" "log" + "time" "github.com/Taehoya/pocket-mate/internal/pkg/entities" ) @@ -19,10 +20,44 @@ func NewTripRepository(db *sql.DB) *TripRepository { } } -func (r *TripRepository) GetTripAll() ([]*entities.Trip, error) { +func (r *TripRepository) SaveTrip(ctx context.Context, name string, userId int, budget float64, countryId int, description string, startDateTime time.Time, endDateTime time.Time) error { + query := ` + INSERT INTO trips + (name, user_id, budget, country_id, description, start_date_time, end_date_time) + VALUES + (?, ?, ?, ?, ?, ?, ?); + ` + + result, err := r.db.ExecContext(ctx, query, name, userId, budget, countryId, description, startDateTime, endDateTime) + if err != nil { + log.Printf("failed to execute query: %v\n", err) + return fmt.Errorf("internal Server Error") + } + + rows, err := result.RowsAffected() + if err != nil { + log.Printf("failed to get affected rows: %\nv", err) + return fmt.Errorf("internal Server Error") + } + + if rows != 1 { + log.Printf("expected 1 affected row, got %d\n", rows) + return fmt.Errorf("internal Server Error") + } + + return nil +} + +func (r *TripRepository) GetTrip(ctx context.Context, userId int) ([]*entities.Trip, error) { var trips []*entities.Trip + query := ` + SELECT + id, name, budget, country_id, description, start_date_time, end_date_time, created_at, updated_at + FROM + trips + WHERE user_id = ?` - rows, err := r.db.Query("SELECT * FROM pm.trip;") + rows, err := r.db.QueryContext(ctx, query, userId) if err != nil { log.Printf("failed to execute query: %v\n", err) return nil, fmt.Errorf("internal server error") @@ -30,31 +65,37 @@ func (r *TripRepository) GetTripAll() ([]*entities.Trip, error) { defer rows.Close() for rows.Next() { - var trip *entities.Trip - if err := rows.Scan(&trip.ID, &trip.Name, &trip.Budget, &trip.CountryId, &trip.Description, &trip.StartDateTime, &trip.EndDateTime); err != nil { - log.Printf("failed to scan row: %v\n", err) + var id int + var name string + var budget float64 + var countryId int + var description string + var startDateTime time.Time + var endDateTime time.Time + var createdAt time.Time + var updatedAt time.Time + + if err := rows.Scan(&id, &name, &budget, &countryId, &description, &startDateTime, &endDateTime, &createdAt, &updatedAt); err != nil { + log.Printf("failed to scan trip: %v\n", err) return nil, fmt.Errorf("internal server error") } + + trip := entities.NewTrip(id, name, budget, countryId, description, startDateTime, endDateTime, createdAt, updatedAt) trips = append(trips, trip) } if err := rows.Err(); err != nil { - log.Printf("failed to scanning rows: %v\n", err) + log.Printf("failed to iterate: %v\n", err) return nil, fmt.Errorf("internal server error") } return trips, nil } -func (r *TripRepository) SaveTrip(ctx context.Context, name string, userId int, budget float64, countryId int, description string, startDateTime string, endDateTime string) error { - query := ` - INSERT INTO trips - (name, user_id, budget, country_id, description, start_date_time, end_date_time) - VALUES - (?, ?, ?, ?, ?, ?, ?); - ` +func (r *TripRepository) DeleteTrip(ctx context.Context, tripId int) error { + query := `DELETE FROM trips WHERE id = ?;` - result, err := r.db.ExecContext(ctx, query, name, userId, budget, countryId, description, startDateTime, endDateTime) + result, err := r.db.ExecContext(ctx, query, tripId) if err != nil { log.Printf("failed to execute query: %v\n", err) return fmt.Errorf("internal Server Error") @@ -73,3 +114,64 @@ func (r *TripRepository) SaveTrip(ctx context.Context, name string, userId int, return nil } + +func (r *TripRepository) UpdateTrip(ctx context.Context, tripId int, name string, budget float64, countryId int, description string, startDateTime time.Time, endDateTime time.Time) error { + query := `UPDATE trips SET name = ?, budget = ?, country_id = ?, description = ?, start_date_time = ?, end_date_time = ? WHERE id = ?;` + + result, err := r.db.ExecContext(ctx, query, name, budget, countryId, description, startDateTime, endDateTime, tripId) + if err != nil { + log.Printf("failed to execute query: %v\n", err) + return fmt.Errorf("internal Server Error") + } + + rows, err := result.RowsAffected() + if err != nil { + log.Printf("failed to get affected rows: %\nv", err) + return fmt.Errorf("internal Server Error") + } + + if rows != 1 { + log.Printf("expected 1 affected row, got %d\n", rows) + return fmt.Errorf("internal Server Error") + } + + return nil +} + +func (r *TripRepository) GetTripById(ctx context.Context, tripId int) (*entities.Trip, error) { + var trip *entities.Trip + query := `SELECT id, name, budget, country_id, description, start_date_time, end_date_time, created_at, updated_at FROM trips WHERE id = ?;` + rows, err := r.db.QueryContext(ctx, query, tripId) + + if err != nil { + log.Printf("failed to execute query: %v\n", err) + return nil, fmt.Errorf("internal server error") + } + defer rows.Close() + + for rows.Next() { + var id int + var name string + var budget float64 + var countryId int + var description string + var startDateTime time.Time + var endDateTime time.Time + var createdAt time.Time + var updatedAt time.Time + + if err := rows.Scan(&id, &name, &budget, &countryId, &description, &startDateTime, &endDateTime, &createdAt, &updatedAt); err != nil { + log.Printf("failed to scan trip: %v\n", err) + return nil, fmt.Errorf("internal server error") + } + + trip = entities.NewTrip(id, name, budget, countryId, description, startDateTime, endDateTime, createdAt, updatedAt) + } + + if err := rows.Err(); err != nil { + log.Printf("failed to iterate: %v\n", err) + return nil, fmt.Errorf("internal server error") + } + + return trip, nil +} diff --git a/server/internal/pkg/repositories/trip/repository_test.go b/server/internal/pkg/repositories/trip/repository_test.go index d4bcd40..fd48014 100644 --- a/server/internal/pkg/repositories/trip/repository_test.go +++ b/server/internal/pkg/repositories/trip/repository_test.go @@ -3,8 +3,10 @@ package repository import ( "context" "testing" + "time" "github.com/Taehoya/go-utils/mysqltest" + "github.com/Taehoya/pocket-mate/internal/pkg/entities" "github.com/stretchr/testify/assert" ) @@ -26,10 +28,92 @@ func TestSaveTrip(t *testing.T) { budget := 1000.0 countryId := 1 description := "test-description" - startDateTime := "2023-10-31 00:00:00" - endDateTime := "2023-10-31 00:00:00" + startDateTime := time.Now() + endDateTime := time.Now() err := repository.SaveTrip(ctx, name, userId, budget, countryId, description, startDateTime, endDateTime) assert.NoError(t, err) }) } + +func TestGetTrip(t *testing.T) { + db, err := mysqltest.InitDB() + assert.NoError(t, err) + defer db.Close() + + repository := NewTripRepository(db) + + t.Run("Successfully get trip", func(t *testing.T) { + defer mysqltest.SetUp(db, "./teardown_test.sql") + mysqltest.SetUp(db, "./teardown_test.sql") + mysqltest.SetUp(db, "./setup_test.sql") + + ctx := context.TODO() + userId := 1 + + expected := []*entities.Trip{ + entities.NewTrip(1, "test-name", 1, 1.0000, "test-description", time.Now(), time.Now(), time.Now(), time.Now()), + } + + trips, err := repository.GetTrip(ctx, userId) + assert.NoError(t, err) + assert.NotNil(t, trips) + assert.Equal(t, trips[0].ID(), expected[0].ID()) + }) +} + +func TestDeleteTrip(t *testing.T) { + db, err := mysqltest.InitDB() + assert.NoError(t, err) + defer db.Close() + + repository := NewTripRepository(db) + + t.Run("Successfully delete trip", func(t *testing.T) { + defer mysqltest.SetUp(db, "./teardown_test.sql") + mysqltest.SetUp(db, "./teardown_test.sql") + mysqltest.SetUp(db, "./setup_test.sql") + + ctx := context.TODO() + tripId := 1 + + err := repository.DeleteTrip(ctx, tripId) + assert.NoError(t, err) + + trip, err := repository.GetTripById(ctx, tripId) + assert.NoError(t, err) + assert.Nil(t, trip) + }) +} + +func TestUpdateTrip(t *testing.T) { + db, err := mysqltest.InitDB() + assert.NoError(t, err) + defer db.Close() + + repository := NewTripRepository(db) + + t.Run("successfully update trip", func(t *testing.T) { + defer mysqltest.SetUp(db, "./teardown_test.sql") + mysqltest.SetUp(db, "./teardown_test.sql") + mysqltest.SetUp(db, "./setup_test.sql") + + ctx := context.TODO() + tripId := 1 + userId := 1 + name := "updated-name" + budget := 1000.0 + countryId := 1 + description := "updated-description" + startDateTime := time.Now() + endDateTime := time.Now() + + err := repository.UpdateTrip(ctx, userId, name, budget, countryId, description, startDateTime, endDateTime) + assert.NoError(t, err) + + trip, err := repository.GetTripById(ctx, tripId) + assert.NoError(t, err) + assert.NotNil(t, trip) + assert.Equal(t, trip.Name(), name) + }) +} diff --git a/server/internal/pkg/repositories/trip/setup_test.sql b/server/internal/pkg/repositories/trip/setup_test.sql index 148c278..d464729 100644 --- a/server/internal/pkg/repositories/trip/setup_test.sql +++ b/server/internal/pkg/repositories/trip/setup_test.sql @@ -1,4 +1,9 @@ INSERT INTO users (id, nickname, email, password) VALUES - (1, "test-nickname", "test-email", "test-password"); \ No newline at end of file + (1, "test-nickname", "test-email", "test-password"); + +INSERT INTO trips + (id, name, user_id, budget, country_id, description, start_date_time, end_date_time, created_at, updated_at, deleted_at) +VALUES + (1, 'test-name', 1, 1.0000, 1, 'test-description', '2023-11-13 15:04:05', '2023-11-13 15:04:05', '2023-11-13 14:05:27', '2023-11-13 14:05:27', NULL); From 80edd208b9b83155d23bf7163fdfb86236d54252 Mon Sep 17 00:00:00 2001 From: Taehoya Date: Mon, 13 Nov 2023 21:56:05 -0600 Subject: [PATCH 04/11] implement trip repository mock --- server/internal/pkg/mocks/trip/repository.go | 73 ++++++++++++++++++- .../internal/pkg/usecases/trip/repository.go | 16 ++++ 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 server/internal/pkg/usecases/trip/repository.go diff --git a/server/internal/pkg/mocks/trip/repository.go b/server/internal/pkg/mocks/trip/repository.go index 5cd56c7..821d790 100644 --- a/server/internal/pkg/mocks/trip/repository.go +++ b/server/internal/pkg/mocks/trip/repository.go @@ -1,6 +1,12 @@ package mocks -import "github.com/stretchr/testify/mock" +import ( + "context" + "time" + + "github.com/Taehoya/pocket-mate/internal/pkg/entities" + "github.com/stretchr/testify/mock" +) type TripRepositoryMock struct { mock.Mock @@ -9,3 +15,68 @@ type TripRepositoryMock struct { func NewTripRepositoryMock() *TripRepositoryMock { return new(TripRepositoryMock) } + +func (m *TripRepositoryMock) SaveTrip(ctx context.Context, name string, userId int, budget float64, countryId int, description string, startDateTime time.Time, endDateTime time.Time) error { + ret := m.Called(ctx, name, userId, budget, countryId, description, startDateTime, endDateTime) + + var r0 error + if ret.Get(0) != nil { + r0 = ret.Get(0).(error) + } + + return r0 +} + +func (m *TripRepositoryMock) GetTripAll() ([]*entities.Trip, error) { + ret := m.Called() + + var r0 []*entities.Trip + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*entities.Trip) + } + + var r1 error + if ret.Get(1) != nil { + r1 = ret.Get(1).(error) + } + + return r0, r1 +} + +func (m *TripRepositoryMock) GetTrip(ctx context.Context, userId int) ([]*entities.Trip, error) { + ret := m.Called(ctx, userId) + + var r0 []*entities.Trip + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*entities.Trip) + } + + var r1 error + if ret.Get(1) != nil { + r1 = ret.Get(1).(error) + } + + return r0, r1 +} + +func (m *TripRepositoryMock) DeleteTrip(ctx context.Context, tripId int) error { + ret := m.Called(ctx, tripId) + + var r0 error + if ret.Get(0) != nil { + r0 = ret.Get(0).(error) + } + + return r0 +} + +func (m *TripRepositoryMock) UpdateTrip(ctx context.Context, tripId int, name string, budget float64, countryId int, description string, startDateTime time.Time, endDateTime time.Time) error { + ret := m.Called(ctx, tripId, name, budget, countryId, description, startDateTime, endDateTime) + + var r0 error + if ret.Get(0) != nil { + r0 = ret.Get(0).(error) + } + + return r0 +} diff --git a/server/internal/pkg/usecases/trip/repository.go b/server/internal/pkg/usecases/trip/repository.go new file mode 100644 index 0000000..4588a24 --- /dev/null +++ b/server/internal/pkg/usecases/trip/repository.go @@ -0,0 +1,16 @@ +package usecase + +import ( + "context" + "time" + + "github.com/Taehoya/pocket-mate/internal/pkg/entities" +) + +type TripRepository interface { + GetTripAll() ([]*entities.Trip, error) + SaveTrip(ctx context.Context, name string, userId int, budget float64, countryId int, description string, startDateTime time.Time, endDateTime time.Time) error + GetTrip(ctx context.Context, userId int) ([]*entities.Trip, error) + DeleteTrip(ctx context.Context, tripId int) error + UpdateTrip(ctx context.Context, tripId int, name string, budget float64, countryId int, description string, startDateTime time.Time, endDateTime time.Time) error +} From 8617b125fc81cc67f55e513a3dda0068efb6fffd Mon Sep 17 00:00:00 2001 From: Taehoya Date: Tue, 14 Nov 2023 17:56:44 -0600 Subject: [PATCH 05/11] add trip dto --- server/internal/pkg/dto/trip.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 server/internal/pkg/dto/trip.go diff --git a/server/internal/pkg/dto/trip.go b/server/internal/pkg/dto/trip.go new file mode 100644 index 0000000..a01d02b --- /dev/null +++ b/server/internal/pkg/dto/trip.go @@ -0,0 +1,22 @@ +package dto + +import "time" + +type TripResponseDTO struct { + ID int `json:"id" example:"1"` + Name string `json:"name" example:"sample-name"` + Budget float64 `json:"budget" example:"2000.12"` + CountryId int `json:"countryId" example:"1"` + Description string `json:"description" example:"sample-description"` + StartDateTime time.Time `json:"startDateTime" example:"2023-05-29"` + EndDateTime time.Time `json:"endDateTime" example:"2023-08-29"` +} + +type TripRequestDTO struct { + Name string `json:"name" binding:"required" example:"sample-name"` + Budget float64 `json:"budget" binding:"required" example:"2000.12"` + CountryId int `json:"countryId" binding:"required" example:"1"` + Description string `json:"description" binding:"required" example:"sample-description"` + StartDateTime time.Time `json:"startDateTime" binding:"required" example:"2023-05-29"` + EndDateTime time.Time `json:"endDateTime" binding:"required" example:"2023-08-29"` +} From b959ed6e19243f2cb41b601badcd30390f73848c Mon Sep 17 00:00:00 2001 From: Taehoya Date: Wed, 15 Nov 2023 07:24:15 -0600 Subject: [PATCH 06/11] implement trip usecase layer --- server/internal/pkg/dto/trip.go | 43 +++- server/internal/pkg/mocks/trip/repository.go | 16 -- .../internal/pkg/usecases/trip/repository.go | 1 - server/internal/pkg/usecases/trip/usecase.go | 45 ++++- .../pkg/usecases/trip/usecase_test.go | 186 ++++++++++++++++++ 5 files changed, 267 insertions(+), 24 deletions(-) create mode 100644 server/internal/pkg/usecases/trip/usecase_test.go diff --git a/server/internal/pkg/dto/trip.go b/server/internal/pkg/dto/trip.go index a01d02b..4ffc475 100644 --- a/server/internal/pkg/dto/trip.go +++ b/server/internal/pkg/dto/trip.go @@ -1,6 +1,10 @@ package dto -import "time" +import ( + "time" + + "github.com/Taehoya/pocket-mate/internal/pkg/entities" +) type TripResponseDTO struct { ID int `json:"id" example:"1"` @@ -12,6 +16,12 @@ type TripResponseDTO struct { EndDateTime time.Time `json:"endDateTime" example:"2023-08-29"` } +type TripStatusResponseDTO struct { + Future []*TripResponseDTO `json:"future"` + Past []*TripResponseDTO `json:"past"` + Current []*TripResponseDTO `json:"current"` +} + type TripRequestDTO struct { Name string `json:"name" binding:"required" example:"sample-name"` Budget float64 `json:"budget" binding:"required" example:"2000.12"` @@ -20,3 +30,34 @@ type TripRequestDTO struct { StartDateTime time.Time `json:"startDateTime" binding:"required" example:"2023-05-29"` EndDateTime time.Time `json:"endDateTime" binding:"required" example:"2023-08-29"` } + +func NewTripResponse(trip *entities.Trip) *TripResponseDTO { + return &TripResponseDTO{ + ID: trip.ID(), + Name: trip.Name(), + Budget: trip.Budget(), + CountryId: trip.CountryID(), + Description: trip.Description(), + StartDateTime: trip.StartDateTime(), + EndDateTime: trip.EndDateTime(), + } +} + +func NewTripResponseList(tripStatusMap map[string][]*entities.Trip) *TripStatusResponseDTO { + return &TripStatusResponseDTO{ + Future: NewTripResponseListByStatus(tripStatusMap["future"]), + Past: NewTripResponseListByStatus(tripStatusMap["past"]), + Current: NewTripResponseListByStatus(tripStatusMap["current"]), + } +} + +func NewTripResponseListByStatus(trips []*entities.Trip) []*TripResponseDTO { + tripResponseList := make([]*TripResponseDTO, 0) + + for _, trip := range trips { + trip := NewTripResponse(trip) + tripResponseList = append(tripResponseList, trip) + } + + return tripResponseList +} diff --git a/server/internal/pkg/mocks/trip/repository.go b/server/internal/pkg/mocks/trip/repository.go index 821d790..1a8e643 100644 --- a/server/internal/pkg/mocks/trip/repository.go +++ b/server/internal/pkg/mocks/trip/repository.go @@ -27,22 +27,6 @@ func (m *TripRepositoryMock) SaveTrip(ctx context.Context, name string, userId i return r0 } -func (m *TripRepositoryMock) GetTripAll() ([]*entities.Trip, error) { - ret := m.Called() - - var r0 []*entities.Trip - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*entities.Trip) - } - - var r1 error - if ret.Get(1) != nil { - r1 = ret.Get(1).(error) - } - - return r0, r1 -} - func (m *TripRepositoryMock) GetTrip(ctx context.Context, userId int) ([]*entities.Trip, error) { ret := m.Called(ctx, userId) diff --git a/server/internal/pkg/usecases/trip/repository.go b/server/internal/pkg/usecases/trip/repository.go index 4588a24..2668af7 100644 --- a/server/internal/pkg/usecases/trip/repository.go +++ b/server/internal/pkg/usecases/trip/repository.go @@ -8,7 +8,6 @@ import ( ) type TripRepository interface { - GetTripAll() ([]*entities.Trip, error) SaveTrip(ctx context.Context, name string, userId int, budget float64, countryId int, description string, startDateTime time.Time, endDateTime time.Time) error GetTrip(ctx context.Context, userId int) ([]*entities.Trip, error) DeleteTrip(ctx context.Context, tripId int) error diff --git a/server/internal/pkg/usecases/trip/usecase.go b/server/internal/pkg/usecases/trip/usecase.go index 5ac36bd..195a484 100644 --- a/server/internal/pkg/usecases/trip/usecase.go +++ b/server/internal/pkg/usecases/trip/usecase.go @@ -2,14 +2,12 @@ package usecase import ( "context" + "time" + "github.com/Taehoya/pocket-mate/internal/pkg/dto" "github.com/Taehoya/pocket-mate/internal/pkg/entities" ) -type TripRepository interface { - GetTripAll() ([]entities.Trip, error) -} - type TripUseCase struct { Repository TripRepository } @@ -20,6 +18,41 @@ func NewTripUseCase(repository TripRepository) *TripUseCase { } } -func (u *TripUseCase) GetTripAll(ctx context.Context) ([]entities.Trip, error) { - return u.Repository.GetTripAll() +func (u *TripUseCase) RegisterTrip(ctx context.Context, userId int, dto dto.TripRequestDTO) error { + return u.Repository.SaveTrip(ctx, dto.Name, userId, dto.Budget, dto.CountryId, dto.Description, dto.StartDateTime, dto.EndDateTime) +} + +func (u *TripUseCase) GetTrips(ctx context.Context, userId int) (*dto.TripStatusResponseDTO, error) { + trips, err := u.Repository.GetTrip(ctx, userId) + tripStatusMap := make(map[string][]*entities.Trip) + tripStatusMap["past"] = make([]*entities.Trip, 0) + tripStatusMap["current"] = make([]*entities.Trip, 0) + tripStatusMap["future"] = make([]*entities.Trip, 0) + + if err != nil { + return nil, err + } + + date := time.Now() + for _, trip := range trips { + if trip.StartDateTime().After(date) { + tripStatusMap["future"] = append(tripStatusMap["future"], trip) + } else if trip.EndDateTime().Before(date) { + tripStatusMap["past"] = append(tripStatusMap["past"], trip) + } else { + tripStatusMap["current"] = append(tripStatusMap["current"], trip) + + } + } + + tripResp := dto.NewTripResponseList(tripStatusMap) + return tripResp, nil +} + +func (u *TripUseCase) DeleteTrip(ctx context.Context, tripId int) error { + return u.Repository.DeleteTrip(ctx, tripId) +} + +func (u *TripUseCase) UpdateTrip(ctx context.Context, tripId int, dto dto.TripRequestDTO) error { + return u.Repository.UpdateTrip(ctx, tripId, dto.Name, dto.Budget, dto.CountryId, dto.Description, dto.StartDateTime, dto.EndDateTime) } diff --git a/server/internal/pkg/usecases/trip/usecase_test.go b/server/internal/pkg/usecases/trip/usecase_test.go new file mode 100644 index 0000000..4bc9514 --- /dev/null +++ b/server/internal/pkg/usecases/trip/usecase_test.go @@ -0,0 +1,186 @@ +package usecase + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/Taehoya/pocket-mate/internal/pkg/dto" + "github.com/Taehoya/pocket-mate/internal/pkg/entities" + mocks "github.com/Taehoya/pocket-mate/internal/pkg/mocks/trip" + "github.com/stretchr/testify/assert" +) + +func TestRegisterTrip(t *testing.T) { + t.Run("successfully save trip", func(t *testing.T) { + repository := mocks.NewTripRepositoryMock() + usecase := NewTripUseCase(repository) + + ctx := context.TODO() + userId := 1 + + name := "test-name" + budget := 1000.0 + countryId := 1 + description := "test-description" + startDateTime := time.Now() + endDateTime := time.Now() + + dto := dto.TripRequestDTO{ + Name: name, + Budget: budget, + CountryId: countryId, + Description: description, + StartDateTime: startDateTime, + EndDateTime: endDateTime, + } + + repository.Mock.On("SaveTrip", ctx, name, userId, budget, countryId, description, startDateTime, endDateTime).Return(nil) + err := usecase.RegisterTrip(ctx, userId, dto) + assert.NoError(t, err) + repository.AssertExpectations(t) + }) + + t.Run("failed to save trip", func(t *testing.T) { + repository := mocks.NewTripRepositoryMock() + usecase := NewTripUseCase(repository) + + ctx := context.TODO() + userId := 1 + name := "test-name" + budget := 1000.0 + countryId := 1 + description := "test-description" + startDateTime := time.Now() + endDateTime := time.Now() + + dto := dto.TripRequestDTO{ + Name: name, + Budget: budget, + CountryId: countryId, + Description: description, + StartDateTime: startDateTime, + EndDateTime: endDateTime, + } + + repository.Mock.On("SaveTrip", ctx, name, userId, budget, countryId, description, startDateTime, endDateTime).Return(fmt.Errorf("error")) + err := usecase.RegisterTrip(ctx, userId, dto) + assert.Error(t, err) + repository.AssertExpectations(t) + }) +} + +func TestGetTrips(t *testing.T) { + t.Run("successfully get trips", func(t *testing.T) { + repository := mocks.NewTripRepositoryMock() + usecase := NewTripUseCase(repository) + + ctx := context.TODO() + userId := 1 + + repository.Mock.On("GetTrip", ctx, userId).Return([]*entities.Trip{}, nil) + _, err := usecase.GetTrips(ctx, userId) + assert.NoError(t, err) + repository.AssertExpectations(t) + }) + + t.Run("failed to get trip", func(t *testing.T) { + repository := mocks.NewTripRepositoryMock() + usecase := NewTripUseCase(repository) + + ctx := context.TODO() + userId := 1 + + repository.Mock.On("GetTrip", ctx, userId).Return(nil, fmt.Errorf("error")) + _, err := usecase.GetTrips(ctx, userId) + assert.Error(t, err) + repository.AssertExpectations(t) + }) +} + +func TestDeleteTrip(t *testing.T) { + t.Run("successfully delete trip", func(t *testing.T) { + repository := mocks.NewTripRepositoryMock() + usecase := NewTripUseCase(repository) + + ctx := context.TODO() + tripId := 1 + + repository.Mock.On("DeleteTrip", ctx, tripId).Return(nil) + err := usecase.DeleteTrip(ctx, tripId) + assert.NoError(t, err) + repository.AssertExpectations(t) + }) + + t.Run("failed to delete trip", func(t *testing.T) { + repository := mocks.NewTripRepositoryMock() + usecase := NewTripUseCase(repository) + + ctx := context.TODO() + tripId := 1 + + repository.Mock.On("DeleteTrip", ctx, tripId).Return(fmt.Errorf("error")) + err := usecase.DeleteTrip(ctx, tripId) + assert.Error(t, err) + repository.AssertExpectations(t) + }) +} + +func TestUpdateTrip(t *testing.T) { + t.Run("successfully update trip", func(t *testing.T) { + repository := mocks.NewTripRepositoryMock() + usecase := NewTripUseCase(repository) + + ctx := context.TODO() + tripId := 1 + name := "test-name" + budget := 1000.0 + countryId := 1 + description := "test-description" + startDateTime := time.Now() + endDateTime := time.Now() + + dto := dto.TripRequestDTO{ + Name: name, + Budget: budget, + CountryId: countryId, + Description: description, + StartDateTime: startDateTime, + EndDateTime: endDateTime, + } + + repository.Mock.On("UpdateTrip", ctx, tripId, name, budget, countryId, description, startDateTime, endDateTime).Return(nil) + err := usecase.UpdateTrip(ctx, tripId, dto) + assert.NoError(t, err) + repository.AssertExpectations(t) + }) + + t.Run("failed to update trip", func(t *testing.T) { + repository := mocks.NewTripRepositoryMock() + usecase := NewTripUseCase(repository) + + ctx := context.TODO() + tripId := 1 + name := "test-name" + budget := 1000.0 + countryId := 1 + description := "test-description" + startDateTime := time.Now() + endDateTime := time.Now() + + dto := dto.TripRequestDTO{ + Name: name, + Budget: budget, + CountryId: countryId, + Description: description, + StartDateTime: startDateTime, + EndDateTime: endDateTime, + } + + repository.Mock.On("UpdateTrip", ctx, tripId, name, budget, countryId, description, startDateTime, endDateTime).Return(fmt.Errorf("error")) + err := usecase.UpdateTrip(ctx, tripId, dto) + assert.Error(t, err) + repository.AssertExpectations(t) + }) +} From abc9b9cd28ecc86ce6fff6b746604f2a08cab9b0 Mon Sep 17 00:00:00 2001 From: Taehoya Date: Wed, 15 Nov 2023 11:40:02 -0600 Subject: [PATCH 07/11] implement trip handler --- server/internal/pkg/handlers/country.go | 2 +- server/internal/pkg/handlers/handler.go | 10 +- server/internal/pkg/handlers/trip.go | 167 ++++++++++++++++- server/internal/pkg/handlers/trip_test.go | 171 ++++++++++++++++++ server/internal/pkg/handlers/user_test.go | 4 +- server/internal/pkg/mocks/trip/usecase.go | 40 +++- .../pkg/repositories/trip/repository.go | 7 +- 7 files changed, 377 insertions(+), 24 deletions(-) create mode 100644 server/internal/pkg/handlers/trip_test.go diff --git a/server/internal/pkg/handlers/country.go b/server/internal/pkg/handlers/country.go index a63fd23..d2152e3 100644 --- a/server/internal/pkg/handlers/country.go +++ b/server/internal/pkg/handlers/country.go @@ -21,7 +21,7 @@ type CountryUsecase interface { // @Produce json // @Success 200 {object} []dto.CountryResponseDTO // @Failure 500 {object} dto.ErrorResponseDTO -// @Router /country [get] +// @Router /countries [get] func (h *Handler) GetCountries(ctx *gin.Context) { countries, err := h.CountryUsecase.GetCountries(ctx) diff --git a/server/internal/pkg/handlers/handler.go b/server/internal/pkg/handlers/handler.go index 294ea59..dcff766 100644 --- a/server/internal/pkg/handlers/handler.go +++ b/server/internal/pkg/handlers/handler.go @@ -31,9 +31,13 @@ func (h *Handler) InitRoutes() http.Handler { engine.GET("/", healthCheck()) apiGroup := engine.Group("api/v1") - apiGroup.POST("/user", h.Register) - apiGroup.POST("/user/login", h.Login) - apiGroup.GET("/country", h.GetCountries) + apiGroup.GET("/countries", h.GetCountries) + apiGroup.POST("/users", h.Register) + apiGroup.POST("/users/login", h.Login) + apiGroup.GET("/trips", middlewares.JwtAuthMiddleware(), h.GetTrip) + apiGroup.POST("/trips", middlewares.JwtAuthMiddleware(), h.RegisterTrip) + apiGroup.DELETE("/trips/:id", middlewares.JwtAuthMiddleware(), h.DeleteTrip) + apiGroup.PUT("/trips/:id", middlewares.JwtAuthMiddleware(), h.UpdateTrip) engine.GET("docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) return engine diff --git a/server/internal/pkg/handlers/trip.go b/server/internal/pkg/handlers/trip.go index 700ede7..ad7b8dd 100644 --- a/server/internal/pkg/handlers/trip.go +++ b/server/internal/pkg/handlers/trip.go @@ -2,34 +2,185 @@ package handlers import ( "context" + "log" "net/http" + "strconv" - "github.com/Taehoya/pocket-mate/internal/pkg/entities" + "github.com/Taehoya/pocket-mate/internal/pkg/dto" + "github.com/Taehoya/pocket-mate/internal/pkg/utils/token" "github.com/gin-gonic/gin" ) type TripUseCase interface { - GetTripAll(ctx context.Context) ([]entities.Trip, error) + RegisterTrip(ctx context.Context, userId int, dto dto.TripRequestDTO) error + GetTrips(ctx context.Context, userId int) (*dto.TripStatusResponseDTO, error) + DeleteTrip(ctx context.Context, tripId int) error + UpdateTrip(ctx context.Context, tripId int, dto dto.TripRequestDTO) error } -// GetTripAll +// register trip // -// @Summary Get all trip +// @Summary register trip +// @Description register trip +// @Tags trip +// @Accept json +// @Produce json +// @Security bearer +// @param Authorization header string true "Authorization" +// @Param request body dto.TripRequestDTO true "register trip" +// @Success 201 {object} dto.BaseResponseDTO +// @Failure 400 {object} dto.ErrorResponseDTO +// @Failure 401 {object} dto.ErrorResponseDTO +// @Failure 500 {object} dto.ErrorResponseDTO +// @Router /trips [post] +func (h *Handler) RegisterTrip(ctx *gin.Context) { + userId, err := token.ExtractTokenID(ctx) + if err != nil { + ctx.JSON(http.StatusUnauthorized, gin.H{ + "error_message": "unathorized user", + }) + return + } + + var req dto.TripRequestDTO + if err := ctx.ShouldBindJSON(&req); err != nil { + log.Printf("%v", err) + ctx.JSON(http.StatusBadRequest, gin.H{ + "error_message": "bad request", + }) + return + } + + err = h.TripUseCase.RegisterTrip(ctx, userId, req) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{ + "error_message": err, + }) + return + } + + ctx.JSON(http.StatusCreated, gin.H{ + "message": "success", + }) +} + +// get trip +// +// @Summary get trip // @Description get trip // @Tags trip // @Accept json // @Produce json -// @Success 200 {object} entities.Trip +// @Security bearer +// @param Authorization header string true "Authorization" +// @Success 200 {object} dto.TripStatusResponseDTO // @Failure 400 {object} dto.ErrorResponseDTO -// @Router /trip [get] -func (h *Handler) GetTripAll(ctx *gin.Context) { - trips, err := h.TripUseCase.GetTripAll(ctx) +// @Failure 401 {object} dto.ErrorResponseDTO +// @Failure 500 {object} dto.ErrorResponseDTO +// @Router /trips [get] +func (h *Handler) GetTrip(ctx *gin.Context) { + userId, err := token.ExtractTokenID(ctx) + if err != nil { + ctx.JSON(http.StatusUnauthorized, gin.H{ + "error_message": "unathorized user", + }) + return + } + trips, err := h.TripUseCase.GetTrips(ctx, userId) if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{ "error_message": err, }) + return } ctx.JSON(http.StatusOK, trips) } + +// delete trip +// +// @Summary delete trip +// @Description delete trip +// @Tags trip +// @Accept json +// @Produce json +// @Security bearer +// @param Authorization header string true "Authorization" +// @Param id path int true "id" +// @Success 200 {object} dto.BaseResponseDTO +// @Failure 400 {object} dto.ErrorResponseDTO +// @Failure 401 {object} dto.ErrorResponseDTO +// @Failure 500 {object} dto.ErrorResponseDTO +// @Router /trips/{id} [delete] +func (h *Handler) DeleteTrip(ctx *gin.Context) { + tripIdParam := ctx.Param("id") + tripId, err := strconv.Atoi(tripIdParam) + + if err != nil || tripIdParam == "" { + ctx.JSON(http.StatusBadRequest, gin.H{ + "error_message": "bad request", + }) + return + } + + err = h.TripUseCase.DeleteTrip(ctx, tripId) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{ + "error_message": err, + }) + return + } + + ctx.JSON(http.StatusOK, gin.H{ + "message": "success", + }) +} + +// update trip +// +// @Summary update trip +// @Description update trip +// @Tags trip +// @Accept json +// @Produce json +// @Security bearer +// @param Authorization header string true "Authorization" +// @Param request body dto.TripRequestDTO true "update trip" +// @Success 200 {object} dto.BaseResponseDTO +// @Failure 400 {object} dto.ErrorResponseDTO +// @Failure 401 {object} dto.ErrorResponseDTO +// @Failure 500 {object} dto.ErrorResponseDTO +// @Router /trips/{id} [put] +func (h *Handler) UpdateTrip(ctx *gin.Context) { + tripIdParam := ctx.Param("id") + tripId, err := strconv.Atoi(tripIdParam) + + if err != nil || tripIdParam == "" { + ctx.JSON(http.StatusBadRequest, gin.H{ + "error_message": "bad request", + }) + return + } + + var req dto.TripRequestDTO + if err := ctx.ShouldBindJSON(&req); err != nil { + log.Printf("%v", err) + ctx.JSON(http.StatusBadRequest, gin.H{ + "error_message": "bad request", + }) + return + } + + err = h.TripUseCase.UpdateTrip(ctx, tripId, req) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{ + "error_message": err, + }) + return + } + + ctx.JSON(http.StatusOK, gin.H{ + "message": "success", + }) +} diff --git a/server/internal/pkg/handlers/trip_test.go b/server/internal/pkg/handlers/trip_test.go new file mode 100644 index 0000000..cb41cae --- /dev/null +++ b/server/internal/pkg/handlers/trip_test.go @@ -0,0 +1,171 @@ +package handlers + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/Taehoya/pocket-mate/internal/pkg/dto" + "github.com/Taehoya/pocket-mate/internal/pkg/entities" + countryMocks "github.com/Taehoya/pocket-mate/internal/pkg/mocks/country" + tripMocks "github.com/Taehoya/pocket-mate/internal/pkg/mocks/trip" + userMocks "github.com/Taehoya/pocket-mate/internal/pkg/mocks/user" + pathutil "github.com/Taehoya/pocket-mate/internal/pkg/utils/path" + "github.com/Taehoya/pocket-mate/internal/pkg/utils/token" + "github.com/joho/godotenv" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestRegisterTrip(t *testing.T) { + t.Run("Successfully create trip", func(t *testing.T) { + projectRootDir, _ := pathutil.GetProjectRootDir() + err := godotenv.Load(fmt.Sprintf("%s/.env", projectRootDir)) + assert.NoError(t, err) + + rr := httptest.NewRecorder() + tripUseCase := tripMocks.NewTripUseCase() + countryUseCase := countryMocks.NewCountryUseCase() + userUseCase := userMocks.NewUserUeseCase() + handler := New(tripUseCase, countryUseCase, userUseCase) + router := handler.InitRoutes() + + name := "test-name" + userId := 1 + budget := 1000.0 + countryId := 1 + description := "test-description" + startDateTime := time.Date(2023, time.November, 15, 12, 0, 0, 0, time.UTC) + endDateTime := time.Date(2023, time.November, 15, 12, 0, 0, 0, time.UTC) + + dto := dto.TripRequestDTO{ + Name: name, + Budget: budget, + CountryId: countryId, + Description: description, + StartDateTime: startDateTime, + EndDateTime: endDateTime, + } + + token, err := token.MakeToken(userId) + assert.NoError(t, err) + + jsonBody, err := json.Marshal(dto) + assert.NoError(t, err) + tripUseCase.On("RegisterTrip", mock.Anything, userId, dto).Return(nil) + request, err := http.NewRequest(http.MethodPost, "/api/v1/trips", bytes.NewBuffer(jsonBody)) + request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + router.ServeHTTP(rr, request) + assert.Equal(t, 200, rr.Code) + assert.NoError(t, err) + }) +} + +func TestGetTrips(t *testing.T) { + t.Run("successfully get the list of user's trip", func(t *testing.T) { + projectRootDir, _ := pathutil.GetProjectRootDir() + err := godotenv.Load(fmt.Sprintf("%s/.env", projectRootDir)) + assert.NoError(t, err) + + rr := httptest.NewRecorder() + tripUseCase := tripMocks.NewTripUseCase() + countryUseCase := countryMocks.NewCountryUseCase() + userUseCase := userMocks.NewUserUeseCase() + handler := New(tripUseCase, countryUseCase, userUseCase) + router := handler.InitRoutes() + + userId := 1 + email := "test-email" + password := "test-password" + mockUser := entities.NewUser(1, "test-nickname", email, password, time.Now(), time.Now()) + + token, err := token.MakeToken(userId) + assert.NoError(t, err) + + tripStatusResponseDTO := dto.TripStatusResponseDTO{} + tripUseCase.On("GetTrips", mock.Anything, mockUser.ID()).Return(&tripStatusResponseDTO, nil) + request, err := http.NewRequest(http.MethodGet, "/api/v1/trips", nil) + request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + router.ServeHTTP(rr, request) + assert.Equal(t, 200, rr.Code) + assert.NoError(t, err) + }) +} + +func TestUpdateTrip(t *testing.T) { + t.Run("successfully update user's trip", func(t *testing.T) { + projectRootDir, _ := pathutil.GetProjectRootDir() + err := godotenv.Load(fmt.Sprintf("%s/.env", projectRootDir)) + assert.NoError(t, err) + + rr := httptest.NewRecorder() + tripUseCase := tripMocks.NewTripUseCase() + countryUseCase := countryMocks.NewCountryUseCase() + userUseCase := userMocks.NewUserUeseCase() + handler := New(tripUseCase, countryUseCase, userUseCase) + router := handler.InitRoutes() + + tripId := 1 + name := "test-name" + userId := 1 + budget := 1000.0 + countryId := 1 + description := "test-description" + startDateTime := time.Date(2023, time.November, 15, 12, 0, 0, 0, time.UTC) + endDateTime := time.Date(2023, time.November, 15, 12, 0, 0, 0, time.UTC) + + dto := dto.TripRequestDTO{ + Name: name, + Budget: budget, + CountryId: countryId, + Description: description, + StartDateTime: startDateTime, + EndDateTime: endDateTime, + } + + jsonBody, err := json.Marshal(dto) + assert.NoError(t, err) + + token, err := token.MakeToken(userId) + assert.NoError(t, err) + + tripUseCase.On("UpdateTrip", mock.Anything, userId, dto).Return(nil) + request, err := http.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/trips/%d", tripId), bytes.NewBuffer(jsonBody)) + request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + router.ServeHTTP(rr, request) + assert.Equal(t, 200, rr.Code) + assert.NoError(t, err) + }) +} + +func TestDeleteTrip(t *testing.T) { + t.Run("successfully delete trip", func(t *testing.T) { + projectRootDir, _ := pathutil.GetProjectRootDir() + err := godotenv.Load(fmt.Sprintf("%s/.env", projectRootDir)) + assert.NoError(t, err) + + rr := httptest.NewRecorder() + tripUseCase := tripMocks.NewTripUseCase() + countryUseCase := countryMocks.NewCountryUseCase() + userUseCase := userMocks.NewUserUeseCase() + handler := New(tripUseCase, countryUseCase, userUseCase) + router := handler.InitRoutes() + + userId := 1 + tripId := 5 + + token, err := token.MakeToken(userId) + assert.NoError(t, err) + + tripUseCase.On("DeleteTrip", mock.Anything, tripId).Return(nil) + request, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("/api/v1/trips/%d", tripId), nil) + request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + router.ServeHTTP(rr, request) + assert.Equal(t, 200, rr.Code) + assert.NoError(t, err) + }) +} diff --git a/server/internal/pkg/handlers/user_test.go b/server/internal/pkg/handlers/user_test.go index 9002e4f..94de4fd 100644 --- a/server/internal/pkg/handlers/user_test.go +++ b/server/internal/pkg/handlers/user_test.go @@ -39,7 +39,7 @@ func TestRegister(t *testing.T) { assert.NoError(t, err) userUseCase.On("Register", mock.Anything, email, password).Return(mockUser, nil) - request, err := http.NewRequest(http.MethodPost, "/api/v1/user", bytes.NewBuffer(jsonBody)) + request, err := http.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBuffer(jsonBody)) assert.NoError(t, err) router.ServeHTTP(rr, request) @@ -69,7 +69,7 @@ func TestLogin(t *testing.T) { assert.NoError(t, err) userUseCase.On("Login", mock.Anything, email, password).Return(token, nil) - request, err := http.NewRequest(http.MethodPost, "/api/v1/user/login", bytes.NewBuffer(jsonBody)) + request, err := http.NewRequest(http.MethodPost, "/api/v1/users/login", bytes.NewBuffer(jsonBody)) assert.NoError(t, err) router.ServeHTTP(rr, request) diff --git a/server/internal/pkg/mocks/trip/usecase.go b/server/internal/pkg/mocks/trip/usecase.go index 8b420a2..df70749 100644 --- a/server/internal/pkg/mocks/trip/usecase.go +++ b/server/internal/pkg/mocks/trip/usecase.go @@ -3,7 +3,7 @@ package mocks import ( "context" - "github.com/Taehoya/pocket-mate/internal/pkg/entities" + "github.com/Taehoya/pocket-mate/internal/pkg/dto" "github.com/stretchr/testify/mock" ) @@ -15,10 +15,20 @@ func NewTripUseCase() *TripUseCaseMock { return new(TripUseCaseMock) } -func (m *TripUseCaseMock) GetTripAll(ctx context.Context) ([]entities.Trip, error) { - ret := m.Called(ctx) +func (m *TripUseCaseMock) RegisterTrip(ctx context.Context, userId int, dto dto.TripRequestDTO) error { + ret := m.Called(ctx, userId, dto) + var r0 error + if ret.Get(0) != nil { + r0 = ret.Get(0).(error) + } + + return r0 +} + +func (m *TripUseCaseMock) GetTrips(ctx context.Context, userId int) (*dto.TripStatusResponseDTO, error) { + ret := m.Called(ctx, userId) - r0 := ret.Get(0).([]entities.Trip) + r0 := ret.Get(0).(*dto.TripStatusResponseDTO) var r1 error if ret.Get(1) != nil { @@ -26,3 +36,25 @@ func (m *TripUseCaseMock) GetTripAll(ctx context.Context) ([]entities.Trip, erro } return r0, r1 } + +func (m *TripUseCaseMock) DeleteTrip(ctx context.Context, tripId int) error { + ret := m.Called(ctx, tripId) + + var r0 error + if ret.Get(0) != nil { + r0 = ret.Get(0).(error) + } + + return r0 +} + +func (m *TripUseCaseMock) UpdateTrip(ctx context.Context, tripId int, dto dto.TripRequestDTO) error { + ret := m.Called(ctx, tripId, dto) + + var r0 error + if ret.Get(0) != nil { + r0 = ret.Get(0).(error) + } + + return r0 +} diff --git a/server/internal/pkg/repositories/trip/repository.go b/server/internal/pkg/repositories/trip/repository.go index a2fd1c8..61dfdb1 100644 --- a/server/internal/pkg/repositories/trip/repository.go +++ b/server/internal/pkg/repositories/trip/repository.go @@ -124,17 +124,12 @@ func (r *TripRepository) UpdateTrip(ctx context.Context, tripId int, name string return fmt.Errorf("internal Server Error") } - rows, err := result.RowsAffected() + _, err = result.RowsAffected() if err != nil { log.Printf("failed to get affected rows: %\nv", err) return fmt.Errorf("internal Server Error") } - if rows != 1 { - log.Printf("expected 1 affected row, got %d\n", rows) - return fmt.Errorf("internal Server Error") - } - return nil } From 20fdec746e95313c9807f15224ca496f3a546fe2 Mon Sep 17 00:00:00 2001 From: Taehoya Date: Wed, 15 Nov 2023 11:40:26 -0600 Subject: [PATCH 08/11] update swagger --- server/docs/docs.go | 318 +++++++++++++++++++++++++++++++++++---- server/docs/swagger.json | 318 +++++++++++++++++++++++++++++++++++---- server/docs/swagger.yaml | 224 +++++++++++++++++++++++---- 3 files changed, 775 insertions(+), 85 deletions(-) diff --git a/server/docs/docs.go b/server/docs/docs.go index 1f3547a..0112c1c 100644 --- a/server/docs/docs.go +++ b/server/docs/docs.go @@ -16,7 +16,7 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { - "/country": { + "/countries": { "get": { "description": "get a list of all available countires", "consumes": [ @@ -48,8 +48,13 @@ const docTemplate = `{ } } }, - "/trip": { + "/trips": { "get": { + "security": [ + { + "bearer": [] + } + ], "description": "get trip", "consumes": [ "application/json" @@ -60,12 +65,21 @@ const docTemplate = `{ "tags": [ "trip" ], - "summary": "Get all trip", + "summary": "get trip", + "parameters": [ + { + "type": "string", + "description": "Authorization", + "name": "Authorization", + "in": "header", + "required": true + } + ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/entities.Trip" + "$ref": "#/definitions/dto.TripStatusResponseDTO" } }, "400": { @@ -73,6 +87,204 @@ const docTemplate = `{ "schema": { "$ref": "#/definitions/dto.ErrorResponseDTO" } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/dto.ErrorResponseDTO" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/dto.ErrorResponseDTO" + } + } + } + }, + "post": { + "security": [ + { + "bearer": [] + } + ], + "description": "register trip", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "trip" + ], + "summary": "register trip", + "parameters": [ + { + "type": "string", + "description": "Authorization", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "description": "register trip", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.TripRequestDTO" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/dto.BaseResponseDTO" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/dto.ErrorResponseDTO" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/dto.ErrorResponseDTO" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/dto.ErrorResponseDTO" + } + } + } + } + }, + "/trips/{id}": { + "put": { + "security": [ + { + "bearer": [] + } + ], + "description": "update trip", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "trip" + ], + "summary": "update trip", + "parameters": [ + { + "type": "string", + "description": "Authorization", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "description": "update trip", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.TripRequestDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.BaseResponseDTO" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/dto.ErrorResponseDTO" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/dto.ErrorResponseDTO" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/dto.ErrorResponseDTO" + } + } + } + }, + "delete": { + "security": [ + { + "bearer": [] + } + ], + "description": "delete trip", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "trip" + ], + "summary": "delete trip", + "parameters": [ + { + "type": "string", + "description": "Authorization", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.BaseResponseDTO" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/dto.ErrorResponseDTO" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/dto.ErrorResponseDTO" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/dto.ErrorResponseDTO" + } } } } @@ -218,65 +430,113 @@ const docTemplate = `{ } } }, - "dto.UserRequestDTO": { + "dto.TripRequestDTO": { "type": "object", "required": [ - "Email", - "Password" + "budget", + "countryId", + "description", + "endDateTime", + "name", + "startDateTime" ], "properties": { - "Email": { + "budget": { + "type": "number", + "example": 2000.12 + }, + "countryId": { + "type": "integer", + "example": 1 + }, + "description": { "type": "string", - "example": "test@email.com" + "example": "sample-description" }, - "Password": { + "endDateTime": { "type": "string", - "example": "test-password" + "example": "2023-08-29" + }, + "name": { + "type": "string", + "example": "sample-name" + }, + "startDateTime": { + "type": "string", + "example": "2023-05-29" } } }, - "entities.Trip": { + "dto.TripResponseDTO": { "type": "object", "properties": { - "Budget": { + "budget": { "type": "number", "example": 2000.12 }, - "CountryId": { + "countryId": { "type": "integer", "example": 1 }, - "CreatedAt": { - "type": "string", - "example": "2023-05-29" - }, - "DeletedAt": { - "type": "string", - "example": "2023-05-29" - }, - "Description": { + "description": { "type": "string", "example": "sample-description" }, - "EndDateTime": { + "endDateTime": { "type": "string", "example": "2023-08-29" }, - "ID": { + "id": { "type": "integer", "example": 1 }, - "Name": { + "name": { "type": "string", "example": "sample-name" }, - "StartDateTime": { + "startDateTime": { "type": "string", "example": "2023-05-29" + } + } + }, + "dto.TripStatusResponseDTO": { + "type": "object", + "properties": { + "current": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.TripResponseDTO" + } + }, + "future": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.TripResponseDTO" + } + }, + "past": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.TripResponseDTO" + } + } + } + }, + "dto.UserRequestDTO": { + "type": "object", + "required": [ + "Email", + "Password" + ], + "properties": { + "Email": { + "type": "string", + "example": "test@email.com" }, - "UpdatedAt": { + "Password": { "type": "string", - "example": "2023-05-29" + "example": "test-password" } } } diff --git a/server/docs/swagger.json b/server/docs/swagger.json index e701102..0799ace 100644 --- a/server/docs/swagger.json +++ b/server/docs/swagger.json @@ -9,7 +9,7 @@ "host": "localhost:8080", "basePath": "/api/v1", "paths": { - "/country": { + "/countries": { "get": { "description": "get a list of all available countires", "consumes": [ @@ -41,8 +41,13 @@ } } }, - "/trip": { + "/trips": { "get": { + "security": [ + { + "bearer": [] + } + ], "description": "get trip", "consumes": [ "application/json" @@ -53,12 +58,21 @@ "tags": [ "trip" ], - "summary": "Get all trip", + "summary": "get trip", + "parameters": [ + { + "type": "string", + "description": "Authorization", + "name": "Authorization", + "in": "header", + "required": true + } + ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/entities.Trip" + "$ref": "#/definitions/dto.TripStatusResponseDTO" } }, "400": { @@ -66,6 +80,204 @@ "schema": { "$ref": "#/definitions/dto.ErrorResponseDTO" } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/dto.ErrorResponseDTO" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/dto.ErrorResponseDTO" + } + } + } + }, + "post": { + "security": [ + { + "bearer": [] + } + ], + "description": "register trip", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "trip" + ], + "summary": "register trip", + "parameters": [ + { + "type": "string", + "description": "Authorization", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "description": "register trip", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.TripRequestDTO" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/dto.BaseResponseDTO" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/dto.ErrorResponseDTO" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/dto.ErrorResponseDTO" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/dto.ErrorResponseDTO" + } + } + } + } + }, + "/trips/{id}": { + "put": { + "security": [ + { + "bearer": [] + } + ], + "description": "update trip", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "trip" + ], + "summary": "update trip", + "parameters": [ + { + "type": "string", + "description": "Authorization", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "description": "update trip", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.TripRequestDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.BaseResponseDTO" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/dto.ErrorResponseDTO" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/dto.ErrorResponseDTO" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/dto.ErrorResponseDTO" + } + } + } + }, + "delete": { + "security": [ + { + "bearer": [] + } + ], + "description": "delete trip", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "trip" + ], + "summary": "delete trip", + "parameters": [ + { + "type": "string", + "description": "Authorization", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.BaseResponseDTO" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/dto.ErrorResponseDTO" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/dto.ErrorResponseDTO" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/dto.ErrorResponseDTO" + } } } } @@ -211,65 +423,113 @@ } } }, - "dto.UserRequestDTO": { + "dto.TripRequestDTO": { "type": "object", "required": [ - "Email", - "Password" + "budget", + "countryId", + "description", + "endDateTime", + "name", + "startDateTime" ], "properties": { - "Email": { + "budget": { + "type": "number", + "example": 2000.12 + }, + "countryId": { + "type": "integer", + "example": 1 + }, + "description": { "type": "string", - "example": "test@email.com" + "example": "sample-description" }, - "Password": { + "endDateTime": { "type": "string", - "example": "test-password" + "example": "2023-08-29" + }, + "name": { + "type": "string", + "example": "sample-name" + }, + "startDateTime": { + "type": "string", + "example": "2023-05-29" } } }, - "entities.Trip": { + "dto.TripResponseDTO": { "type": "object", "properties": { - "Budget": { + "budget": { "type": "number", "example": 2000.12 }, - "CountryId": { + "countryId": { "type": "integer", "example": 1 }, - "CreatedAt": { - "type": "string", - "example": "2023-05-29" - }, - "DeletedAt": { - "type": "string", - "example": "2023-05-29" - }, - "Description": { + "description": { "type": "string", "example": "sample-description" }, - "EndDateTime": { + "endDateTime": { "type": "string", "example": "2023-08-29" }, - "ID": { + "id": { "type": "integer", "example": 1 }, - "Name": { + "name": { "type": "string", "example": "sample-name" }, - "StartDateTime": { + "startDateTime": { "type": "string", "example": "2023-05-29" + } + } + }, + "dto.TripStatusResponseDTO": { + "type": "object", + "properties": { + "current": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.TripResponseDTO" + } + }, + "future": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.TripResponseDTO" + } + }, + "past": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.TripResponseDTO" + } + } + } + }, + "dto.UserRequestDTO": { + "type": "object", + "required": [ + "Email", + "Password" + ], + "properties": { + "Email": { + "type": "string", + "example": "test@email.com" }, - "UpdatedAt": { + "Password": { "type": "string", - "example": "2023-05-29" + "example": "test-password" } } } diff --git a/server/docs/swagger.yaml b/server/docs/swagger.yaml index 2be4bf7..ee8b751 100644 --- a/server/docs/swagger.yaml +++ b/server/docs/swagger.yaml @@ -33,50 +33,84 @@ definitions: example: Bearer type: string type: object - dto.UserRequestDTO: + dto.TripRequestDTO: properties: - Email: - example: test@email.com + budget: + example: 2000.12 + type: number + countryId: + example: 1 + type: integer + description: + example: sample-description type: string - Password: - example: test-password + endDateTime: + example: "2023-08-29" + type: string + name: + example: sample-name + type: string + startDateTime: + example: "2023-05-29" type: string required: - - Email - - Password + - budget + - countryId + - description + - endDateTime + - name + - startDateTime type: object - entities.Trip: + dto.TripResponseDTO: properties: - Budget: + budget: example: 2000.12 type: number - CountryId: + countryId: example: 1 type: integer - CreatedAt: - example: "2023-05-29" - type: string - DeletedAt: - example: "2023-05-29" - type: string - Description: + description: example: sample-description type: string - EndDateTime: + endDateTime: example: "2023-08-29" type: string - ID: + id: example: 1 type: integer - Name: + name: example: sample-name type: string - StartDateTime: + startDateTime: example: "2023-05-29" type: string - UpdatedAt: - example: "2023-05-29" + type: object + dto.TripStatusResponseDTO: + properties: + current: + items: + $ref: '#/definitions/dto.TripResponseDTO' + type: array + future: + items: + $ref: '#/definitions/dto.TripResponseDTO' + type: array + past: + items: + $ref: '#/definitions/dto.TripResponseDTO' + type: array + type: object + dto.UserRequestDTO: + properties: + Email: + example: test@email.com + type: string + Password: + example: test-password type: string + required: + - Email + - Password type: object host: localhost:8080 info: @@ -85,7 +119,7 @@ info: title: Pocket Mate API version: 1.0.0 paths: - /country: + /countries: get: consumes: - application/json @@ -106,23 +140,159 @@ paths: summary: Get all country tags: - country - /trip: + /trips: get: consumes: - application/json description: get trip + parameters: + - description: Authorization + in: header + name: Authorization + required: true + type: string produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/entities.Trip' + $ref: '#/definitions/dto.TripStatusResponseDTO' "400": description: Bad Request schema: $ref: '#/definitions/dto.ErrorResponseDTO' - summary: Get all trip + "401": + description: Unauthorized + schema: + $ref: '#/definitions/dto.ErrorResponseDTO' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/dto.ErrorResponseDTO' + security: + - bearer: [] + summary: get trip + tags: + - trip + post: + consumes: + - application/json + description: register trip + parameters: + - description: Authorization + in: header + name: Authorization + required: true + type: string + - description: register trip + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.TripRequestDTO' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/dto.BaseResponseDTO' + "400": + description: Bad Request + schema: + $ref: '#/definitions/dto.ErrorResponseDTO' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/dto.ErrorResponseDTO' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/dto.ErrorResponseDTO' + security: + - bearer: [] + summary: register trip + tags: + - trip + /trips/{id}: + delete: + consumes: + - application/json + description: delete trip + parameters: + - description: Authorization + in: header + name: Authorization + required: true + type: string + - description: id + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.BaseResponseDTO' + "400": + description: Bad Request + schema: + $ref: '#/definitions/dto.ErrorResponseDTO' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/dto.ErrorResponseDTO' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/dto.ErrorResponseDTO' + security: + - bearer: [] + summary: delete trip + tags: + - trip + put: + consumes: + - application/json + description: update trip + parameters: + - description: Authorization + in: header + name: Authorization + required: true + type: string + - description: update trip + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.TripRequestDTO' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.BaseResponseDTO' + "400": + description: Bad Request + schema: + $ref: '#/definitions/dto.ErrorResponseDTO' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/dto.ErrorResponseDTO' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/dto.ErrorResponseDTO' + security: + - bearer: [] + summary: update trip tags: - trip /user: From cc19ceb82665c81b1151d7e28d435560e7c95323 Mon Sep 17 00:00:00 2001 From: Taehoya Date: Wed, 15 Nov 2023 11:41:29 -0600 Subject: [PATCH 09/11] add godotenv in migrate command --- server/cmd/migrate/main.go | 6 ++---- server/internal/pkg/usecases/user/usecase_test.go | 8 ++++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/server/cmd/migrate/main.go b/server/cmd/migrate/main.go index 64d7b8e..5a076a5 100644 --- a/server/cmd/migrate/main.go +++ b/server/cmd/migrate/main.go @@ -10,15 +10,13 @@ import ( "github.com/golang-migrate/migrate/v4" _ "github.com/golang-migrate/migrate/v4/database/mysql" _ "github.com/golang-migrate/migrate/v4/source/file" + "github.com/joho/godotenv" ) func main() { var direction string - // err := godotenv.Load() - // if err != nil { - // log.Fatal("failed to load .env file") - // } + godotenv.Load() flag.StringVar(&direction, "direction", "up", "migrate cmd: up or down") flag.Parse() diff --git a/server/internal/pkg/usecases/user/usecase_test.go b/server/internal/pkg/usecases/user/usecase_test.go index 1f22e9b..a7efbc7 100644 --- a/server/internal/pkg/usecases/user/usecase_test.go +++ b/server/internal/pkg/usecases/user/usecase_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/Taehoya/go-utils/mysql" + "github.com/Taehoya/go-utils/mysqltest" "github.com/Taehoya/pocket-mate/internal/pkg/entities" mocks "github.com/Taehoya/pocket-mate/internal/pkg/mocks/user" repo "github.com/Taehoya/pocket-mate/internal/pkg/repositories/user" @@ -58,12 +58,12 @@ func TestGet(t *testing.T) { // Currently test is integration test because of encrpyt function func TestRegister(t *testing.T) { t.Run("successfully register user", func(t *testing.T) { - db, err := mysql.InitTestDB() + db, err := mysqltest.InitDB() assert.NoError(t, err) defer db.Close() - defer mysql.SetUp(db, "./teardown_test.sql") - mysql.SetUp(db, "./teardown_test.sql") + defer mysqltest.SetUp(db, "./teardown_test.sql") + mysqltest.SetUp(db, "./teardown_test.sql") email := "test-email" password := "test-password" From 8a8de1d9fc0c0509627c525b8b6beaab2f518629 Mon Sep 17 00:00:00 2001 From: Taehoya Date: Wed, 15 Nov 2023 12:19:24 -0600 Subject: [PATCH 10/11] update workflow to use env sample --- .github/workflows/test.yml | 7 +++++-- server/.env.sample | 8 +++++--- server/cmd/migrate/main.go | 5 ++++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7c80931..a361bcc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,6 +19,7 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v3 + - name: Setup Golang uses: actions/setup-go@v4 with: @@ -30,9 +31,11 @@ jobs: sudo /etc/init.d/mysql start mysql -e 'CREATE DATABASE ${{ env.DB_NAME }};' -u${{ env.DB_USER }} -p${{ env.DB_PASSWORD }} - - name: Database migration + - name: run migration working-directory: ./server - run: make migrate + run: | + cp .env.sample .env + make migrate - name: run test working-directory: ./server diff --git a/server/.env.sample b/server/.env.sample index 4eb7de4..ca0b121 100644 --- a/server/.env.sample +++ b/server/.env.sample @@ -1,5 +1,7 @@ DB_HOST=localhost -DB_NAME=pm +DB_NAME=test DB_USERNAME=root -DB_PASSWORD=password -DB_PORT=3305 \ No newline at end of file +DB_PASSWORD=root +DB_PORT=3306 +ACCESS_TOKEN_SECRET=test-access-token +ACCESS_TOKEN_EXPIRED_AT=1000000 \ No newline at end of file diff --git a/server/cmd/migrate/main.go b/server/cmd/migrate/main.go index 5a076a5..6678bc6 100644 --- a/server/cmd/migrate/main.go +++ b/server/cmd/migrate/main.go @@ -16,7 +16,10 @@ import ( func main() { var direction string - godotenv.Load() + err := godotenv.Load() + if err != nil { + log.Fatal("failed to load .env file") + } flag.StringVar(&direction, "direction", "up", "migrate cmd: up or down") flag.Parse() From 30b43c4c7d19c67e6ea89b8c4bf1e610263e2c5e Mon Sep 17 00:00:00 2001 From: Taehoya Date: Wed, 15 Nov 2023 12:22:30 -0600 Subject: [PATCH 11/11] correct post http status code --- server/internal/pkg/handlers/trip_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/internal/pkg/handlers/trip_test.go b/server/internal/pkg/handlers/trip_test.go index cb41cae..38d18c9 100644 --- a/server/internal/pkg/handlers/trip_test.go +++ b/server/internal/pkg/handlers/trip_test.go @@ -60,7 +60,7 @@ func TestRegisterTrip(t *testing.T) { request, err := http.NewRequest(http.MethodPost, "/api/v1/trips", bytes.NewBuffer(jsonBody)) request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) router.ServeHTTP(rr, request) - assert.Equal(t, 200, rr.Code) + assert.Equal(t, 201, rr.Code) assert.NoError(t, err) }) }