Skip to content

Commit 2bc691e

Browse files
committed
feat(notification): integrates discord and slack along with email, creates migrations, ui, and controllers and service files to add update delete the webhooks configs
1 parent b5e3c35 commit 2bc691e

File tree

24 files changed

+1433
-324
lines changed

24 files changed

+1433
-324
lines changed

api/doc/openapi.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package controller
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/go-fuego/fuego"
7+
"github.com/google/uuid"
8+
"github.com/raghavyuva/nixopus-api/internal/features/notification"
9+
"github.com/raghavyuva/nixopus-api/internal/utils"
10+
11+
shared_types "github.com/raghavyuva/nixopus-api/internal/types"
12+
)
13+
14+
func (c *NotificationController) CreateWebhookConfig(f fuego.ContextWithBody[notification.CreateWebhookConfigRequest]) (*shared_types.Response, error) {
15+
req, err := f.Body()
16+
if err != nil {
17+
return nil, fuego.HTTPError{
18+
Err: err,
19+
Status: http.StatusBadRequest,
20+
}
21+
}
22+
23+
user := utils.GetUser(f.Response(), f.Request())
24+
if user == nil {
25+
return nil, fuego.HTTPError{
26+
Err: nil,
27+
Status: http.StatusUnauthorized,
28+
}
29+
}
30+
31+
orgID := utils.GetOrganizationID(f.Request())
32+
if orgID == uuid.Nil {
33+
return nil, fuego.HTTPError{
34+
Err: nil,
35+
Status: http.StatusUnauthorized,
36+
}
37+
}
38+
39+
config, err := c.service.CreateWebhookConfig(f, &req, user.ID, orgID)
40+
if err != nil {
41+
return nil, fuego.HTTPError{
42+
Err: err,
43+
Status: http.StatusInternalServerError,
44+
}
45+
}
46+
47+
return &shared_types.Response{
48+
Status: "success",
49+
Message: "Webhook config created successfully",
50+
Data: config,
51+
}, nil
52+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package controller
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/go-fuego/fuego"
7+
"github.com/google/uuid"
8+
"github.com/raghavyuva/nixopus-api/internal/features/notification"
9+
shared_types "github.com/raghavyuva/nixopus-api/internal/types"
10+
"github.com/raghavyuva/nixopus-api/internal/utils"
11+
)
12+
13+
func (c *NotificationController) DeleteWebhookConfig(f fuego.ContextWithBody[notification.DeleteWebhookConfigRequest]) (*shared_types.Response, error) {
14+
req, err := f.Body()
15+
if err != nil {
16+
return nil, fuego.HTTPError{
17+
Err: err,
18+
Status: http.StatusBadRequest,
19+
}
20+
}
21+
22+
orgID := utils.GetOrganizationID(f.Request())
23+
if orgID == uuid.Nil {
24+
return nil, fuego.HTTPError{
25+
Err: nil,
26+
Status: http.StatusUnauthorized,
27+
}
28+
}
29+
30+
err = c.service.DeleteWebhookConfig(f, &req, orgID)
31+
if err != nil {
32+
return nil, fuego.HTTPError{
33+
Err: err,
34+
Status: http.StatusInternalServerError,
35+
}
36+
}
37+
38+
return &shared_types.Response{
39+
Status: "success",
40+
Message: "Webhook config deleted successfully",
41+
Data: nil,
42+
}, nil
43+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package controller
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/go-fuego/fuego"
7+
"github.com/google/uuid"
8+
"github.com/raghavyuva/nixopus-api/internal/features/notification"
9+
"github.com/raghavyuva/nixopus-api/internal/utils"
10+
11+
shared_types "github.com/raghavyuva/nixopus-api/internal/types"
12+
)
13+
14+
func (c *NotificationController) GetWebhookConfig(f fuego.ContextNoBody) (*shared_types.Response, error) {
15+
orgID := utils.GetOrganizationID(f.Request())
16+
if orgID == uuid.Nil {
17+
return nil, fuego.HTTPError{
18+
Err: nil,
19+
Status: http.StatusUnauthorized,
20+
}
21+
}
22+
23+
webhookType := f.PathParam("type")
24+
25+
config, err := c.service.GetWebhookConfig(f, &notification.GetWebhookConfigRequest{Type: webhookType}, orgID)
26+
if err != nil {
27+
return nil, fuego.HTTPError{
28+
Err: err,
29+
Status: http.StatusInternalServerError,
30+
}
31+
}
32+
33+
return &shared_types.Response{
34+
Status: "success",
35+
Message: "Webhook config retrieved successfully",
36+
Data: config,
37+
}, nil
38+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package controller
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/go-fuego/fuego"
7+
"github.com/google/uuid"
8+
"github.com/raghavyuva/nixopus-api/internal/features/notification"
9+
"github.com/raghavyuva/nixopus-api/internal/utils"
10+
11+
shared_types "github.com/raghavyuva/nixopus-api/internal/types"
12+
)
13+
14+
func (c *NotificationController) UpdateWebhookConfig(f fuego.ContextWithBody[notification.UpdateWebhookConfigRequest]) (*shared_types.Response, error) {
15+
req, err := f.Body()
16+
if err != nil {
17+
return nil, fuego.HTTPError{
18+
Err: err,
19+
Status: http.StatusBadRequest,
20+
}
21+
}
22+
23+
orgID := utils.GetOrganizationID(f.Request())
24+
if orgID == uuid.Nil {
25+
return nil, fuego.HTTPError{
26+
Err: nil,
27+
Status: http.StatusUnauthorized,
28+
}
29+
}
30+
31+
config, err := c.service.UpdateWebhookConfig(f, &req, orgID)
32+
if err != nil {
33+
return nil, fuego.HTTPError{
34+
Err: err,
35+
Status: http.StatusInternalServerError,
36+
}
37+
}
38+
39+
return &shared_types.Response{
40+
Status: "success",
41+
Message: "Webhook config updated successfully",
42+
Data: config,
43+
}, nil
44+
}

api/internal/features/notification/init.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@ package notification
33
import (
44
"bytes"
55
"context"
6+
"encoding/json"
67
"fmt"
78
"html/template"
89
"log"
10+
"net/http"
911
"net/smtp"
1012
"os"
1113
"path/filepath"
1214
"time"
1315

1416
"github.com/google/uuid"
1517
shared_types "github.com/raghavyuva/nixopus-api/internal/types"
18+
"github.com/slack-go/slack"
1619
"github.com/uptrace/bun"
1720
)
1821

@@ -67,12 +70,16 @@ func (m *NotificationManager) Start() {
6770
fmt.Printf("Password Reset Notification - %+v", payload)
6871
if data, ok := payload.Data.(NotificationPasswordResetData); ok {
6972
m.SendPasswordResetEmail(payload.UserID, data.Token)
73+
m.SendSlackNotification(payload.UserID, "Password reset requested")
74+
m.SendDiscordNotification(payload.UserID, "Password reset requested")
7075
}
7176
}
7277
if payload.Type == NotificationPayloadTypeVerificationEmail {
7378
fmt.Printf("Verification Email Notification - %+v", payload)
7479
if data, ok := payload.Data.(NotificationVerificationEmailData); ok {
7580
m.SendVerificationEmail(payload.UserID, data.Token)
81+
m.SendSlackNotification(payload.UserID, "Email verification requested")
82+
m.SendDiscordNotification(payload.UserID, "Email verification requested")
7683
}
7784
}
7885
case NotificationCategoryOrganization:
@@ -81,6 +88,8 @@ func (m *NotificationManager) Start() {
8188
fmt.Printf("Update User Role Notification - %+v", payload)
8289
if data, ok := payload.Data.(NotificationOrganizationData); ok {
8390
m.SendUpdateUserRoleEmail(payload.UserID, data.OrganizationID, data.UserID)
91+
m.SendSlackNotification(payload.UserID, fmt.Sprintf("User role updated in organization %s", data.OrganizationID))
92+
m.SendDiscordNotification(payload.UserID, fmt.Sprintf("User role updated in organization %s", data.OrganizationID))
8493
}
8594
}
8695
}
@@ -320,3 +329,46 @@ func (m *NotificationManager) GetSmtp(ID string) (*shared_types.SMTPConfigs, err
320329
}
321330
return config, nil
322331
}
332+
333+
func (m *NotificationManager) SendSlackNotification(userID string, message string) error {
334+
if m.Channels.Slack == nil || m.Channels.Slack.SlackClient == nil {
335+
return nil
336+
}
337+
338+
_, _, err := m.Channels.Slack.SlackClient.PostMessage(
339+
m.Channels.Slack.ChannelID,
340+
slack.MsgOptionText(message, false),
341+
)
342+
if err != nil {
343+
return fmt.Errorf("failed to send slack notification: %w", err)
344+
}
345+
346+
return nil
347+
}
348+
349+
func (m *NotificationManager) SendDiscordNotification(userID string, message string) error {
350+
if m.Channels.Discord == nil || m.Channels.Discord.WebhookUrl == "" {
351+
return nil
352+
}
353+
354+
payload := map[string]interface{}{
355+
"content": message,
356+
}
357+
358+
jsonData, err := json.Marshal(payload)
359+
if err != nil {
360+
return fmt.Errorf("failed to marshal discord payload: %w", err)
361+
}
362+
363+
resp, err := http.Post(m.Channels.Discord.WebhookUrl, "application/json", bytes.NewBuffer(jsonData))
364+
if err != nil {
365+
return fmt.Errorf("failed to send discord message: %w", err)
366+
}
367+
defer resp.Body.Close()
368+
369+
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
370+
return fmt.Errorf("discord webhook returned non-200 status code: %d", resp.StatusCode)
371+
}
372+
373+
return nil
374+
}

0 commit comments

Comments
 (0)