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"` +}