Add support for multiple instances of the same notifier type
This commit is contained in:
+21
-19
@@ -66,18 +66,19 @@ enum NotificationStatus {
|
|||||||
message Notification {
|
message Notification {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
NotificationType type = 2;
|
NotificationType type = 2;
|
||||||
Priority priority = 3;
|
string account = 3; // Optional account name for multi-account configs
|
||||||
NotificationStatus status = 4;
|
Priority priority = 4;
|
||||||
string subject = 5;
|
NotificationStatus status = 5;
|
||||||
string body = 6;
|
string subject = 6;
|
||||||
repeated string recipients = 7;
|
string body = 7;
|
||||||
map<string, string> metadata = 8;
|
repeated string recipients = 8;
|
||||||
google.protobuf.Timestamp created_at = 9;
|
map<string, string> metadata = 9;
|
||||||
google.protobuf.Timestamp scheduled_for = 10;
|
google.protobuf.Timestamp created_at = 10;
|
||||||
google.protobuf.Timestamp sent_at = 11;
|
google.protobuf.Timestamp scheduled_for = 11;
|
||||||
int32 retry_count = 12;
|
google.protobuf.Timestamp sent_at = 12;
|
||||||
int32 max_retries = 13;
|
int32 retry_count = 13;
|
||||||
string last_error = 14;
|
int32 max_retries = 14;
|
||||||
|
string last_error = 15;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotificationResult represents the outcome of sending a notification
|
// NotificationResult represents the outcome of sending a notification
|
||||||
@@ -93,13 +94,14 @@ message NotificationResult {
|
|||||||
// SendNotificationRequest sends a single notification
|
// SendNotificationRequest sends a single notification
|
||||||
message SendNotificationRequest {
|
message SendNotificationRequest {
|
||||||
NotificationType type = 1;
|
NotificationType type = 1;
|
||||||
Priority priority = 2;
|
string account = 2; // Optional account name for multi-account configs
|
||||||
string subject = 3;
|
Priority priority = 3;
|
||||||
string body = 4;
|
string subject = 4;
|
||||||
repeated string recipients = 5;
|
string body = 5;
|
||||||
map<string, string> metadata = 6;
|
repeated string recipients = 6;
|
||||||
google.protobuf.Timestamp scheduled_for = 7;
|
map<string, string> metadata = 7;
|
||||||
int32 max_retries = 8;
|
google.protobuf.Timestamp scheduled_for = 8;
|
||||||
|
int32 max_retries = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendNotificationResponse returns the result of sending a notification
|
// SendNotificationResponse returns the result of sending a notification
|
||||||
|
|||||||
@@ -201,18 +201,19 @@ type Notification struct {
|
|||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
Type NotificationType `protobuf:"varint,2,opt,name=type,proto3,enum=notifier.v1.NotificationType" json:"type,omitempty"`
|
Type NotificationType `protobuf:"varint,2,opt,name=type,proto3,enum=notifier.v1.NotificationType" json:"type,omitempty"`
|
||||||
Priority Priority `protobuf:"varint,3,opt,name=priority,proto3,enum=notifier.v1.Priority" json:"priority,omitempty"`
|
Account string `protobuf:"bytes,3,opt,name=account,proto3" json:"account,omitempty"` // Optional account name for multi-account configs
|
||||||
Status NotificationStatus `protobuf:"varint,4,opt,name=status,proto3,enum=notifier.v1.NotificationStatus" json:"status,omitempty"`
|
Priority Priority `protobuf:"varint,4,opt,name=priority,proto3,enum=notifier.v1.Priority" json:"priority,omitempty"`
|
||||||
Subject string `protobuf:"bytes,5,opt,name=subject,proto3" json:"subject,omitempty"`
|
Status NotificationStatus `protobuf:"varint,5,opt,name=status,proto3,enum=notifier.v1.NotificationStatus" json:"status,omitempty"`
|
||||||
Body string `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"`
|
Subject string `protobuf:"bytes,6,opt,name=subject,proto3" json:"subject,omitempty"`
|
||||||
Recipients []string `protobuf:"bytes,7,rep,name=recipients,proto3" json:"recipients,omitempty"`
|
Body string `protobuf:"bytes,7,opt,name=body,proto3" json:"body,omitempty"`
|
||||||
Metadata map[string]string `protobuf:"bytes,8,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
Recipients []string `protobuf:"bytes,8,rep,name=recipients,proto3" json:"recipients,omitempty"`
|
||||||
CreatedAt *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
|
Metadata map[string]string `protobuf:"bytes,9,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
ScheduledFor *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=scheduled_for,json=scheduledFor,proto3" json:"scheduled_for,omitempty"`
|
CreatedAt *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
|
||||||
SentAt *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=sent_at,json=sentAt,proto3" json:"sent_at,omitempty"`
|
ScheduledFor *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=scheduled_for,json=scheduledFor,proto3" json:"scheduled_for,omitempty"`
|
||||||
RetryCount int32 `protobuf:"varint,12,opt,name=retry_count,json=retryCount,proto3" json:"retry_count,omitempty"`
|
SentAt *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=sent_at,json=sentAt,proto3" json:"sent_at,omitempty"`
|
||||||
MaxRetries int32 `protobuf:"varint,13,opt,name=max_retries,json=maxRetries,proto3" json:"max_retries,omitempty"`
|
RetryCount int32 `protobuf:"varint,13,opt,name=retry_count,json=retryCount,proto3" json:"retry_count,omitempty"`
|
||||||
LastError string `protobuf:"bytes,14,opt,name=last_error,json=lastError,proto3" json:"last_error,omitempty"`
|
MaxRetries int32 `protobuf:"varint,14,opt,name=max_retries,json=maxRetries,proto3" json:"max_retries,omitempty"`
|
||||||
|
LastError string `protobuf:"bytes,15,opt,name=last_error,json=lastError,proto3" json:"last_error,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -261,6 +262,13 @@ func (x *Notification) GetType() NotificationType {
|
|||||||
return NotificationType_NOTIFICATION_TYPE_UNSPECIFIED
|
return NotificationType_NOTIFICATION_TYPE_UNSPECIFIED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Notification) GetAccount() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Account
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (x *Notification) GetPriority() Priority {
|
func (x *Notification) GetPriority() Priority {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Priority
|
return x.Priority
|
||||||
@@ -434,13 +442,14 @@ func (x *NotificationResult) GetProviderResponse() map[string]string {
|
|||||||
type SendNotificationRequest struct {
|
type SendNotificationRequest struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Type NotificationType `protobuf:"varint,1,opt,name=type,proto3,enum=notifier.v1.NotificationType" json:"type,omitempty"`
|
Type NotificationType `protobuf:"varint,1,opt,name=type,proto3,enum=notifier.v1.NotificationType" json:"type,omitempty"`
|
||||||
Priority Priority `protobuf:"varint,2,opt,name=priority,proto3,enum=notifier.v1.Priority" json:"priority,omitempty"`
|
Account string `protobuf:"bytes,2,opt,name=account,proto3" json:"account,omitempty"` // Optional account name for multi-account configs
|
||||||
Subject string `protobuf:"bytes,3,opt,name=subject,proto3" json:"subject,omitempty"`
|
Priority Priority `protobuf:"varint,3,opt,name=priority,proto3,enum=notifier.v1.Priority" json:"priority,omitempty"`
|
||||||
Body string `protobuf:"bytes,4,opt,name=body,proto3" json:"body,omitempty"`
|
Subject string `protobuf:"bytes,4,opt,name=subject,proto3" json:"subject,omitempty"`
|
||||||
Recipients []string `protobuf:"bytes,5,rep,name=recipients,proto3" json:"recipients,omitempty"`
|
Body string `protobuf:"bytes,5,opt,name=body,proto3" json:"body,omitempty"`
|
||||||
Metadata map[string]string `protobuf:"bytes,6,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
Recipients []string `protobuf:"bytes,6,rep,name=recipients,proto3" json:"recipients,omitempty"`
|
||||||
ScheduledFor *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=scheduled_for,json=scheduledFor,proto3" json:"scheduled_for,omitempty"`
|
Metadata map[string]string `protobuf:"bytes,7,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
MaxRetries int32 `protobuf:"varint,8,opt,name=max_retries,json=maxRetries,proto3" json:"max_retries,omitempty"`
|
ScheduledFor *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=scheduled_for,json=scheduledFor,proto3" json:"scheduled_for,omitempty"`
|
||||||
|
MaxRetries int32 `protobuf:"varint,9,opt,name=max_retries,json=maxRetries,proto3" json:"max_retries,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -482,6 +491,13 @@ func (x *SendNotificationRequest) GetType() NotificationType {
|
|||||||
return NotificationType_NOTIFICATION_TYPE_UNSPECIFIED
|
return NotificationType_NOTIFICATION_TYPE_UNSPECIFIED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *SendNotificationRequest) GetAccount() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Account
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (x *SendNotificationRequest) GetPriority() Priority {
|
func (x *SendNotificationRequest) GetPriority() Priority {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Priority
|
return x.Priority
|
||||||
@@ -1375,29 +1391,30 @@ var File_api_grpc_notifier_proto protoreflect.FileDescriptor
|
|||||||
|
|
||||||
const file_api_grpc_notifier_proto_rawDesc = "" +
|
const file_api_grpc_notifier_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\x17api/grpc/notifier.proto\x12\vnotifier.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\x9f\x05\n" +
|
"\x17api/grpc/notifier.proto\x12\vnotifier.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\xb9\x05\n" +
|
||||||
"\fNotification\x12\x0e\n" +
|
"\fNotification\x12\x0e\n" +
|
||||||
"\x02id\x18\x01 \x01(\tR\x02id\x121\n" +
|
"\x02id\x18\x01 \x01(\tR\x02id\x121\n" +
|
||||||
"\x04type\x18\x02 \x01(\x0e2\x1d.notifier.v1.NotificationTypeR\x04type\x121\n" +
|
"\x04type\x18\x02 \x01(\x0e2\x1d.notifier.v1.NotificationTypeR\x04type\x12\x18\n" +
|
||||||
"\bpriority\x18\x03 \x01(\x0e2\x15.notifier.v1.PriorityR\bpriority\x127\n" +
|
"\aaccount\x18\x03 \x01(\tR\aaccount\x121\n" +
|
||||||
"\x06status\x18\x04 \x01(\x0e2\x1f.notifier.v1.NotificationStatusR\x06status\x12\x18\n" +
|
"\bpriority\x18\x04 \x01(\x0e2\x15.notifier.v1.PriorityR\bpriority\x127\n" +
|
||||||
"\asubject\x18\x05 \x01(\tR\asubject\x12\x12\n" +
|
"\x06status\x18\x05 \x01(\x0e2\x1f.notifier.v1.NotificationStatusR\x06status\x12\x18\n" +
|
||||||
"\x04body\x18\x06 \x01(\tR\x04body\x12\x1e\n" +
|
"\asubject\x18\x06 \x01(\tR\asubject\x12\x12\n" +
|
||||||
|
"\x04body\x18\a \x01(\tR\x04body\x12\x1e\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"recipients\x18\a \x03(\tR\n" +
|
"recipients\x18\b \x03(\tR\n" +
|
||||||
"recipients\x12C\n" +
|
"recipients\x12C\n" +
|
||||||
"\bmetadata\x18\b \x03(\v2'.notifier.v1.Notification.MetadataEntryR\bmetadata\x129\n" +
|
"\bmetadata\x18\t \x03(\v2'.notifier.v1.Notification.MetadataEntryR\bmetadata\x129\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"created_at\x18\t \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12?\n" +
|
"created_at\x18\n" +
|
||||||
"\rscheduled_for\x18\n" +
|
" \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12?\n" +
|
||||||
" \x01(\v2\x1a.google.protobuf.TimestampR\fscheduledFor\x123\n" +
|
"\rscheduled_for\x18\v \x01(\v2\x1a.google.protobuf.TimestampR\fscheduledFor\x123\n" +
|
||||||
"\asent_at\x18\v \x01(\v2\x1a.google.protobuf.TimestampR\x06sentAt\x12\x1f\n" +
|
"\asent_at\x18\f \x01(\v2\x1a.google.protobuf.TimestampR\x06sentAt\x12\x1f\n" +
|
||||||
"\vretry_count\x18\f \x01(\x05R\n" +
|
"\vretry_count\x18\r \x01(\x05R\n" +
|
||||||
"retryCount\x12\x1f\n" +
|
"retryCount\x12\x1f\n" +
|
||||||
"\vmax_retries\x18\r \x01(\x05R\n" +
|
"\vmax_retries\x18\x0e \x01(\x05R\n" +
|
||||||
"maxRetries\x12\x1d\n" +
|
"maxRetries\x12\x1d\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"last_error\x18\x0e \x01(\tR\tlastError\x1a;\n" +
|
"last_error\x18\x0f \x01(\tR\tlastError\x1a;\n" +
|
||||||
"\rMetadataEntry\x12\x10\n" +
|
"\rMetadataEntry\x12\x10\n" +
|
||||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xe5\x02\n" +
|
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xe5\x02\n" +
|
||||||
@@ -1410,18 +1427,19 @@ const file_api_grpc_notifier_proto_rawDesc = "" +
|
|||||||
"\x11provider_response\x18\x06 \x03(\v25.notifier.v1.NotificationResult.ProviderResponseEntryR\x10providerResponse\x1aC\n" +
|
"\x11provider_response\x18\x06 \x03(\v25.notifier.v1.NotificationResult.ProviderResponseEntryR\x10providerResponse\x1aC\n" +
|
||||||
"\x15ProviderResponseEntry\x12\x10\n" +
|
"\x15ProviderResponseEntry\x12\x10\n" +
|
||||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xbc\x03\n" +
|
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xd6\x03\n" +
|
||||||
"\x17SendNotificationRequest\x121\n" +
|
"\x17SendNotificationRequest\x121\n" +
|
||||||
"\x04type\x18\x01 \x01(\x0e2\x1d.notifier.v1.NotificationTypeR\x04type\x121\n" +
|
"\x04type\x18\x01 \x01(\x0e2\x1d.notifier.v1.NotificationTypeR\x04type\x12\x18\n" +
|
||||||
"\bpriority\x18\x02 \x01(\x0e2\x15.notifier.v1.PriorityR\bpriority\x12\x18\n" +
|
"\aaccount\x18\x02 \x01(\tR\aaccount\x121\n" +
|
||||||
"\asubject\x18\x03 \x01(\tR\asubject\x12\x12\n" +
|
"\bpriority\x18\x03 \x01(\x0e2\x15.notifier.v1.PriorityR\bpriority\x12\x18\n" +
|
||||||
"\x04body\x18\x04 \x01(\tR\x04body\x12\x1e\n" +
|
"\asubject\x18\x04 \x01(\tR\asubject\x12\x12\n" +
|
||||||
|
"\x04body\x18\x05 \x01(\tR\x04body\x12\x1e\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"recipients\x18\x05 \x03(\tR\n" +
|
"recipients\x18\x06 \x03(\tR\n" +
|
||||||
"recipients\x12N\n" +
|
"recipients\x12N\n" +
|
||||||
"\bmetadata\x18\x06 \x03(\v22.notifier.v1.SendNotificationRequest.MetadataEntryR\bmetadata\x12?\n" +
|
"\bmetadata\x18\a \x03(\v22.notifier.v1.SendNotificationRequest.MetadataEntryR\bmetadata\x12?\n" +
|
||||||
"\rscheduled_for\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\fscheduledFor\x12\x1f\n" +
|
"\rscheduled_for\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\fscheduledFor\x12\x1f\n" +
|
||||||
"\vmax_retries\x18\b \x01(\x05R\n" +
|
"\vmax_retries\x18\t \x01(\x05R\n" +
|
||||||
"maxRetries\x1a;\n" +
|
"maxRetries\x1a;\n" +
|
||||||
"\rMetadataEntry\x12\x10\n" +
|
"\rMetadataEntry\x12\x10\n" +
|
||||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
// SendNotificationRequest is the REST API request for sending a notification
|
// SendNotificationRequest is the REST API request for sending a notification
|
||||||
type SendNotificationRequest struct {
|
type SendNotificationRequest struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
Account string `json:"account,omitempty"` // Optional account name for multi-account configs
|
||||||
Priority int `json:"priority,omitempty"`
|
Priority int `json:"priority,omitempty"`
|
||||||
Subject string `json:"subject"`
|
Subject string `json:"subject"`
|
||||||
Body string `json:"body"`
|
Body string `json:"body"`
|
||||||
@@ -47,6 +48,7 @@ func (r *SendNotificationRequest) ToNotification() *domain.Notification {
|
|||||||
return &domain.Notification{
|
return &domain.Notification{
|
||||||
ID: uuid.New().String(),
|
ID: uuid.New().String(),
|
||||||
Type: domain.NotificationType(r.Type),
|
Type: domain.NotificationType(r.Type),
|
||||||
|
Account: r.Account,
|
||||||
Priority: domain.Priority(r.Priority),
|
Priority: domain.Priority(r.Priority),
|
||||||
Status: domain.StatusPending,
|
Status: domain.StatusPending,
|
||||||
Subject: r.Subject,
|
Subject: r.Subject,
|
||||||
@@ -79,6 +81,7 @@ type SendBatchNotificationsResponse struct {
|
|||||||
type Notification struct {
|
type Notification struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
Account string `json:"account,omitempty"`
|
||||||
Priority int `json:"priority"`
|
Priority int `json:"priority"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Subject string `json:"subject"`
|
Subject string `json:"subject"`
|
||||||
@@ -98,6 +101,7 @@ func NotificationFromDomain(n *domain.Notification) Notification {
|
|||||||
return Notification{
|
return Notification{
|
||||||
ID: n.ID,
|
ID: n.ID,
|
||||||
Type: string(n.Type),
|
Type: string(n.Type),
|
||||||
|
Account: n.Account,
|
||||||
Priority: int(n.Priority),
|
Priority: int(n.Priority),
|
||||||
Status: string(n.Status),
|
Status: string(n.Status),
|
||||||
Subject: n.Subject,
|
Subject: n.Subject,
|
||||||
|
|||||||
+36
-21
@@ -69,8 +69,8 @@ func main() {
|
|||||||
|
|
||||||
logger.Infof("Supported notification types: %v", factory.SupportedTypes())
|
logger.Infof("Supported notification types: %v", factory.SupportedTypes())
|
||||||
|
|
||||||
// Create notification service
|
// Create notification service (pass config as account resolver)
|
||||||
svc := service.NewNotificationService(factory, q, cfg.Queue.WorkerCount)
|
svc := service.NewNotificationService(factory, q, cfg.Queue.WorkerCount, cfg)
|
||||||
|
|
||||||
// Start workers
|
// Start workers
|
||||||
if err := svc.Start(ctx); err != nil {
|
if err := svc.Start(ctx); err != nil {
|
||||||
@@ -132,45 +132,60 @@ func main() {
|
|||||||
func registerNotifiers(cfg *config.Config, factory *notifier.Factory, logger *logging.Logger) {
|
func registerNotifiers(cfg *config.Config, factory *notifier.Factory, logger *logging.Logger) {
|
||||||
if cfg.Notifiers.Stdout {
|
if cfg.Notifiers.Stdout {
|
||||||
stdoutNotifier := notifier.NewStdoutNotifier()
|
stdoutNotifier := notifier.NewStdoutNotifier()
|
||||||
if err := factory.RegisterNotifier(domain.TypeStdout, stdoutNotifier); err != nil {
|
if err := factory.RegisterNotifier(domain.TypeStdout, "", stdoutNotifier); err != nil {
|
||||||
logger.Fatalf("Failed to register stdout notifier: %v", err)
|
logger.Fatalf("Failed to register stdout notifier: %v", err)
|
||||||
}
|
}
|
||||||
logger.Info("Registered stdout notifier")
|
logger.Info("Registered stdout notifier")
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Notifiers.SMTP != nil {
|
// Register SMTP notifiers (now supports multiple accounts)
|
||||||
smtpNotifier, err := notifier.NewSMTPNotifier(cfg.Notifiers.SMTP)
|
for accountName, smtpConfig := range cfg.Notifiers.SMTP {
|
||||||
|
smtpNotifier, err := notifier.NewSMTPNotifier(smtpConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warnf("Failed to create SMTP notifier: %v", err)
|
logger.Warnf("Failed to create SMTP notifier for account '%s': %v", accountName, err)
|
||||||
} else {
|
} else {
|
||||||
if err := factory.RegisterNotifier(domain.TypeEmail, smtpNotifier); err != nil {
|
if err := factory.RegisterNotifier(domain.TypeEmail, accountName, smtpNotifier); err != nil {
|
||||||
logger.Fatalf("Failed to register SMTP notifier: %v", err)
|
logger.Fatalf("Failed to register SMTP notifier for account '%s': %v", accountName, err)
|
||||||
}
|
}
|
||||||
logger.Info("Registered SMTP notifier")
|
defaultStr := ""
|
||||||
|
if smtpConfig.Default {
|
||||||
|
defaultStr = " (default)"
|
||||||
|
}
|
||||||
|
logger.Infof("Registered SMTP notifier for account '%s'%s", accountName, defaultStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Notifiers.Slack != nil {
|
// Register Slack notifiers (now supports multiple accounts)
|
||||||
slackNotifier, err := notifier.NewSlackNotifier(cfg.Notifiers.Slack)
|
for accountName, slackConfig := range cfg.Notifiers.Slack {
|
||||||
|
slackNotifier, err := notifier.NewSlackNotifier(slackConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warnf("Failed to create Slack notifier: %v", err)
|
logger.Warnf("Failed to create Slack notifier for account '%s': %v", accountName, err)
|
||||||
} else {
|
} else {
|
||||||
if err := factory.RegisterNotifier(domain.TypeSlack, slackNotifier); err != nil {
|
if err := factory.RegisterNotifier(domain.TypeSlack, accountName, slackNotifier); err != nil {
|
||||||
logger.Fatalf("Failed to register Slack notifier: %v", err)
|
logger.Fatalf("Failed to register Slack notifier for account '%s': %v", accountName, err)
|
||||||
}
|
}
|
||||||
logger.Info("Registered Slack notifier")
|
defaultStr := ""
|
||||||
|
if slackConfig.Default {
|
||||||
|
defaultStr = " (default)"
|
||||||
|
}
|
||||||
|
logger.Infof("Registered Slack notifier for account '%s'%s", accountName, defaultStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Notifiers.Ntfy != nil {
|
// Register Ntfy notifiers (now supports multiple accounts)
|
||||||
ntfyNotifier, err := notifier.NewNtfyNotifier(cfg.Notifiers.Ntfy)
|
for accountName, ntfyConfig := range cfg.Notifiers.Ntfy {
|
||||||
|
ntfyNotifier, err := notifier.NewNtfyNotifier(ntfyConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warnf("Failed to create Ntfy notifier: %v", err)
|
logger.Warnf("Failed to create Ntfy notifier for account '%s': %v", accountName, err)
|
||||||
} else {
|
} else {
|
||||||
if err := factory.RegisterNotifier(domain.TypeNtfy, ntfyNotifier); err != nil {
|
if err := factory.RegisterNotifier(domain.TypeNtfy, accountName, ntfyNotifier); err != nil {
|
||||||
logger.Fatalf("Failed to register Ntfy notifier: %v", err)
|
logger.Fatalf("Failed to register Ntfy notifier for account '%s': %v", accountName, err)
|
||||||
}
|
}
|
||||||
logger.Info("Registered Ntfy notifier")
|
defaultStr := ""
|
||||||
|
if ntfyConfig.Default {
|
||||||
|
defaultStr = " (default)"
|
||||||
|
}
|
||||||
|
logger.Infof("Registered Ntfy notifier for account '%s'%s", accountName, defaultStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-32
@@ -34,44 +34,63 @@ notifiers:
|
|||||||
# Enable stdout notifier (useful for development/debugging)
|
# Enable stdout notifier (useful for development/debugging)
|
||||||
stdout: true
|
stdout: true
|
||||||
|
|
||||||
# SMTP email configuration
|
# SMTP email configuration (supports multiple accounts)
|
||||||
smtp:
|
smtp:
|
||||||
host: "smtp.gmail.com"
|
# Personal email account (marked as default)
|
||||||
port: 587
|
personal:
|
||||||
username: "your-email@gmail.com"
|
host: "smtp.gmail.com"
|
||||||
password: "your-app-password"
|
port: 587
|
||||||
from: "notifications@yourservice.com"
|
username: "your-personal@gmail.com"
|
||||||
use_tls: true
|
password: "your-app-password"
|
||||||
|
from: "your-personal@gmail.com"
|
||||||
|
use_tls: true
|
||||||
|
default: true # This account will be used if no account is specified in the API request
|
||||||
|
|
||||||
# Slack configuration
|
# Work email account
|
||||||
|
# work:
|
||||||
|
# host: "smtp.company.com"
|
||||||
|
# port: 587
|
||||||
|
# username: "you@company.com"
|
||||||
|
# password: "your-work-password"
|
||||||
|
# from: "notifications@company.com"
|
||||||
|
# use_tls: true
|
||||||
|
# default: false
|
||||||
|
|
||||||
|
# Slack configuration (supports multiple workspaces/webhooks)
|
||||||
slack:
|
slack:
|
||||||
webhook_url: "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
|
# Main workspace (marked as default)
|
||||||
# token: "xoxb-your-bot-token" # Alternative to webhook
|
main:
|
||||||
# channel: "#notifications" # Default channel
|
webhook_url: "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
|
||||||
username: "Notifier Bot"
|
username: "Notifier Bot"
|
||||||
icon_emoji: ":bell:"
|
icon_emoji: ":bell:"
|
||||||
# Channel-specific webhooks
|
default: true # This workspace will be used if no account is specified
|
||||||
# webhooks:
|
|
||||||
# "#alerts": "https://hooks.slack.com/services/ALERTS/WEBHOOK"
|
|
||||||
# "#monitoring": "https://hooks.slack.com/services/MONITORING/WEBHOOK"
|
|
||||||
|
|
||||||
# Ntfy configuration
|
# Additional workspace example
|
||||||
|
# team-a:
|
||||||
|
# webhook_url: "https://hooks.slack.com/services/TEAM-A/WEBHOOK/URL"
|
||||||
|
# username: "Team A Bot"
|
||||||
|
# icon_emoji: ":rocket:"
|
||||||
|
# # Channel-specific webhooks within this workspace
|
||||||
|
# webhooks:
|
||||||
|
# "#alerts": "https://hooks.slack.com/services/ALERTS/WEBHOOK"
|
||||||
|
# "#monitoring": "https://hooks.slack.com/services/MONITORING/WEBHOOK"
|
||||||
|
|
||||||
|
# Ntfy configuration (supports multiple servers)
|
||||||
ntfy:
|
ntfy:
|
||||||
server_url: "https://ntfy.sh"
|
# Public ntfy.sh server (marked as default)
|
||||||
|
public:
|
||||||
|
server_url: "https://ntfy.sh"
|
||||||
|
# token: "tk_your_access_token" # Token-based auth (recommended)
|
||||||
|
# default_topic: "notifications"
|
||||||
|
default: true # This server will be used if no account is specified
|
||||||
|
|
||||||
# Token-based authentication (recommended for ntfy.sh)
|
# Private ntfy server example
|
||||||
# Supports both access tokens (tk_...) and publish tokens
|
# private:
|
||||||
# token: "tk_your_access_token"
|
# server_url: "https://ntfy.mycompany.com"
|
||||||
|
# username: "your-username"
|
||||||
# Basic authentication (alternative to token)
|
# password: "your-password"
|
||||||
# username: "your-username"
|
# default_topic: "company-notifications"
|
||||||
# password: "your-password"
|
# insecure_skip_verify: false # Set to true for self-signed certs
|
||||||
|
|
||||||
# Default topic if recipient not specified
|
|
||||||
# default_topic: "default-notifications"
|
|
||||||
|
|
||||||
# Skip TLS verification (for self-hosted servers with self-signed certs)
|
|
||||||
# insecure_skip_verify: false
|
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
level: "info" # Options: debug, info, warn, error
|
level: "info" # Options: debug, info, warn, error
|
||||||
|
|||||||
+47
-10
@@ -29,10 +29,10 @@ type ServerConfig struct {
|
|||||||
|
|
||||||
// NotifiersConfig contains configuration for all notifier types
|
// NotifiersConfig contains configuration for all notifier types
|
||||||
type NotifiersConfig struct {
|
type NotifiersConfig struct {
|
||||||
SMTP *notifier.SMTPConfig `mapstructure:"smtp"`
|
SMTP map[string]*notifier.SMTPConfig `mapstructure:"smtp"`
|
||||||
Slack *notifier.SlackConfig `mapstructure:"slack"`
|
Slack map[string]*notifier.SlackConfig `mapstructure:"slack"`
|
||||||
Ntfy *notifier.NtfyConfig `mapstructure:"ntfy"`
|
Ntfy map[string]*notifier.NtfyConfig `mapstructure:"ntfy"`
|
||||||
Stdout bool `mapstructure:"stdout"` // Enable stdout notifier
|
Stdout bool `mapstructure:"stdout"` // Enable stdout notifier
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoggingConfig contains logging configuration
|
// LoggingConfig contains logging configuration
|
||||||
@@ -185,9 +185,9 @@ func (c *Config) Validate() error {
|
|||||||
// HasAnyNotifier checks if at least one notifier is configured
|
// HasAnyNotifier checks if at least one notifier is configured
|
||||||
func (c *Config) HasAnyNotifier() bool {
|
func (c *Config) HasAnyNotifier() bool {
|
||||||
return c.Notifiers.Stdout ||
|
return c.Notifiers.Stdout ||
|
||||||
c.Notifiers.SMTP != nil ||
|
len(c.Notifiers.SMTP) > 0 ||
|
||||||
c.Notifiers.Slack != nil ||
|
len(c.Notifiers.Slack) > 0 ||
|
||||||
c.Notifiers.Ntfy != nil
|
len(c.Notifiers.Ntfy) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEnabledNotifiers returns a list of enabled notifier types
|
// GetEnabledNotifiers returns a list of enabled notifier types
|
||||||
@@ -197,15 +197,52 @@ func (c *Config) GetEnabledNotifiers() []domain.NotificationType {
|
|||||||
if c.Notifiers.Stdout {
|
if c.Notifiers.Stdout {
|
||||||
enabled = append(enabled, domain.TypeStdout)
|
enabled = append(enabled, domain.TypeStdout)
|
||||||
}
|
}
|
||||||
if c.Notifiers.SMTP != nil {
|
if len(c.Notifiers.SMTP) > 0 {
|
||||||
enabled = append(enabled, domain.TypeEmail)
|
enabled = append(enabled, domain.TypeEmail)
|
||||||
}
|
}
|
||||||
if c.Notifiers.Slack != nil {
|
if len(c.Notifiers.Slack) > 0 {
|
||||||
enabled = append(enabled, domain.TypeSlack)
|
enabled = append(enabled, domain.TypeSlack)
|
||||||
}
|
}
|
||||||
if c.Notifiers.Ntfy != nil {
|
if len(c.Notifiers.Ntfy) > 0 {
|
||||||
enabled = append(enabled, domain.TypeNtfy)
|
enabled = append(enabled, domain.TypeNtfy)
|
||||||
}
|
}
|
||||||
|
|
||||||
return enabled
|
return enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDefaultAccount returns the default account name for a notifier type, or the first account if no default is set
|
||||||
|
func (c *Config) GetDefaultAccount(notifierType domain.NotificationType) string {
|
||||||
|
switch notifierType {
|
||||||
|
case domain.TypeEmail:
|
||||||
|
for name, cfg := range c.Notifiers.SMTP {
|
||||||
|
if cfg.Default {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Return first account if no default is set
|
||||||
|
for name := range c.Notifiers.SMTP {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
case domain.TypeSlack:
|
||||||
|
for name, cfg := range c.Notifiers.Slack {
|
||||||
|
if cfg.Default {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Return first account if no default is set
|
||||||
|
for name := range c.Notifiers.Slack {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
case domain.TypeNtfy:
|
||||||
|
for name, cfg := range c.Notifiers.Ntfy {
|
||||||
|
if cfg.Default {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Return first account if no default is set
|
||||||
|
for name := range c.Notifiers.Ntfy {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ type Notification struct {
|
|||||||
// Type specifies which notifier should handle this notification
|
// Type specifies which notifier should handle this notification
|
||||||
Type NotificationType `json:"type"`
|
Type NotificationType `json:"type"`
|
||||||
|
|
||||||
|
// Account specifies which named account/instance to use for this notifier type (optional)
|
||||||
|
// If not specified, the default account for the notifier type will be used
|
||||||
|
Account string `json:"account,omitempty"`
|
||||||
|
|
||||||
// Priority determines urgency and retry behavior
|
// Priority determines urgency and retry behavior
|
||||||
Priority Priority `json:"priority"`
|
Priority Priority `json:"priority"`
|
||||||
|
|
||||||
|
|||||||
@@ -21,14 +21,18 @@ type Notifier interface {
|
|||||||
|
|
||||||
// NotifierFactory creates notifier instances based on configuration
|
// NotifierFactory creates notifier instances based on configuration
|
||||||
type NotifierFactory interface {
|
type NotifierFactory interface {
|
||||||
// Create creates a notifier for the given type
|
// Create creates a notifier for the given type and account
|
||||||
Create(notificationType NotificationType) (Notifier, error)
|
// If account is empty, the default account for the type will be used
|
||||||
|
Create(notificationType NotificationType, account string) (Notifier, error)
|
||||||
|
|
||||||
// RegisterNotifier registers a custom notifier implementation
|
// RegisterNotifier registers a custom notifier implementation
|
||||||
RegisterNotifier(notificationType NotificationType, notifier Notifier) error
|
RegisterNotifier(notificationType NotificationType, account string, notifier Notifier) error
|
||||||
|
|
||||||
// SupportedTypes returns all supported notification types
|
// SupportedTypes returns all supported notification types
|
||||||
SupportedTypes() []NotificationType
|
SupportedTypes() []NotificationType
|
||||||
|
|
||||||
|
// GetAccounts returns all registered accounts for a given notification type
|
||||||
|
GetAccounts(notificationType NotificationType) []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotificationService is the high-level service interface for managing notifications
|
// NotificationService is the high-level service interface for managing notifications
|
||||||
|
|||||||
@@ -10,24 +10,38 @@ import (
|
|||||||
|
|
||||||
// Factory creates and manages notifier instances
|
// Factory creates and manages notifier instances
|
||||||
type Factory struct {
|
type Factory struct {
|
||||||
notifiers map[domain.NotificationType]domain.Notifier
|
// Map of "type:account" -> notifier instance
|
||||||
|
notifiers map[string]domain.Notifier
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFactory creates a new notifier factory
|
// NewFactory creates a new notifier factory
|
||||||
func NewFactory() *Factory {
|
func NewFactory() *Factory {
|
||||||
return &Factory{
|
return &Factory{
|
||||||
notifiers: make(map[domain.NotificationType]domain.Notifier),
|
notifiers: make(map[string]domain.Notifier),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create creates a notifier for the given type
|
// makeKey creates a compound key from notification type and account
|
||||||
func (f *Factory) Create(notificationType domain.NotificationType) (domain.Notifier, error) {
|
func makeKey(notificationType domain.NotificationType, account string) string {
|
||||||
|
if account == "" {
|
||||||
|
// For backward compatibility, if account is empty, just use the type
|
||||||
|
return string(notificationType)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s:%s", notificationType, account)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create creates a notifier for the given type and account
|
||||||
|
func (f *Factory) Create(notificationType domain.NotificationType, account string) (domain.Notifier, error) {
|
||||||
f.mu.RLock()
|
f.mu.RLock()
|
||||||
defer f.mu.RUnlock()
|
defer f.mu.RUnlock()
|
||||||
|
|
||||||
notifier, exists := f.notifiers[notificationType]
|
key := makeKey(notificationType, account)
|
||||||
|
notifier, exists := f.notifiers[key]
|
||||||
if !exists {
|
if !exists {
|
||||||
|
if account != "" {
|
||||||
|
return nil, fmt.Errorf("unsupported notification type: %s with account: %s", notificationType, account)
|
||||||
|
}
|
||||||
return nil, fmt.Errorf("unsupported notification type: %s", notificationType)
|
return nil, fmt.Errorf("unsupported notification type: %s", notificationType)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,31 +49,65 @@ func (f *Factory) Create(notificationType domain.NotificationType) (domain.Notif
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RegisterNotifier registers a custom notifier implementation
|
// RegisterNotifier registers a custom notifier implementation
|
||||||
func (f *Factory) RegisterNotifier(notificationType domain.NotificationType, notifier domain.Notifier) error {
|
func (f *Factory) RegisterNotifier(notificationType domain.NotificationType, account string, notifier domain.Notifier) error {
|
||||||
f.mu.Lock()
|
f.mu.Lock()
|
||||||
defer f.mu.Unlock()
|
defer f.mu.Unlock()
|
||||||
|
|
||||||
if _, exists := f.notifiers[notificationType]; exists {
|
key := makeKey(notificationType, account)
|
||||||
|
if _, exists := f.notifiers[key]; exists {
|
||||||
|
if account != "" {
|
||||||
|
return fmt.Errorf("notifier already registered for type: %s with account: %s", notificationType, account)
|
||||||
|
}
|
||||||
return fmt.Errorf("notifier already registered for type: %s", notificationType)
|
return fmt.Errorf("notifier already registered for type: %s", notificationType)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.notifiers[notificationType] = notifier
|
f.notifiers[key] = notifier
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportedTypes returns all supported notification types
|
// SupportedTypes returns all supported notification types (unique types only)
|
||||||
func (f *Factory) SupportedTypes() []domain.NotificationType {
|
func (f *Factory) SupportedTypes() []domain.NotificationType {
|
||||||
f.mu.RLock()
|
f.mu.RLock()
|
||||||
defer f.mu.RUnlock()
|
defer f.mu.RUnlock()
|
||||||
|
|
||||||
types := make([]domain.NotificationType, 0, len(f.notifiers))
|
typeMap := make(map[domain.NotificationType]bool)
|
||||||
for t := range f.notifiers {
|
for key := range f.notifiers {
|
||||||
|
// Extract the type from the key (type:account)
|
||||||
|
var notifType domain.NotificationType
|
||||||
|
if n, err := fmt.Sscanf(key, "%s:", ¬ifType); err == nil && n > 0 {
|
||||||
|
typeMap[notifType] = true
|
||||||
|
} else {
|
||||||
|
// Backward compatibility: key might just be the type
|
||||||
|
typeMap[domain.NotificationType(key)] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
types := make([]domain.NotificationType, 0, len(typeMap))
|
||||||
|
for t := range typeMap {
|
||||||
types = append(types, t)
|
types = append(types, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
return types
|
return types
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAccounts returns all registered accounts for a given notification type
|
||||||
|
func (f *Factory) GetAccounts(notificationType domain.NotificationType) []string {
|
||||||
|
f.mu.RLock()
|
||||||
|
defer f.mu.RUnlock()
|
||||||
|
|
||||||
|
accounts := []string{}
|
||||||
|
prefix := string(notificationType) + ":"
|
||||||
|
|
||||||
|
for key := range f.notifiers {
|
||||||
|
if len(key) > len(prefix) && key[:len(prefix)] == prefix {
|
||||||
|
account := key[len(prefix):]
|
||||||
|
accounts = append(accounts, account)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return accounts
|
||||||
|
}
|
||||||
|
|
||||||
// BaseNotifier provides common functionality for all notifiers
|
// BaseNotifier provides common functionality for all notifiers
|
||||||
type BaseNotifier struct {
|
type BaseNotifier struct {
|
||||||
notificationType domain.NotificationType
|
notificationType domain.NotificationType
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ type NtfyConfig struct {
|
|||||||
|
|
||||||
// InsecureSkipVerify skips TLS verification (for self-hosted servers with self-signed certs)
|
// InsecureSkipVerify skips TLS verification (for self-hosted servers with self-signed certs)
|
||||||
InsecureSkipVerify bool `mapstructure:"insecure_skip_verify"`
|
InsecureSkipVerify bool `mapstructure:"insecure_skip_verify"`
|
||||||
|
|
||||||
|
// Default marks this instance as default
|
||||||
|
Default bool `mapstructure:"default"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NtfyNotifier sends notifications via ntfy.sh
|
// NtfyNotifier sends notifications via ntfy.sh
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ type SlackConfig struct {
|
|||||||
Username string `mapstructure:"username"`
|
Username string `mapstructure:"username"`
|
||||||
IconEmoji string `mapstructure:"icon_emoji"`
|
IconEmoji string `mapstructure:"icon_emoji"`
|
||||||
Webhooks map[string]string `mapstructure:"webhooks"` // Channel-specific webhooks
|
Webhooks map[string]string `mapstructure:"webhooks"` // Channel-specific webhooks
|
||||||
|
Default bool `mapstructure:"default"` // Mark this instance as default
|
||||||
}
|
}
|
||||||
|
|
||||||
// SlackNotifier sends notifications to Slack
|
// SlackNotifier sends notifications to Slack
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type SMTPConfig struct {
|
|||||||
Password string `mapstructure:"password"`
|
Password string `mapstructure:"password"`
|
||||||
From string `mapstructure:"from"`
|
From string `mapstructure:"from"`
|
||||||
UseTLS bool `mapstructure:"use_tls"`
|
UseTLS bool `mapstructure:"use_tls"`
|
||||||
|
Default bool `mapstructure:"default"` // Mark this instance as default
|
||||||
}
|
}
|
||||||
|
|
||||||
// SMTPNotifier sends notifications via email using SMTP
|
// SMTPNotifier sends notifications via email using SMTP
|
||||||
|
|||||||
+27
-14
@@ -9,29 +9,36 @@ import (
|
|||||||
"github.com/igodwin/notifier/internal/domain"
|
"github.com/igodwin/notifier/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AccountResolver is an interface for resolving default accounts
|
||||||
|
type AccountResolver interface {
|
||||||
|
GetDefaultAccount(notifierType domain.NotificationType) string
|
||||||
|
}
|
||||||
|
|
||||||
// NotificationService implements the domain.NotificationService interface
|
// NotificationService implements the domain.NotificationService interface
|
||||||
type NotificationService struct {
|
type NotificationService struct {
|
||||||
factory domain.NotifierFactory
|
factory domain.NotifierFactory
|
||||||
queue domain.Queue
|
queue domain.Queue
|
||||||
notifications map[string]*domain.Notification
|
accountResolver AccountResolver
|
||||||
mu sync.RWMutex
|
notifications map[string]*domain.Notification
|
||||||
workerCount int
|
mu sync.RWMutex
|
||||||
stopChan chan struct{}
|
workerCount int
|
||||||
wg sync.WaitGroup
|
stopChan chan struct{}
|
||||||
|
wg sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewNotificationService creates a new notification service
|
// NewNotificationService creates a new notification service
|
||||||
func NewNotificationService(factory domain.NotifierFactory, queue domain.Queue, workerCount int) *NotificationService {
|
func NewNotificationService(factory domain.NotifierFactory, queue domain.Queue, workerCount int, accountResolver AccountResolver) *NotificationService {
|
||||||
if workerCount <= 0 {
|
if workerCount <= 0 {
|
||||||
workerCount = 10
|
workerCount = 10
|
||||||
}
|
}
|
||||||
|
|
||||||
return &NotificationService{
|
return &NotificationService{
|
||||||
factory: factory,
|
factory: factory,
|
||||||
queue: queue,
|
queue: queue,
|
||||||
notifications: make(map[string]*domain.Notification),
|
accountResolver: accountResolver,
|
||||||
workerCount: workerCount,
|
notifications: make(map[string]*domain.Notification),
|
||||||
stopChan: make(chan struct{}),
|
workerCount: workerCount,
|
||||||
|
stopChan: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,8 +97,14 @@ func (s *NotificationService) worker(ctx context.Context, id int) {
|
|||||||
func (s *NotificationService) processNotification(ctx context.Context, msg *domain.QueueMessage) {
|
func (s *NotificationService) processNotification(ctx context.Context, msg *domain.QueueMessage) {
|
||||||
notification := msg.Notification
|
notification := msg.Notification
|
||||||
|
|
||||||
|
// Resolve account if not specified
|
||||||
|
account := notification.Account
|
||||||
|
if account == "" && s.accountResolver != nil {
|
||||||
|
account = s.accountResolver.GetDefaultAccount(notification.Type)
|
||||||
|
}
|
||||||
|
|
||||||
// Get the appropriate notifier
|
// Get the appropriate notifier
|
||||||
notifier, err := s.factory.Create(notification.Type)
|
notifier, err := s.factory.Create(notification.Type, account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
notification.Status = domain.StatusFailed
|
notification.Status = domain.StatusFailed
|
||||||
notification.LastError = fmt.Sprintf("failed to create notifier: %v", err)
|
notification.LastError = fmt.Sprintf("failed to create notifier: %v", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user