Skip to content

Commit

Permalink
feat: add notification package with various notification providers (#10)
Browse files Browse the repository at this point in the history
* Update git clone URL and add authorize middleware

* Update dependencies in README.md

* feat: add notification package with various notification providers

* refactor: update config.yaml.example with notification settings

* refactor: update notification configuration and load notifications

* feat: add SendNotification and SendNotificationByType functions

* Update Gin gonic starter with cron and notification

* Comment out notification sending in sayHello function

* docs: update configuration file with rate limit settings
  • Loading branch information
funnyzak authored Feb 18, 2024
1 parent 5715a9a commit 202f7cc
Show file tree
Hide file tree
Showing 14 changed files with 455 additions and 28 deletions.
74 changes: 72 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Go Gin

Gin gonic starter with zerolog, viper, gorm, jwt basic, go-cache, cron basic configuration.
Gin gonic starter with zerolog, viper, gorm, jwt, go-cache, rate-limit, cron, notification, etc.

[![Build Status][build-status-image]][build-status]
[![license][license-image]][repository-url]
Expand Down Expand Up @@ -75,6 +75,76 @@ You can fork this repository and add Secrets Keys: `DOCKER_USERNAME` and `DOCKER
└── singleton // Singleton services for the application
```

## Configuration

The configuration file is in the `config.yaml` file, you can copy the `config.yaml.example` file to `config.yaml` and update the values, the configuration file is as follows:

```yaml
server:
port: 8080 # Server port
site:
brand: Go-Gin # Site brand
description: A simple web application using Go and Gin # Site description
base_url: http://localhost:8080 # Site base URL, used for generating absolute URLs
debug: false # Debug mode, if true, the server will print detailed error messages
log:
level: debug # debug, info, warn, error, fatal, panic
path: logs/go-gin.log # Log file path
db_path: db/go-gin.sqlite # Database path
rate_limit:
max: 100 # requests per minute
upload:
dir: upload # Upload directory
max_size: 10485760 # 10MB
jwt: # JWT settings
access_secret: qhkxjrRmYcVYKSEobqsvhxhtPVeTWquu # Access token secret
refresh_secret: qhkxjrRmYcVYKSEobqsvhxhtPV3TWquu # Refresh token secret
access_token_expiration: 60 # minutes
refresh_token_expiration: 720 # minutes
access_token_cookie_name: go-gin-access # Access token cookie name
refresh_token_cookie_name: go-gin-refresh # Refresh token cookie name
location: Asia/Chongqing # Timezone
notifications: # Notification settings
- type: apprise # You must install apprise first, more details: https://github.com/caronc/apprise
instances:
- url: "apprise-url-1"
- url: "apprise-url-2"
- type: dingtalk
instances:
- webhook: "dingtalk-webhook-1"
- webhook: "dingtalk-webhook-2"
- type: ifttt
instances:
- key: "ifttt-key-1"
event: "event-1"
- key: "ifttt-key-2"
event: "event-2"
- type: smtp
instances:
- host: "smtp-host-1"
port: 587
username: "user-1"
password: "password-1"
from: "from-1"
to: "to-1"
- host: "smtp-host-2"
port: 587
username: "user-2"
password: "password-2"
from: "from-2"
to: "to-2"
- type: telegram
instances:
- botToken: "telegram-bot-token-1"
chatID: "chat-id-1"
- botToken: "telegram-bot-token-2"
chatID: "chat-id-2"
- type: wecom
instances:
- key: "wecom-key-1"
- key: "wecom-key-2"
```

## Build

```bash
Expand Down Expand Up @@ -113,7 +183,7 @@ docker run -d \

```bash
# Pull source code
git clone https://go-gin && cd go-gin
git clone [email protected]:funnyzak/go-gin.git && cd go-gin
# Compose startup, startup parameter configuration can be done by modifying the docker-compose.yml file
docker compose up -d
```
Expand Down
5 changes: 3 additions & 2 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,12 @@ func initService() {
// Load all services in the singleton package
singleton.LoadSingleton()

if _, err := singleton.Cron.AddFunc("0 * * * * *", writeHello); err != nil {
if _, err := singleton.Cron.AddFunc("0 * * * * *", sayHello); err != nil {
panic(err)
}
}

func writeHello() {
func sayHello() {
singleton.Log.Info().Msg("Hello world, I am a cron task")
// singleton.SendNotificationByType("wecom", "Hello world", "I am a cron task")
}
74 changes: 53 additions & 21 deletions config.yaml.example
Original file line number Diff line number Diff line change
@@ -1,31 +1,63 @@
server:
port: 8080

port: 8080 # Server port
site:
brand: Go-Gin
description: A simple web application using Go and Gin
base_url: http://localhost:8080

debug: false

brand: Go-Gin # Site brand
description: A simple web application using Go and Gin # Site description
base_url: http://localhost:8080 # Site base URL, used for generating absolute URLs
debug: false # Debug mode, if true, the server will print detailed error messages
log:
level: debug
path: logs/go-gin.log

db_path: db/go-gin.sqlite

level: debug # debug, info, warn, error, fatal, panic
path: logs/go-gin.log # Log file path
db_path: db/go-gin.sqlite # Database path
rate_limit:
max: 100 # requests per minute

upload:
dir: upload
dir: upload # Upload directory
max_size: 10485760 # 10MB

jwt:
access_secret: qhkxjrRmYcVYKSEobqsvhxhtPVeTWquu
refresh_secret: qhkxjrRmYcVYKSEobqsvhxhtPV3TWquu
jwt: # JWT settings
access_secret: qhkxjrRmYcVYKSEobqsvhxhtPVeTWquu # Access token secret
refresh_secret: qhkxjrRmYcVYKSEobqsvhxhtPV3TWquu # Refresh token secret
access_token_expiration: 60 # minutes
refresh_token_expiration: 720 # minutes
access_token_cookie_name: go-gin-access
refresh_token_cookie_name: go-gin-refresh
access_token_cookie_name: go-gin-access # Access token cookie name
refresh_token_cookie_name: go-gin-refresh # Refresh token cookie name
location: Asia/Chongqing # Timezone
notifications: # Notification settings
- type: apprise # You must install apprise first, more details: https://github.com/caronc/apprise
instances:
- url: "apprise-url-1"
- url: "apprise-url-2"
- type: dingtalk
instances:
- webhook: "dingtalk-webhook-1"
- webhook: "dingtalk-webhook-2"
- type: ifttt
instances:
- key: "ifttt-key-1"
event: "event-1"
- key: "ifttt-key-2"
event: "event-2"
- type: smtp
instances:
- host: "smtp-host-1"
port: 587
username: "user-1"
password: "password-1"
from: "from-1"
to: "to-1"
- host: "smtp-host-2"
port: 587
username: "user-2"
password: "password-2"
from: "from-2"
to: "to-2"
- type: telegram
instances:
- botToken: "telegram-bot-token-1"
chatID: "chat-id-1"
- botToken: "telegram-bot-token-2"
chatID: "chat-id-2"
- type: wecom
instances:
- key: "wecom-key-1"
- key: "wecom-key-2"
8 changes: 7 additions & 1 deletion internal/gconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ const (
DefaultPprofRoutePath = "/debug/pprof"
)

type Notification struct {
Type string `mapstructure:"type"`
Instances []map[string]string `mapstructure:"instances"`
}

type Config struct {
Server struct {
Port uint `mapstructure:"port"`
Expand Down Expand Up @@ -35,5 +40,6 @@ type Config struct {
AccessTokenCookieName string `mapstructure:"access_token_cookie_name"`
RefreshTokenCookieName string `mapstructure:"refresh_token_cookie_name"`
} `mapstructure:"jwt"`
Location string `mapstructure:"location"`
Location string `mapstructure:"location"`
Notifications []Notification `mapstructure:"notifications"`
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

type AuthorizeOption struct {
User bool // if true, only logged user can access
User bool // if true, only logined user can access
Guest bool // if true, only guest can access
IsPage bool
AllowAPI bool // if true, allow API token
Expand Down
29 changes: 29 additions & 0 deletions pkg/notification/apprise.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package notification

import (
"fmt"
"os/exec"
)

type ApprisePayload struct {
AppriseUrl string
}

type Apprise struct {
Payload ApprisePayload
}

func (a *Apprise) Send(title string, message string) error {
cmd := exec.Command("apprise", "-vv", "-b", message, a.Payload.AppriseUrl)
if title != "" {
cmd.Args = append(cmd.Args, "-t", title)
}
output, err := cmd.Output()
if err != nil {
return err
}
if string(output) == "ERROR: maybe apprise not found" {
return fmt.Errorf("ERROR: maybe apprise not found")
}
return nil
}
36 changes: 36 additions & 0 deletions pkg/notification/dingtalk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package notification

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)

type DingTalkPayload struct {
Webhook string
}

type DingTalk struct {
Payload DingTalkPayload
}

func (d DingTalk) Send(title string, message string) error {
webhook := d.Payload.Webhook

sendMessageUrl := webhook
sendMessageBody := map[string]interface{}{
"msgtype": "text",
"text": map[string]string{
"content": fmt.Sprintf("%s\n%s", title, message),
},
}
jsonBody, _ := json.Marshal(sendMessageBody)
resp, err := http.Post(sendMessageUrl, "application/json", bytes.NewBuffer(jsonBody))
if err != nil {
return err
}
defer resp.Body.Close()

return nil
}
38 changes: 38 additions & 0 deletions pkg/notification/ifttt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package notification

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)

type IFTTTPayload struct {
Key string
Event string
}

type IFTTT struct {
Payload IFTTTPayload
}

func (i IFTTT) Send(title string, message string) error {
key := i.Payload.Key
event := i.Payload.Event
value1 := title
value2 := message

sendMessageUrl := fmt.Sprintf("https://maker.ifttt.com/trigger/%s/with/key/%s", event, key)
sendMessageBody := map[string]string{
"value1": value1,
"value2": value2,
}
jsonBody, _ := json.Marshal(sendMessageBody)
resp, err := http.Post(sendMessageUrl, "application/json", bytes.NewBuffer(jsonBody))
if err != nil {
return err
}
defer resp.Body.Close()

return nil
}
13 changes: 13 additions & 0 deletions pkg/notification/notification.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package notification

type NotificationProvider interface {
Send(title string, message string) error
}

type Notification struct {
Provider NotificationProvider
}

func (n *Notification) Send(title string, message string) error {
return n.Provider.Send(title, message)
}
37 changes: 37 additions & 0 deletions pkg/notification/smtp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package notification

import (
"net/smtp"
"strconv"
)

type SMTPPayload struct {
Host string
Port int
Security bool
IgnoreTLS bool
Username string
Password string
From string
To string
Cc string
Bcc string
}

type SMTP struct {
Payload SMTPPayload
}

func (s *SMTP) Send(title string, message string) error {
auth := smtp.PlainAuth("", s.Payload.Username, s.Payload.Password, s.Payload.Host)
to := []string{s.Payload.To}
msg := []byte("To: " + s.Payload.To + "\r\n" +
"Subject: " + title + "\r\n" +
"\r\n" +
message + "\r\n")
err := smtp.SendMail(s.Payload.Host+":"+strconv.Itoa(s.Payload.Port), auth, s.Payload.From, to, msg)
if err != nil {
return err
}
return nil
}
Loading

0 comments on commit 202f7cc

Please sign in to comment.