From f4756345e0ba56599d15b389b41a5ed8fe309f28 Mon Sep 17 00:00:00 2001 From: mzfarshad Date: Fri, 17 Apr 2026 19:36:23 +0330 Subject: [PATCH 1/5] refactor shoppingbasketapp structure. service, repository in to domain directory --- .../shoppingbasket/entity}/entity.go | 2 +- .../shoppingbasket}/repository/cart.go | 24 +++++++++---------- .../shoppingbasket/service}/param.go | 17 +++++++------ .../shoppingbasket/service}/service.go | 9 +++---- .../shoppingbasket/service}/validator.go | 2 +- shoppingbasketapp/app.go | 15 ++++++------ .../delivery/http/{ => cart}/handler.go | 12 +++++----- shoppingbasketapp/delivery/http/server.go | 15 ++++++------ 8 files changed, 51 insertions(+), 45 deletions(-) rename {shoppingbasketapp/service/cart => domain/shoppingbasket/entity}/entity.go (95%) rename {shoppingbasketapp => domain/shoppingbasket}/repository/cart.go (90%) rename {shoppingbasketapp/service/cart => domain/shoppingbasket/service}/param.go (60%) rename {shoppingbasketapp/service/cart => domain/shoppingbasket/service}/service.go (91%) rename {shoppingbasketapp/service/cart => domain/shoppingbasket/service}/validator.go (99%) rename shoppingbasketapp/delivery/http/{ => cart}/handler.go (90%) diff --git a/shoppingbasketapp/service/cart/entity.go b/domain/shoppingbasket/entity/entity.go similarity index 95% rename from shoppingbasketapp/service/cart/entity.go rename to domain/shoppingbasket/entity/entity.go index f3032daf..99cc6c96 100644 --- a/shoppingbasketapp/service/cart/entity.go +++ b/domain/shoppingbasket/entity/entity.go @@ -1,4 +1,4 @@ -package cart +package entity import ( "git.gocasts.ir/ebhomengo/niki/types" diff --git a/shoppingbasketapp/repository/cart.go b/domain/shoppingbasket/repository/cart.go similarity index 90% rename from shoppingbasketapp/repository/cart.go rename to domain/shoppingbasket/repository/cart.go index 7e45ce92..67b6c405 100644 --- a/shoppingbasketapp/repository/cart.go +++ b/domain/shoppingbasket/repository/cart.go @@ -4,8 +4,8 @@ import ( "context" "encoding/json" "fmt" + "git.gocasts.ir/ebhomengo/niki/domain/shoppingbasket/entity" richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" - "git.gocasts.ir/ebhomengo/niki/shoppingbasketapp/service/cart" "git.gocasts.ir/ebhomengo/niki/types" "github.com/redis/go-redis/v9" "strconv" @@ -43,7 +43,7 @@ func (r Repo) itemKey(productID types.ID) string { return fmt.Sprintf("item:%d", productID) } -func (r Repo) AddItem(ctx context.Context, userID types.ID, item cart.Item) error { +func (r Repo) AddItem(ctx context.Context, userID types.ID, item entity.Item) error { const op = "shoppingbasketapp.repository.AddItem" cartKey := r.cartKey(userID) @@ -65,7 +65,7 @@ func (r Repo) AddItem(ctx context.Context, userID types.ID, item cart.Item) erro } else { existsItem, _ := r.client.HGet(ctx, cartKey, itemKey).Result() if existsItem != "" { - var i cart.Item + var i entity.Item if err := json.Unmarshal([]byte(existsItem), &i); err != nil { return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err) } @@ -91,31 +91,31 @@ func parsInt(s string) int64 { return i } -func (r Repo) GetCart(ctx context.Context, userID types.ID) (cart.Cart, error) { +func (r Repo) GetCart(ctx context.Context, userID types.ID) (entity.Cart, error) { const op = "shoppingbasketapp.repository.GetCart" cartKey := r.cartKey(userID) exists, err := r.client.Exists(ctx, cartKey).Result() if err != nil { - return cart.Cart{}, richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err) + return entity.Cart{}, richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err) } if exists == 0 { - return cart.Cart{}, richerror.New(op).WithKind(richerror.KindNotFound).WithMessage("not found shopping basket") + return entity.Cart{}, richerror.New(op).WithKind(richerror.KindNotFound).WithMessage("not found shopping basket") } allCart, err := r.client.HGetAll(ctx, cartKey).Result() if err != nil { - return cart.Cart{}, richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err) + return entity.Cart{}, richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err) } - c := cart.Cart{Items: []cart.Item{}} + c := entity.Cart{Items: []entity.Item{}} for field, value := range allCart { if strings.HasPrefix(field, "item:") { - var i cart.Item + var i entity.Item if err := json.Unmarshal([]byte(value), &i); err != nil { - return cart.Cart{}, richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err) + return entity.Cart{}, richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err) } c.Items = append(c.Items, i) @@ -185,7 +185,7 @@ func (r Repo) UpdateQuantity(ctx context.Context, userID, productID types.ID, qu return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err) } - var item cart.Item + var item entity.Item if err := json.Unmarshal([]byte(data), &item); err != nil { return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err) } @@ -228,7 +228,7 @@ func (r Repo) updateTotalPrice(ctx context.Context, cartKey string) error { for field, value := range allFields { if strings.HasPrefix(field, "item:") { - var item cart.Item + var item entity.Item if err := json.Unmarshal([]byte(value), &item); err != nil { return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err) diff --git a/shoppingbasketapp/service/cart/param.go b/domain/shoppingbasket/service/param.go similarity index 60% rename from shoppingbasketapp/service/cart/param.go rename to domain/shoppingbasket/service/param.go index 97d8a520..f8c589b9 100644 --- a/shoppingbasketapp/service/cart/param.go +++ b/domain/shoppingbasket/service/param.go @@ -1,6 +1,9 @@ -package cart +package service -import "git.gocasts.ir/ebhomengo/niki/types" +import ( + "git.gocasts.ir/ebhomengo/niki/domain/shoppingbasket/entity" + "git.gocasts.ir/ebhomengo/niki/types" +) type AddToCartRequest struct { UserID types.ID `json:"user_id"` @@ -11,11 +14,11 @@ type AddToCartRequest struct { } type GetCartResponse struct { - UserID types.ID `json:"user_id"` - Items []Item `json:"items"` - TotalPrice types.Price `json:"total_price"` - CreatedAt int64 `json:"created_at"` - ExpireAt int64 `json:"expire_at"` + UserID types.ID `json:"user_id"` + Items []entity.Item `json:"items"` + TotalPrice types.Price `json:"total_price"` + CreatedAt int64 `json:"created_at"` + ExpireAt int64 `json:"expire_at"` } type RemoveFromCartRequest struct { diff --git a/shoppingbasketapp/service/cart/service.go b/domain/shoppingbasket/service/service.go similarity index 91% rename from shoppingbasketapp/service/cart/service.go rename to domain/shoppingbasket/service/service.go index 1e2da37e..78a2dd1e 100644 --- a/shoppingbasketapp/service/cart/service.go +++ b/domain/shoppingbasket/service/service.go @@ -1,7 +1,8 @@ -package cart +package service import ( "context" + "git.gocasts.ir/ebhomengo/niki/domain/shoppingbasket/entity" "git.gocasts.ir/ebhomengo/niki/pkg/logger" richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" "git.gocasts.ir/ebhomengo/niki/types" @@ -9,8 +10,8 @@ import ( ) type Repository interface { - AddItem(ctx context.Context, userID types.ID, item Item) error - GetCart(ctx context.Context, userID types.ID) (Cart, error) + AddItem(ctx context.Context, userID types.ID, item entity.Item) error + GetCart(ctx context.Context, userID types.ID) (entity.Cart, error) DeleteItem(ctx context.Context, userID, productID types.ID) error UpdateQuantity(ctx context.Context, userID, productID types.ID, quantity int) error DeleteCart(ctx context.Context, userID types.ID) error @@ -33,7 +34,7 @@ func (s Service) AddToBasket(ctx context.Context, req AddToCartRequest) error { return err } - return s.repo.AddItem(ctx, req.UserID, Item{ + return s.repo.AddItem(ctx, req.UserID, entity.Item{ ProductID: req.ProductID, Quantity: req.Quantity, Price: req.Price, diff --git a/shoppingbasketapp/service/cart/validator.go b/domain/shoppingbasket/service/validator.go similarity index 99% rename from shoppingbasketapp/service/cart/validator.go rename to domain/shoppingbasket/service/validator.go index 2ce4b2f7..8ab07773 100644 --- a/shoppingbasketapp/service/cart/validator.go +++ b/domain/shoppingbasket/service/validator.go @@ -1,4 +1,4 @@ -package cart +package service import ( richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" diff --git a/shoppingbasketapp/app.go b/shoppingbasketapp/app.go index 2ce4df1e..6fca62d0 100644 --- a/shoppingbasketapp/app.go +++ b/shoppingbasketapp/app.go @@ -4,11 +4,12 @@ import ( "context" "fmt" "git.gocasts.ir/ebhomengo/niki/adapter/redis" + "git.gocasts.ir/ebhomengo/niki/domain/shoppingbasket/repository" + "git.gocasts.ir/ebhomengo/niki/domain/shoppingbasket/service" "git.gocasts.ir/ebhomengo/niki/pkg/httpserver" "git.gocasts.ir/ebhomengo/niki/pkg/logger" "git.gocasts.ir/ebhomengo/niki/shoppingbasketapp/delivery/http" - "git.gocasts.ir/ebhomengo/niki/shoppingbasketapp/repository" - "git.gocasts.ir/ebhomengo/niki/shoppingbasketapp/service/cart" + "git.gocasts.ir/ebhomengo/niki/shoppingbasketapp/delivery/http/cart" "os" "os/signal" "sync" @@ -17,8 +18,8 @@ import ( type Application struct { Repo repository.Repo - Service cart.Service - Handler http.Handler + Service service.Service + Handler cart.Handler Server http.Server Config Config } @@ -28,10 +29,10 @@ func Setup(ctx context.Context, cfg Config) (Application, error) { adapter := redis.New(cfg.Redis) repo := repository.New(adapter.Client(), cfg.Repo) - validator := cart.NewValidate() - svc := cart.New(validator, repo) + validator := service.NewValidate() + svc := service.New(validator, repo) - handler := http.NewHandler(svc) + handler := cart.NewHandler(svc) httpServer, err := httpserver.New(cfg.HTTPServer) if err != nil { diff --git a/shoppingbasketapp/delivery/http/handler.go b/shoppingbasketapp/delivery/http/cart/handler.go similarity index 90% rename from shoppingbasketapp/delivery/http/handler.go rename to shoppingbasketapp/delivery/http/cart/handler.go index f593c076..15e08310 100644 --- a/shoppingbasketapp/delivery/http/handler.go +++ b/shoppingbasketapp/delivery/http/cart/handler.go @@ -1,4 +1,4 @@ -package http +package cart import ( "git.gocasts.ir/ebhomengo/niki/pkg/claim" @@ -18,7 +18,7 @@ func NewHandler(svc cart.Service) Handler { return Handler{svc: svc} } -func (h Handler) addToBasket(c echo.Context) error { +func (h Handler) AddToBasket(c echo.Context) error { claims := claim.GetClaimsFromEchoContext(c) var req cart.AddToCartRequest @@ -37,7 +37,7 @@ func (h Handler) addToBasket(c echo.Context) error { return c.NoContent(http.StatusNoContent) } -func (h Handler) getCart(c echo.Context) error { +func (h Handler) GetCart(c echo.Context) error { claims := claim.GetClaimsFromEchoContext(c) res, err := h.svc.GetCart(c.Request().Context(), types.ID(claims.UserID)) @@ -49,7 +49,7 @@ func (h Handler) getCart(c echo.Context) error { return c.JSON(http.StatusOK, res) } -func (h Handler) removeCart(c echo.Context) error { +func (h Handler) RemoveCart(c echo.Context) error { claims := claim.GetClaimsFromEchoContext(c) if err := h.svc.ClearCart(c.Request().Context(), types.ID(claims.UserID)); err != nil { @@ -60,7 +60,7 @@ func (h Handler) removeCart(c echo.Context) error { return c.NoContent(http.StatusNoContent) } -func (h Handler) removeItem(c echo.Context) error { +func (h Handler) RemoveItem(c echo.Context) error { claims := claim.GetClaimsFromEchoContext(c) p := c.Param("productID") @@ -84,7 +84,7 @@ func (h Handler) removeItem(c echo.Context) error { return c.NoContent(http.StatusNoContent) } -func (h Handler) updateQuantity(c echo.Context) error { +func (h Handler) UpdateQuantity(c echo.Context) error { claims := claim.GetClaimsFromEchoContext(c) p := c.Param("productID") diff --git a/shoppingbasketapp/delivery/http/server.go b/shoppingbasketapp/delivery/http/server.go index cfe5fe8c..029d3789 100644 --- a/shoppingbasketapp/delivery/http/server.go +++ b/shoppingbasketapp/delivery/http/server.go @@ -3,14 +3,15 @@ package http import ( "context" "git.gocasts.ir/ebhomengo/niki/pkg/httpserver" + "git.gocasts.ir/ebhomengo/niki/shoppingbasketapp/delivery/http/cart" ) type Server struct { - handler Handler + handler cart.Handler HTTPServer *httpserver.Server } -func NewServer(handler Handler, hS *httpserver.Server) Server { +func NewServer(handler cart.Handler, hS *httpserver.Server) Server { return Server{handler: handler, HTTPServer: hS} } @@ -34,10 +35,10 @@ func (s Server) registerRoutes() { r := router.Group("shoppingbasket/cart") // Authentication is required - r.GET("/", s.handler.getCart) - r.DELETE("/", s.handler.removeCart) + r.GET("/", s.handler.GetCart) + r.DELETE("/", s.handler.RemoveCart) - r.POST("/items", s.handler.addToBasket) - r.DELETE("/items/:productID", s.handler.removeItem) - r.PUT("/items/:productID/:quantity", s.handler.updateQuantity) + r.POST("/items", s.handler.AddToBasket) + r.DELETE("/items/:productID", s.handler.RemoveItem) + r.PUT("/items/:productID/:quantity", s.handler.UpdateQuantity) } From e7f7dfc6bd639c20f15f4bbdafb64c165c1c1e08 Mon Sep 17 00:00:00 2001 From: Sahar Mokarrami Date: Fri, 17 Apr 2026 20:46:02 +0330 Subject: [PATCH 2/5] fix: fix import package bug --- purchaseapp/app.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/purchaseapp/app.go b/purchaseapp/app.go index a846535b..4506e836 100644 --- a/purchaseapp/app.go +++ b/purchaseapp/app.go @@ -3,10 +3,10 @@ package purchaseapp import ( "context" "fmt" - purchaseMysql "git.gocasts.ir/ebhomengo/niki/domain/purchase/repository/mysql" + purchaseMysql "git.gocasts.ir/ebhomengo/niki/domain/order/repository/mysql" + purchaseService "git.gocasts.ir/ebhomengo/niki/domain/order/service" purchaseHTTP "git.gocasts.ir/ebhomengo/niki/purchaseapp/delivery/http" purchaseHandler "git.gocasts.ir/ebhomengo/niki/purchaseapp/delivery/http/order" - purchaseService "git.gocasts.ir/ebhomengo/niki/purchaseapp/service/order" "git.gocasts.ir/ebhomengo/niki/repository/mysql" ) From 3369df246c940117308bc2bca69b0acbaa34c752 Mon Sep 17 00:00:00 2001 From: mzfarshad Date: Fri, 17 Apr 2026 20:53:50 +0330 Subject: [PATCH 3/5] bugfix imports path --- shoppingbasketapp/config.go | 2 +- shoppingbasketapp/delivery/http/cart/handler.go | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/shoppingbasketapp/config.go b/shoppingbasketapp/config.go index 291c301f..91915263 100644 --- a/shoppingbasketapp/config.go +++ b/shoppingbasketapp/config.go @@ -2,9 +2,9 @@ package shoppingbasketapp import ( "git.gocasts.ir/ebhomengo/niki/adapter/redis" + "git.gocasts.ir/ebhomengo/niki/domain/shoppingbasket/repository" "git.gocasts.ir/ebhomengo/niki/pkg/httpserver" logger "git.gocasts.ir/ebhomengo/niki/pkg/logger" - "git.gocasts.ir/ebhomengo/niki/shoppingbasketapp/repository" ) type Config struct { diff --git a/shoppingbasketapp/delivery/http/cart/handler.go b/shoppingbasketapp/delivery/http/cart/handler.go index 15e08310..49fcc236 100644 --- a/shoppingbasketapp/delivery/http/cart/handler.go +++ b/shoppingbasketapp/delivery/http/cart/handler.go @@ -1,9 +1,9 @@ package cart import ( + "git.gocasts.ir/ebhomengo/niki/domain/shoppingbasket/service" "git.gocasts.ir/ebhomengo/niki/pkg/claim" httpmsg "git.gocasts.ir/ebhomengo/niki/pkg/http_msg" - "git.gocasts.ir/ebhomengo/niki/shoppingbasketapp/service/cart" "git.gocasts.ir/ebhomengo/niki/types" "github.com/labstack/echo/v4" "net/http" @@ -11,17 +11,17 @@ import ( ) type Handler struct { - svc cart.Service + svc service.Service } -func NewHandler(svc cart.Service) Handler { +func NewHandler(svc service.Service) Handler { return Handler{svc: svc} } func (h Handler) AddToBasket(c echo.Context) error { claims := claim.GetClaimsFromEchoContext(c) - var req cart.AddToCartRequest + var req service.AddToCartRequest if err := c.Bind(&req); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{ "error": "invalid request body", @@ -71,7 +71,7 @@ func (h Handler) RemoveItem(c echo.Context) error { }) } - var req cart.RemoveFromCartRequest + var req service.RemoveFromCartRequest req.UserID = types.ID(claims.UserID) req.ProductID = types.ID(pID) @@ -103,7 +103,7 @@ func (h Handler) UpdateQuantity(c echo.Context) error { }) } - var req cart.UpdateQuantityRequest + var req service.UpdateQuantityRequest req.UserID = types.ID(claims.UserID) req.ProductID = types.ID(pID) req.Quantity = q From d57adaebecb0216a82f08057fc55e9380f93c37a Mon Sep 17 00:00:00 2001 From: matina Date: Sun, 19 Apr 2026 16:16:36 -0700 Subject: [PATCH 4/5] improve structure of campaign & adding interfaces --- domain/campaign/entity/entitty.go | 4 +- .../1775633533_add_campaign_table.sql | 0 domain/campaign/repository/mysql/db.go | 57 +++++++++++ domain/campaign/repository/repo.go | 29 ------ domain/campaign/service/Interfaces.go | 7 -- domain/campaign/service/createCampaign.go | 98 +++++++++---------- domain/campaign/service/services.go | 35 +++++++ donate_app/repository/mysql/db.go | 70 +++---------- .../service/entity/campaignParticipant.go | 17 ++-- 9 files changed, 161 insertions(+), 156 deletions(-) rename {donate_app => domain/campaign}/repository/migrations/1775633533_add_campaign_table.sql (100%) create mode 100644 domain/campaign/repository/mysql/db.go delete mode 100644 domain/campaign/repository/repo.go delete mode 100644 domain/campaign/service/Interfaces.go create mode 100644 domain/campaign/service/services.go diff --git a/domain/campaign/entity/entitty.go b/domain/campaign/entity/entitty.go index 9df48283..55d7cf9c 100644 --- a/domain/campaign/entity/entitty.go +++ b/domain/campaign/entity/entitty.go @@ -1,4 +1,4 @@ -package domain +package entity import "time" @@ -26,7 +26,7 @@ type Campaign struct { AdminID ID `json:"creator_id"` } -// Behavior +// Behavior func (c *Campaign) Activate() { if c.Status == CampaignDraft { c.Status = CampaignActive diff --git a/donate_app/repository/migrations/1775633533_add_campaign_table.sql b/domain/campaign/repository/migrations/1775633533_add_campaign_table.sql similarity index 100% rename from donate_app/repository/migrations/1775633533_add_campaign_table.sql rename to domain/campaign/repository/migrations/1775633533_add_campaign_table.sql diff --git a/domain/campaign/repository/mysql/db.go b/domain/campaign/repository/mysql/db.go new file mode 100644 index 00000000..eefb0b6f --- /dev/null +++ b/domain/campaign/repository/mysql/db.go @@ -0,0 +1,57 @@ +package mysql + +import ( + "context" + "git.gocasts.ir/ebhomengo/niki/domain/campaign/entity" + richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" + "git.gocasts.ir/ebhomengo/niki/repository/mysql" + "git.gocasts.ir/ebhomengo/niki/types" +) + +type DB struct { + conn *mysql.DB +} + +func New(db *mysql.DB) *DB { + return &DB{conn: db} +} + +// CreateCampaign creates a new campaign +func (d *DB) CreateAndSave(ctx context.Context, campaign entity.Campaign) (types.ID, error) { + const Op = "repository.mysql.campaign.create" + + tx, err := d.conn.Conn().BeginTx(ctx, nil) + if err != nil { + return 0, richerror.New(Op).WithErr(err) + } + defer tx.Rollback() + + query := `INSERT INTO campaigns (title, description, goal_amount, raised_amount, + status, deadline_at ,admin_id , created_at ) + VALUES (?, ?, ?, ?, ?, ?, ? , NOW() )` + + result, err := tx.ExecContext(ctx, query, + campaign.Title, + campaign.Description, + campaign.GoalAmount, + campaign.RaisedAmount, + campaign.Status, + campaign.DeadlineAt, + campaign.AdminID, + campaign.CreatedAt, + ) + if err != nil { + return 0, richerror.New(Op).WithErr(err) + } + + campaignID, err := result.LastInsertId() + if err != nil { + return 0, richerror.New(Op).WithErr(err) + } + + if err := tx.Commit(); err != nil { + return 0, richerror.New(Op).WithErr(err) + } + + return types.ID(campaignID), nil +} diff --git a/domain/campaign/repository/repo.go b/domain/campaign/repository/repo.go deleted file mode 100644 index 74605d96..00000000 --- a/domain/campaign/repository/repo.go +++ /dev/null @@ -1,29 +0,0 @@ -package mysql - -import ( - "context" - "git.gocasts.ir/ebhomengo/niki/campaign/entity" - richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" - "git.gocasts.ir/ebhomengo/niki/repository/mysql" - "git.gocasts.ir/ebhomengo/niki/types" -) - - - -type CampaignRepository interface { - CreateAndSave(ctx context.Context, campaign *Campaign) error - FindByID(ctx context.Context, id ID) (*Campaign, error) - List(ctx context.Context, status CampaignStatus, limit, offset int) ([]*Campaign, error) - Delete(ctx context.Context, id ID) error - Update(ctx context.Context, campaign *Campaign) error -} - - - - - - - - - - diff --git a/domain/campaign/service/Interfaces.go b/domain/campaign/service/Interfaces.go deleted file mode 100644 index b82ad074..00000000 --- a/domain/campaign/service/Interfaces.go +++ /dev/null @@ -1,7 +0,0 @@ -package domain - -import ( - "context" - "time" -) - diff --git a/domain/campaign/service/createCampaign.go b/domain/campaign/service/createCampaign.go index 319f5b36..24514606 100644 --- a/domain/campaign/service/createCampaign.go +++ b/domain/campaign/service/createCampaign.go @@ -1,48 +1,50 @@ -package campaign +package service import ( "context" - "errors" // For standard errors + "fmt" + "git.gocasts.ir/ebhomengo/niki/domain/campaign/entity" + richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" + "git.gocasts.ir/ebhomengo/niki/types" "time" - - "your_project/domain" - "your_project/pkg/richerror" - "your_project/repository" ) -type ID = unit64 -type EntityCampaign = domain.Campaign +// CreateCampaign handles creation of a new campaign. +func (s *CampaignService) CreateCampaign(ctx context.Context, req entity.Campaign) (types.ID, error) { + const op = "service.campaign.create_campaign" - -type CampaignServiceImp interface { - CreateCampaign(ctx context.Context, req CampaignRepository) (types.ID, error) -} - - -type CampaignService struct { - repo repository.CampaignRepository -} - -func NewCampaignService( - campaignRepo repository.CampaignRepository, -) *CampaignService { - return &CampaignService{ - repo: campaignRepo, + if err := validateCreateCampaignRequest(req); err != nil { + return 0, richerror.New(op).WithErr(err) } + + campaign := entity.Campaign{ + Title: req.Title, + Description: req.Description, + GoalAmount: req.GoalAmount, + RaisedAmount: 0, + Status: req.Status, + DeadlineAt: req.DeadlineAt, + AdminID: req.AdminID, + CreatedAt: time.Now(), + } + + id, err := s.repo.Create(ctx, campaign) + if err != nil { + return 0, richerror.New(op).WithErr(err) + } + + return id, nil } - -func (s *CampaignService) CreateCampaign(ctx context.Context, req CreateCampaignRequest) (ID, error) { - const Op = "service.campaign.create_campaign" - +func validateCreateCampaignRequest(req entity.Campaign) error { if req.Title == "" { - return 0, richerror.New(Op).WithMessage("title is required") + return errRequired("title") } if req.GoalAmount <= 0 { - return 0, richerror.New(Op).WithMessage("goal_amount must be greater than 0") + return errInvalid("goal_amount must be greater than 0") } - if req.AdminID == 0 { - return 0, richerror.New(Op).WithMessage("admin_id is required") + if req.AdminID == 0 { + return errRequired("admin_id") } validStatuses := map[string]bool{ @@ -50,32 +52,20 @@ func (s *CampaignService) CreateCampaign(ctx context.Context, req CreateCampaign "active": true, "completed": true, "cancelled": true, - "paused": true + "paused": true, } - if !validStatuses[req.Status] { - return 0, richerror.New(Op).WithMessage("invalid status provided") + if !validStatuses[string(req.Status)] { + return errInvalid("invalid status provided") } - - newCampaign := &EntityCampaign{ - Title: req.Title, - Description: req.Description, - GoalAmount: req.GoalAmount, - RaisedAmount: 0, // Initially 0 - Status: EntityCampaign.Status(req.Status), - DeadlineAt: req.DeadlineAt, - AdminID: req.AdminID, - CreatedAt: time.Now(), - } - - createdCampaignID, err := s.repo.CreateAndSave(ctx, newCampaign) - if err != nil { - return 0, richerror.New(Op).WithErr(err) - } - - return createdCampaignID, nil + return nil } +// --- Helpers --- +func errRequired(field string) error { + return fmt.Errorf("%s is required", field) +} - - +func errInvalid(msg string) error { + return fmt.Errorf(msg) +} diff --git a/domain/campaign/service/services.go b/domain/campaign/service/services.go new file mode 100644 index 00000000..cc85d8b3 --- /dev/null +++ b/domain/campaign/service/services.go @@ -0,0 +1,35 @@ +package service + +import ( + "context" + _ "fmt" + "git.gocasts.ir/ebhomengo/niki/domain/campaign/entity" + "git.gocasts.ir/ebhomengo/niki/types" +) + +type CampaignFilterParam struct { + AdminID types.ID + Status string + //nil true false + IsArchived *bool +} + +type CampaignStorage interface { + Create(ctx context.Context, c entity.Campaign) (types.ID, error) // باید ID برگرداند + Update(ctx context.Context, c entity.Campaign) error + FindByID(ctx context.Context, id types.ID) (entity.Campaign, error) + FindAll(ctx context.Context, filter CampaignFilterParam) ([]entity.Campaign, error) + Archive(ctx context.Context, id types.ID) error // instead Delete + TotalDonations(ctx context.Context, campaignID types.ID) (int64, error) +} + +type CampaignService struct { + repo CampaignStorage +} + +// NewCampaignService constructs a new CampaignService. +func NewCampaignService(storage CampaignStorage) *CampaignService { + return &CampaignService{ + repo: storage, + } +} diff --git a/donate_app/repository/mysql/db.go b/donate_app/repository/mysql/db.go index 26c0d5ef..358ea1cd 100644 --- a/donate_app/repository/mysql/db.go +++ b/donate_app/repository/mysql/db.go @@ -2,7 +2,7 @@ package mysql import ( "context" - "git.gocasts.ir/ebhomengo/niki/campaign/entity" + "git.gocasts.ir/ebhomengo/niki/donate_app/service/entity" richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" "git.gocasts.ir/ebhomengo/niki/repository/mysql" "git.gocasts.ir/ebhomengo/niki/types" @@ -16,11 +16,9 @@ func New(db *mysql.DB) *DB { return &DB{conn: db} } - - -// CreateCampaign creates a new campaign -func (d *DB) CreateAndSave(ctx context.Context, campaign entity.Campaign) (types.ID, error) { - const Op = "repository.mysql.campaign.create" +// Create adds a new participant to a campaign +func (d *DB) CreateCampaignParticipants(ctx context.Context, participant entity.CampaignParticipant) (types.ID, error) { + const Op = "repository.mysql.campaign_participant.create" tx, err := d.conn.Conn().BeginTx(ctx, nil) if err != nil { @@ -28,67 +26,29 @@ func (d *DB) CreateAndSave(ctx context.Context, campaign entity.Campaign) (types } defer tx.Rollback() - query := `INSERT INTO campaigns (title, description, goal_amount, raised_amount, - status, deadline_at ,admin_id , created_at ) - VALUES (?, ?, ?, ?, ?, ?, ? , NOW() )` + query := `INSERT INTO campaign_participants (id,campaign_id, user_id, amount , created_at) + VALUES (?, ?, ? , ? , NOW())` result, err := tx.ExecContext(ctx, query, - campaign.Title, - campaign.Description, - campaign.GoalAmount, - campaign.RaisedAmount, - campaign.Status, - campaign.DeadlineAt, - campaign.AdminID, - campaign.CreatedAt, - - + participant.ID, + participant.CampaignID, + participant.UserID, + participant.Amount, + participant.CreatedAt, ) + if err != nil { return 0, richerror.New(Op).WithErr(err) } - campaignID, err := result.LastInsertId() + id, err := result.LastInsertId() if err != nil { return 0, richerror.New(Op).WithErr(err) } - if err := tx.Commit(); err != nil { return 0, richerror.New(Op).WithErr(err) } - return types.ID(campaignID), nil + return types.ID(id), nil + } - - - -// Create adds a new participant to a campaign -func (d *DB) CreateCampaignParticipants(ctx context.Context, participant entity.CampaignParticipant) (types.ID, error) { - const Op = "repository.mysql.campaign_participant.create" - - query := `INSERT INTO campaign_participants (id,campaign_id, user_id, amount , created_at) - VALUES (?, ?, ? , ? , NOW())` - - result, err := d.conn.ExecContext(ctx, query, - participant.ID, - participant.CampaignID, - participant.UserID, - participant.Amount, - participant.CreatedAt - ) - if err != nil { - return 0, richerror.New(Op).WithErr(err) - } - - id, err := result.LastInsertId() - if err != nil { - return 0, richerror.New(Op).WithErr(err) - } - - return types.ID(id), nil -} - - - - - diff --git a/donate_app/service/entity/campaignParticipant.go b/donate_app/service/entity/campaignParticipant.go index fb493429..8199852a 100644 --- a/donate_app/service/entity/campaignParticipant.go +++ b/donate_app/service/entity/campaignParticipant.go @@ -1,14 +1,13 @@ -package service +package entity +import "time" type ID uint64 - type CampaignParticipant struct { - ID ID `json:"id"` - CampaignID ID `json:"campaign_id"` - UserID ID `json:"user_id"` - Amount float64 `json:"amount"` - CreatedAt time.Time `json:"created_at"` - -} \ No newline at end of file + ID ID `json:"id"` + CampaignID ID `json:"campaign_id"` + UserID ID `json:"user_id"` + Amount float64 `json:"amount"` + CreatedAt time.Time `json:"created_at"` +} From a986f03e4416261d44ad7a0a4393cd8dd0b11abb Mon Sep 17 00:00:00 2001 From: matina Date: Tue, 21 Apr 2026 00:04:58 -0700 Subject: [PATCH 5/5] delivery & handler --- domain/campaign/entity/entitty.go | 11 ++--- domain/campaign/service/param.go | 27 ++++++++++++ donate_app/delivery/donate_server/handler.go | 5 --- donate_app/delivery/donate_server/routes.go | 7 ---- donate_app/delivery/donate_server/server.go | 16 -------- donate_app/delivery/http/handler.go | 43 ++++++++++++++++++++ donate_app/delivery/http/health_check.go | 12 ++++++ donate_app/delivery/http/server.go | 39 ++++++++++++++++++ donate_app/service/param.go | 25 ++++++++++++ donate_app/service/service.go | 37 ----------------- 10 files changed, 152 insertions(+), 70 deletions(-) create mode 100644 domain/campaign/service/param.go delete mode 100644 donate_app/delivery/donate_server/handler.go delete mode 100644 donate_app/delivery/donate_server/routes.go delete mode 100644 donate_app/delivery/donate_server/server.go create mode 100644 donate_app/delivery/http/handler.go create mode 100644 donate_app/delivery/http/health_check.go create mode 100644 donate_app/delivery/http/server.go diff --git a/domain/campaign/entity/entitty.go b/domain/campaign/entity/entitty.go index 55d7cf9c..f6eca8a0 100644 --- a/domain/campaign/entity/entitty.go +++ b/domain/campaign/entity/entitty.go @@ -1,8 +1,9 @@ package entity -import "time" - -type ID uint64 +import ( + "git.gocasts.ir/ebhomengo/niki/types" + "time" +) type CampaignStatus string @@ -15,7 +16,7 @@ const ( ) type Campaign struct { - ID ID `json:"id"` + ID types.ID `json:"id"` Title string `json:"title"` Description string `json:"description"` GoalAmount float64 `json:"goal_amount"` @@ -23,7 +24,7 @@ type Campaign struct { Status CampaignStatus `json:"status"` CreatedAt time.Time `json:"created_at"` DeadlineAt *time.Time `json:"deadline_at,omitempty"` - AdminID ID `json:"creator_id"` + AdminID types.ID `json:"creator_id"` } // Behavior diff --git a/domain/campaign/service/param.go b/domain/campaign/service/param.go new file mode 100644 index 00000000..f54649ab --- /dev/null +++ b/domain/campaign/service/param.go @@ -0,0 +1,27 @@ +package service + +import ( + "git.gocasts.ir/ebhomengo/niki/types" + "time" +) + +type GetCampaignResponse struct { + ID types.ID `json:"user_id"` +} + +type AddCampaignRequest struct { + ID uint64 `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + GoalAmount float64 `json:"goal_amount"` + DeadlineAt *time.Time `json:"deadline_at,omitempty"` + AdminID types.ID `json:"admin_id"` +} + +type UpdateCampaignRequest struct { + Title *string `json:"title,omitempty"` + Description *string `json:"description,omitempty"` + GoalAmount *float64 `json:"goal_amount,omitempty"` + DeadlineAt *time.Time `json:"deadline_at,omitempty"` + Status *string `json:"status,omitempty"` // draft/active/completed/paused/cancelled +} diff --git a/donate_app/delivery/donate_server/handler.go b/donate_app/delivery/donate_server/handler.go deleted file mode 100644 index 9f94a9c3..00000000 --- a/donate_app/delivery/donate_server/handler.go +++ /dev/null @@ -1,5 +0,0 @@ -package donate_server - -type Handler struct{} - - diff --git a/donate_app/delivery/donate_server/routes.go b/donate_app/delivery/donate_server/routes.go deleted file mode 100644 index 778a11e5..00000000 --- a/donate_app/delivery/donate_server/routes.go +++ /dev/null @@ -1,7 +0,0 @@ -package donate_server - -import "github.com/labstack/echo/v4" - -func (h Handler) RegisterRoutes(e *echo.Echo) { - -} diff --git a/donate_app/delivery/donate_server/server.go b/donate_app/delivery/donate_server/server.go deleted file mode 100644 index 218c4c71..00000000 --- a/donate_app/delivery/donate_server/server.go +++ /dev/null @@ -1,16 +0,0 @@ -package donate_server - -import ( - httpserver "git.gocasts.ir/ebhomengo/niki/delivery/http_server" - "github.com/labstack/echo/v4" -) - -type Server struct { - Server httpserver.Server - Handler Handler - Router *echo.Echo -} - -func (s Server) Start() { - s.Handler.RegisterRoutes(s.Router) -} diff --git a/donate_app/delivery/http/handler.go b/donate_app/delivery/http/handler.go new file mode 100644 index 00000000..b20f6dd6 --- /dev/null +++ b/donate_app/delivery/http/handler.go @@ -0,0 +1,43 @@ +package http + +import ( + "git.gocasts.ir/ebhomengo/niki/domain/campaign/entity" + "git.gocasts.ir/ebhomengo/niki/domain/campaign/service" + httpmsg "git.gocasts.ir/ebhomengo/niki/pkg/http_msg" + "github.com/labstack/echo/v4" + "net/http" + "time" +) + +type Handler struct { + svc service.CampaignService +} + +func NewHandler(svc service.CampaignService) Handler { + return Handler{svc: svc} +} + +func (h Handler) createCampaign(c echo.Context) error { + + var req entity.Campaign + if err := c.Bind(&req); err != nil { + return c.JSON(http.StatusBadRequest, map[string]string{ + "error": "invalid request body", + }) + } + req.CreatedAt = time.Now() + req.RaisedAmount = 0 + + createdID, err := h.svc.CreateCampaign(c.Request().Context(), req) + if err != nil { + msg, code := httpmsg.Error(err) + c.Logger().Errorf("Service error creating campaign: %v (Code: %d)", err, code) + return c.JSON(code, msg) + } + + return c.JSON(http.StatusCreated, map[string]interface{}{ + "message": "campaign created successfully", + "id": createdID, + }) + +} diff --git a/donate_app/delivery/http/health_check.go b/donate_app/delivery/http/health_check.go new file mode 100644 index 00000000..aa0a0254 --- /dev/null +++ b/donate_app/delivery/http/health_check.go @@ -0,0 +1,12 @@ +package http + +import ( + "github.com/labstack/echo/v4" + "net/http" +) + +func (s Server) healthCheck(c echo.Context) error { + return c.JSON(http.StatusOK, echo.Map{ + "message": "everything is good!", + }) +} diff --git a/donate_app/delivery/http/server.go b/donate_app/delivery/http/server.go new file mode 100644 index 00000000..16b4363b --- /dev/null +++ b/donate_app/delivery/http/server.go @@ -0,0 +1,39 @@ +package http + +import ( + "context" + "git.gocasts.ir/ebhomengo/niki/pkg/httpserver" +) + +type Server struct { + handler Handler + HTTPServer *httpserver.Server +} + +func NewServer(handler Handler, hS *httpserver.Server) Server { + return Server{handler: handler, HTTPServer: hS} +} + +func (s Server) Serve() error { + s.registerRoutes() + if err := s.HTTPServer.Start(); err != nil { + return err + } + + return nil +} + +func (s Server) Stop(ctx context.Context) error { + return s.HTTPServer.Stop(ctx) +} + +func (s Server) registerRoutes() { + router := s.HTTPServer.GetRouter() + + router.GET("campaign/health-check", s.healthCheck) + + r := router.Group("campaign") + + r.POST("/", s.handler.createCampaign) + +} diff --git a/donate_app/service/param.go b/donate_app/service/param.go index 6d43c336..f064f16e 100644 --- a/donate_app/service/param.go +++ b/donate_app/service/param.go @@ -1 +1,26 @@ package service + +import ( + "git.gocasts.ir/ebhomengo/niki/types" + "time" +) + +type GetCampaignResponse struct { + ID types.ID `json:"user_id"` +} + +type AddCampaignRequest struct { + Title string `json:"title"` + Description string `json:"description"` + GoalAmount float64 `json:"goal_amount"` + DeadlineAt *time.Time `json:"deadline_at,omitempty"` + AdminID types.ID `json:"admin_id"` +} + +type UpdateCampaignRequest struct { + Title *string `json:"title,omitempty"` + Description *string `json:"description,omitempty"` + GoalAmount *float64 `json:"goal_amount,omitempty"` + DeadlineAt *time.Time `json:"deadline_at,omitempty"` + Status *string `json:"status,omitempty"` // draft/active/completed/paused/cancelled +} diff --git a/donate_app/service/service.go b/donate_app/service/service.go index e3a7192a..6d43c336 100644 --- a/donate_app/service/service.go +++ b/donate_app/service/service.go @@ -1,38 +1 @@ package service - - - - -import ( - "context" - "errors" - "time" - - "git.gocasts.ir/ebhomengo/niki/campaign/entity" - "git.gocasts.ir/ebhomengo/niki/repository" - richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" - "git.gocasts.ir/ebhomengo/niki/types" -) - - - - -type CampaignService struct { - repo repository.repo -} - - -// type CampaignServiceInterface interface { -// CreateCampaign(ctx context.Context, req CampaignRepository) (types.ID, error) -// } - -func NewCampaignService( - repo repository.CampaignRepository, - participantRepo repository.CampaignParticipantRepository, -) *CampaignService { - return &CampaignService{ - repo: repo, - participantRepo: participantRepo, - } -} -