forked from ebhomengo/niki
Merge pull request 'donate' (#279) from donate into develop
Reviewed-on: ebhomengo/niki#279
This commit is contained in:
commit
03cba27f4a
|
|
@ -0,0 +1,48 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type ID uint64
|
||||||
|
|
||||||
|
type CampaignStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CampaignDraft CampaignStatus = "draft"
|
||||||
|
CampaignActive CampaignStatus = "active"
|
||||||
|
CampaignFinished CampaignStatus = "completed"
|
||||||
|
CampaignPaused CampaignStatus = "paused"
|
||||||
|
CampaignCanceled CampaignStatus = "cancelled"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Campaign struct {
|
||||||
|
ID ID `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
GoalAmount float64 `json:"goal_amount"`
|
||||||
|
RaisedAmount float64 `json:"raised_amount"`
|
||||||
|
Status CampaignStatus `json:"status"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
DeadlineAt *time.Time `json:"deadline_at,omitempty"`
|
||||||
|
AdminID ID `json:"creator_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Behavior
|
||||||
|
func (c *Campaign) Activate() {
|
||||||
|
if c.Status == CampaignDraft {
|
||||||
|
c.Status = CampaignActive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Campaign) AddFunds(amount float64) {
|
||||||
|
c.RaisedAmount += amount
|
||||||
|
if c.RaisedAmount >= c.GoalAmount {
|
||||||
|
c.Status = CampaignFinished
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Campaign) IsExpired(now time.Time) bool {
|
||||||
|
if c.DeadlineAt == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return now.After(*c.DeadlineAt)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
package campaign
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors" // For standard errors
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"your_project/domain"
|
||||||
|
"your_project/pkg/richerror"
|
||||||
|
"your_project/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ID = unit64
|
||||||
|
type EntityCampaign = domain.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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (s *CampaignService) CreateCampaign(ctx context.Context, req CreateCampaignRequest) (ID, error) {
|
||||||
|
const Op = "service.campaign.create_campaign"
|
||||||
|
|
||||||
|
if req.Title == "" {
|
||||||
|
return 0, richerror.New(Op).WithMessage("title is required")
|
||||||
|
}
|
||||||
|
if req.GoalAmount <= 0 {
|
||||||
|
return 0, richerror.New(Op).WithMessage("goal_amount must be greater than 0")
|
||||||
|
}
|
||||||
|
if req.AdminID == 0 {
|
||||||
|
return 0, richerror.New(Op).WithMessage("admin_id is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
validStatuses := map[string]bool{
|
||||||
|
"draft": true,
|
||||||
|
"active": true,
|
||||||
|
"completed": true,
|
||||||
|
"cancelled": true,
|
||||||
|
"paused": true
|
||||||
|
}
|
||||||
|
if !validStatuses[req.Status] {
|
||||||
|
return 0, richerror.New(Op).WithMessage("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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -16,8 +16,10 @@ func New(db *mysql.DB) *DB {
|
||||||
return &DB{conn: db}
|
return &DB{conn: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// CreateCampaign creates a new campaign
|
// CreateCampaign creates a new campaign
|
||||||
func (d *DB) CreateCampaign(ctx context.Context, campaign entity.Campaign) (types.ID, error) {
|
func (d *DB) CreateAndSave(ctx context.Context, campaign entity.Campaign) (types.ID, error) {
|
||||||
const Op = "repository.mysql.campaign.create"
|
const Op = "repository.mysql.campaign.create"
|
||||||
|
|
||||||
tx, err := d.conn.Conn().BeginTx(ctx, nil)
|
tx, err := d.conn.Conn().BeginTx(ctx, nil)
|
||||||
|
|
@ -58,6 +60,8 @@ func (d *DB) CreateCampaign(ctx context.Context, campaign entity.Campaign) (type
|
||||||
return types.ID(campaignID), nil
|
return types.ID(campaignID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Create adds a new participant to a campaign
|
// Create adds a new participant to a campaign
|
||||||
func (d *DB) CreateCampaignParticipants(ctx context.Context, participant entity.CampaignParticipant) (types.ID, error) {
|
func (d *DB) CreateCampaignParticipants(ctx context.Context, participant entity.CampaignParticipant) (types.ID, error) {
|
||||||
const Op = "repository.mysql.campaign_participant.create"
|
const Op = "repository.mysql.campaign_participant.create"
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CampaignStatus represents the possible states of a campaign
|
|
||||||
type CampaignStatus string
|
|
||||||
|
|
||||||
const (
|
|
||||||
StatusActive CampaignStatus = "active"
|
|
||||||
StatusCompleted CampaignStatus = "completed"
|
|
||||||
StatusCancelled CampaignStatus = "cancelled"
|
|
||||||
StatusExpired CampaignStatus = "expired" // New status for deadlines
|
|
||||||
)
|
|
||||||
|
|
||||||
type ID uint64
|
|
||||||
|
|
||||||
type Campaign struct {
|
|
||||||
ID ID `json:"id"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
GoalAmount float64 `json:"goal_amount"`
|
|
||||||
RaisedAmount float64 `json:"raised_amount"`
|
|
||||||
Status CampaignStatus `json:"status"`
|
|
||||||
CreatedAt time.Time `json:"created_at"`
|
|
||||||
DeadlineAt *time.Time `json:"deadline_at,omitempty"`
|
|
||||||
|
|
||||||
AdminID ID `json:"creator_id"`
|
|
||||||
}
|
|
||||||
|
|
@ -18,16 +18,14 @@ import (
|
||||||
|
|
||||||
|
|
||||||
type CampaignService struct {
|
type CampaignService struct {
|
||||||
repo repository.CampaignRepository
|
repo repository.repo
|
||||||
participantRepo repository.CampaignParticipantRepository
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type CampaignServiceInterface interface {
|
// type CampaignServiceInterface interface {
|
||||||
CreateCampaign(ctx context.Context, req CampaignRepository) (types.ID, error)
|
// CreateCampaign(ctx context.Context, req CampaignRepository) (types.ID, error)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// NewCampaignService creates a new campaign service
|
|
||||||
func NewCampaignService(
|
func NewCampaignService(
|
||||||
repo repository.CampaignRepository,
|
repo repository.CampaignRepository,
|
||||||
participantRepo repository.CampaignParticipantRepository,
|
participantRepo repository.CampaignParticipantRepository,
|
||||||
|
|
@ -38,54 +36,3 @@ func NewCampaignService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// CreateCampaign creates a new campaign
|
|
||||||
func (s *CampaignService) CreateCampaign(ctx context.Context, req CreateCampaignRequest) (types.ID, error) {
|
|
||||||
const Op = "service.campaign.create_campaign"
|
|
||||||
|
|
||||||
// Business Logic: Validation
|
|
||||||
if req.Title == "" {
|
|
||||||
return 0, richerror.New(Op).WithErr(errors.New("title is required"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.GoalAmount <= 0 {
|
|
||||||
return 0, richerror.New(Op).WithErr(errors.New("goal_amount must be greater than 0"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.AdminID == 0 {
|
|
||||||
return 0, richerror.New(Op).WithErr(errors.New("admin_id is required"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Business Logic: Validate status
|
|
||||||
validStatuses := map[string]bool{
|
|
||||||
"draft": true,
|
|
||||||
"active": true,
|
|
||||||
"completed": true,
|
|
||||||
"cancelled": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !validStatuses[req.Status] {
|
|
||||||
return 0, richerror.New(Op).WithErr(errors.New("invalid status"))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Create campaign entity
|
|
||||||
campaign := entity.Campaign{
|
|
||||||
Title: req.Title,
|
|
||||||
Description: req.Description,
|
|
||||||
GoalAmount: req.GoalAmount,
|
|
||||||
RaisedAmount: 0, // Initially 0
|
|
||||||
Status: entity.CampaignStatus(req.Status),
|
|
||||||
DeadlineAt: req.DeadlineAt,
|
|
||||||
AdminID: req.AdminID,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call repository
|
|
||||||
campaignID, err := s.repo.CreateCampaign(ctx, campaign)
|
|
||||||
if err != nil {
|
|
||||||
return 0, richerror.New(Op).WithErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return campaignID, nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue