Compare commits
	
		
			6 Commits
		
	
	
		
			4cdfe753c8
			...
			6f9ff9ab96
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 6f9ff9ab96 | |||
| 7b8abf8e5c | |||
| 4546665461 | |||
| 2c1beb30f6 | |||
| 43a1d0509c | |||
| b14b8788ab | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -25,3 +25,4 @@ go.work.sum
 | 
				
			|||||||
# Custom
 | 
					# Custom
 | 
				
			||||||
/_output
 | 
					/_output
 | 
				
			||||||
/deployment/db_data
 | 
					/deployment/db_data
 | 
				
			||||||
 | 
					/tmp/**
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										5
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								Makefile
									
									
									
									
									
								
							@ -33,9 +33,12 @@ all: add-copyright format build
 | 
				
			|||||||
# ==============================================================================
 | 
					# ==============================================================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PHONY: build
 | 
					.PHONY: build
 | 
				
			||||||
build: tidy # build.
 | 
					build: tidy sqlc # build.
 | 
				
			||||||
	@go build -v -ldflags "$(GO_LDFLAGS)" -o $(OUTPUT_DIR)/howmuch $(ROOT_DIR)/cmd/howmuch/main.go 2>/dev/null
 | 
						@go build -v -ldflags "$(GO_LDFLAGS)" -o $(OUTPUT_DIR)/howmuch $(ROOT_DIR)/cmd/howmuch/main.go 2>/dev/null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.PHONY: sqlc
 | 
				
			||||||
 | 
						@sqlc generate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PHONY: format
 | 
					.PHONY: format
 | 
				
			||||||
format: # format code.
 | 
					format: # format code.
 | 
				
			||||||
	@gofmt -s -w ./
 | 
						@gofmt -s -w ./
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										39
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								README.md
									
									
									
									
									
								
							@ -198,9 +198,46 @@ type User struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Use Buffalo pop `Soda CLI` to create database migrations.
 | 
					Use Buffalo pop `Soda CLI` to create database migrations.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 2024/10/07
 | 
					### 2024/10/06
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Implement the architecture design for User entity.
 | 
					Implement the architecture design for User entity.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Checked out OpenAPI, and found that it was not that simple at all. It needs
 | 
					Checked out OpenAPI, and found that it was not that simple at all. It needs
 | 
				
			||||||
a whole package of knowledge about the web development!
 | 
					a whole package of knowledge about the web development!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For the test-driven part,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- model layer: just model designs, **nothing to test**
 | 
				
			||||||
 | 
					- infra: routes and db connections, it works when it works. Nothing to test.
 | 
				
			||||||
 | 
					- registry: Just return some structs, no logic. **Not worth testing**
 | 
				
			||||||
 | 
					- adapter:
 | 
				
			||||||
 | 
					    - input-port (controller) test: it is about testing parsing the input
 | 
				
			||||||
 | 
					    value, and the output results writing. The unit test of controller is to
 | 
				
			||||||
 | 
					    **make sure that they behave as defined in the API documentation**. To
 | 
				
			||||||
 | 
					    test, we have to mock the **business service**.
 | 
				
			||||||
 | 
					    - output-port (repo) test: it is about testing converting business model
 | 
				
			||||||
 | 
					    to database model and the interaction with the database. If we are going
 | 
				
			||||||
 | 
					    to test them, it's about simulating different type of database behaviour
 | 
				
			||||||
 | 
					    (success, timeout, etc.). To test, we have to mock the
 | 
				
			||||||
 | 
					    **database connection**.
 | 
				
			||||||
 | 
					- usecase: This is the core part to test, it's about the core business.
 | 
				
			||||||
 | 
					We provide the data input and we check the data output in a fake repository.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					With this design, although it may seem overkill for this little project, fits
 | 
				
			||||||
 | 
					perfectly well with the TDD method.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Concretely, I will do the TDD for my usecase level development, and for the
 | 
				
			||||||
 | 
					rest, I just put unit tests aside for later.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Workflow
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. OAS Definition
 | 
				
			||||||
 | 
					2. (Integration/Validation test)
 | 
				
			||||||
 | 
					3. Usecase unit test cases
 | 
				
			||||||
 | 
					4. Usecase development
 | 
				
			||||||
 | 
					5. Refactor (2-3-4)
 | 
				
			||||||
 | 
					6. Input-port/Output-port
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					That should be the correct workflow. But to save time, I will cut off the
 | 
				
			||||||
 | 
					integration test part (the 2nd point).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -39,7 +39,7 @@ tags:
 | 
				
			|||||||
  - name: user
 | 
					  - name: user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
paths:
 | 
					paths:
 | 
				
			||||||
  /signup:
 | 
					  /user/signup:
 | 
				
			||||||
    post:
 | 
					    post:
 | 
				
			||||||
      tags:
 | 
					      tags:
 | 
				
			||||||
        - user
 | 
					        - user
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										7
									
								
								docs/error_code.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/error_code.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					# Platform level error code design
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- InternalError
 | 
				
			||||||
 | 
					- InvalidParameter
 | 
				
			||||||
 | 
					- AuthFailure
 | 
				
			||||||
 | 
					- ResourceNotFound
 | 
				
			||||||
 | 
					- FailedOperation
 | 
				
			||||||
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							@ -35,7 +35,6 @@ require (
 | 
				
			|||||||
	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 | 
						github.com/inconshreveable/mousetrap v1.1.0 // indirect
 | 
				
			||||||
	github.com/jackc/pgpassfile v1.0.0 // indirect
 | 
						github.com/jackc/pgpassfile v1.0.0 // indirect
 | 
				
			||||||
	github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
 | 
						github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
 | 
				
			||||||
	github.com/jackc/puddle/v2 v2.2.2 // indirect
 | 
					 | 
				
			||||||
	github.com/json-iterator/go v1.1.12 // indirect
 | 
						github.com/json-iterator/go v1.1.12 // indirect
 | 
				
			||||||
	github.com/klauspost/cpuid/v2 v2.2.7 // indirect
 | 
						github.com/klauspost/cpuid/v2 v2.2.7 // indirect
 | 
				
			||||||
	github.com/leodido/go-urn v1.4.0 // indirect
 | 
						github.com/leodido/go-urn v1.4.0 // indirect
 | 
				
			||||||
@ -61,7 +60,6 @@ require (
 | 
				
			|||||||
	golang.org/x/arch v0.8.0 // indirect
 | 
						golang.org/x/arch v0.8.0 // indirect
 | 
				
			||||||
	golang.org/x/crypto v0.27.0 // indirect
 | 
						golang.org/x/crypto v0.27.0 // indirect
 | 
				
			||||||
	golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
 | 
						golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
 | 
				
			||||||
	golang.org/x/sync v0.8.0 // indirect
 | 
					 | 
				
			||||||
	golang.org/x/sys v0.25.0 // indirect
 | 
						golang.org/x/sys v0.25.0 // indirect
 | 
				
			||||||
	golang.org/x/text v0.18.0 // indirect
 | 
						golang.org/x/text v0.18.0 // indirect
 | 
				
			||||||
	google.golang.org/protobuf v1.34.1 // indirect
 | 
						google.golang.org/protobuf v1.34.1 // indirect
 | 
				
			||||||
 | 
				
			|||||||
@ -25,33 +25,66 @@ package controller
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model"
 | 
				
			||||||
 | 
						"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/usecase"
 | 
				
			||||||
	"git.vinchent.xyz/vinchent/howmuch/internal/pkg/core"
 | 
						"git.vinchent.xyz/vinchent/howmuch/internal/pkg/core"
 | 
				
			||||||
 | 
						"git.vinchent.xyz/vinchent/howmuch/internal/pkg/errno"
 | 
				
			||||||
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// User is the user controller interface, it describes all the handlers
 | 
					// User is the user controller interface, it describes all the handlers
 | 
				
			||||||
// that need to be implemented for the /user endpoint
 | 
					// that need to be implemented for the /user endpoint
 | 
				
			||||||
type User interface {
 | 
					type User interface {
 | 
				
			||||||
	Signup(core.Context)
 | 
						Signup(core.Context)
 | 
				
			||||||
	UpdateInfo(core.Context)
 | 
						UpdateInfo(*gin.Context)
 | 
				
			||||||
	Login(core.Context)
 | 
						Login(*gin.Context)
 | 
				
			||||||
	Logout(core.Context)
 | 
						Logout(*gin.Context)
 | 
				
			||||||
	ChangePassword(core.Context)
 | 
						ChangePassword(*gin.Context)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type UserController struct{}
 | 
					type UserController struct {
 | 
				
			||||||
 | 
						userUsecase usecase.User
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var UserParamsErr = &errno.Errno{
 | 
				
			||||||
 | 
						HTTP:    http.StatusBadRequest,
 | 
				
			||||||
 | 
						Code:    errno.ErrorCode(errno.InvalidParameterCode, "UserParamsErr"),
 | 
				
			||||||
 | 
						Message: "user info is not correct",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewUserController(us usecase.User) User {
 | 
				
			||||||
 | 
						return &UserController{
 | 
				
			||||||
 | 
							userUsecase: us,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (uc *UserController) Signup(ctx core.Context) {
 | 
					func (uc *UserController) Signup(ctx core.Context) {
 | 
				
			||||||
	ctx.JSON(http.StatusOK, "hello")
 | 
						var params model.User
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := ctx.Bind(¶ms); err != nil {
 | 
				
			||||||
 | 
							core.WriteResponse(ctx, UserParamsErr, nil)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (uc *UserController) UpdateInfo(ctx core.Context) {
 | 
						// TODO: check params validity (govalidator)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err := uc.userUsecase.Create(ctx, ¶ms)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							core.WriteResponse(ctx, err, nil)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (uc *UserController) Login(ctx core.Context) {
 | 
						core.WriteResponse(ctx, errno.OK, nil)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (uc *UserController) Logout(ctx core.Context) {
 | 
					func (uc *UserController) UpdateInfo(ctx *gin.Context) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (uc *UserController) ChangePassword(ctx core.Context) {
 | 
					func (uc *UserController) Login(ctx *gin.Context) {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (uc *UserController) Logout(ctx *gin.Context) {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (uc *UserController) ChangePassword(ctx *gin.Context) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										68
									
								
								internal/howmuch/adapter/repo/db.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								internal/howmuch/adapter/repo/db.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					// MIT License
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					// of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					// in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					// copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					// furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					// copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||||
 | 
					// SOFTWARE.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package repo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/repo"
 | 
				
			||||||
 | 
						"git.vinchent.xyz/vinchent/howmuch/internal/pkg/log"
 | 
				
			||||||
 | 
						"github.com/jackc/pgx/v5"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type dbRepository struct {
 | 
				
			||||||
 | 
						db *pgx.Conn
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewDBRepository(db *pgx.Conn) repo.DBRepository {
 | 
				
			||||||
 | 
						return &dbRepository{
 | 
				
			||||||
 | 
							db: db,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// XXX: Do I need rollback? in which cases?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (dr *dbRepository) Transaction(
 | 
				
			||||||
 | 
						ctx context.Context,
 | 
				
			||||||
 | 
						txFunc func(txCtx context.Context, tx interface{}) (interface{}, error),
 | 
				
			||||||
 | 
					) (interface{}, error) {
 | 
				
			||||||
 | 
						tx, err := dr.db.Begin(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							if p := recover(); p != nil {
 | 
				
			||||||
 | 
								tx.Rollback(ctx)
 | 
				
			||||||
 | 
								log.PanicLog("transaction panicked!")
 | 
				
			||||||
 | 
							} else if err != nil {
 | 
				
			||||||
 | 
								tx.Rollback(ctx)
 | 
				
			||||||
 | 
								log.ErrorLog("transaction failed!", "err", err)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								err = tx.Commit(ctx)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data, err := txFunc(ctx, tx)
 | 
				
			||||||
 | 
						return data, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										88
									
								
								internal/howmuch/adapter/repo/user.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								internal/howmuch/adapter/repo/user.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,88 @@
 | 
				
			|||||||
 | 
					// MIT License
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					// of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					// in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					// copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					// furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					// copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||||
 | 
					// SOFTWARE.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package repo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/repo/sqlc"
 | 
				
			||||||
 | 
						"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model"
 | 
				
			||||||
 | 
						"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/repo"
 | 
				
			||||||
 | 
						"github.com/jackc/pgx/v5"
 | 
				
			||||||
 | 
						"github.com/jackc/pgx/v5/pgtype"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type userRepository struct {
 | 
				
			||||||
 | 
						db *pgx.Conn
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const insertTimeout = 1 * time.Second
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewUserRepository(db *pgx.Conn) repo.UserRepository {
 | 
				
			||||||
 | 
						return &userRepository{
 | 
				
			||||||
 | 
							db: db,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Create
 | 
				
			||||||
 | 
					func (ur *userRepository) Create(
 | 
				
			||||||
 | 
						ctx context.Context,
 | 
				
			||||||
 | 
						transaction interface{},
 | 
				
			||||||
 | 
						u *model.User,
 | 
				
			||||||
 | 
					) (*model.User, error) {
 | 
				
			||||||
 | 
						timeoutCtx, cancel := context.WithTimeout(ctx, insertTimeout)
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						args := sqlc.InsertUserParams{
 | 
				
			||||||
 | 
							Email:     u.Email,
 | 
				
			||||||
 | 
							FirstName: u.FirstName,
 | 
				
			||||||
 | 
							LastName:  u.LastName,
 | 
				
			||||||
 | 
							Password:  u.Password,
 | 
				
			||||||
 | 
							CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true},
 | 
				
			||||||
 | 
							UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tx, ok := transaction.(pgx.Tx)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, errors.New("transaction is not a pgx.Tx")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						queries := sqlc.New(tx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						userDB, err := queries.InsertUser(timeoutCtx, args)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &model.User{
 | 
				
			||||||
 | 
							ID:        int(userDB.ID),
 | 
				
			||||||
 | 
							Email:     userDB.Email,
 | 
				
			||||||
 | 
							FirstName: userDB.FirstName,
 | 
				
			||||||
 | 
							LastName:  userDB.LastName,
 | 
				
			||||||
 | 
							Password:  userDB.Password,
 | 
				
			||||||
 | 
							CreatedAt: userDB.CreatedAt.Time,
 | 
				
			||||||
 | 
							UpdatedAt: userDB.CreatedAt.Time,
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -37,6 +37,7 @@ import (
 | 
				
			|||||||
	"git.vinchent.xyz/vinchent/howmuch/internal/pkg/log"
 | 
						"git.vinchent.xyz/vinchent/howmuch/internal/pkg/log"
 | 
				
			||||||
	"git.vinchent.xyz/vinchent/howmuch/pkg/version/verflag"
 | 
						"git.vinchent.xyz/vinchent/howmuch/pkg/version/verflag"
 | 
				
			||||||
	"github.com/gin-gonic/gin"
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
						"github.com/jackc/pgx/v5"
 | 
				
			||||||
	"github.com/spf13/cobra"
 | 
						"github.com/spf13/cobra"
 | 
				
			||||||
	"github.com/spf13/viper"
 | 
						"github.com/spf13/viper"
 | 
				
			||||||
	"golang.org/x/net/context"
 | 
						"golang.org/x/net/context"
 | 
				
			||||||
@ -105,8 +106,7 @@ func run() error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Init DB
 | 
						// Init DB
 | 
				
			||||||
	dbPool, err := datastore.NewDB(
 | 
						dbConfString := fmt.Sprintf(
 | 
				
			||||||
		fmt.Sprintf(
 | 
					 | 
				
			||||||
		"host=%s port=%d dbname=%s user=%s password=%s sslmode=%s",
 | 
							"host=%s port=%d dbname=%s user=%s password=%s sslmode=%s",
 | 
				
			||||||
		viper.GetString("db.host"),
 | 
							viper.GetString("db.host"),
 | 
				
			||||||
		viper.GetInt("db.port"),
 | 
							viper.GetInt("db.port"),
 | 
				
			||||||
@ -114,15 +114,19 @@ func run() error {
 | 
				
			|||||||
		viper.GetString("db.username"),
 | 
							viper.GetString("db.username"),
 | 
				
			||||||
		viper.GetString("db.password"),
 | 
							viper.GetString("db.password"),
 | 
				
			||||||
		viper.GetString("db.sslmode"),
 | 
							viper.GetString("db.sslmode"),
 | 
				
			||||||
		),
 | 
					 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
						dbConf, err := pgx.ParseConfig(dbConfString)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.FatalLog("DB connection config failure", "err", err, "cfg string", dbConfString)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						dbConn, err := datastore.NewDB(dbConf)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.FatalLog("DB connection failure", "err", err)
 | 
							log.FatalLog("DB connection failure", "err", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer dbPool.Close()
 | 
						defer dbConn.Close(context.Background())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Register the core service
 | 
						// Register the core service
 | 
				
			||||||
	r := registry.NewRegistry(dbPool)
 | 
						r := registry.NewRegistry(dbConn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	engine := gin.Default()
 | 
						engine := gin.Default()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -25,12 +25,12 @@ package datastore
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/jackc/pgx/v5/pgxpool"
 | 
						"github.com/jackc/pgx/v5"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewDB creates a new database for the application
 | 
					// NewDB creates a new database for the application
 | 
				
			||||||
func NewDB(dsn string) (*pgxpool.Pool, error) {
 | 
					func NewDB(connConfig *pgx.ConnConfig) (*pgx.Conn, error) {
 | 
				
			||||||
	conn, err := pgxpool.New(context.Background(), dsn)
 | 
						conn, err := pgx.ConnectConfig(context.Background(), connConfig)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -48,7 +48,13 @@ func Routes(engine *gin.Engine, c controller.AppController) *gin.Engine {
 | 
				
			|||||||
		core.WriteResponse(ctx, errno.PageNotFoundErr, nil)
 | 
							core.WriteResponse(ctx, errno.PageNotFoundErr, nil)
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	engine.POST("/signup", func(ctx *gin.Context) { c.User.Signup(ctx) })
 | 
						v1 := engine.Group("/v1")
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							userV1 := v1.Group("/user")
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								userV1.POST("/signup", func(ctx *gin.Context) { c.User.Signup(ctx) })
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return engine
 | 
						return engine
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										36
									
								
								internal/howmuch/model/user.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								internal/howmuch/model/user.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					// MIT License
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					// of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					// in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					// copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					// furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					// copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||||
 | 
					// SOFTWARE.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// User model
 | 
				
			||||||
 | 
					type User struct {
 | 
				
			||||||
 | 
						ID        int       `json:"id"`
 | 
				
			||||||
 | 
						Email     string    `json:"email"`
 | 
				
			||||||
 | 
						FirstName string    `json:"first_name"`
 | 
				
			||||||
 | 
						LastName  string    `json:"last_name"`
 | 
				
			||||||
 | 
						Password  string    `json:"password"`
 | 
				
			||||||
 | 
						CreatedAt time.Time `json:"created_at"`
 | 
				
			||||||
 | 
						UpdatedAt time.Time `json:"updated_at"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -24,7 +24,7 @@ package registry
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/controller"
 | 
						"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/controller"
 | 
				
			||||||
	"github.com/jackc/pgx/v5/pgxpool"
 | 
						"github.com/jackc/pgx/v5"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// registry is an implementation of Registry interface.
 | 
					// registry is an implementation of Registry interface.
 | 
				
			||||||
@ -32,7 +32,7 @@ import (
 | 
				
			|||||||
// It might holds other drivers when the projects grows. For example
 | 
					// It might holds other drivers when the projects grows. For example
 | 
				
			||||||
// the object needed to connect to Redis or Kafka.
 | 
					// the object needed to connect to Redis or Kafka.
 | 
				
			||||||
type registry struct {
 | 
					type registry struct {
 | 
				
			||||||
	db *pgxpool.Pool
 | 
						db *pgx.Conn
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Registry returns a new app controller that will be used by main()/run()
 | 
					// Registry returns a new app controller that will be used by main()/run()
 | 
				
			||||||
@ -43,7 +43,7 @@ type Registry interface {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewRegistry returns a new Registry's implementation.
 | 
					// NewRegistry returns a new Registry's implementation.
 | 
				
			||||||
func NewRegistry(db *pgxpool.Pool) Registry {
 | 
					func NewRegistry(db *pgx.Conn) Registry {
 | 
				
			||||||
	return ®istry{db: db}
 | 
						return ®istry{db: db}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -22,9 +22,14 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package registry
 | 
					package registry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/controller"
 | 
					import (
 | 
				
			||||||
 | 
						"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/controller"
 | 
				
			||||||
 | 
						"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/repo"
 | 
				
			||||||
 | 
						"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/usecase"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewUserController returns a user controller's implementation
 | 
					// NewUserController returns a user controller's implementation
 | 
				
			||||||
func (r *registry) NewUserController() controller.User {
 | 
					func (r *registry) NewUserController() controller.User {
 | 
				
			||||||
	return &controller.UserController{}
 | 
						u := usecase.NewUserUsecase(repo.NewUserRepository(r.db), repo.NewDBRepository(r.db))
 | 
				
			||||||
 | 
						return controller.NewUserController(u)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										32
									
								
								internal/howmuch/usecase/repo/db.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								internal/howmuch/usecase/repo/db.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					// MIT License
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					// of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					// in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					// copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					// furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					// copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||||
 | 
					// SOFTWARE.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package repo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type DBRepository interface {
 | 
				
			||||||
 | 
						Transaction(
 | 
				
			||||||
 | 
							ctx context.Context,
 | 
				
			||||||
 | 
							txFunc func(txCtx context.Context, tx interface{}) (interface{}, error),
 | 
				
			||||||
 | 
						) (interface{}, error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										34
									
								
								internal/howmuch/usecase/repo/testdb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								internal/howmuch/usecase/repo/testdb.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					// MIT License
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					// of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					// in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					// copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					// furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					// copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||||
 | 
					// SOFTWARE.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package repo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type TestDBRepository struct{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (tdr *TestDBRepository) Transaction(
 | 
				
			||||||
 | 
						ctx context.Context,
 | 
				
			||||||
 | 
						txFunc func(txCtx context.Context, tx interface{}) (interface{}, error),
 | 
				
			||||||
 | 
					) (interface{}, error) {
 | 
				
			||||||
 | 
						return txFunc(ctx, nil)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										42
									
								
								internal/howmuch/usecase/repo/testuser.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								internal/howmuch/usecase/repo/testuser.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					// MIT License
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					// of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					// in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					// copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					// furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					// copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||||
 | 
					// SOFTWARE.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package repo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type TestUserRepository struct{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (tur *TestUserRepository) Create(
 | 
				
			||||||
 | 
						ctx context.Context,
 | 
				
			||||||
 | 
						transaction interface{},
 | 
				
			||||||
 | 
						u *model.User,
 | 
				
			||||||
 | 
					) (*model.User, error) {
 | 
				
			||||||
 | 
						user := *u
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						user.ID = 123
 | 
				
			||||||
 | 
						return &user, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										33
									
								
								internal/howmuch/usecase/repo/user.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								internal/howmuch/usecase/repo/user.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					// MIT License
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					// of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					// in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					// copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					// furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					// copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||||
 | 
					// SOFTWARE.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package repo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type UserRepository interface {
 | 
				
			||||||
 | 
						Create(ctx context.Context, transaction interface{}, u *model.User) (*model.User, error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										78
									
								
								internal/howmuch/usecase/usecase/user.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								internal/howmuch/usecase/usecase/user.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					// MIT License
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					// of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					// in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					// copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					// furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					// copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||||
 | 
					// SOFTWARE.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package usecase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model"
 | 
				
			||||||
 | 
						"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/repo"
 | 
				
			||||||
 | 
						"git.vinchent.xyz/vinchent/howmuch/internal/pkg/log"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type userUsecase struct {
 | 
				
			||||||
 | 
						userRepo repo.UserRepository
 | 
				
			||||||
 | 
						dbRepo   repo.DBRepository
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type User interface {
 | 
				
			||||||
 | 
						Create(ctx context.Context, u *model.User) (*model.User, error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewUserUsecase(r repo.UserRepository, d repo.DBRepository) User {
 | 
				
			||||||
 | 
						return &userUsecase{
 | 
				
			||||||
 | 
							userRepo: r,
 | 
				
			||||||
 | 
							dbRepo:   d,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (uuc *userUsecase) Create(ctx context.Context, u *model.User) (*model.User, error) {
 | 
				
			||||||
 | 
						data, err := uuc.dbRepo.Transaction(
 | 
				
			||||||
 | 
							ctx,
 | 
				
			||||||
 | 
							func(txCtx context.Context, tx interface{}) (interface{}, error) {
 | 
				
			||||||
 | 
								u, err := uuc.userRepo.Create(txCtx, tx, u)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// TODO: App log, maybe can be sent to some third party service.
 | 
				
			||||||
 | 
								log.InfoLog(
 | 
				
			||||||
 | 
									"created new user",
 | 
				
			||||||
 | 
									"email",
 | 
				
			||||||
 | 
									u.Email,
 | 
				
			||||||
 | 
									"name",
 | 
				
			||||||
 | 
									fmt.Sprintf("%s %s", u.FirstName, u.LastName),
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return u, err
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						user := data.(*model.User)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return user, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										49
									
								
								internal/howmuch/usecase/usecase/user_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								internal/howmuch/usecase/usecase/user_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					// MIT License
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					// of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					// in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					// copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					// furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					// copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||||
 | 
					// SOFTWARE.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package usecase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model"
 | 
				
			||||||
 | 
						"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/repo"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCreateUser(t *testing.T) {
 | 
				
			||||||
 | 
						ctx := context.Background()
 | 
				
			||||||
 | 
						userUsecase := NewUserUsecase(&repo.TestUserRepository{}, &repo.TestDBRepository{})
 | 
				
			||||||
 | 
						input := &model.User{
 | 
				
			||||||
 | 
							Email:     "a@b.c",
 | 
				
			||||||
 | 
							FirstName: "James",
 | 
				
			||||||
 | 
							LastName:  "Bond",
 | 
				
			||||||
 | 
							Password:  "verystrong",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						want := input
 | 
				
			||||||
 | 
						want.ID = 123
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						got, err := userUsecase.Create(ctx, input)
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
						assert.Equal(t, want, got)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -22,6 +22,18 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package core
 | 
					package core
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Context interface {
 | 
					type Context interface {
 | 
				
			||||||
 | 
						// Context
 | 
				
			||||||
 | 
						Deadline() (deadline time.Time, ok bool)
 | 
				
			||||||
 | 
						Done() <-chan struct{}
 | 
				
			||||||
 | 
						Err() error
 | 
				
			||||||
 | 
						Value(key any) any
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Request
 | 
				
			||||||
 | 
						Bind(obj any) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Response
 | 
				
			||||||
	JSON(code int, obj any)
 | 
						JSON(code int, obj any)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -24,18 +24,28 @@ package errno
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import "net/http"
 | 
					import "net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type PlatformLevelErrCode string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						InternalErrorCode    = "InternalError"
 | 
				
			||||||
 | 
						InvalidParameterCode = "InvalidParameter"
 | 
				
			||||||
 | 
						AuthFailureCode      = "AuthFailure"
 | 
				
			||||||
 | 
						ResourceNotFoundCode = "ResourceNotFound"
 | 
				
			||||||
 | 
						FailedOperationCode  = "FailedOperation"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	OK = &Errno{HTTP: http.StatusOK, Code: "", Message: ""}
 | 
						OK = &Errno{HTTP: http.StatusOK, Code: "", Message: ""}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	InternalServerErr = &Errno{
 | 
						InternalServerErr = &Errno{
 | 
				
			||||||
		HTTP:    http.StatusInternalServerError,
 | 
							HTTP:    http.StatusInternalServerError,
 | 
				
			||||||
		Code:    "InternalError",
 | 
							Code:    InternalErrorCode,
 | 
				
			||||||
		Message: "Internal server error",
 | 
							Message: "Internal server error",
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	PageNotFoundErr = &Errno{
 | 
						PageNotFoundErr = &Errno{
 | 
				
			||||||
		HTTP:    http.StatusNotFound,
 | 
							HTTP:    http.StatusNotFound,
 | 
				
			||||||
		Code:    "ResourceNotFound.PageNotFound",
 | 
							Code:    ErrorCode(ResourceNotFoundCode, "PageNotFound"),
 | 
				
			||||||
		Message: "Page not found",
 | 
							Message: "Page not found",
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
@ -28,6 +28,10 @@ type Errno struct {
 | 
				
			|||||||
	Message string
 | 
						Message string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ErrorCode(platformErrCode string, resourceErrCode string) string {
 | 
				
			||||||
 | 
						return platformErrCode + "." + resourceErrCode
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Error implements Error() method in error interface
 | 
					// Error implements Error() method in error interface
 | 
				
			||||||
func (err *Errno) Error() string {
 | 
					func (err *Errno) Error() string {
 | 
				
			||||||
	return err.Message
 | 
						return err.Message
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					ALTER TABLE "admin"
 | 
				
			||||||
 | 
					ADD CONSTRAINT unique_email UNIQUE ("email");
 | 
				
			||||||
							
								
								
									
										4
									
								
								sqlc.yml
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								sqlc.yml
									
									
									
									
									
								
							@ -23,9 +23,9 @@
 | 
				
			|||||||
version: "2"
 | 
					version: "2"
 | 
				
			||||||
sql:
 | 
					sql:
 | 
				
			||||||
  - engine: "postgresql"
 | 
					  - engine: "postgresql"
 | 
				
			||||||
    queries: "internal/app/adapter/repo/sqlc"
 | 
					    queries: "internal/howmuch/adapter/repo/sqlc"
 | 
				
			||||||
    schema: "migrations"
 | 
					    schema: "migrations"
 | 
				
			||||||
    gen:
 | 
					    gen:
 | 
				
			||||||
      go:
 | 
					      go:
 | 
				
			||||||
        out: "internal/app/controller/repo/sqlc"
 | 
					        out: "internal/howmuch/adapter/repo/sqlc"
 | 
				
			||||||
        sql_package: "pgx/v5"
 | 
					        sql_package: "pgx/v5"
 | 
				
			||||||
 | 
				
			|||||||
@ -1 +0,0 @@
 | 
				
			|||||||
exit status 2exit status 2exit status 2exit status 2exit status 2
 | 
					 | 
				
			||||||
		Reference in New Issue
	
	Block a user