From 5fcb5c2cd511687a39f5f75579e1bfaa66cccd54 Mon Sep 17 00:00:00 2001 From: ericprd Date: Sun, 23 Feb 2025 22:34:26 +0800 Subject: [PATCH] feat: login service and implement database storage --- .gitignore | 1 + README.md | 102 ++++++++++++++++++ cmd/legalgo/main.go | 58 ++++++++-- database/module.go | 7 ++ database/new_db.go | 43 ++++++++ database/staff/model.go | 11 ++ go.mod | 21 ++++ go.sum | 41 +++++++ internal/accessor/module.go | 10 ++ internal/accessor/staff/get_staff.go | 25 +++++ internal/accessor/staff/impl.go | 18 ++++ internal/api/http/auth/login.go | 67 ++++++++++++ internal/api/http/auth/module.go | 9 ++ internal/api/http/router.go | 8 +- internal/domain/auth/staff.go | 10 ++ internal/services/auth/impl.go | 19 ++++ internal/services/auth/login_as_staff.go | 21 ++++ internal/services/module.go | 12 +++ internal/utilities/response/error_code.go | 15 +++ .../utilities/response/error_with_code.go | 23 ++++ .../utilities/response/set_default_headers.go | 19 ++++ internal/utilities/response/success.go | 23 ++++ internal/utilities/utils/body_payload.go | 21 ++++ internal/utilities/utils/jwt.go | 6 +- 24 files changed, 579 insertions(+), 11 deletions(-) create mode 100644 README.md create mode 100644 database/module.go create mode 100644 database/new_db.go create mode 100644 database/staff/model.go create mode 100644 internal/accessor/module.go create mode 100644 internal/accessor/staff/get_staff.go create mode 100644 internal/accessor/staff/impl.go create mode 100644 internal/api/http/auth/login.go create mode 100644 internal/api/http/auth/module.go create mode 100644 internal/domain/auth/staff.go create mode 100644 internal/services/auth/impl.go create mode 100644 internal/services/auth/login_as_staff.go create mode 100644 internal/services/module.go create mode 100644 internal/utilities/response/error_code.go create mode 100644 internal/utilities/response/error_with_code.go create mode 100644 internal/utilities/response/set_default_headers.go create mode 100644 internal/utilities/response/success.go create mode 100644 internal/utilities/utils/body_payload.go diff --git a/.gitignore b/.gitignore index ba077a4..d84cea3 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ bin +.env diff --git a/README.md b/README.md new file mode 100644 index 0000000..c10e39a --- /dev/null +++ b/README.md @@ -0,0 +1,102 @@ +# Instructions: Setup Go Version 1.24.0, Manage Dependencies, Build, and Run + +## Prerequisites + +Before proceeding, make sure you have the following installed on your computer: + +- **Go (Golang) version 1.24.0** +- **Make** + +## Steps to Install Go 1.24.0 + +### For Windows: +1. Download the installer from [official Go website](https://golang.org/dl/). +2. Run the downloaded `.msi` file and follow the installation wizard. +3. During installation, you may be prompted to set the `GOPATH` and `GOROOT`. Choose the appropriate settings +based on your preference or keep the default values. + +### For macOS: +1. Open Terminal. +2. Download the package using Homebrew (if not installed, visit [Homebrew website](https://brew.sh/) for +installation instructions): + ```bash + brew install go@1.24 + ``` +3. Link the installed version to your PATH: + ```bash + brew link --overwrite --force go@1.24 + ``` + +### For Linux: +1. Open Terminal. +2. Download the appropriate tarball from [official Go website](https://golang.org/dl/). +3. Extract the downloaded archive: + ```bash + tar -C /usr/local -xzf go1.24.linux-amd64.tar.gz + ``` +4. Add Go to your PATH by editing your shell configuration file (e.g., `.bashrc`, `.zshrc`): + ```bash + echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc + source ~/.bashrc + ``` + +## Verify Installation + +After installation, verify that Go 1.24.0 is installed correctly by running: +```bash +go version +``` +You should see an output like `go version go1.24 darwin/amd64` (or similar, depending on your OS). + +## Install Make (if not already installed) + +### For Windows: +- Download and install [Make for Windows](http://gnuwin32.sourceforge.net/packages/make.htm). +- Add the installation directory to your PATH. + +### For macOS: +1. Open Terminal. +2. Install Make using Homebrew: + ```bash + brew install make + ``` + +### For Linux: +Make is usually pre-installed on most Linux distributions. If it's not installed, you can install it using your +package manager (e.g., `sudo apt-get install make` for Ubuntu). + +## Navigate to Your Go Project Directory + +Open Terminal and navigate to the root directory of your Go project. + +## Resolve Dependencies with `go mod tidy` + +Run the following command to download and resolve all dependencies specified in your `go.mod` file: +```bash +go mod tidy +``` + +## Build the Binary Using `make build` + +Run the following command to build your project. The binary will be placed in the `bin` folder. +```bash +make build +``` + +## Run the Program Using `make run` + +Finally, you can run your program using: +```bash +make run +``` + +This command will compile and execute your Go application. + +--- + +**Note:** Ensure that your `Makefile` is correctly set up to handle these commands. If not, you may need to create +or adjust it accordingly. +``` + +Please follow the above instructions to set up Go version 1.24.0, manage dependencies using `go mod tidy`, build a +binary with `make build`, and run your application with `make run`. diff --git a/cmd/legalgo/main.go b/cmd/legalgo/main.go index b659595..e828503 100644 --- a/cmd/legalgo/main.go +++ b/cmd/legalgo/main.go @@ -1,17 +1,61 @@ package main import ( + "context" + "fmt" + "log" + + "github.com/ardeman/project-legalgo-go/database" + repository "github.com/ardeman/project-legalgo-go/internal/accessor" internalhttp "github.com/ardeman/project-legalgo-go/internal/api/http" pkgconfig "github.com/ardeman/project-legalgo-go/internal/config" + "github.com/ardeman/project-legalgo-go/internal/services" "github.com/go-chi/chi/v5" + "github.com/joho/godotenv" "go.uber.org/fx" ) -func main() { - fx.New( - internalhttp.Module, - fx.Invoke(func(apiRouter chi.Router) { - pkgconfig.Router(apiRouter) - }), - ) +func init() { + if err := godotenv.Load(); err != nil { + log.Fatal("cannot load environment file") + } +} + +func run(lc fx.Lifecycle, db *database.DB, apiRouter chi.Router) { + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + fmt.Println("Application has started...") + // Run migration + if err := db.Migrate(); err != nil { + log.Fatal("Migration failed: ", err) + } + + pkgconfig.Router(apiRouter) + + return nil + }, + OnStop: func(ctx context.Context) error { + fmt.Println("Shutting down...") + return nil + }, + }) +} + +func main() { + app := fx.New( + database.Module, + repository.Module, + internalhttp.Module, + services.Module, + fx.Invoke(run), + ) + + if err := app.Start(context.Background()); err != nil { + log.Fatal("Error starting app:", err) + } + + // Wait for the app to stop gracefully + if err := app.Stop(context.Background()); err != nil { + log.Fatal("Error stopping app:", err) + } } diff --git a/database/module.go b/database/module.go new file mode 100644 index 0000000..c5bae70 --- /dev/null +++ b/database/module.go @@ -0,0 +1,7 @@ +package database + +import "go.uber.org/fx" + +var Module = fx.Module("database", fx.Provide( + NewDB, +)) diff --git a/database/new_db.go b/database/new_db.go new file mode 100644 index 0000000..fe6e821 --- /dev/null +++ b/database/new_db.go @@ -0,0 +1,43 @@ +package database + +import ( + "fmt" + "os" + + staffmodel "github.com/ardeman/project-legalgo-go/database/staff" + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +type DB struct { + *gorm.DB +} + +func NewDB() (*DB, error) { + var ( + host = os.Getenv("DB_HOST") + user = os.Getenv("DB_USER") + password = os.Getenv("DB_PASSWORD") + dbName = os.Getenv("DB_NAME") + dbPort = os.Getenv("DB_PORT") + ) + + dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%v sslmode=disable", host, user, password, dbName, dbPort) + + if dsn == "" { + return nil, fmt.Errorf("DATABASE_URL environment is not set") + } + + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) + + if err != nil { + return nil, fmt.Errorf("failed to connect to database: %w", err) + } + + return &DB{db}, nil +} + +func (db *DB) Migrate() error { + // Auto Migrate the User model + return db.AutoMigrate(&staffmodel.Staff{}) +} diff --git a/database/staff/model.go b/database/staff/model.go new file mode 100644 index 0000000..e43ce93 --- /dev/null +++ b/database/staff/model.go @@ -0,0 +1,11 @@ +package staffmodel + +import ( + "github.com/google/uuid" +) + +type Staff struct { + ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"` + Email string `gorm:"unique,not null" json:"email"` + Password string `gorm:"not null" json:"password"` +} diff --git a/go.mod b/go.mod index b8d367d..8aad9eb 100644 --- a/go.mod +++ b/go.mod @@ -7,12 +7,32 @@ require ( go.uber.org/fx v1.23.0 ) +require ( + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.7.2 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + golang.org/x/crypto v0.34.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/text v0.22.0 // indirect + gorm.io/driver/postgres v1.5.11 // indirect +) + require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/go-chi/chi v1.5.5 github.com/go-chi/chi/v5 v5.2.1 // indirect github.com/go-chi/cors v1.2.1 + github.com/go-playground/validator/v10 v10.25.0 + github.com/google/uuid v1.6.0 github.com/redis/go-redis/v9 v9.7.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect go.uber.org/dig v1.18.0 // indirect @@ -20,4 +40,5 @@ require ( go.uber.org/zap v1.26.0 // indirect golang.org/x/sync v0.11.0 golang.org/x/sys v0.30.0 // indirect + gorm.io/gorm v1.25.12 ) diff --git a/go.sum b/go.sum index dc119d8..778b3b3 100644 --- a/go.sum +++ b/go.sum @@ -5,14 +5,40 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE= github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw= github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8= +github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI= +github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.7.1 h1:4LhKRCIduqXqtvCUlaq9c8bdHOkICjDMrr1+Zb3osAc= @@ -20,6 +46,7 @@ github.com/redis/go-redis/v9 v9.7.1/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93Ge github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -33,12 +60,26 @@ go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.34.0 h1:+/C6tk6rf/+t5DhUketUbD1aNGqiSX3j15Z6xuIDlBA= +golang.org/x/crypto v0.34.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314= +gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= diff --git a/internal/accessor/module.go b/internal/accessor/module.go new file mode 100644 index 0000000..7bc49e5 --- /dev/null +++ b/internal/accessor/module.go @@ -0,0 +1,10 @@ +package repository + +import ( + staffrepository "github.com/ardeman/project-legalgo-go/internal/accessor/staff" + "go.uber.org/fx" +) + +var Module = fx.Module("repository", fx.Provide( + staffrepository.New, +)) diff --git a/internal/accessor/staff/get_staff.go b/internal/accessor/staff/get_staff.go new file mode 100644 index 0000000..123f90f --- /dev/null +++ b/internal/accessor/staff/get_staff.go @@ -0,0 +1,25 @@ +package staffrepository + +import ( + "errors" + + staffmodel "github.com/ardeman/project-legalgo-go/database/staff" + "gorm.io/gorm" +) + +func (sr *StaffRepository) GetStaff(email string) (*staffmodel.Staff, error) { + var staff staffmodel.Staff + + if email == "" { + return nil, errors.New("email is empty") + } + + if err := sr.DB.Where("email = ?", email).First(&staff).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, errors.New("staff not found") + } + return nil, err + } + + return &staff, nil +} diff --git a/internal/accessor/staff/impl.go b/internal/accessor/staff/impl.go new file mode 100644 index 0000000..3e91285 --- /dev/null +++ b/internal/accessor/staff/impl.go @@ -0,0 +1,18 @@ +package staffrepository + +import ( + "github.com/ardeman/project-legalgo-go/database" + staffmodel "github.com/ardeman/project-legalgo-go/database/staff" +) + +type StaffRepository struct { + DB *database.DB +} + +type StaffInterface interface { + GetStaff(string) (*staffmodel.Staff, error) +} + +func New(db *database.DB) StaffInterface { + return &StaffRepository{db} +} diff --git a/internal/api/http/auth/login.go b/internal/api/http/auth/login.go new file mode 100644 index 0000000..727461b --- /dev/null +++ b/internal/api/http/auth/login.go @@ -0,0 +1,67 @@ +package authhttp + +import ( + "net/http" + + domain "github.com/ardeman/project-legalgo-go/internal/domain/auth" + serviceauth "github.com/ardeman/project-legalgo-go/internal/services/auth" + "github.com/ardeman/project-legalgo-go/internal/utilities/response" + "github.com/ardeman/project-legalgo-go/internal/utilities/utils" + "github.com/go-chi/chi/v5" + "github.com/go-playground/validator/v10" +) + +func LoginStaff( + router chi.Router, + authSvc serviceauth.LoginStaffIntf, + validate *validator.Validate, +) { + router.Post("/staff/login", func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var request domain.StaffLoginReq + + if err := utils.UnmarshalBody(r, &request); err != nil { + response.ResponseWithErrorCode( + ctx, + w, + err, + response.ErrBadRequest.Code, + response.ErrBadRequest.HttpCode, + "failed to unmarshal request", + ) + return + } + + if err := validate.Struct(request); err != nil { + response.ResponseWithErrorCode( + ctx, + w, + err, + response.ErrBadRequest.Code, + response.ErrBadRequest.HttpCode, + err.(validator.ValidationErrors).Error(), + ) + return + } + + token, err := authSvc.LoginAsStaff(request.Email) + if err != nil { + response.ResponseWithErrorCode( + ctx, + w, + err, + response.ErrBadRequest.Code, + response.ErrBadRequest.HttpCode, + err.Error(), + ) + return + } + + responsePayload := &domain.StaffLoginResponse{ + Token: token, + } + + response.RespondJsonSuccess(ctx, w, responsePayload) + }) +} diff --git a/internal/api/http/auth/module.go b/internal/api/http/auth/module.go new file mode 100644 index 0000000..faba3f8 --- /dev/null +++ b/internal/api/http/auth/module.go @@ -0,0 +1,9 @@ +package authhttp + +import "go.uber.org/fx" + +var Module = fx.Module("auth-api", + fx.Invoke( + LoginStaff, + ), +) diff --git a/internal/api/http/router.go b/internal/api/http/router.go index 4e73cef..a38b667 100644 --- a/internal/api/http/router.go +++ b/internal/api/http/router.go @@ -1,15 +1,21 @@ package internalhttp import ( + authhttp "github.com/ardeman/project-legalgo-go/internal/api/http/auth" "github.com/go-chi/chi/v5" "github.com/go-chi/cors" + "github.com/go-playground/validator/v10" "go.uber.org/fx" chimware "github.com/go-chi/chi/v5/middleware" ) var Module = fx.Module("router", - fx.Provide(initRouter), + fx.Provide( + initRouter, + validator.New, + ), + authhttp.Module, ) func initRouter() chi.Router { diff --git a/internal/domain/auth/staff.go b/internal/domain/auth/staff.go new file mode 100644 index 0000000..35201a5 --- /dev/null +++ b/internal/domain/auth/staff.go @@ -0,0 +1,10 @@ +package domain + +type StaffLoginReq struct { + Email string `json:"email" validate:"required"` + Password string `json:"password" validate:"required"` +} + +type StaffLoginResponse struct { + Token string `json:"token"` +} diff --git a/internal/services/auth/impl.go b/internal/services/auth/impl.go new file mode 100644 index 0000000..b53f9cd --- /dev/null +++ b/internal/services/auth/impl.go @@ -0,0 +1,19 @@ +package serviceauth + +import staffrepository "github.com/ardeman/project-legalgo-go/internal/accessor/staff" + +type LoginStaffSvc struct { + staffAcs staffrepository.StaffInterface +} + +type LoginStaffIntf interface { + LoginAsStaff(string) (string, error) +} + +func New( + staffAcs staffrepository.StaffInterface, +) LoginStaffIntf { + return &LoginStaffSvc{ + staffAcs: staffAcs, + } +} diff --git a/internal/services/auth/login_as_staff.go b/internal/services/auth/login_as_staff.go new file mode 100644 index 0000000..554ac66 --- /dev/null +++ b/internal/services/auth/login_as_staff.go @@ -0,0 +1,21 @@ +package serviceauth + +import ( + "errors" + + "github.com/ardeman/project-legalgo-go/internal/utilities/utils" +) + +func (sv *LoginStaffSvc) LoginAsStaff(email string) (string, error) { + staff, err := sv.staffAcs.GetStaff(email) + if err != nil { + return "", errors.New(err.Error()) + } + + token, err := utils.GenerateToken2(staff.Email) + if err != nil { + return "", errors.New(err.Error()) + } + + return token, nil +} diff --git a/internal/services/module.go b/internal/services/module.go new file mode 100644 index 0000000..545f4a3 --- /dev/null +++ b/internal/services/module.go @@ -0,0 +1,12 @@ +package services + +import ( + serviceauth "github.com/ardeman/project-legalgo-go/internal/services/auth" + "go.uber.org/fx" +) + +var Module = fx.Module("services", + fx.Provide( + serviceauth.New, + ), +) diff --git a/internal/utilities/response/error_code.go b/internal/utilities/response/error_code.go new file mode 100644 index 0000000..6e8bb91 --- /dev/null +++ b/internal/utilities/response/error_code.go @@ -0,0 +1,15 @@ +package response + +import "net/http" + +type ErrorCode struct { + Code string + Message string + HttpCode int +} + +var ( + // 4xx + ErrBadRequest = ErrorCode{Code: "BAD_REQUEST", Message: "BAD_REQUEST", HttpCode: http.StatusBadRequest} + ErrDBRequest = ErrorCode{Code: "BAD_DB_REQUEST", Message: "DB_ERROR", HttpCode: http.StatusBadRequest} +) diff --git a/internal/utilities/response/error_with_code.go b/internal/utilities/response/error_with_code.go new file mode 100644 index 0000000..49c7296 --- /dev/null +++ b/internal/utilities/response/error_with_code.go @@ -0,0 +1,23 @@ +package response + +import ( + "context" + "encoding/json" + "net/http" +) + +func ResponseWithErrorCode(ctx context.Context, w http.ResponseWriter, + err error, code string, statusCode int, message string) { + setDefaultHeaders(ctx, w.Header()) + + w.WriteHeader(statusCode) + + b, _ := json.Marshal(ErrorResponse{ + Error: ErrorResponseData{ + Code: code, + Message: message, + }, + }) + + w.Write(b) +} diff --git a/internal/utilities/response/set_default_headers.go b/internal/utilities/response/set_default_headers.go new file mode 100644 index 0000000..375448c --- /dev/null +++ b/internal/utilities/response/set_default_headers.go @@ -0,0 +1,19 @@ +package response + +import ( + "context" + "net/http" +) + +type ErrorResponseData struct { + Code string `json:"code"` + Message string `json:"message"` +} + +type ErrorResponse struct { + Error ErrorResponseData `json:"error"` +} + +func setDefaultHeaders(ctx context.Context, headers http.Header) { + headers.Set("Content-Type", "application/json") +} diff --git a/internal/utilities/response/success.go b/internal/utilities/response/success.go new file mode 100644 index 0000000..68d40a9 --- /dev/null +++ b/internal/utilities/response/success.go @@ -0,0 +1,23 @@ +package response + +import ( + "context" + "encoding/json" + "net/http" +) + +type Response[T any] struct { + Data T `json:"data"` +} + +func RespondJsonSuccess[T any](ctx context.Context, w http.ResponseWriter, data T) { + setDefaultHeaders(ctx, w.Header()) + + w.WriteHeader(http.StatusOK) + + b, _ := json.Marshal(Response[T]{ + Data: data, + }) + + w.Write(b) +} diff --git a/internal/utilities/utils/body_payload.go b/internal/utilities/utils/body_payload.go new file mode 100644 index 0000000..e2c3fac --- /dev/null +++ b/internal/utilities/utils/body_payload.go @@ -0,0 +1,21 @@ +package utils + +import ( + "encoding/json" + "io" + "net/http" +) + +func UnmarshalBody(r *http.Request, v any) error { + body, err := io.ReadAll(r.Body) + if err != nil { + return err + } + + err = json.Unmarshal(body, v) + if err != nil { + return err + } + + return nil +} diff --git a/internal/utilities/utils/jwt.go b/internal/utilities/utils/jwt.go index 68df162..2d1e757 100644 --- a/internal/utilities/utils/jwt.go +++ b/internal/utilities/utils/jwt.go @@ -28,11 +28,11 @@ func GenerateToken(options ...ClaimOption) (string, error) { return token.SignedString(jwtSecret) } -func GenerateToken2(username string) (string, error) { +func GenerateToken2(email string) (string, error) { now := timeutils.Now() - token := jwt.New(jwt.SigningMethodES256) + token := jwt.New(jwt.SigningMethodHS256) claims := token.Claims.(jwt.MapClaims) - claims["username"] = username + claims["email"] = email claims["exp"] = now.Add(time.Hour).Unix() return token.SignedString(jwtSecret)