Skip to content

Commit

Permalink
Merge pull request #68 from mcarey1590/forks/mcarey/fix-server-edit
Browse files Browse the repository at this point in the history
feat: update server requests
  • Loading branch information
mrz1836 authored Jan 13, 2025
2 parents 1806d09 + f36b361 commit 64ba815
Show file tree
Hide file tree
Showing 2 changed files with 294 additions and 13 deletions.
147 changes: 136 additions & 11 deletions servers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"net/url"
)

// Server represents a server registered in your Postmark account
Expand All @@ -29,10 +30,12 @@ type Server struct {
InboundAddress string `json:"InboundAddress"`
// InboundHookURL to POST to every time an inbound event occurs.
InboundHookURL string `json:"InboundHookUrl"`
// BounceHookURL to POST to every time a bounce event occurs.
// Deprecated: Use the Bounce Webhook API instead.
BounceHookURL string `json:"BounceHookUrl"`
// OpenHookURL to POST to every time an open event occurs.
// Deprecated: Use the Open Tracking Webhook API instead.
OpenHookURL string `json:"OpenHookUrl"`
// Deprecated: Use the Delivery Webhook API instead.
DeliveryHookURL string `json:"DeliveryHookUrl"`
// PostFirstOpenOnly - If set to true, only the first open by a particular recipient will initiate the open webhook. Any
// subsequent opens of the same email by the same recipient will not initiate the webhook.
PostFirstOpenOnly bool `json:"PostFirstOpenOnly"`
Expand All @@ -52,6 +55,89 @@ type Server struct {
EnableSMTPAPIErrorHooks bool `json:"EnableSmtpApiErrorHooks"`
}

// ServerCreateRequest represents the fields to create a server
type ServerCreateRequest struct {
// Name of server
Name string `json:"Name" binding:"required"`
// Color of the server in the server list, for quick identification. Purple Blue Turquoise Green Red Yellow Grey Orange
Color string `json:"Color"`
// SMTPAPIActivated specifies whether SMTP is enabled on this server.
SMTPAPIActivated bool `json:"SmtpApiActivated"`
// When enabled, the raw email content will be included with inbound webhook payloads under the RawEmail key.
RawEmailEnabled bool `json:"RawEmailEnabled"`
// Specifies the type of environment for your server. Possible options: Live Sandbox. Defaults to Live if not
// specified. This cannot be changed after the server has been created.
DeliveryType string `json:"DeliveryType"`
// URL to POST to every time an inbound event occurs.
InboundHookURL string `json:"InboundHookUrl"`
// Deprecated: Use the Bounce Webhook API instead.
BounceHookURL string `json:"BounceHookUrl"`
// Deprecated: Use the Open Tracking Webhook API instead.
OpenHookURL string `json:"OpenHookUrl"`
// Deprecated: Use the Delivery Webhook API instead.
DeliveryHookURL string `json:"DeliveryHookUrl"`
// Deprecated: Use the Click Webhook API instead.
ClickHookURL string `json:"ClickHookUrl"`
// PostFirstOpenOnly - If set to true, only the first open by a particular recipient will initiate the open webhook. Any
// subsequent opens of the same email by the same recipient will not initiate the webhook.
PostFirstOpenOnly bool `json:"PostFirstOpenOnly"`
// InboundDomain is the inbound domain for MX setup
InboundDomain string `json:"InboundDomain"`
// InboundSpamThreshold is the maximum spam score for an inbound message before it's blocked.
InboundSpamThreshold int64 `json:"InboundSpamThreshold"`
// TrackOpens indicates if all emails being sent through this server have open tracking enabled.
TrackOpens bool `json:"TrackOpens"`
// TrackLinks specifies link tracking in emails: None, HtmlAndText, HtmlOnly, TextOnly, defaults to "None"
TrackLinks string `json:"TrackLinks"`
// IncludeBounceContentInHook determines if bounce content is included in webhook.
IncludeBounceContentInHook bool `json:"IncludeBounceContentInHook"`
// EnableSMTPAPIErrorHooks specifies whether SMTP API Errors will be included with bounce webhooks.
EnableSMTPAPIErrorHooks bool `json:"EnableSmtpApiErrorHooks"`
}

// ServerEditRequest represents the fields that can be updated for a server
type ServerEditRequest struct {
// Name of server
Name string `json:"Name" binding:"required"`
// Color of the server in the server list, for quick identification. Purple Blue Turquoise Green Red Yellow Grey Orange
Color string `json:"Color"`
// SMTPAPIActivated specifies whether SMTP is enabled on this server.
SMTPAPIActivated bool `json:"SmtpApiActivated"`
// When enabled, the raw email content will be included with inbound webhook payloads under the RawEmail key.
RawEmailEnabled bool `json:"RawEmailEnabled"`
// URL to POST to every time an inbound event occurs.
InboundHookURL string `json:"InboundHookUrl"`
// Deprecated: Use the Bounce Webhook API instead.
BounceHookURL string `json:"BounceHookUrl"`
// Deprecated: Use the Open Tracking Webhook API instead.
OpenHookURL string `json:"OpenHookUrl"`
// Deprecated: Use the Delivery Webhook API instead.
DeliveryHookURL string `json:"DeliveryHookUrl"`
// Deprecated: Use the Click Webhook API instead.
ClickHookURL string `json:"ClickHookUrl"`
// PostFirstOpenOnly - If set to true, only the first open by a particular recipient will initiate the open webhook. Any
// subsequent opens of the same email by the same recipient will not initiate the webhook.
PostFirstOpenOnly bool `json:"PostFirstOpenOnly"`
// InboundDomain is the inbound domain for MX setup
InboundDomain string `json:"InboundDomain"`
// InboundSpamThreshold is the maximum spam score for an inbound message before it's blocked.
InboundSpamThreshold int64 `json:"InboundSpamThreshold"`
// TrackOpens indicates if all emails being sent through this server have open tracking enabled.
TrackOpens bool `json:"TrackOpens"`
// TrackLinks specifies link tracking in emails: None, HtmlAndText, HtmlOnly, TextOnly, defaults to "None"
TrackLinks string `json:"TrackLinks"`
// IncludeBounceContentInHook determines if bounce content is included in webhook.
IncludeBounceContentInHook bool `json:"IncludeBounceContentInHook"`
// EnableSMTPAPIErrorHooks specifies whether SMTP API Errors will be included with bounce webhooks.
EnableSMTPAPIErrorHooks bool `json:"EnableSmtpApiErrorHooks"`
}

// ServersList is just a list of Server as they are in the response
type ServersList struct {
TotalCount int
Servers []Server
}

// MarshalJSON customizes the JSON representation of the Server struct by setting default values for specific fields.
func (s Server) MarshalJSON() ([]byte, error) {
type Aux Server
Expand Down Expand Up @@ -80,35 +166,74 @@ func (s Server) MarshalJSON() ([]byte, error) {
}

// GetServer fetches a specific server via serverID
func (client *Client) GetServer(ctx context.Context, serverID string) (Server, error) {
func (client *Client) GetServer(ctx context.Context, serverID int64) (Server, error) {
res := Server{}
err := client.doRequest(ctx, parameters{
Method: http.MethodGet,
Path: fmt.Sprintf("servers/%s", serverID),
Path: fmt.Sprintf("servers/%d", serverID),
TokenType: accountToken,
}, &res)
return res, err
}

// GetServers fetches a list of servers on the account, limited by count and paged by offset
// Optionally filter by a specific server name. Note that this is a string search, so MyServer will match
// MyServer, MyServer Production, and MyServer Test.
func (client *Client) GetServers(ctx context.Context, count, offset int64, name string) (ServersList, error) {
res := ServersList{}

values := &url.Values{}
values.Add("count", fmt.Sprintf("%d", count))
values.Add("offset", fmt.Sprintf("%d", offset))

if name != "" {
values.Add("name", name)
}

err := client.doRequest(ctx, parameters{
Method: "GET",
Path: fmt.Sprintf("servers?%s", values.Encode()),
TokenType: accountToken,
}, &res)
return res, err
}

// EditServer updates details for a specific server with serverID
func (client *Client) EditServer(ctx context.Context, serverID string, server Server) (Server, error) {
// res := Server{}
func (client *Client) EditServer(ctx context.Context, serverID int64, request ServerEditRequest) (Server, error) {
res := Server{}
err := client.doRequest(ctx, parameters{
Method: http.MethodPut,
Path: fmt.Sprintf("servers/%s", serverID),
Path: fmt.Sprintf("servers/%d", serverID),
TokenType: accountToken,
}, &server)
return server, err
Payload: request,
}, &res)
return res, err
}

// CreateServer creates a server
func (client *Client) CreateServer(ctx context.Context, server Server) (Server, error) {
func (client *Client) CreateServer(ctx context.Context, request ServerCreateRequest) (Server, error) {
res := Server{}
err := client.doRequest(ctx, parameters{
Method: http.MethodPost,
Path: "servers",
TokenType: accountToken,
Payload: server,
Payload: request,
}, &res)
return res, err
}

// DeleteServer removes a server.
func (client *Client) DeleteServer(ctx context.Context, serverID int64) error {
res := APIError{}
err := client.doRequest(ctx, parameters{
Method: http.MethodDelete,
Path: fmt.Sprintf("servers/%d", serverID),
TokenType: accountToken,
}, &res)

if res.ErrorCode != 0 {
return res
}

return err
}
160 changes: 158 additions & 2 deletions servers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,83 @@ import (
"goji.io/pat"
)

func TestGetServers(t *testing.T) {
responseJSON := `{
"TotalCount": 2,
"Servers": [
{
"ID": 1,
"Name": "Production01",
"ApiTokens": [
"server token"
],
"Color": "red",
"SmtpApiActivated": true,
"RawEmailEnabled": false,
"DeliveryType": "Live",
"ServerLink": "https://postmarkapp.com/servers/1/streams",
"InboundAddress": "[email protected]",
"InboundHookUrl": "http://inboundhook.example.com/inbound",
"BounceHookUrl": "http://bouncehook.example.com/bounce",
"OpenHookUrl": "http://openhook.example.com/open",
"DeliveryHookUrl": "http://hooks.example.com/delivery",
"PostFirstOpenOnly": true,
"InboundDomain": "",
"InboundHash": "yourhash",
"InboundSpamThreshold": 5,
"TrackOpens": false,
"TrackLinks": "None",
"IncludeBounceContentInHook": true,
"ClickHookUrl": "http://hooks.example.com/click",
"EnableSmtpApiErrorHooks": false
},
{
"ID": 2,
"Name": "Production02",
"ApiTokens": [
"server token"
],
"Color": "green",
"SmtpApiActivated": true,
"RawEmailEnabled": false,
"DeliveryType": "Sandbox",
"ServerLink": "https://postmarkapp.com/servers/2/streams",
"InboundAddress": "[email protected]",
"InboundHookUrl": "",
"BounceHookUrl": "",
"OpenHookUrl": "",
"DeliveryHookUrl": "http://hooks.example.com/delivery",
"PostFirstOpenOnly": false,
"InboundDomain": "",
"InboundHash": "yourhash",
"InboundSpamThreshold": 0,
"TrackOpens": true,
"TrackLinks": "HtmlAndText",
"IncludeBounceContentInHook": false,
"ClickHookUrl": "",
"EnableSmtpApiErrorHooks": false
}
]
}`

tMux.HandleFunc(pat.Get("/servers"), func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte(responseJSON))
})

res, err := client.GetServers(context.Background(), 100, 10, "")
if err != nil {
t.Fatalf("GetServers: %s", err.Error())
}

if len(res.Servers) == 0 {
t.Fatalf("GetServers: unmarshaled to empty")
}

if res.TotalCount != 2 {
t.Fatalf("GetServers: unmarshaled to empty")
}
}

func TestGetServer(t *testing.T) {
responseJSON := `{
"ID": 1,
Expand All @@ -34,7 +111,7 @@ func TestGetServer(t *testing.T) {
_, _ = w.Write([]byte(responseJSON))
})

res, err := client.GetServer(context.Background(), "1")
res, err := client.GetServer(context.Background(), 1)
if err != nil {
t.Fatalf("GetServer: %s", err.Error())
}
Expand All @@ -44,6 +121,57 @@ func TestGetServer(t *testing.T) {
}
}

func TestCreateServer(t *testing.T) {
responseJSON := `{
"ID": 1,
"Name": "Staging Testing",
"ApiTokens": [
"server token"
],
"Color": "red",
"SmtpApiActivated": true,
"RawEmailEnabled": false,
"DeliveryType": "Live",
"ServerLink": "https://postmarkapp.com/servers/1/streams",
"InboundAddress": "[email protected]",
"InboundHookUrl": "http://hooks.example.com/inbound",
"PostFirstOpenOnly": false,
"InboundDomain": "",
"InboundHash": "yourhash",
"InboundSpamThreshold": 5,
"TrackOpens": false,
"TrackLinks": "None",
"IncludeBounceContentInHook": true,
"EnableSmtpApiErrorHooks": false
}`

tMux.HandleFunc(pat.Post("/servers"), func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte(responseJSON))
})

res, err := client.CreateServer(context.Background(), ServerCreateRequest{
Name: "Staging Testing",
Color: "red",
SMTPAPIActivated: true,
RawEmailEnabled: false,
InboundHookURL: "http://hooks.example.com/inbound",
PostFirstOpenOnly: false,
InboundDomain: "",
InboundSpamThreshold: 5,
TrackOpens: false,
TrackLinks: "None",
IncludeBounceContentInHook: true,
EnableSMTPAPIErrorHooks: false,
})
if err != nil {
t.Fatalf("CreateServer: %s", err.Error())
}

if res.Name != "Staging Testing" {
t.Fatalf("CreateServer: wrong name!")
}
}

func TestEditServer(t *testing.T) {
responseJSON := `{
"ID": 1,
Expand All @@ -70,7 +198,7 @@ func TestEditServer(t *testing.T) {
_, _ = w.Write([]byte(responseJSON))
})

res, err := client.EditServer(context.Background(), "1234", Server{
res, err := client.EditServer(context.Background(), 1234, ServerEditRequest{
Name: "Production Testing",
})
if err != nil {
Expand All @@ -81,3 +209,31 @@ func TestEditServer(t *testing.T) {
t.Fatalf("EditServer: wrong name!: %s", res.Name)
}
}

func TestDeleteServer(t *testing.T) {
responseJSON := `{
"ErrorCode": 0,
"Message": "Server 1234 removed."
}`

tMux.HandleFunc(pat.Delete("/servers/:serverID"), func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte(responseJSON))
})

// Success
err := client.DeleteServer(context.Background(), 1234)
if err != nil {
t.Fatalf("DeleteServer: %s", err.Error())
}

// Failure
responseJSON = `{
"ErrorCode": 402,
"Message": "Invalid JSON"
}`

err = client.DeleteServer(context.Background(), 1234)
if err == nil {
t.Fatalf("DeleteServer: should have failed")
}
}

0 comments on commit 64ba815

Please sign in to comment.