diff --git a/domain/campaign/entity/entitty.go b/domain/campaign/entity/entitty.go index f6eca8a0..5e79f0b6 100644 --- a/domain/campaign/entity/entitty.go +++ b/domain/campaign/entity/entitty.go @@ -19,6 +19,8 @@ type Campaign struct { ID types.ID `json:"id"` Title string `json:"title"` Description string `json:"description"` + Link string `json:"link"` + Slogan string `json:"slogan"` // GoalAmount float64 `json:"goal_amount"` RaisedAmount float64 `json:"raised_amount"` Status CampaignStatus `json:"status"` diff --git a/domain/campaign/repository/migrations/1775633533_add_campaign_table.sql b/domain/campaign/repository/migrations/1775633533_add_campaign_table.sql index 65aead6e..d3e973ee 100644 --- a/domain/campaign/repository/migrations/1775633533_add_campaign_table.sql +++ b/domain/campaign/repository/migrations/1775633533_add_campaign_table.sql @@ -4,6 +4,8 @@ CREATE TABLE `campaigns` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `title` VARCHAR(255) NOT NULL, `description` TEXT, + `link` VARCHAR(255) NULL, + `slogan` VARCHAR(255) NULL, `goal_amount` DECIMAL(15,2) NOT NULL, `raised_amount` DECIMAL(15,2) DEFAULT 0, `status` VARCHAR(50) NOT NULL, diff --git a/domain/campaign/repository/mysql/db.go b/domain/campaign/repository/mysql/db.go index eefb0b6f..e9cddcc1 100644 --- a/domain/campaign/repository/mysql/db.go +++ b/domain/campaign/repository/mysql/db.go @@ -17,7 +17,7 @@ func New(db *mysql.DB) *DB { } // CreateCampaign creates a new campaign -func (d *DB) CreateAndSave(ctx context.Context, campaign entity.Campaign) (types.ID, error) { +func (d *DB) Create(ctx context.Context, campaign entity.Campaign) (types.ID, error) { const Op = "repository.mysql.campaign.create" tx, err := d.conn.Conn().BeginTx(ctx, nil) @@ -26,13 +26,16 @@ func (d *DB) CreateAndSave(ctx context.Context, campaign entity.Campaign) (types } defer tx.Rollback() - query := `INSERT INTO campaigns (title, description, goal_amount, raised_amount, + query := `INSERT INTO campaigns (title, description,link, slogan , + goal_amount, raised_amount, status, deadline_at ,admin_id , created_at ) VALUES (?, ?, ?, ?, ?, ?, ? , NOW() )` result, err := tx.ExecContext(ctx, query, campaign.Title, campaign.Description, + campaign.Link, + campaign.Slogan, campaign.GoalAmount, campaign.RaisedAmount, campaign.Status, diff --git a/domain/campaign/service/createCampaign.go b/domain/campaign/service/create-campaign.go similarity index 73% rename from domain/campaign/service/createCampaign.go rename to domain/campaign/service/create-campaign.go index 24514606..372ee293 100644 --- a/domain/campaign/service/createCampaign.go +++ b/domain/campaign/service/create-campaign.go @@ -3,30 +3,20 @@ package service import ( "context" "fmt" - "git.gocasts.ir/ebhomengo/niki/domain/campaign/entity" + "git.gocasts.ir/ebhomengo/niki/domain/campaign/service/mapper" richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error" "git.gocasts.ir/ebhomengo/niki/types" - "time" ) // CreateCampaign handles creation of a new campaign. -func (s *CampaignService) CreateCampaign(ctx context.Context, req entity.Campaign) (types.ID, error) { +func (s *CampaignService) CreateCampaign(ctx context.Context, req CreateCampaignRequest) (types.ID, error) { const op = "service.campaign.create_campaign" 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(), - } + campaign := mapper.ToCampaignEntity(req) id, err := s.repo.Create(ctx, campaign) if err != nil { @@ -36,7 +26,7 @@ func (s *CampaignService) CreateCampaign(ctx context.Context, req entity.Campaig return id, nil } -func validateCreateCampaignRequest(req entity.Campaign) error { +func validateCreateCampaignRequest(req CreateCampaignRequest) error { if req.Title == "" { return errRequired("title") } diff --git a/domain/campaign/service/mapper/main.go b/domain/campaign/service/mapper/main.go new file mode 100644 index 00000000..a03c256f --- /dev/null +++ b/domain/campaign/service/mapper/main.go @@ -0,0 +1,22 @@ +package mapper + +import ( + "git.gocasts.ir/ebhomengo/niki/domain/campaign/entity" + param "git.gocasts.ir/ebhomengo/niki/domain/campaign/service" + "time" +) + +func ToCampaignEntity(req param.CreateCampaignRequest) entity.Campaign { + return entity.Campaign{ + Title: req.Title, + Description: req.Description, + Link: req.Link, + Slogan: req.Slogan, + GoalAmount: req.GoalAmount, + RaisedAmount: 0, + Status: entity.CampaignStatus(req.Status), + DeadlineAt: req.DeadlineAt, + AdminID: req.AdminID, + CreatedAt: time.Now(), + } +} diff --git a/domain/campaign/service/param.go b/domain/campaign/service/param.go index f54649ab..ecb9a430 100644 --- a/domain/campaign/service/param.go +++ b/domain/campaign/service/param.go @@ -6,22 +6,16 @@ import ( ) type GetCampaignResponse struct { - ID types.ID `json:"user_id"` + ID types.ID `json:"campaign_id"` } -type AddCampaignRequest struct { - ID uint64 `json:"id"` +type CreateCampaignRequest struct { Title string `json:"title"` Description string `json:"description"` + Link string `json:"link"` + Slogan string `json:"slogan" validate:"max=255"` GoalAmount float64 `json:"goal_amount"` + Status string `json:"status,omitempty"` 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 + AdminID types.ID `json:"admin_id" validate:"required"` } diff --git a/domain/campaign/service/services.go b/domain/campaign/service/services.go index cc85d8b3..9e027fb9 100644 --- a/domain/campaign/service/services.go +++ b/domain/campaign/service/services.go @@ -15,7 +15,7 @@ type CampaignFilterParam struct { } type CampaignStorage interface { - Create(ctx context.Context, c entity.Campaign) (types.ID, error) // باید ID برگرداند + Create(ctx context.Context, c entity.Campaign) (types.ID, error) 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) diff --git a/donate_app/config.go b/donate_app/config.go index b670aced..575e20db 100644 --- a/donate_app/config.go +++ b/donate_app/config.go @@ -1,8 +1,5 @@ package donate_app - - - import ( "git.gocasts.ir/ebhomengo/niki/repository/mysql" ) @@ -10,8 +7,3 @@ import ( type Config struct { Mysql mysql.Config `koanf:"mariadb"` } - - -type Config struct{ - -} diff --git a/donate_app/delivery/http/handler.go b/donate_app/delivery/http/handler.go index b20f6dd6..357b0ddf 100644 --- a/donate_app/delivery/http/handler.go +++ b/donate_app/delivery/http/handler.go @@ -1,12 +1,13 @@ package http import ( - "git.gocasts.ir/ebhomengo/niki/domain/campaign/entity" "git.gocasts.ir/ebhomengo/niki/domain/campaign/service" + param "git.gocasts.ir/ebhomengo/niki/domain/campaign/service" + "git.gocasts.ir/ebhomengo/niki/pkg/claim" httpmsg "git.gocasts.ir/ebhomengo/niki/pkg/http_msg" + "git.gocasts.ir/ebhomengo/niki/types" "github.com/labstack/echo/v4" "net/http" - "time" ) type Handler struct { @@ -19,14 +20,16 @@ func NewHandler(svc service.CampaignService) Handler { func (h Handler) createCampaign(c echo.Context) error { - var req entity.Campaign + claims := claim.GetClaimsFromEchoContext(c) + + var req param.CreateCampaignRequest + + req.AdminID = types.ID(claims.UserID) 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 { diff --git a/donate_app/pkg/types.go b/donate_app/pkg/types.go deleted file mode 100644 index ad2823b5..00000000 --- a/donate_app/pkg/types.go +++ /dev/null @@ -1,4 +0,0 @@ -// --- Type Aliases --- -package pkg - -type ID uint64 diff --git a/donate_app/repository/migrations/1775633805_add_campaign_participants_table.sql b/donate_app/repository/migrations/1775633805_add_campaign_participants_table.sql deleted file mode 100644 index 779a9bd2..00000000 --- a/donate_app/repository/migrations/1775633805_add_campaign_participants_table.sql +++ /dev/null @@ -1,19 +0,0 @@ --- +migrate Up - -CREATE TABLE `campaign_participants` ( - `id` BIGINT PRIMARY KEY AUTO_INCREMENT, - `campaign_id` BIGINT NOT NULL, - `user_id` BIGINT NOT NULL, - `amount` DECIMAL(15,2) NOT NULL, - `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (`campaign_id`) - REFERENCES `campaigns`(`id`) - ON DELETE CASCADE, - FOREIGN KEY (`user_id`) - REFERENCES `users`(`id`) - ON DELETE CASCADE, - UNIQUE KEY `unique_campaign_user` (`campaign_id`, `user_id`) -); - --- +migrate Down -DROP TABLE `campaign_participants`; diff --git a/donate_app/repository/migrations/1775633805_donation_campaign_table.sql b/donate_app/repository/migrations/1775633805_donation_campaign_table.sql new file mode 100644 index 00000000..8898d22a --- /dev/null +++ b/donate_app/repository/migrations/1775633805_donation_campaign_table.sql @@ -0,0 +1,28 @@ +-- +migrate Up +CREATE TABLE `donation_flows` ( + `id` BIGINT PRIMARY KEY AUTO_INCREMENT, + `campaign_id` BIGINT NOT NULL, + `user_id` BIGINT NULL, + `source_type` VARCHAR(50) NOT NULL, -- e.g., "affiliate", "app", "qr", "sms" + `source_name` VARCHAR(100) NOT NULL, -- e.g., "instagram", "donate_app" + `referral_code` VARCHAR(100), + `link` VARCHAR(255) NOT NULL, + `clicks` BIGINT DEFAULT 0, + `conversions` BIGINT DEFAULT 0, + `donations_total` DECIMAL(15,2) DEFAULT 0.00, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + + FOREIGN KEY (`campaign_id`) + REFERENCES `campaigns`(`id`) + ON DELETE CASCADE, + + FOREIGN KEY (`user_id`) + REFERENCES `users`(`id`) + ON DELETE SET NULL +); + +CREATE INDEX `idx_flow_campaign_id` ON `donation_flows`(`campaign_id`); +CREATE INDEX `idx_flow_source` ON `donation_flows`(`source_type`, `source_name`); + +-- +migrate Down +DROP TABLE IF EXISTS `donation_flows`; diff --git a/donate_app/repository/mysql/db.go b/donate_app/repository/mysql/db.go index 358ea1cd..6e11f9aa 100644 --- a/donate_app/repository/mysql/db.go +++ b/donate_app/repository/mysql/db.go @@ -1,11 +1,7 @@ package mysql import ( - "context" - "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" ) type DB struct { @@ -15,40 +11,3 @@ type DB struct { func New(db *mysql.DB) *DB { return &DB{conn: db} } - -// 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 { - return 0, richerror.New(Op).WithErr(err) - } - defer tx.Rollback() - - query := `INSERT INTO campaign_participants (id,campaign_id, user_id, amount , created_at) - VALUES (?, ?, ? , ? , NOW())` - - result, err := tx.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) - } - if err := tx.Commit(); 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 deleted file mode 100644 index 8199852a..00000000 --- a/donate_app/service/entity/campaignParticipant.go +++ /dev/null @@ -1,13 +0,0 @@ -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"` -} diff --git a/donate_app/service/entity/donation.go b/donate_app/service/entity/donation.go new file mode 100644 index 00000000..740a360a --- /dev/null +++ b/donate_app/service/entity/donation.go @@ -0,0 +1,20 @@ +package entity + +import ( + "git.gocasts.ir/ebhomengo/niki/types" + "time" +) + +type DonationApp struct { + ID types.ID `json:"id"` + CampaignID types.ID `json:"campaign_id"` + UserID *types.ID `json:"user_id,omitempty"` + SourceType string `json:"source_type"` // : "affiliate", "app", "qr", "sms", "social" + SourceName string `json:"source_name"` // "user_name", "donate_app", "instagram" + ReferralCode string `json:"referral_code"` + Link string `json:"link"` + Clicks int64 `json:"clicks"` + Conversions int64 `json:"conversions"` + DonationsTotal float64 `json:"donations_total"` + CreatedAt time.Time `json:"created_at"` +} diff --git a/donate_app/service/param.go b/donate_app/service/param.go deleted file mode 100644 index f064f16e..00000000 --- a/donate_app/service/param.go +++ /dev/null @@ -1,26 +0,0 @@ -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/validator.go b/donate_app/service/validator.go deleted file mode 100644 index 6d43c336..00000000 --- a/donate_app/service/validator.go +++ /dev/null @@ -1 +0,0 @@ -package service