From c3932c32226ee2068ba3cb25a079f00c38b75215 Mon Sep 17 00:00:00 2001 From: linuxMate Date: Fri, 1 Dec 2023 17:51:59 +0300 Subject: [PATCH] hotfix Tag + typo transaction repo --- build/schema/initdb.sql | 27 ++++ docker-compose.yml | 6 + .../auth/delivery/http/handlers.go | 16 +- .../category/repository/postgres/postgres.go | 2 +- .../goal/delivery/http/handler.go | 145 ++++++++++++++++++ internal/microservices/goal/goal.go | 26 ++++ internal/microservices/goal/goal_models.go | 26 ++++ .../goal/repository/postgres/repository.go | 81 ++++++++++ .../microservices/goal/usecase/usecase.go | 66 ++++++++ .../repository/postgresql/postgres.go | 10 +- internal/models/goal.go | 34 ++-- 11 files changed, 412 insertions(+), 27 deletions(-) create mode 100644 internal/microservices/goal/delivery/http/handler.go create mode 100644 internal/microservices/goal/goal.go create mode 100644 internal/microservices/goal/goal_models.go create mode 100644 internal/microservices/goal/repository/postgres/repository.go create mode 100644 internal/microservices/goal/usecase/usecase.go diff --git a/build/schema/initdb.sql b/build/schema/initdb.sql index bcaed2a8..ed6ffffc 100644 --- a/build/schema/initdb.sql +++ b/build/schema/initdb.sql @@ -52,6 +52,33 @@ CREATE TABLE IF NOT EXISTS TransactionCategory ( PRIMARY KEY (transaction_id, category_id) ); +CREATE TABLE IF NOT EXISTS goal ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + user_id UUID REFERENCES "user"(user_id) NOT NULL, + "name" TEXT CHECK(LENGTH("name") <= 50) NOT NULL, + "description" TEXT DEFAULT '' CHECK(LENGTH("description") <= 255), + "target" NUMERIC(10,2) NOT NULL, + "date" DATE, + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL, +); + +--======================================================================== + +CREATE OR REPLACE FUNCTION public.moddatetime() + RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE TRIGGER modify_updated_at + BEFORE UPDATE + ON goal + FOR EACH ROW +EXECUTE PROCEDURE public.moddatetime(updated_at); + --======================================================================== CREATE OR REPLACE FUNCTION add_default_categories_accounts_transactions() diff --git a/docker-compose.yml b/docker-compose.yml index 847ccebb..ffa5fe21 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -72,6 +72,8 @@ services: - hammy-postgres container_name: ${AUTH_CONTAINER} image: ${REGISTRY}/${AUTH_CONTAINER}:${GITHUB_SHA_SHORT} + #container_name: hammywallet-auth + #image: codemaster482/hammywallet-auth:latest #build: # context: . # dockerfile: build/auth.Dockerfile @@ -88,6 +90,8 @@ services: - hammy-postgres container_name: ${ACCOUNT_CONTAINER} image: ${REGISTRY}/${ACCOUNT_CONTAINER}:${GITHUB_SHA_SHORT} + #container_name: hammywallet-account + #image: codemaster482/hammywallet-account:latest #build: # context: . # dockerfile: build/account.Dockerfile @@ -104,6 +108,8 @@ services: - hammy-postgres container_name: ${CATEGORY_CONTAINER} image: ${REGISTRY}/${CATEGORY_CONTAINER}:${GITHUB_SHA_SHORT} + #container_name: hammywallet-category + #image: codemaster482/hammywallet-category:latest #build: # context: . # dockerfile: build/category.Dockerfile diff --git a/internal/microservices/auth/delivery/http/handlers.go b/internal/microservices/auth/delivery/http/handlers.go index 2509be52..2887d8de 100644 --- a/internal/microservices/auth/delivery/http/handlers.go +++ b/internal/microservices/auth/delivery/http/handlers.go @@ -44,15 +44,16 @@ func NewHandler( // @Description Create Account // @Accept json // @Produce json -// @Param user body models.User true "user info" -// @Success 201 {object} Response[auth.SignResponse] "User Created" -// @Failure 400 {object} ResponseError "Incorrect Input" -// @Failure 429 {object} ResponseError "Server error" +// @Param user body models.User true "user info" +// @Success 201 {object} Response[auth.SignResponse] "User Created" +// @Failure 400 {object} ResponseError "Incorrect Input" +// @Failure 409 {object} ResponseError "User already exists" +// @Failure 429 {object} ResponseError "Server error" // @Router /api/auth/signup [post] func (h *Handler) SignUp(w http.ResponseWriter, r *http.Request) { var signUpUser auth.SignUpInput - // Unmarshal r.Body + // Unmarshal request.Body decoder := json.NewDecoder(r.Body) if err := decoder.Decode(&signUpUser); err != nil { h.log.WithField( @@ -76,6 +77,11 @@ func (h *Handler) SignUp(w http.ResponseWriter, r *http.Request) { Password: signUpUser.PlaintPassword, }) if err != nil { + //var errUserAlreadyExists *models.UserAlreadyExistsError + //if errors.As(err, &errUserAlreadyExists) { + // response.ErrorResponse(w, http.StatusConflict, err, "User already exists", h.log) + // return + //} h.log.WithField( "Request-Id", contextutils.GetReqID(r.Context()), ).Errorf("Error in sign up: %v", err) diff --git a/internal/microservices/category/repository/postgres/postgres.go b/internal/microservices/category/repository/postgres/postgres.go index a11a907d..6d6032cb 100644 --- a/internal/microservices/category/repository/postgres/postgres.go +++ b/internal/microservices/category/repository/postgres/postgres.go @@ -37,7 +37,7 @@ const ( WHERE user_id = $1 AND id = $2 );` - transactionAssociationDelete = "DELETE FROM transactionCategory WHERE category_id = $1;" + transactionAssociationDelete = "DELETE FROM TransactionCategory WHERE category_id = $1;" ) type Repository struct { diff --git a/internal/microservices/goal/delivery/http/handler.go b/internal/microservices/goal/delivery/http/handler.go new file mode 100644 index 00000000..175efe07 --- /dev/null +++ b/internal/microservices/goal/delivery/http/handler.go @@ -0,0 +1,145 @@ +package http + +import ( + "encoding/json" + "net/http" + + contextutils "github.com/go-park-mail-ru/2023_2_Hamster/internal/common/context_utils" + response "github.com/go-park-mail-ru/2023_2_Hamster/internal/common/http" + "github.com/go-park-mail-ru/2023_2_Hamster/internal/models" + + "github.com/go-park-mail-ru/2023_2_Hamster/internal/common/logger" + "github.com/go-park-mail-ru/2023_2_Hamster/internal/microservices/goal" +) + +type Handler struct { + goalUsecase goal.Useace + log logger.Logger +} + +func NewHandler(gu goal.Useace, l logger.Logger) *Handler { + return &Handler{ + goalUsecase: gu, + log: l, + } +} + +func (h *Handler) CreateGoal(w http.ResponseWriter, r *http.Request) { + // get user from context + user, err := response.GetUserFromRequest(r) + if err != nil { + response.ErrorResponse(w, http.StatusUnauthorized, err, response.ErrUnauthorized.Error(), h.log) + return + } + + // unmarshal request body + var goalInput goal.GoalCreateRequest + + decoder := json.NewDecoder(r.Body) + if err := decoder.Decode(&goalInput); err != nil { + h.log.WithField( + "Request-Id", contextutils.GetReqID(r.Context()), + ).Errorf("[handler] Error Corupted request body: %v", err) + response.ErrorResponse(w, http.StatusBadRequest, err, "Corrupted request body can't unmarshal", h.log) + return + } + defer r.Body.Close() + + goalInput.UserId = user.ID + + goalId, err := h.goalUsecase.CreateGoal(r.Context(), goalInput) + if err != nil { + response.ErrorResponse(w, http.StatusBadRequest, err, "can't create goal", h.log) + return + } + + response.SuccessResponse(w, http.StatusOK, goalId) +} + +func (h *Handler) UpdateGoal(w http.ResponseWriter, r *http.Request) { + user, err := response.GetUserFromRequest(r) + if err != nil { + response.ErrorResponse(w, http.StatusUnauthorized, err, response.ErrUnauthorized.Error(), h.log) + return + } + + var goalInput models.Goal + + decoder := json.NewDecoder(r.Body) + if err := decoder.Decode(&goalInput); err != nil { + h.log.WithField( + "Request-Id", contextutils.GetReqID(r.Context()), + ).Errorf("[handler] Error Corupted request body: %v", err) + response.ErrorResponse(w, http.StatusBadRequest, err, "Corrupted request body can't unmarshal", h.log) + return + } + defer r.Body.Close() + + goalInput.UserId = user.ID + + if err := h.goalUsecase.UpdateGoal(r.Context(), &goalInput); err != nil { + response.ErrorResponse(w, http.StatusBadRequest, err, "can't update goal", h.log) + return + } + + response.SuccessResponse(w, http.StatusOK, response.NilBody{}) +} + +func (h *Handler) DeleteGoal(w http.ResponseWriter, r *http.Request) { + user, err := response.GetUserFromRequest(r) + if err != nil { + response.ErrorResponse(w, http.StatusUnauthorized, err, response.ErrUnauthorized.Error(), h.log) + return + } + + var goalInput goal.GoalDeleteRequest + + decoder := json.NewDecoder(r.Body) + if err := decoder.Decode(&goalInput); err != nil { + h.log.WithField( + "Request-Id", contextutils.GetReqID(r.Context()), + ).Errorf("[handler] Error Corupted request body: %v", err) + response.ErrorResponse(w, http.StatusBadRequest, err, "Corrupted request body can't unmarshal", h.log) + return + } + defer r.Body.Close() + + if err := h.goalUsecase.DeleteGoal(r.Context(), goalInput.ID, user.ID); err != nil { + response.ErrorResponse(w, http.StatusBadRequest, err, "can't delete goal", h.log) + return + } + + response.SuccessResponse(w, http.StatusOK, response.NilBody{}) +} + +func (h *Handler) GetGoals(w http.ResponseWriter, r *http.Request) { + user, err := response.GetUserFromRequest(r) + if err != nil { + response.ErrorResponse(w, http.StatusUnauthorized, err, response.ErrUnauthorized.Error(), h.log) + return + } + + goals, err := h.goalUsecase.GetGoals(r.Context(), user.ID) + if err != nil { + response.ErrorResponse(w, http.StatusInternalServerError, err, "can't get goals", h.log) + return + } + + response.SuccessResponse(w, http.StatusOK, goals) +} + +func (h *Handler) CheckGoalsState(w http.ResponseWriter, r *http.Request) { + user, err := response.GetUserFromRequest(r) + if err != nil { + response.ErrorResponse(w, http.StatusUnauthorized, err, response.ErrUnauthorized.Error(), h.log) + return + } + + goals, err := h.goalUsecase.CheckGoalsState(r.Context(), user.ID) + if err != nil { + response.ErrorResponse(w, http.StatusBadRequest, err, "can't check goals state", h.log) + return + } + + response.SuccessResponse(w, http.StatusOK, goals) +} diff --git a/internal/microservices/goal/goal.go b/internal/microservices/goal/goal.go new file mode 100644 index 00000000..7e20b780 --- /dev/null +++ b/internal/microservices/goal/goal.go @@ -0,0 +1,26 @@ +package goal + +import ( + "context" + + "github.com/go-park-mail-ru/2023_2_Hamster/internal/models" + "github.com/google/uuid" +) + +type Useace interface { + CreateGoal(ctx context.Context, goal GoalCreateRequest) (uuid.UUID, error) + UpdateGoal(ctx context.Context, goal *models.Goal) error + DeleteGoal(ctx context.Context, goalId uuid.UUID, userId uuid.UUID) error + GetGoals(ctx context.Context, userId uuid.UUID) ([]models.Goal, error) + + CheckGoalsState(ctx context.Context, userId uuid.UUID) ([]models.Goal, error) // check if any goals are completed +} + +type Repository interface { + CreateGoal(ctx context.Context, goal models.Goal) (uuid.UUID, error) + UpdateGoal(ctx context.Context, goal *models.Goal) error + DeleteGoal(ctx context.Context, goalId uuid.UUID) error + GetGoals(ctx context.Context, userId uuid.UUID) ([]models.Goal, error) + + CheckGoalsState(ctx context.Context, userId uuid.UUID) ([]models.Goal, error) // check if any goals are completed +} diff --git a/internal/microservices/goal/goal_models.go b/internal/microservices/goal/goal_models.go new file mode 100644 index 00000000..d7d739df --- /dev/null +++ b/internal/microservices/goal/goal_models.go @@ -0,0 +1,26 @@ +package goal + +import "github.com/google/uuid" + +type ( + GoalCreateRequest struct { + UserId uuid.UUID `json:"user_id" valid:"required"` + Name string `json:"name" valid:"required"` + Description string `json:"description" valid:"-"` + Total float64 `json:"total" valid:"required,greaterzero"` + Date string `json:"date" valid:"isdate"` + } + + GoalUpdateRequest struct { + ID uuid.UUID `json:"id" valid:"required"` + UserId uuid.UUID `json:"user_id" valid:"required"` + Name string `json:"name" valid:"required"` + Description string `json:"description" valid:"-"` + Total float64 `json:"total" valid:"required,greaterzero"` + Date string `json:"date" valid:"isdate"` + } + + GoalDeleteRequest struct { + ID uuid.UUID `json:"id" valid:"required"` + } +) diff --git a/internal/microservices/goal/repository/postgres/repository.go b/internal/microservices/goal/repository/postgres/repository.go new file mode 100644 index 00000000..202dc9c9 --- /dev/null +++ b/internal/microservices/goal/repository/postgres/repository.go @@ -0,0 +1,81 @@ +package postgres + +import ( + "context" + "fmt" + + "github.com/go-park-mail-ru/2023_2_Hamster/cmd/api/init/db/postgresql" + "github.com/go-park-mail-ru/2023_2_Hamster/internal/common/logger" + "github.com/go-park-mail-ru/2023_2_Hamster/internal/models" + "github.com/google/uuid" +) + +const ( + GoalGet = `SELECT user_id, "name", "description", target, "date" FROM goal WHERE id=$1;` + + GoalCreate = `INSERT INTO goal (user_id, "name", "description", target, "date") + VALUES ($1, $2, $3, $4, $5) + RETURNING id;` + + GoalUpdate = `UPDATE goal SET "name"=$1, "description"=$2, target=$3, "date"=$4 WHERE id=$5;` + + GoalDelete = "DELETE FROM goal WHERE id = $1;" + + GoalAll = `SELECT * FROM goal WHERE user_id = $1;` +) + +type Repository struct { + db postgresql.DbConn + log logger.Logger +} + +func NewRepository(db postgresql.DbConn, log logger.Logger) *Repository { + return &Repository{ + db: db, + log: log, + } +} + +func (r *Repository) CreateGoal(ctx context.Context, goal models.Goal) (uuid.UUID, error) { + row := r.db.QueryRow(ctx, GoalCreate, + goal.UserId, + goal.Name, + goal.Description, + goal.Target, + goal.Date, + ) + + var id uuid.UUID + if err := row.Scan(&id); err != nil { + return uuid.Nil, err + } + + return id, nil +} + +func (r *Repository) UpdateGoal(ctx context.Context, goal *models.Goal) error { + _, err := r.db.Exec(ctx, GoalUpdate, + goal.Name, + goal.Description, + goal.Target, + goal.Date, + goal.ID, + ) + if err != nil { + return fmt.Errorf("[repo] UpdateGoal: %w", err) + } + + return nil +} + +func (r *Repository) DeleteGoal(ctx context.Context, goalId uuid.UUID) error { + +} + +func (r *Repository) GetGoals(ctx context.Context, userId uuid.UUID) ([]models.Goal, error) { + +} + +func (r *Repository) CheckGoalsState(ctx context.Context, userId uuid.UUID) ([]models.Goal, error) { + +} diff --git a/internal/microservices/goal/usecase/usecase.go b/internal/microservices/goal/usecase/usecase.go new file mode 100644 index 00000000..68a671fa --- /dev/null +++ b/internal/microservices/goal/usecase/usecase.go @@ -0,0 +1,66 @@ +package usecase + +import ( + "context" + "fmt" + + "github.com/go-park-mail-ru/2023_2_Hamster/internal/common/logger" + "github.com/go-park-mail-ru/2023_2_Hamster/internal/microservices/goal" + "github.com/go-park-mail-ru/2023_2_Hamster/internal/models" + "github.com/google/uuid" +) + +type Usecase struct { + goalRepo goal.Repository + log logger.Logger +} + +func NewUsecase(gr goal.Repository, log logger.Logger) *Usecase { + return &Usecase{ + goalRepo: gr, + log: log, + } +} + +func (u *Usecase) CreateGoal(ctx context.Context, goal models.Goal) (uuid.UUID, error) { + goalId, err := u.goalRepo.CreateGoal(ctx, goal) + if err != nil { + return uuid.Nil, fmt.Errorf("[usecase] Error goal creation: %w", err) + } + + return goalId, nil +} + +func (u *Usecase) UpdateGoal(ctx context.Context, goal *models.Goal) error { + if err := u.goalRepo.UpdateGoal(ctx, goal); err != nil { + return fmt.Errorf("[usecase] update goal Error: %w", err) + } + + return nil +} + +func (u *Usecase) DeleteGoal(ctx context.Context, goalId uuid.UUID) error { + if err := u.goalRepo.DeleteGoal(ctx, goalId); err != nil { + return fmt.Errorf("[usecase] delete goal Error: %w", err) + } + + return nil +} + +func (u *Usecase) GetGoals(ctx context.Context, userId uuid.UUID) ([]models.Goal, error) { + goals, err := u.goalRepo.GetGoals(ctx, userId) + if err != nil { + return nil, fmt.Errorf("[usecase] get goals Error: %w", err) + } + + return goals, nil +} + +func (u *Usecase) CheckGoalsState(ctx context.Context, userId uuid.UUID) ([]models.Goal, error) { + goals, err := u.goalRepo.CheckGoalsState(ctx, userId) + if err != nil { + return nil, fmt.Errorf("[usecase] check goals state Error: %w", err) + } + + return goals, nil +} diff --git a/internal/microservices/transaction/repository/postgresql/postgres.go b/internal/microservices/transaction/repository/postgresql/postgres.go index e75310ad..73fdf8c8 100644 --- a/internal/microservices/transaction/repository/postgresql/postgres.go +++ b/internal/microservices/transaction/repository/postgresql/postgres.go @@ -100,6 +100,7 @@ func (r *transactionRep) GetFeed(ctx context.Context, user_id uuid.UUID, queryGe queryParamsSlice = append(queryParamsSlice, queryGet.EndDate) } } + query += " ORDER BY date DESC;" rows, err := r.db.Query(ctx, query, queryParamsSlice...) if err != nil { @@ -121,6 +122,7 @@ func (r *transactionRep) GetFeed(ctx context.Context, user_id uuid.UUID, queryGe ); err != nil { return nil, fmt.Errorf("[repo] %w", err) } + categories, err := r.getCategoriesForTransaction(ctx, transaction.ID) if err != nil { return nil, fmt.Errorf("[repo] %w", err) @@ -209,10 +211,9 @@ func (r *transactionRep) insertTransaction(ctx context.Context, tx pgx.Tx, trans transaction.Payer, transaction.Description, ) - var id uuid.UUID - err := row.Scan(&id) - if err != nil { + var id uuid.UUID + if err := row.Scan(&id); err != nil { return id, fmt.Errorf("[repo] failed create transaction: %w", err) } @@ -306,7 +307,8 @@ func (r *transactionRep) updateTransactionInfo(ctx context.Context, tx pgx.Tx, t transaction.Outcome, transaction.Date, transaction.Payer, - transaction.Description) + transaction.Description, + ) if err != nil { return fmt.Errorf("[repo] failed to update transaction information: %w", err) } diff --git a/internal/models/goal.go b/internal/models/goal.go index 1c7fca91..279ba99f 100644 --- a/internal/models/goal.go +++ b/internal/models/goal.go @@ -1,22 +1,22 @@ package models -// import ( -// "time" +import ( + "time" -// valid "github.com/asaskevich/govalidator" -// "github.com/google/uuid" -// ) + valid "github.com/asaskevich/govalidator" + "github.com/google/uuid" +) -// type Goal struct { -// ID uuid.UUID `json:"id" valid:"-"` -// UserID uint `json:"user_id" valid:"-"` -// Name string `json:"name" valid:"required"` -// Description string `json:"description" valid:"-"` -// Total float64 `json:"total" valid:"required,greaterzero"` -// Date time.Time `json:"date" valid:"isdate"` -// } +type Goal struct { + ID uuid.UUID `json:"id" valid:"-"` + UserId uuid.UUID `json:"user_id" valid:"-"` + Name string `json:"name" valid:"required"` + Description string `json:"description" valid:"-"` + Target float64 `json:"total" valid:"required,greaterzero"` + Date time.Time `json:"date" valid:"isdate"` +} -// func (g *Goal) GoalValidate() error { -// _, err := valid.ValidateStruct(g) -// return err -// } +func (g *Goal) GoalValidate() error { + _, err := valid.ValidateStruct(g) + return err +}