Prechádzať zdrojové kódy

Docblocks, a handful of tests, but not enough

Philipp Heckel 4 rokov pred
rodič
commit
e3dfea1991
9 zmenil súbory, kde vykonal 121 pridanie a 12 odobranie
  1. 44 2
      client/client.go
  2. 3 0
      client/config.go
  3. 48 2
      client/options.go
  4. 1 1
      cmd/publish.go
  5. 1 1
      scripts/postrm.sh
  6. 1 1
      server/config.go
  7. 3 4
      server/server_test.go
  8. 2 1
      util/util.go
  9. 18 0
      util/util_test.go

+ 44 - 2
client/client.go

@@ -1,3 +1,4 @@
+// Package client provides a ntfy client to publish and subscribe to topics
 package client
 
 import (
@@ -12,12 +13,14 @@ import (
 	"time"
 )
 
+// Event type constants
 const (
 	MessageEvent   = "message"
 	KeepaliveEvent = "keepalive"
 	OpenEvent      = "open"
 )
 
+// Client is the ntfy client that can be used to publish and subscribe to ntfy topics
 type Client struct {
 	Messages      chan *Message
 	config        *Config
@@ -25,6 +28,7 @@ type Client struct {
 	mu            sync.Mutex
 }
 
+// Message is a struct that represents a ntfy message
 type Message struct {
 	ID       string
 	Event    string
@@ -42,6 +46,7 @@ type subscription struct {
 	cancel context.CancelFunc
 }
 
+// New creates a new Client using a given Config
 func New(config *Config) *Client {
 	return &Client{
 		Messages:      make(chan *Message),
@@ -50,6 +55,14 @@ func New(config *Config) *Client {
 	}
 }
 
+// Publish sends a message to a specific topic, optionally using options.
+//
+// A topic can be either a full URL (e.g. https://myhost.lan/mytopic), a short URL which is then prepended https://
+// (e.g. myhost.lan -> https://myhost.lan), or a short name which is expanded using the default host in the
+// config (e.g. mytopic -> https://ntfy.sh/mytopic).
+//
+// To pass title, priority and tags, check out WithTitle, WithPriority, WithTagsList, WithDelay, WithNoCache,
+// WithNoFirebase, and the generic WithHeader.
 func (c *Client) Publish(topic, message string, options ...PublishOption) error {
 	topicURL := c.expandTopicURL(topic)
 	req, _ := http.NewRequest("POST", topicURL, strings.NewReader(message))
@@ -68,6 +81,15 @@ func (c *Client) Publish(topic, message string, options ...PublishOption) error
 	return err
 }
 
+// Poll queries a topic for all (or a limited set) of messages. Unlike Subscribe, this method only polls for
+// messages and does not subscribe to messages that arrive after this call.
+//
+// A topic can be either a full URL (e.g. https://myhost.lan/mytopic), a short URL which is then prepended https://
+// (e.g. myhost.lan -> https://myhost.lan), or a short name which is expanded using the default host in the
+// config (e.g. mytopic -> https://ntfy.sh/mytopic).
+//
+// By default, all messages will be returned, but you can change this behavior using a SubscribeOption.
+// See WithSince, WithSinceAll, WithSinceUnixTime, WithScheduled, and the generic WithQueryParam.
 func (c *Client) Poll(topic string, options ...SubscribeOption) ([]*Message, error) {
 	ctx := context.Background()
 	messages := make([]*Message, 0)
@@ -85,6 +107,22 @@ func (c *Client) Poll(topic string, options ...SubscribeOption) ([]*Message, err
 	return messages, <-errChan
 }
 
+// Subscribe subscribes to a topic to listen for newly incoming messages. The method starts a connection in the
+// background and returns new messages via the Messages channel.
+//
+// A topic can be either a full URL (e.g. https://myhost.lan/mytopic), a short URL which is then prepended https://
+// (e.g. myhost.lan -> https://myhost.lan), or a short name which is expanded using the default host in the
+// config (e.g. mytopic -> https://ntfy.sh/mytopic).
+//
+// By default, only new messages will be returned, but you can change this behavior using a SubscribeOption.
+// See WithSince, WithSinceAll, WithSinceUnixTime, WithScheduled, and the generic WithQueryParam.
+//
+// Example:
+//   c := client.New(client.NewConfig())
+//   c.Subscribe("mytopic")
+//   for m := range c.Messages {
+//     fmt.Printf("New message: %s", m.Message)
+//   }
 func (c *Client) Subscribe(topic string, options ...SubscribeOption) string {
 	c.mu.Lock()
 	defer c.mu.Unlock()
@@ -98,6 +136,11 @@ func (c *Client) Subscribe(topic string, options ...SubscribeOption) string {
 	return topicURL
 }
 
+// Unsubscribe unsubscribes from a topic that has been previously subscribed with Subscribe.
+//
+// A topic can be either a full URL (e.g. https://myhost.lan/mytopic), a short URL which is then prepended https://
+// (e.g. myhost.lan -> https://myhost.lan), or a short name which is expanded using the default host in the
+// config (e.g. mytopic -> https://ntfy.sh/mytopic).
 func (c *Client) Unsubscribe(topic string) {
 	c.mu.Lock()
 	defer c.mu.Unlock()
@@ -107,7 +150,6 @@ func (c *Client) Unsubscribe(topic string) {
 		return
 	}
 	sub.cancel()
-	return
 }
 
 func (c *Client) expandTopicURL(topic string) string {
@@ -128,7 +170,7 @@ func handleSubscribeConnLoop(ctx context.Context, msgChan chan *Message, topicUR
 		case <-ctx.Done():
 			log.Printf("Connection to %s exited", topicURL)
 			return
-		case <-time.After(5 * time.Second):
+		case <-time.After(10 * time.Second): // TODO Add incremental backoff
 		}
 	}
 }

+ 3 - 0
client/config.go

@@ -1,9 +1,11 @@
 package client
 
 const (
+	// DefaultBaseURL is the base URL used to expand short topic names
 	DefaultBaseURL = "https://ntfy.sh"
 )
 
+// Config is the config struct for a Client
 type Config struct {
 	DefaultHost string
 	Subscribe   []struct {
@@ -12,6 +14,7 @@ type Config struct {
 	}
 }
 
+// NewConfig creates a new Config struct for a Client
 func NewConfig() *Config {
 	return &Config{
 		DefaultHost: DefaultBaseURL,

+ 48 - 2
client/options.go

@@ -1,49 +1,94 @@
 package client
 
 import (
+	"fmt"
 	"net/http"
+	"strings"
+	"time"
 )
 
-type RequestOption func(r *http.Request) error
+// RequestOption is a generic request option that can be added to Client calls
+type RequestOption = func(r *http.Request) error
+
+// PublishOption is an option that can be passed to the Client.Publish call
 type PublishOption = RequestOption
+
+// SubscribeOption is an option that can be passed to a Client.Subscribe or Client.Poll call
 type SubscribeOption = RequestOption
 
+// WithTitle adds a title to a message
 func WithTitle(title string) PublishOption {
 	return WithHeader("X-Title", title)
 }
 
+// WithPriority adds a priority to a message. The priority can be either a number (1=min, 5=max),
+// or the corresponding names (see util.ParsePriority).
 func WithPriority(priority string) PublishOption {
 	return WithHeader("X-Priority", priority)
 }
 
-func WithTags(tags string) PublishOption {
+// WithTagsList adds a list of tags to a message. The tags parameter must be a comma-separated list
+// of tags. To use a slice, use WithTags instead
+func WithTagsList(tags string) PublishOption {
 	return WithHeader("X-Tags", tags)
 }
 
+// WithTags adds a list of a tags to a message
+func WithTags(tags []string) PublishOption {
+	return WithTagsList(strings.Join(tags, ","))
+}
+
+// WithDelay instructs the server to send the message at a later date. The delay parameter can be a
+// Unix timestamp, a duration string or a natural langage string. See https://ntfy.sh/docs/publish/#scheduled-delivery
+// for details.
 func WithDelay(delay string) PublishOption {
 	return WithHeader("X-Delay", delay)
 }
 
+// WithNoCache instructs the server not to cache the message server-side
 func WithNoCache() PublishOption {
 	return WithHeader("X-Cache", "no")
 }
 
+// WithNoFirebase instructs the server not to forward the message to Firebase
 func WithNoFirebase() PublishOption {
 	return WithHeader("X-Firebase", "no")
 }
 
+// WithSince limits the number of messages returned from the server. The parameter since can be a Unix
+// timestamp (see WithSinceUnixTime), a duration (WithSinceDuration) the word "all" (see WithSinceAll).
 func WithSince(since string) SubscribeOption {
 	return WithQueryParam("since", since)
 }
 
+// WithSinceAll instructs the server to return all messages for the given topic from the server
+func WithSinceAll() SubscribeOption {
+	return WithSince("all")
+}
+
+// WithSinceDuration instructs the server to return all messages since the given duration ago
+func WithSinceDuration(since time.Duration) SubscribeOption {
+	return WithSinceUnixTime(time.Now().Add(-1 * since).Unix())
+}
+
+// WithSinceUnixTime instructs the server to return only messages newer or equal to the given timestamp
+func WithSinceUnixTime(since int64) SubscribeOption {
+	return WithSince(fmt.Sprintf("%d", since))
+}
+
+// WithPoll instructs the server to close the connection after messages have been returned. Don't use this option
+// directly. Use Client.Poll instead.
 func WithPoll() SubscribeOption {
 	return WithQueryParam("poll", "1")
 }
 
+// WithScheduled instructs the server to also return messages that have not been sent yet, i.e. delayed/scheduled
+// messages (see WithDelay). The messages will have a future date.
 func WithScheduled() SubscribeOption {
 	return WithQueryParam("scheduled", "1")
 }
 
+// WithHeader is a generic option to add headers to a request
 func WithHeader(header, value string) RequestOption {
 	return func(r *http.Request) error {
 		if value != "" {
@@ -53,6 +98,7 @@ func WithHeader(header, value string) RequestOption {
 	}
 }
 
+// WithQueryParam is a generic option to add query parameters to a request
 func WithQueryParam(param, value string) RequestOption {
 	return func(r *http.Request) error {
 		if value != "" {

+ 1 - 1
cmd/publish.go

@@ -59,7 +59,7 @@ func execPublish(c *cli.Context) error {
 		options = append(options, client.WithPriority(priority))
 	}
 	if tags != "" {
-		options = append(options, client.WithTags(tags))
+		options = append(options, client.WithTagsList(tags))
 	}
 	if delay != "" {
 		options = append(options, client.WithDelay(delay))

+ 1 - 1
scripts/postrm.sh

@@ -4,7 +4,7 @@ set -e
 # Delete the config if package is purged
 if [ "$1" = "purge" ]; then
   id ntfy >/dev/null 2>&1 && userdel ntfy
-  rm -f /etc/ntfy/server.yml
+  rm -f /etc/ntfy/server.yml /etc/ntfy/client.yml
   rmdir /etc/ntfy || true
 fi
 

+ 1 - 1
server/config.go

@@ -51,7 +51,7 @@ type Config struct {
 	BehindProxy                  bool
 }
 
-// New instantiates a default new config
+// NewConfig instantiates a default new server config
 func NewConfig(listenHTTP string) *Config {
 	return &Config{
 		ListenHTTP:                   listenHTTP,

+ 3 - 4
server/server_test.go

@@ -6,7 +6,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"github.com/stretchr/testify/require"
-	"heckel.io/ntfy/config"
 	"net/http"
 	"net/http/httptest"
 	"os"
@@ -393,13 +392,13 @@ func TestServer_PublishFirebase(t *testing.T) {
 	time.Sleep(500 * time.Millisecond) // Time for sends
 }
 
-func newTestConfig(t *testing.T) *config.Config {
-	conf := config.New(":80")
+func newTestConfig(t *testing.T) *Config {
+	conf := NewConfig(":80")
 	conf.CacheFile = filepath.Join(t.TempDir(), "cache.db")
 	return conf
 }
 
-func newTestServer(t *testing.T, config *config.Config) *Server {
+func newTestServer(t *testing.T, config *Config) *Server {
 	server, err := New(config)
 	if err != nil {
 		t.Fatal(err)

+ 2 - 1
util/util.go

@@ -80,8 +80,9 @@ func DurationToHuman(d time.Duration) (str string) {
 	return
 }
 
+// ParsePriority parses a priority string into its equivalent integer value
 func ParsePriority(priority string) (int, error) {
-	switch strings.ToLower(priority) {
+	switch strings.TrimSpace(strings.ToLower(priority)) {
 	case "":
 		return 0, nil
 	case "1", "min":

+ 18 - 0
util/util_test.go

@@ -63,3 +63,21 @@ func TestExpandHome_WithTilde(t *testing.T) {
 func TestExpandHome_NoTilde(t *testing.T) {
 	require.Equal(t, "/this/is/an/absolute/path", ExpandHome("/this/is/an/absolute/path"))
 }
+
+func TestParsePriority(t *testing.T) {
+	priorities := []string{"", "1", "2", "3", "4", "5", "min", "LOW", "   default ", "HIgh", "max", "urgent"}
+	expected := []int{0, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 5}
+	for i, priority := range priorities {
+		actual, err := ParsePriority(priority)
+		require.Nil(t, err)
+		require.Equal(t, expected[i], actual)
+	}
+}
+
+func TestParsePriority_Invalid(t *testing.T) {
+	priorities := []string{"-1", "6", "aa", "-"}
+	for _, priority := range priorities {
+		_, err := ParsePriority(priority)
+		require.Equal(t, errInvalidPriority, err)
+	}
+}