forked from ebhomengo/niki
Merge pull request 'campaign-domain' (#305) from campaign-domain into develop
Reviewed-on: ebhomengo/niki#305
This commit is contained in:
commit
9f4e011357
|
|
@ -5,39 +5,31 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CampaignStatus string
|
|
||||||
|
|
||||||
const (
|
|
||||||
CampaignDraft CampaignStatus = "draft"
|
|
||||||
CampaignActive CampaignStatus = "active"
|
|
||||||
CampaignFinished CampaignStatus = "completed"
|
|
||||||
CampaignPaused CampaignStatus = "paused"
|
|
||||||
CampaignCanceled CampaignStatus = "cancelled"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Campaign struct {
|
type Campaign struct {
|
||||||
ID types.ID `json:"id"`
|
ID types.ID `json:"id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
GoalAmount float64 `json:"goal_amount"`
|
Link string `json:"link"`
|
||||||
RaisedAmount float64 `json:"raised_amount"`
|
Slogan string `json:"slogan"` //
|
||||||
Status CampaignStatus `json:"status"`
|
GoalAmount float64 `json:"goal_amount"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
RaisedAmount float64 `json:"raised_amount"`
|
||||||
DeadlineAt *time.Time `json:"deadline_at,omitempty"`
|
Status types.CampaignStatus `json:"status"`
|
||||||
AdminID types.ID `json:"creator_id"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
DeadlineAt *time.Time `json:"deadline_at,omitempty"`
|
||||||
|
AdminID types.ID `json:"creator_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Behavior
|
// Behavior
|
||||||
func (c *Campaign) Activate() {
|
func (c *Campaign) Activate() {
|
||||||
if c.Status == CampaignDraft {
|
if c.Status == types.CampaignDraft {
|
||||||
c.Status = CampaignActive
|
c.Status = types.CampaignActive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Campaign) AddFunds(amount float64) {
|
func (c *Campaign) AddFunds(amount float64) {
|
||||||
c.RaisedAmount += amount
|
c.RaisedAmount += amount
|
||||||
if c.RaisedAmount >= c.GoalAmount {
|
if c.RaisedAmount >= c.GoalAmount {
|
||||||
c.Status = CampaignFinished
|
c.Status = types.CampaignFinished
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ CREATE TABLE `campaigns` (
|
||||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
`title` VARCHAR(255) NOT NULL,
|
`title` VARCHAR(255) NOT NULL,
|
||||||
`description` TEXT,
|
`description` TEXT,
|
||||||
|
`link` VARCHAR(255) NULL,
|
||||||
|
`slogan` VARCHAR(255) NULL,
|
||||||
`goal_amount` DECIMAL(15,2) NOT NULL,
|
`goal_amount` DECIMAL(15,2) NOT NULL,
|
||||||
`raised_amount` DECIMAL(15,2) DEFAULT 0,
|
`raised_amount` DECIMAL(15,2) DEFAULT 0,
|
||||||
`status` VARCHAR(50) NOT NULL,
|
`status` VARCHAR(50) NOT NULL,
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ func New(db *mysql.DB) *DB {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateCampaign creates a new campaign
|
// 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"
|
const Op = "repository.mysql.campaign.create"
|
||||||
|
|
||||||
tx, err := d.conn.Conn().BeginTx(ctx, nil)
|
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()
|
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 )
|
status, deadline_at ,admin_id , created_at )
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ? , NOW() )`
|
VALUES (?, ?, ?, ?, ?, ?, ? , NOW() )`
|
||||||
|
|
||||||
result, err := tx.ExecContext(ctx, query,
|
result, err := tx.ExecContext(ctx, query,
|
||||||
campaign.Title,
|
campaign.Title,
|
||||||
campaign.Description,
|
campaign.Description,
|
||||||
|
campaign.Link,
|
||||||
|
campaign.Slogan,
|
||||||
campaign.GoalAmount,
|
campaign.GoalAmount,
|
||||||
campaign.RaisedAmount,
|
campaign.RaisedAmount,
|
||||||
campaign.Status,
|
campaign.Status,
|
||||||
|
|
|
||||||
|
|
@ -9,24 +9,30 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ToCampaignEntity(req CreateCampaignRequest) entity.Campaign {
|
||||||
|
return entity.Campaign{
|
||||||
|
Title: req.Title,
|
||||||
|
Description: req.Description,
|
||||||
|
Link: req.Link,
|
||||||
|
Slogan: req.Slogan,
|
||||||
|
GoalAmount: req.GoalAmount,
|
||||||
|
RaisedAmount: 0,
|
||||||
|
Status: types.CampaignStatus(req.Status),
|
||||||
|
DeadlineAt: req.DeadlineAt,
|
||||||
|
AdminID: req.AdminID,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// CreateCampaign handles creation of a new campaign.
|
// 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"
|
const op = "service.campaign.create_campaign"
|
||||||
|
|
||||||
if err := validateCreateCampaignRequest(req); err != nil {
|
if err := validateCreateCampaignRequest(req); err != nil {
|
||||||
return 0, richerror.New(op).WithErr(err)
|
return 0, richerror.New(op).WithErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
campaign := entity.Campaign{
|
campaign := ToCampaignEntity(req)
|
||||||
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)
|
id, err := s.repo.Create(ctx, campaign)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -36,7 +42,7 @@ func (s *CampaignService) CreateCampaign(ctx context.Context, req entity.Campaig
|
||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateCreateCampaignRequest(req entity.Campaign) error {
|
func validateCreateCampaignRequest(req CreateCampaignRequest) error {
|
||||||
if req.Title == "" {
|
if req.Title == "" {
|
||||||
return errRequired("title")
|
return errRequired("title")
|
||||||
}
|
}
|
||||||
|
|
@ -6,22 +6,25 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type GetCampaignResponse struct {
|
type GetCampaignResponse struct {
|
||||||
ID types.ID `json:"user_id"`
|
ID types.ID `json:"campaign_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AddCampaignRequest struct {
|
type CreateCampaignRequest struct {
|
||||||
ID uint64 `json:"id"`
|
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
Link string `json:"link"`
|
||||||
|
Slogan string `json:"slogan" validate:"max=255"`
|
||||||
GoalAmount float64 `json:"goal_amount"`
|
GoalAmount float64 `json:"goal_amount"`
|
||||||
|
Status string `json:"status,omitempty"`
|
||||||
DeadlineAt *time.Time `json:"deadline_at,omitempty"`
|
DeadlineAt *time.Time `json:"deadline_at,omitempty"`
|
||||||
AdminID types.ID `json:"admin_id"`
|
AdminID types.ID `json:"admin_id" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateCampaignRequest struct {
|
type CompletedCampaignResponse struct {
|
||||||
Title *string `json:"title,omitempty"`
|
TotalChecked uint64 `json:"total_checked"`
|
||||||
Description *string `json:"description,omitempty"`
|
TotalFinished uint64 `json:"total_finished"`
|
||||||
GoalAmount *float64 `json:"goal_amount,omitempty"`
|
}
|
||||||
DeadlineAt *time.Time `json:"deadline_at,omitempty"`
|
|
||||||
Status *string `json:"status,omitempty"` // draft/active/completed/paused/cancelled
|
type FilterRequest struct {
|
||||||
|
Limit uint32 `json:"total_checked"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,13 @@ type CampaignFilterParam struct {
|
||||||
IsArchived *bool
|
IsArchived *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CampaignStatus interface {
|
||||||
|
FindActiveCampaigns(ctx context.Context) ([]entity.Campaign, error)
|
||||||
|
UpdateStatus(ctx context.Context, id types.ID, status types.CampaignStatus) error
|
||||||
|
}
|
||||||
|
|
||||||
type CampaignStorage interface {
|
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
|
Update(ctx context.Context, c entity.Campaign) error
|
||||||
FindByID(ctx context.Context, id types.ID) (entity.Campaign, error)
|
FindByID(ctx context.Context, id types.ID) (entity.Campaign, error)
|
||||||
FindAll(ctx context.Context, filter CampaignFilterParam) ([]entity.Campaign, error)
|
FindAll(ctx context.Context, filter CampaignFilterParam) ([]entity.Campaign, error)
|
||||||
|
|
@ -24,10 +29,10 @@ type CampaignStorage interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type CampaignService struct {
|
type CampaignService struct {
|
||||||
repo CampaignStorage
|
repo CampaignStorage
|
||||||
|
repoStatus CampaignStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCampaignService constructs a new CampaignService.
|
|
||||||
func NewCampaignService(storage CampaignStorage) *CampaignService {
|
func NewCampaignService(storage CampaignStorage) *CampaignService {
|
||||||
return &CampaignService{
|
return &CampaignService{
|
||||||
repo: storage,
|
repo: storage,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/types"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *CampaignService) MonitorCampaignProgress(ctx context.Context, req FilterRequest) {
|
||||||
|
|
||||||
|
ticker := time.NewTicker(1 * time.Hour)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
s.CheckAndCompleteCampaigns(ctx, req)
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CampaignService) CheckAndCompleteCampaigns(ctx context.Context, req FilterRequest) (CompletedCampaignResponse, error) {
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
//TODO:with filter request later complete
|
||||||
|
activeCampaigns, err := s.repoStatus.FindActiveCampaigns(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return CompletedCampaignResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalChecked uint64
|
||||||
|
var totalFinished uint64
|
||||||
|
|
||||||
|
for _, campaign := range activeCampaigns {
|
||||||
|
|
||||||
|
totalChecked++
|
||||||
|
|
||||||
|
shouldFinish := false
|
||||||
|
|
||||||
|
if campaign.DeadlineAt != nil && campaign.DeadlineAt.Before(now) {
|
||||||
|
shouldFinish = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if campaign.RaisedAmount >= campaign.GoalAmount {
|
||||||
|
shouldFinish = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldFinish && campaign.Status != types.CampaignFinished {
|
||||||
|
if err := s.repoStatus.UpdateStatus(ctx, campaign.ID, types.CampaignFinished); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
totalFinished++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompletedCampaignResponse{
|
||||||
|
TotalChecked: totalChecked,
|
||||||
|
TotalFinished: totalFinished,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
package donate_app
|
package donate_app
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
||||||
)
|
)
|
||||||
|
|
@ -10,8 +7,3 @@ import (
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Mysql mysql.Config `koanf:"mariadb"`
|
Mysql mysql.Config `koanf:"mariadb"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type Config struct{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.gocasts.ir/ebhomengo/niki/domain/campaign/entity"
|
|
||||||
"git.gocasts.ir/ebhomengo/niki/domain/campaign/service"
|
"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"
|
httpmsg "git.gocasts.ir/ebhomengo/niki/pkg/http_msg"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/types"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
|
|
@ -19,14 +20,16 @@ func NewHandler(svc service.CampaignService) Handler {
|
||||||
|
|
||||||
func (h Handler) createCampaign(c echo.Context) error {
|
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 {
|
if err := c.Bind(&req); err != nil {
|
||||||
return c.JSON(http.StatusBadRequest, map[string]string{
|
return c.JSON(http.StatusBadRequest, map[string]string{
|
||||||
"error": "invalid request body",
|
"error": "invalid request body",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
req.CreatedAt = time.Now()
|
|
||||||
req.RaisedAmount = 0
|
|
||||||
|
|
||||||
createdID, err := h.svc.CreateCampaign(c.Request().Context(), req)
|
createdID, err := h.svc.CreateCampaign(c.Request().Context(), req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
// --- Type Aliases ---
|
|
||||||
package pkg
|
|
||||||
|
|
||||||
type ID uint64
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package campaign;
|
||||||
|
|
||||||
|
option go_package = "git.gocasts.ir/ebhomengo/niki/donate_app/protobuf;campaignpb";
|
||||||
|
|
||||||
|
service CampaignService {
|
||||||
|
rpc CheckAndCompleteCampaigns (CheckAndCompleteCampaignsRequest) returns (CheckAndCompleteCampaignsResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
message CheckAndCompleteCampaignsRequest {
|
||||||
|
FilterRequest filter = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CheckAndCompleteCampaignsResponse {
|
||||||
|
uint64 total_checked = 1;
|
||||||
|
uint64 total_finished = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FilterRequest {
|
||||||
|
uint32 limit = 1;
|
||||||
|
}
|
||||||
|
|
@ -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`;
|
|
||||||
|
|
@ -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`;
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
import (
|
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/repository/mysql"
|
||||||
"git.gocasts.ir/ebhomengo/niki/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DB struct {
|
type DB struct {
|
||||||
|
|
@ -15,40 +11,3 @@ type DB struct {
|
||||||
func New(db *mysql.DB) *DB {
|
func New(db *mysql.DB) *DB {
|
||||||
return &DB{conn: 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
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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"`
|
|
||||||
}
|
|
||||||
|
|
@ -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"`
|
||||||
|
}
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
type CampaignStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CampaignDraft CampaignStatus = "draft"
|
||||||
|
CampaignActive CampaignStatus = "active"
|
||||||
|
CampaignFinished CampaignStatus = "completed"
|
||||||
|
CampaignPaused CampaignStatus = "paused"
|
||||||
|
CampaignCanceled CampaignStatus = "cancelled"
|
||||||
|
)
|
||||||
Loading…
Reference in New Issue