From a2e740eefda99a8443627957ddb5646cc7b45d05 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Sat, 20 Apr 2024 08:09:24 -0700 Subject: [PATCH] moved away from the api directory to internal per go doc recommendations --- {api => internal}/domain/dto.go | 0 {api => internal}/domain/entities.go | 9 ++ {api => internal}/domain/models.go | 0 {api => internal}/domain/requests.go | 6 + {api => internal}/domain/responses.go | 0 {api => internal}/domain/scopes.go | 0 {api => internal}/handlers/v1/auth.go | 14 ++- {api => internal}/handlers/v1/auth_test.go | 4 +- {api => internal}/handlers/v1/demo.go | 2 +- {api => internal}/handlers/v1/handler.go | 22 ++-- {api => internal}/handlers/v1/jwt.go | 2 +- {api => internal}/handlers/v1/users.go | 0 .../migrations/20240321194227_user_table.sql | 0 .../migrations/20240329211828_user_scopes.sql | 0 .../20240416180636_refreshtoken.sql | 17 +++ {api => internal}/repositories/recipe.go | 2 +- internal/repositories/refreshTokens.go | 111 ++++++++++++++++++ internal/repositories/refreshTokens_test.go | 111 ++++++++++++++++++ {api => internal}/repositories/users.go | 2 +- {api => internal}/repositories/users_test.go | 4 +- {api => internal}/services/env.go | 2 +- {api => internal}/services/userService.go | 4 +- .../services/userService_test.go | 2 +- 23 files changed, 290 insertions(+), 24 deletions(-) rename {api => internal}/domain/dto.go (100%) rename {api => internal}/domain/entities.go (67%) rename {api => internal}/domain/models.go (100%) rename {api => internal}/domain/requests.go (58%) rename {api => internal}/domain/responses.go (100%) rename {api => internal}/domain/scopes.go (100%) rename {api => internal}/handlers/v1/auth.go (92%) rename {api => internal}/handlers/v1/auth_test.go (94%) rename {api => internal}/handlers/v1/demo.go (95%) rename {api => internal}/handlers/v1/handler.go (72%) rename {api => internal}/handlers/v1/jwt.go (97%) rename {api => internal}/handlers/v1/users.go (100%) rename {api => internal}/migrations/20240321194227_user_table.sql (100%) rename {api => internal}/migrations/20240329211828_user_scopes.sql (100%) create mode 100644 internal/migrations/20240416180636_refreshtoken.sql rename {api => internal}/repositories/recipe.go (94%) create mode 100644 internal/repositories/refreshTokens.go create mode 100644 internal/repositories/refreshTokens_test.go rename {api => internal}/repositories/users.go (98%) rename {api => internal}/repositories/users_test.go (90%) rename {api => internal}/services/env.go (90%) rename {api => internal}/services/userService.go (96%) rename {api => internal}/services/userService_test.go (93%) diff --git a/api/domain/dto.go b/internal/domain/dto.go similarity index 100% rename from api/domain/dto.go rename to internal/domain/dto.go diff --git a/api/domain/entities.go b/internal/domain/entities.go similarity index 67% rename from api/domain/entities.go rename to internal/domain/entities.go index f26454a..1a310a6 100644 --- a/api/domain/entities.go +++ b/internal/domain/entities.go @@ -11,6 +11,15 @@ type UserEntity struct { Scopes string } +type RefreshTokenEntity struct { + Id int + Username string + Token string + ExpiresAt time.Time + CreatedAt time.Time + LastUpdated time.Time +} + type RecipeEntity struct { Id int32 CreatedAt time.Time diff --git a/api/domain/models.go b/internal/domain/models.go similarity index 100% rename from api/domain/models.go rename to internal/domain/models.go diff --git a/api/domain/requests.go b/internal/domain/requests.go similarity index 58% rename from api/domain/requests.go rename to internal/domain/requests.go index 5be5b3b..6092867 100644 --- a/api/domain/requests.go +++ b/internal/domain/requests.go @@ -8,3 +8,9 @@ type UpdateScopesRequest struct { Username string `json:"username"` Scopes []string `json:"scopes" validate:"required"` } + +type RefreshTokenRequest struct { + Username string `json:"username"` + RefreshToken string `json:"refreshToken"` + ExpiresAt string `json:"expiresAt"` +} diff --git a/api/domain/responses.go b/internal/domain/responses.go similarity index 100% rename from api/domain/responses.go rename to internal/domain/responses.go diff --git a/api/domain/scopes.go b/internal/domain/scopes.go similarity index 100% rename from api/domain/scopes.go rename to internal/domain/scopes.go diff --git a/api/handlers/v1/auth.go b/internal/handlers/v1/auth.go similarity index 92% rename from api/handlers/v1/auth.go rename to internal/handlers/v1/auth.go index f7404dc..5d41a71 100644 --- a/api/handlers/v1/auth.go +++ b/internal/handlers/v1/auth.go @@ -4,8 +4,8 @@ import ( "errors" "net/http" - "git.jamestombleson.com/jtom38/go-cook/api/domain" - "git.jamestombleson.com/jtom38/go-cook/api/repositories" + "git.jamestombleson.com/jtom38/go-cook/internal/domain" + "git.jamestombleson.com/jtom38/go-cook/internal/repositories" "github.com/golang-jwt/jwt/v5" "github.com/labstack/echo/v4" @@ -116,6 +116,16 @@ func (h *Handler) validateAdminToken(c echo.Context, password string) error { return c.JSON(http.StatusOK, token) } +func (h *Handler) GenerateRefreshToken(c echo.Context) error { + // Check the context for the refresh token + var request domain.RefreshTokenRequest + err := (&echo.DefaultBinder{}).BindBody(c, &request) + if err != nil { + return err + } + h.refreshTokenRepo.Create() +} + func (h *Handler) AddScopes(c echo.Context) error { token, err := h.getJwtToken(c) if err != nil { diff --git a/api/handlers/v1/auth_test.go b/internal/handlers/v1/auth_test.go similarity index 94% rename from api/handlers/v1/auth_test.go rename to internal/handlers/v1/auth_test.go index 800127d..f564533 100644 --- a/api/handlers/v1/auth_test.go +++ b/internal/handlers/v1/auth_test.go @@ -8,8 +8,8 @@ import ( "strings" "testing" - v1 "git.jamestombleson.com/jtom38/go-cook/api/handlers/v1" - "git.jamestombleson.com/jtom38/go-cook/api/domain" + v1 "git.jamestombleson.com/jtom38/go-cook/internal/handlers/v1" + "git.jamestombleson.com/jtom38/go-cook/internal/domain" _ "github.com/glebarez/go-sqlite" "github.com/labstack/echo/v4" diff --git a/api/handlers/v1/demo.go b/internal/handlers/v1/demo.go similarity index 95% rename from api/handlers/v1/demo.go rename to internal/handlers/v1/demo.go index 9c888d3..bb6899d 100644 --- a/api/handlers/v1/demo.go +++ b/internal/handlers/v1/demo.go @@ -4,7 +4,7 @@ import ( "fmt" "net/http" - "git.jamestombleson.com/jtom38/go-cook/api/domain" + "git.jamestombleson.com/jtom38/go-cook/internal/domain" "github.com/labstack/echo/v4" ) diff --git a/api/handlers/v1/handler.go b/internal/handlers/v1/handler.go similarity index 72% rename from api/handlers/v1/handler.go rename to internal/handlers/v1/handler.go index 4428740..d40e92a 100644 --- a/api/handlers/v1/handler.go +++ b/internal/handlers/v1/handler.go @@ -4,9 +4,9 @@ import ( "database/sql" "net/http" - "git.jamestombleson.com/jtom38/go-cook/api/repositories" - "git.jamestombleson.com/jtom38/go-cook/api/services" - "git.jamestombleson.com/jtom38/go-cook/api/domain" + "git.jamestombleson.com/jtom38/go-cook/internal/domain" + "git.jamestombleson.com/jtom38/go-cook/internal/repositories" + "git.jamestombleson.com/jtom38/go-cook/internal/services" "github.com/golang-jwt/jwt/v5" echojwt "github.com/labstack/echo-jwt/v4" @@ -16,17 +16,19 @@ import ( type Handler struct { Config domain.EnvConfig - UserService services.UserService - userRepo repositories.IUserTable - recipeRepo repositories.IRecipeTable + UserService services.UserService + userRepo repositories.IUserTable + recipeRepo repositories.IRecipeTable + refreshTokenRepo repositories.RefreshTokenRepository } func NewHandler(conn *sql.DB, cfg domain.EnvConfig) *Handler { return &Handler{ - Config: cfg, - UserService: services.NewUserService(conn), - userRepo: repositories.NewUserRepository(conn), - recipeRepo: repositories.NewRecipeRepository(conn), + Config: cfg, + UserService: services.NewUserService(conn), + userRepo: repositories.NewUserRepository(conn), + recipeRepo: repositories.NewRecipeRepository(conn), + refreshTokenRepo: repositories.NewRefreshTokenRepository(conn), } } diff --git a/api/handlers/v1/jwt.go b/internal/handlers/v1/jwt.go similarity index 97% rename from api/handlers/v1/jwt.go rename to internal/handlers/v1/jwt.go index 0fc2f7d..0af8311 100644 --- a/api/handlers/v1/jwt.go +++ b/internal/handlers/v1/jwt.go @@ -5,7 +5,7 @@ import ( "strings" "time" - "git.jamestombleson.com/jtom38/go-cook/api/domain" + "git.jamestombleson.com/jtom38/go-cook/internal/domain" "github.com/golang-jwt/jwt/v5" ) diff --git a/api/handlers/v1/users.go b/internal/handlers/v1/users.go similarity index 100% rename from api/handlers/v1/users.go rename to internal/handlers/v1/users.go diff --git a/api/migrations/20240321194227_user_table.sql b/internal/migrations/20240321194227_user_table.sql similarity index 100% rename from api/migrations/20240321194227_user_table.sql rename to internal/migrations/20240321194227_user_table.sql diff --git a/api/migrations/20240329211828_user_scopes.sql b/internal/migrations/20240329211828_user_scopes.sql similarity index 100% rename from api/migrations/20240329211828_user_scopes.sql rename to internal/migrations/20240329211828_user_scopes.sql diff --git a/internal/migrations/20240416180636_refreshtoken.sql b/internal/migrations/20240416180636_refreshtoken.sql new file mode 100644 index 0000000..7457fb4 --- /dev/null +++ b/internal/migrations/20240416180636_refreshtoken.sql @@ -0,0 +1,17 @@ +-- +goose Up +-- +goose StatementBegin +SELECT 'up SQL query'; +CREATE TABLE RefreshTokens ( + ID INTEGER PRIMARY KEY AUTOINCREMENT, + Username TEXT NOT NULL, + Token TEXT NOT NULL, + ExpiresAt DATETIME NOT NULL, + CreatedAt DATETIME NOT NULL, + LastUpdated DATETIME NOT NULL +) +-- +goose StatementEnd +-- +goose Down +-- +goose StatementBegin +SELECT 'down SQL query'; +DROP TABLE IF EXISTS RefreshTokens; +-- +goose StatementEnd \ No newline at end of file diff --git a/api/repositories/recipe.go b/internal/repositories/recipe.go similarity index 94% rename from api/repositories/recipe.go rename to internal/repositories/recipe.go index b510c07..ab8c0b4 100644 --- a/api/repositories/recipe.go +++ b/internal/repositories/recipe.go @@ -4,7 +4,7 @@ import ( "database/sql" "errors" - "git.jamestombleson.com/jtom38/go-cook/api/domain" + "git.jamestombleson.com/jtom38/go-cook/internal/domain" ) type IRecipeTable interface { diff --git a/internal/repositories/refreshTokens.go b/internal/repositories/refreshTokens.go new file mode 100644 index 0000000..58e5a97 --- /dev/null +++ b/internal/repositories/refreshTokens.go @@ -0,0 +1,111 @@ +package repositories + +import ( + "database/sql" + "errors" + "fmt" + "time" + + "git.jamestombleson.com/jtom38/go-cook/internal/domain" + "github.com/huandu/go-sqlbuilder" +) + +const ( + refreshTokenTableName = "RefreshTokens" +) + +type RefreshTokenTable interface { +} + +type RefreshTokenRepository struct { + connection *sql.DB +} + +func NewRefreshTokenRepository(conn *sql.DB) RefreshTokenRepository { + return RefreshTokenRepository{ + connection: conn, + } +} + +func (rt RefreshTokenRepository) Create(username string, token string, expiresAt time.Time) (int64, error) { + dt := time.Now() + builder := sqlbuilder.NewInsertBuilder() + builder.InsertInto(refreshTokenTableName) + builder.Cols("Username", "Token", "ExpiresAt", "CreatedAt", "LastUpdated") + builder.Values(username, token, expiresAt, dt, dt) + query, args := builder.Build() + + _, err := rt.connection.Exec(query, args...) + if err != nil { + return 0, err + } + + return 1, nil +} + +func (rt RefreshTokenRepository) GetByUsername(name string) (domain.RefreshTokenEntity, error) { + builder := sqlbuilder.NewSelectBuilder() + builder.Select("*").From(refreshTokenTableName).Where( + builder.E("Username", name), + ) + + query, args := builder.Build() + rows, err := rt.connection.Query(query, args...) + if err != nil { + return domain.RefreshTokenEntity{}, err + } + + data := rt.processRows(rows) + if len(data) == 0 { + return domain.RefreshTokenEntity{}, errors.New("no token found for user") + } + + return data[0], nil +} + +func (rt RefreshTokenRepository) DeleteById(id int) (int64, error) { + builder := sqlbuilder.NewDeleteBuilder() + builder.DeleteFrom(refreshTokenTableName) + builder.Where( + builder.EQ("Id", id), + ) + + query, args := builder.Build() + _, err := rt.connection.Exec(query, args...) + if err != nil { + return -1, err + } + + return 1, nil +} + +func (rd RefreshTokenRepository) processRows(rows *sql.Rows) []domain.RefreshTokenEntity { + items := []domain.RefreshTokenEntity{} + + for rows.Next() { + var id int + var username string + var token string + var expiresAt time.Time + var createdAt time.Time + var lastUpdated time.Time + + err := rows.Scan(&id, &username, &token, &expiresAt, &createdAt, &lastUpdated) + if err != nil { + fmt.Println(err) + } + + items = append(items, domain.RefreshTokenEntity{ + Id: id, + Username: username, + Token: token, + ExpiresAt: expiresAt, + CreatedAt: createdAt, + LastUpdated: lastUpdated, + }) + } + + return items +} + +//func (rt RefreshTokenRepository) Delete() diff --git a/internal/repositories/refreshTokens_test.go b/internal/repositories/refreshTokens_test.go new file mode 100644 index 0000000..1cd14f4 --- /dev/null +++ b/internal/repositories/refreshTokens_test.go @@ -0,0 +1,111 @@ +package repositories_test + +import ( + "database/sql" + "testing" + "time" + + "git.jamestombleson.com/jtom38/go-cook/internal/repositories" + _ "github.com/glebarez/go-sqlite" + "github.com/pressly/goose/v3" +) + +func TestRefreshTokenCreate(t *testing.T) { + conn, err := setupInMemoryDb() + if err != nil { + t.Log(err) + t.FailNow() + } + + client := repositories.NewRefreshTokenRepository(conn) + rows, err := client.Create("tester", "BadTokenDontUse", time.Now().Add(time.Hour+1)) + if err != nil { + t.Log(err) + t.FailNow() + } + + if rows == 0 { + t.Log("expected one row to come back but got 0") + } +} + +func TestRefreshTokenGetByUsername(t *testing.T) { + conn, err := setupInMemoryDb() + if err != nil { + t.Log(err) + t.FailNow() + } + + client := repositories.NewRefreshTokenRepository(conn) + rows, err := client.Create("tester", "BadTokenDoNotUse", time.Now().Add(time.Hour+1)) + if err != nil { + t.Log(err) + t.FailNow() + } + + if rows != 1 { + t.Log("expected a row to be added but not the wrong value back") + t.FailNow() + } + + model, err := client.GetByUsername("tester") + if err != nil { + t.Log(err) + t.FailNow() + } + + if model.Username != "tester" { + t.Log("got the wrong user back") + t.FailNow() + } +} + +func TestRefreshTokenDeleteById(t *testing.T) { + conn, err := setupInMemoryDb() + if err != nil { + t.Log(err) + t.FailNow() + } + + client := repositories.NewRefreshTokenRepository(conn) + _, err = client.Create("tester", "BadTokenDoNotUse", time.Now().Add(time.Hour+1)) + if err != nil { + t.Log(err) + t.FailNow() + } + + model, err := client.GetByUsername("tester") + if err != nil { + t.Log(err) + t.FailNow() + } + + updated, err := client.DeleteById(model.Id) + if err != nil { + t.Log(err) + t.FailNow() + } + + if updated != 1 { + t.Log("deleted the wrong number of records") + t.FailNow() + } +} + +func setupInMemoryDb() (*sql.DB, error) { + db, err := sql.Open("sqlite", ":memory:") + if err != nil { + return nil, err + } + + err = goose.SetDialect("sqlite3") + if err != nil { + return nil, err + } + + err = goose.Up(db, "../migrations") + if err != nil { + return nil, err + } + return db, nil +} diff --git a/api/repositories/users.go b/internal/repositories/users.go similarity index 98% rename from api/repositories/users.go rename to internal/repositories/users.go index 78cd5cf..3fc1e52 100644 --- a/api/repositories/users.go +++ b/internal/repositories/users.go @@ -6,7 +6,7 @@ import ( "fmt" "time" - "git.jamestombleson.com/jtom38/go-cook/api/domain" + "git.jamestombleson.com/jtom38/go-cook/internal/domain" "github.com/huandu/go-sqlbuilder" "golang.org/x/crypto/bcrypt" diff --git a/api/repositories/users_test.go b/internal/repositories/users_test.go similarity index 90% rename from api/repositories/users_test.go rename to internal/repositories/users_test.go index 4d62136..32c7a30 100644 --- a/api/repositories/users_test.go +++ b/internal/repositories/users_test.go @@ -5,8 +5,8 @@ import ( "log" "testing" - "git.jamestombleson.com/jtom38/go-cook/api/repositories" - "git.jamestombleson.com/jtom38/go-cook/api/domain" + "git.jamestombleson.com/jtom38/go-cook/internal/repositories" + "git.jamestombleson.com/jtom38/go-cook/internal/domain" "github.com/DATA-DOG/go-sqlmock" _ "github.com/glebarez/go-sqlite" diff --git a/api/services/env.go b/internal/services/env.go similarity index 90% rename from api/services/env.go rename to internal/services/env.go index 3ea6d90..3c1f73d 100644 --- a/api/services/env.go +++ b/internal/services/env.go @@ -5,7 +5,7 @@ import ( "os" "strconv" - "git.jamestombleson.com/jtom38/go-cook/api/domain" + "git.jamestombleson.com/jtom38/go-cook/internal/domain" "github.com/joho/godotenv" ) diff --git a/api/services/userService.go b/internal/services/userService.go similarity index 96% rename from api/services/userService.go rename to internal/services/userService.go index 48f0948..decb30f 100644 --- a/api/services/userService.go +++ b/internal/services/userService.go @@ -5,8 +5,8 @@ import ( "errors" "strings" - "git.jamestombleson.com/jtom38/go-cook/api/domain" - "git.jamestombleson.com/jtom38/go-cook/api/repositories" + "git.jamestombleson.com/jtom38/go-cook/internal/domain" + "git.jamestombleson.com/jtom38/go-cook/internal/repositories" "golang.org/x/crypto/bcrypt" ) diff --git a/api/services/userService_test.go b/internal/services/userService_test.go similarity index 93% rename from api/services/userService_test.go rename to internal/services/userService_test.go index 5be3811..38106c9 100644 --- a/api/services/userService_test.go +++ b/internal/services/userService_test.go @@ -3,7 +3,7 @@ package services_test import ( "testing" - "git.jamestombleson.com/jtom38/go-cook/api/services" + "git.jamestombleson.com/jtom38/go-cook/internal/services" "github.com/DATA-DOG/go-sqlmock" )