From 539a3162775ba0b345b94b8c362d3d7810945f69 Mon Sep 17 00:00:00 2001 From: mahdi simin Date: Wed, 1 Apr 2026 00:20:52 +0330 Subject: [PATCH 1/7] Impelement Notification Structure --- notification-app/entity/channel.go | 8 ++++ notification-app/entity/notification.go | 51 +++++++++++++++++++++++++ notification-app/messagebroker/redis.go | 17 +++++++++ notification-app/service/additem.go | 48 +++++++++++++++++++++++ notification-app/service/send.go | 7 ++++ 5 files changed, 131 insertions(+) create mode 100644 notification-app/entity/channel.go create mode 100644 notification-app/entity/notification.go create mode 100644 notification-app/messagebroker/redis.go create mode 100644 notification-app/service/additem.go create mode 100644 notification-app/service/send.go diff --git a/notification-app/entity/channel.go b/notification-app/entity/channel.go new file mode 100644 index 00000000..fa114320 --- /dev/null +++ b/notification-app/entity/channel.go @@ -0,0 +1,8 @@ +package entity + +type Channel struct { + ID int8 + Type notificationType + Provider string + Config string +} diff --git a/notification-app/entity/notification.go b/notification-app/entity/notification.go new file mode 100644 index 00000000..96887c62 --- /dev/null +++ b/notification-app/entity/notification.go @@ -0,0 +1,51 @@ +package entity + +type Notification struct { + ID int8 + Type NotificationType + Recipinet string + Body string + Status NotificationStatus +} + +type NotificationType uint8 + +const ( + Email NotificationType = iota + 1 + SMS + Push +) + +func (t NotificationType) String() string { + switch t { + case Email: + return "Email" + case SMS: + return "SMS" + case Push: + return "Push" + default: + return "Unknown" + } +} + +type NotificationStatus uint8 + +const ( + Pending NotificationStatus = iota + 1 + Success + Failed +) + +func (t NotificationStatus) String() string { + switch t { + case Pending: + return "Pending" + case Success: + return "Success" + case Failed: + return "Failed" + default: + return "Unknown" + } +} diff --git a/notification-app/messagebroker/redis.go b/notification-app/messagebroker/redis.go new file mode 100644 index 00000000..31dc8033 --- /dev/null +++ b/notification-app/messagebroker/redis.go @@ -0,0 +1,17 @@ +package messagebroker + +import "git.gocasts.ir/ebhomengo/niki/notification-app/entity" + +type redis struct { +} + +func (r *redis) AddItem(notification entity.Notification) error { + return nil +} + +func (r *redis) RemoveItem(notification entity.Notification) error { + return nil +} +func (r *redis) HealthCheck() error { + return nil +} diff --git a/notification-app/service/additem.go b/notification-app/service/additem.go new file mode 100644 index 00000000..c4702e35 --- /dev/null +++ b/notification-app/service/additem.go @@ -0,0 +1,48 @@ +package service + +import ( + _ "time" + + "git.gocasts.ir/ebhomengo/niki/notification-app/entity" +) + +type notifservice struct { + //MessageBroker MessageBroker +} + +func NewNotifservice() *notifservice { + return ¬ifservice{} +} + +func (n *notifservice) NewNotification(notificationType entity.NotificationType, Recipinet, Body string) *entity.Notification { + + return &entity.Notification{ + Type: notificationType, + Recipinet: Recipinet, + Body: Body, + Status: entity.Pending, + } +} + +func (n *notifservice) Send(notification entity.Notification) error { + return nil +} + +func (n *notifservice) Add(notification *entity.Notification) error { + return nil +} + +type MessageBroker interface { + AddItem(notification entity.Notification) error + RemoveItem(notification entity.Notification) error + HealthCheck() error +} + +func main() { + f := NewNotifservice() + + notif := f.NewNotification(entity.SMS, "09201008697", "Hello World") + + f.Add(notif) + +} diff --git a/notification-app/service/send.go b/notification-app/service/send.go new file mode 100644 index 00000000..ef8b6d87 --- /dev/null +++ b/notification-app/service/send.go @@ -0,0 +1,7 @@ +package service + +import "git.gocasts.ir/ebhomengo/niki/notification-app/entity" + +type sender interface { + Send(notification entity.Notification) error +} From c6210967af3f93d9dc4a575bf797b3c89fb9bf9c Mon Sep 17 00:00:00 2001 From: matina Date: Wed, 1 Apr 2026 02:07:31 -0700 Subject: [PATCH 2/7] entity added for donate pr --- donateApp/service/entity.go | 1 - donateApp/service/entity/campiagn.go | 47 ++++++++++++++++++++++++++++ donateApp/service/entity/donation.go | 25 +++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) delete mode 100644 donateApp/service/entity.go create mode 100644 donateApp/service/entity/campiagn.go create mode 100644 donateApp/service/entity/donation.go diff --git a/donateApp/service/entity.go b/donateApp/service/entity.go deleted file mode 100644 index 6d43c336..00000000 --- a/donateApp/service/entity.go +++ /dev/null @@ -1 +0,0 @@ -package service diff --git a/donateApp/service/entity/campiagn.go b/donateApp/service/entity/campiagn.go new file mode 100644 index 00000000..bd914808 --- /dev/null +++ b/donateApp/service/entity/campiagn.go @@ -0,0 +1,47 @@ +package service + + +import ( + "time" +) + +// --- Type Aliases --- + +// UserID is a type alias for clarity, representing foreign keys to the User service +type UserID uint32 + +// TransactionID is a type alias for clarity, representing foreign keys to the Transaction service +type TransactionID uint32 + +// CampaignID is a type alias for clarity, representing primary keys for Campaigns +type CampaignID uint32 + +// DonationID is a type alias for clarity, representing primary keys for Donations +type DonationID uint32 + +// --- Campaign Module Entities --- + +// 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 +) + +// Campaign represents a fundraising campaign entity +type Campaign struct { + ID CampaignID `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + GoalAmount float64 `json:"goal_amount"` // Using float64 for decimal, consider using a dedicated decimal type for precision + RaisedAmount float64 `json:"raised_amount"` + Status CampaignStatus `json:"status"` + CreatedAt time.Time `json:"created_at"` + DeadlineAt *time.Time `json:"deadline_at,omitempty"` // Pointer to time.Time, allows nil for no deadline. omitempty hides if nil. + AdminID UserID `json:"admin_id"` // Foreign Key to User service + InitiatedByUserID *UserID `json:"initiated_by_user_id,omitempty"` // Pointer to UserID, allows nil. omitempty hides if nil. +} + diff --git a/donateApp/service/entity/donation.go b/donateApp/service/entity/donation.go new file mode 100644 index 00000000..3c064929 --- /dev/null +++ b/donateApp/service/entity/donation.go @@ -0,0 +1,25 @@ +package service + + + +// DonationLevel represents the level of a donation (e.g., Bronze, Silver, Gold) +type DonationLevel string + +const ( + DonationLevelBronze DonationLevel = "Bronze" + DonationLevelSilver DonationLevel = "Silver" + DonationLevelGold DonationLevel = "Gold" +) + +// Donation represents a donation made to a campaign +type Donation struct { + ID DonationID `json:"id"` + UserID UserID `json:"user_id"` // Foreign Key to User service + CampaignID CampaignID `json:"campaign_id"` // Foreign Key to Campaign + Amount float64 `json:"amount"` // Using float64 for decimal, consider a dedicated decimal type + PointsEarned int `json:"points_earned"` // امتیازی که از این تراکنش خاص کسب شده + TransactionID TransactionID `json:"transaction_id"` // Foreign Key to Transaction service + CreatedAt time.Time `json:"created_at"` + DonationLevel *DonationLevel `json:"donation_level,omitempty"` // Pointer to DonationLevel, allows nil. omitempty hides if nil. +} + From bd58162f3e70232c5c26d569c624ee3626f2f4f3 Mon Sep 17 00:00:00 2001 From: matina Date: Wed, 1 Apr 2026 02:20:53 -0700 Subject: [PATCH 3/7] remove cmd --- donateApp/cmd/donate/main.go | 1 - 1 file changed, 1 deletion(-) delete mode 100644 donateApp/cmd/donate/main.go diff --git a/donateApp/cmd/donate/main.go b/donateApp/cmd/donate/main.go deleted file mode 100644 index 30b9cd45..00000000 --- a/donateApp/cmd/donate/main.go +++ /dev/null @@ -1 +0,0 @@ -package donate From 6b150111c7e564107ffdcea4498dcbd48b43c697 Mon Sep 17 00:00:00 2001 From: Mahdi Simin Date: Sat, 4 Apr 2026 04:20:12 +0330 Subject: [PATCH 4/7] feat/notification - notification service --- notification-app/entity/channel.go | 2 +- notification-app/service/additem.go | 79 ++++++++++++++++++----------- 2 files changed, 50 insertions(+), 31 deletions(-) diff --git a/notification-app/entity/channel.go b/notification-app/entity/channel.go index fa114320..4dfcc563 100644 --- a/notification-app/entity/channel.go +++ b/notification-app/entity/channel.go @@ -2,7 +2,7 @@ package entity type Channel struct { ID int8 - Type notificationType + Type NotificationType Provider string Config string } diff --git a/notification-app/service/additem.go b/notification-app/service/additem.go index c4702e35..b213b828 100644 --- a/notification-app/service/additem.go +++ b/notification-app/service/additem.go @@ -7,29 +7,9 @@ import ( ) type notifservice struct { - //MessageBroker MessageBroker -} - -func NewNotifservice() *notifservice { - return ¬ifservice{} -} - -func (n *notifservice) NewNotification(notificationType entity.NotificationType, Recipinet, Body string) *entity.Notification { - - return &entity.Notification{ - Type: notificationType, - Recipinet: Recipinet, - Body: Body, - Status: entity.Pending, - } -} - -func (n *notifservice) Send(notification entity.Notification) error { - return nil -} - -func (n *notifservice) Add(notification *entity.Notification) error { - return nil + MessageBroker MessageBroker + Repository Repository + Channel Channel } type MessageBroker interface { @@ -38,11 +18,50 @@ type MessageBroker interface { HealthCheck() error } -func main() { - f := NewNotifservice() - - notif := f.NewNotification(entity.SMS, "09201008697", "Hello World") - - f.Add(notif) - +type Repository interface { + AddItem(notification entity.Notification) error +} + +type Channel interface { + SendMessage(notification entity.Notification) error +} +type NotificationServiceRequest struct { + Body string + Type entity.NotificationType + Recipinet string +} + +func (n *notifservice) NewNotification(r NotificationServiceRequest) *entity.Notification { + // TODO add validation of notification properties + return &entity.Notification{ + Type: r.Type, + Recipinet: r.Recipinet, + Body: r.Body, + Status: entity.Pending, + } +} + +func (n *notifservice) Send(notification entity.Notification) error { + if err := n.Channel.SendMessage(notification); err != nil { + return err + } + return nil +} + +func (n *notifservice) Add(notification *entity.Notification) error { + err := n.MessageBroker.AddItem(*notification) + if err != nil { + return err + } + return nil +} + +func (n *notifservice) Archive(notification *entity.Notification) error { + if err := n.MessageBroker.RemoveItem(*notification); err != nil { + return err + } + if err := n.Repository.AddItem(*notification); err != nil { + return err + } + return nil } From bc37b8472687328c7ba2e8f101f934e00021c449 Mon Sep 17 00:00:00 2001 From: Mahdi Simin Date: Sat, 4 Apr 2026 04:51:28 +0330 Subject: [PATCH 5/7] feat/notification - restructure Notification app with new structure --- .../notification}/entity/channel.go | 0 .../notification}/entity/notification.go | 0 .../notification}/messagebroker/redis.go | 2 +- .../notification}/service/additem.go | 12 ++++++------ .../notification}/service/send.go | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) rename {notification-app => domain/notification}/entity/channel.go (100%) rename {notification-app => domain/notification}/entity/notification.go (100%) rename {notification-app => domain/notification}/messagebroker/redis.go (79%) rename {notification-app => domain/notification}/service/additem.go (76%) rename {notification-app => domain/notification}/service/send.go (57%) diff --git a/notification-app/entity/channel.go b/domain/notification/entity/channel.go similarity index 100% rename from notification-app/entity/channel.go rename to domain/notification/entity/channel.go diff --git a/notification-app/entity/notification.go b/domain/notification/entity/notification.go similarity index 100% rename from notification-app/entity/notification.go rename to domain/notification/entity/notification.go diff --git a/notification-app/messagebroker/redis.go b/domain/notification/messagebroker/redis.go similarity index 79% rename from notification-app/messagebroker/redis.go rename to domain/notification/messagebroker/redis.go index 31dc8033..c6974a9e 100644 --- a/notification-app/messagebroker/redis.go +++ b/domain/notification/messagebroker/redis.go @@ -1,6 +1,6 @@ package messagebroker -import "git.gocasts.ir/ebhomengo/niki/notification-app/entity" +import "git.gocasts.ir/ebhomengo/niki/domain/notification/entity" type redis struct { } diff --git a/notification-app/service/additem.go b/domain/notification/service/additem.go similarity index 76% rename from notification-app/service/additem.go rename to domain/notification/service/additem.go index b213b828..d79a9677 100644 --- a/notification-app/service/additem.go +++ b/domain/notification/service/additem.go @@ -3,10 +3,10 @@ package service import ( _ "time" - "git.gocasts.ir/ebhomengo/niki/notification-app/entity" + "git.gocasts.ir/ebhomengo/niki/domain/notification/entity" ) -type notifservice struct { +type Notifservice struct { MessageBroker MessageBroker Repository Repository Channel Channel @@ -31,7 +31,7 @@ type NotificationServiceRequest struct { Recipinet string } -func (n *notifservice) NewNotification(r NotificationServiceRequest) *entity.Notification { +func (n *Notifservice) NewNotification(r NotificationServiceRequest) *entity.Notification { // TODO add validation of notification properties return &entity.Notification{ Type: r.Type, @@ -41,14 +41,14 @@ func (n *notifservice) NewNotification(r NotificationServiceRequest) *entity.Not } } -func (n *notifservice) Send(notification entity.Notification) error { +func (n *Notifservice) Send(notification entity.Notification) error { if err := n.Channel.SendMessage(notification); err != nil { return err } return nil } -func (n *notifservice) Add(notification *entity.Notification) error { +func (n *Notifservice) Add(notification *entity.Notification) error { err := n.MessageBroker.AddItem(*notification) if err != nil { return err @@ -56,7 +56,7 @@ func (n *notifservice) Add(notification *entity.Notification) error { return nil } -func (n *notifservice) Archive(notification *entity.Notification) error { +func (n *Notifservice) Archive(notification *entity.Notification) error { if err := n.MessageBroker.RemoveItem(*notification); err != nil { return err } diff --git a/notification-app/service/send.go b/domain/notification/service/send.go similarity index 57% rename from notification-app/service/send.go rename to domain/notification/service/send.go index ef8b6d87..1d2c9211 100644 --- a/notification-app/service/send.go +++ b/domain/notification/service/send.go @@ -1,6 +1,6 @@ package service -import "git.gocasts.ir/ebhomengo/niki/notification-app/entity" +import "git.gocasts.ir/ebhomengo/niki/domain/notification/entity" type sender interface { Send(notification entity.Notification) error From e96553be9ce9c339da4b0a41e3a4c12a934430c4 Mon Sep 17 00:00:00 2001 From: matina Date: Wed, 8 Apr 2026 01:04:53 -0700 Subject: [PATCH 6/7] features:(db config & tables migrations & improve entities) --- donateApp/config.go | 5 -- donateApp/delivery/donate_server/handler.go | 8 -- .../1774070672_add_donate_table.sql | 11 --- donateApp/repository/mysql/db.go | 1 - donateApp/service/entity/campiagn.go | 47 ---------- donateApp/service/entity/donation.go | 25 ------ {donateApp => donate_app}/app.go | 6 +- donate_app/config.go | 17 ++++ donate_app/delivery/donate_server/handler.go | 5 ++ .../delivery/donate_server/routes.go | 0 .../delivery/donate_server/server.go | 0 donate_app/pkg/types.go | 4 + .../1775633533_add_campaign_table.sql | 18 ++++ ...633805_add_campaign_participants_table.sql | 19 ++++ donate_app/repository/mysql/db.go | 90 +++++++++++++++++++ .../service/entity/campaignParticipant.go | 14 +++ donate_app/service/entity/campiagn.go | 30 +++++++ {donateApp => donate_app}/service/param.go | 0 {donateApp => donate_app}/service/service.go | 0 .../service/validator.go | 0 20 files changed, 201 insertions(+), 99 deletions(-) delete mode 100644 donateApp/config.go delete mode 100644 donateApp/delivery/donate_server/handler.go delete mode 100644 donateApp/repository/migrations/1774070672_add_donate_table.sql delete mode 100644 donateApp/repository/mysql/db.go delete mode 100644 donateApp/service/entity/campiagn.go delete mode 100644 donateApp/service/entity/donation.go rename {donateApp => donate_app}/app.go (79%) create mode 100644 donate_app/config.go create mode 100644 donate_app/delivery/donate_server/handler.go rename {donateApp => donate_app}/delivery/donate_server/routes.go (100%) rename {donateApp => donate_app}/delivery/donate_server/server.go (100%) create mode 100644 donate_app/pkg/types.go create mode 100644 donate_app/repository/migrations/1775633533_add_campaign_table.sql create mode 100644 donate_app/repository/migrations/1775633805_add_campaign_participants_table.sql create mode 100644 donate_app/repository/mysql/db.go create mode 100644 donate_app/service/entity/campaignParticipant.go create mode 100644 donate_app/service/entity/campiagn.go rename {donateApp => donate_app}/service/param.go (100%) rename {donateApp => donate_app}/service/service.go (100%) rename {donateApp => donate_app}/service/validator.go (100%) diff --git a/donateApp/config.go b/donateApp/config.go deleted file mode 100644 index c55690d5..00000000 --- a/donateApp/config.go +++ /dev/null @@ -1,5 +0,0 @@ -package donateapp - -type Config struct{ - -} diff --git a/donateApp/delivery/donate_server/handler.go b/donateApp/delivery/donate_server/handler.go deleted file mode 100644 index 7cc44499..00000000 --- a/donateApp/delivery/donate_server/handler.go +++ /dev/null @@ -1,8 +0,0 @@ -package donate_server - -type Handler struct { -} - -func NewHandler() Handler { - return Handler{} -} diff --git a/donateApp/repository/migrations/1774070672_add_donate_table.sql b/donateApp/repository/migrations/1774070672_add_donate_table.sql deleted file mode 100644 index 7950546f..00000000 --- a/donateApp/repository/migrations/1774070672_add_donate_table.sql +++ /dev/null @@ -1,11 +0,0 @@ --- +migrate Up -CREATE TABLE `donates` ( - `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, - `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `id`(`id` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 84 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_persian_ci ROW_FORMAT = Dynamic; - --- +migrate Down -DROP TABLE IF EXISTS `donates`; \ No newline at end of file diff --git a/donateApp/repository/mysql/db.go b/donateApp/repository/mysql/db.go deleted file mode 100644 index b0843023..00000000 --- a/donateApp/repository/mysql/db.go +++ /dev/null @@ -1 +0,0 @@ -package mysql diff --git a/donateApp/service/entity/campiagn.go b/donateApp/service/entity/campiagn.go deleted file mode 100644 index bd914808..00000000 --- a/donateApp/service/entity/campiagn.go +++ /dev/null @@ -1,47 +0,0 @@ -package service - - -import ( - "time" -) - -// --- Type Aliases --- - -// UserID is a type alias for clarity, representing foreign keys to the User service -type UserID uint32 - -// TransactionID is a type alias for clarity, representing foreign keys to the Transaction service -type TransactionID uint32 - -// CampaignID is a type alias for clarity, representing primary keys for Campaigns -type CampaignID uint32 - -// DonationID is a type alias for clarity, representing primary keys for Donations -type DonationID uint32 - -// --- Campaign Module Entities --- - -// 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 -) - -// Campaign represents a fundraising campaign entity -type Campaign struct { - ID CampaignID `json:"id"` - Title string `json:"title"` - Description string `json:"description"` - GoalAmount float64 `json:"goal_amount"` // Using float64 for decimal, consider using a dedicated decimal type for precision - RaisedAmount float64 `json:"raised_amount"` - Status CampaignStatus `json:"status"` - CreatedAt time.Time `json:"created_at"` - DeadlineAt *time.Time `json:"deadline_at,omitempty"` // Pointer to time.Time, allows nil for no deadline. omitempty hides if nil. - AdminID UserID `json:"admin_id"` // Foreign Key to User service - InitiatedByUserID *UserID `json:"initiated_by_user_id,omitempty"` // Pointer to UserID, allows nil. omitempty hides if nil. -} - diff --git a/donateApp/service/entity/donation.go b/donateApp/service/entity/donation.go deleted file mode 100644 index 3c064929..00000000 --- a/donateApp/service/entity/donation.go +++ /dev/null @@ -1,25 +0,0 @@ -package service - - - -// DonationLevel represents the level of a donation (e.g., Bronze, Silver, Gold) -type DonationLevel string - -const ( - DonationLevelBronze DonationLevel = "Bronze" - DonationLevelSilver DonationLevel = "Silver" - DonationLevelGold DonationLevel = "Gold" -) - -// Donation represents a donation made to a campaign -type Donation struct { - ID DonationID `json:"id"` - UserID UserID `json:"user_id"` // Foreign Key to User service - CampaignID CampaignID `json:"campaign_id"` // Foreign Key to Campaign - Amount float64 `json:"amount"` // Using float64 for decimal, consider a dedicated decimal type - PointsEarned int `json:"points_earned"` // امتیازی که از این تراکنش خاص کسب شده - TransactionID TransactionID `json:"transaction_id"` // Foreign Key to Transaction service - CreatedAt time.Time `json:"created_at"` - DonationLevel *DonationLevel `json:"donation_level,omitempty"` // Pointer to DonationLevel, allows nil. omitempty hides if nil. -} - diff --git a/donateApp/app.go b/donate_app/app.go similarity index 79% rename from donateApp/app.go rename to donate_app/app.go index d762c024..d280a647 100644 --- a/donateApp/app.go +++ b/donate_app/app.go @@ -1,8 +1,10 @@ -package doanteApp +package donate_app import "net/http" type Application struct { Config Config HTTPServer *http.Server -} \ No newline at end of file +} + + diff --git a/donate_app/config.go b/donate_app/config.go new file mode 100644 index 00000000..b670aced --- /dev/null +++ b/donate_app/config.go @@ -0,0 +1,17 @@ +package donate_app + + + + +import ( + "git.gocasts.ir/ebhomengo/niki/repository/mysql" +) + +type Config struct { + Mysql mysql.Config `koanf:"mariadb"` +} + + +type Config struct{ + +} diff --git a/donate_app/delivery/donate_server/handler.go b/donate_app/delivery/donate_server/handler.go new file mode 100644 index 00000000..9f94a9c3 --- /dev/null +++ b/donate_app/delivery/donate_server/handler.go @@ -0,0 +1,5 @@ +package donate_server + +type Handler struct{} + + diff --git a/donateApp/delivery/donate_server/routes.go b/donate_app/delivery/donate_server/routes.go similarity index 100% rename from donateApp/delivery/donate_server/routes.go rename to donate_app/delivery/donate_server/routes.go diff --git a/donateApp/delivery/donate_server/server.go b/donate_app/delivery/donate_server/server.go similarity index 100% rename from donateApp/delivery/donate_server/server.go rename to donate_app/delivery/donate_server/server.go diff --git a/donate_app/pkg/types.go b/donate_app/pkg/types.go new file mode 100644 index 00000000..ad2823b5 --- /dev/null +++ b/donate_app/pkg/types.go @@ -0,0 +1,4 @@ +// --- Type Aliases --- +package pkg + +type ID uint64 diff --git a/donate_app/repository/migrations/1775633533_add_campaign_table.sql b/donate_app/repository/migrations/1775633533_add_campaign_table.sql new file mode 100644 index 00000000..65aead6e --- /dev/null +++ b/donate_app/repository/migrations/1775633533_add_campaign_table.sql @@ -0,0 +1,18 @@ +-- +migrate Up + +CREATE TABLE `campaigns` ( + `id` BIGINT PRIMARY KEY AUTO_INCREMENT, + `title` VARCHAR(255) NOT NULL, + `description` TEXT, + `goal_amount` DECIMAL(15,2) NOT NULL, + `raised_amount` DECIMAL(15,2) DEFAULT 0, + `status` VARCHAR(50) NOT NULL, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + `deadline_at` TIMESTAMP, + `admin_id` BIGINT NOT NULL, + FOREIGN KEY (`admin_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT +); + + +-- +migrate Down +DROP TABLE `campaigns`; diff --git a/donate_app/repository/migrations/1775633805_add_campaign_participants_table.sql b/donate_app/repository/migrations/1775633805_add_campaign_participants_table.sql new file mode 100644 index 00000000..779a9bd2 --- /dev/null +++ b/donate_app/repository/migrations/1775633805_add_campaign_participants_table.sql @@ -0,0 +1,19 @@ +-- +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/mysql/db.go b/donate_app/repository/mysql/db.go new file mode 100644 index 00000000..6a89ad81 --- /dev/null +++ b/donate_app/repository/mysql/db.go @@ -0,0 +1,90 @@ +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 DB struct { + conn *mysql.DB +} + +func New(db *mysql.DB) *DB { + return &DB{conn: db} +} + +// CreateCampaign creates a new campaign +func (d *DB) CreateCampaign(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 +} + +// 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 new file mode 100644 index 00000000..fb493429 --- /dev/null +++ b/donate_app/service/entity/campaignParticipant.go @@ -0,0 +1,14 @@ +package service + + +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 diff --git a/donate_app/service/entity/campiagn.go b/donate_app/service/entity/campiagn.go new file mode 100644 index 00000000..157ef030 --- /dev/null +++ b/donate_app/service/entity/campiagn.go @@ -0,0 +1,30 @@ +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"` +} diff --git a/donateApp/service/param.go b/donate_app/service/param.go similarity index 100% rename from donateApp/service/param.go rename to donate_app/service/param.go diff --git a/donateApp/service/service.go b/donate_app/service/service.go similarity index 100% rename from donateApp/service/service.go rename to donate_app/service/service.go diff --git a/donateApp/service/validator.go b/donate_app/service/validator.go similarity index 100% rename from donateApp/service/validator.go rename to donate_app/service/validator.go From 891cbf098f5e39c0e39b414ce13777750d11d340 Mon Sep 17 00:00:00 2001 From: matina Date: Wed, 8 Apr 2026 02:14:50 -0700 Subject: [PATCH 7/7] feat:create service logic for campaign creation --- donate_app/service/service.go | 90 +++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/donate_app/service/service.go b/donate_app/service/service.go index 6d43c336..b5141dd2 100644 --- a/donate_app/service/service.go +++ b/donate_app/service/service.go @@ -1 +1,91 @@ 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.CampaignRepository + participantRepo repository.CampaignParticipantRepository +} + + +type CampaignServiceInterface interface { + CreateCampaign(ctx context.Context, req CampaignRepository) (types.ID, error) +} + +// NewCampaignService creates a new campaign service +func NewCampaignService( + repo repository.CampaignRepository, + participantRepo repository.CampaignParticipantRepository, +) *CampaignService { + return &CampaignService{ + repo: repo, + participantRepo: participantRepo, + } +} + + +// 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 +}